1. Dockerize and Build Multi-Architecture Images

Goal

Build a Docker container image that runs on multiple processor architectures (AMD64 and ARM64) and publish it to Docker Hub for worldwide distribution.

What you’ll learn:

  • How to create a Dockerfile for a PHP application
  • How to build multi-architecture Docker images using buildx
  • How to push images to Docker Hub for distribution
  • Best practices for containerizing web applications

Table of Contents

Prerequisites

Before starting, ensure you have:

  • ✓ Docker Desktop installed and running
  • ✓ A Docker Hub account at https://hub.docker.com
  • ✓ Basic understanding of Docker concepts
  • ✓ A simple PHP application (provided in appendix)

Exercise Steps

Overview

  1. Create the Application Files
  2. Write the Dockerfile
  3. Build and Test Locally
  4. Build Multi-Architecture Image
  5. Push to Docker Hub

Step 1: Create the Application Files

Set up a simple PHP web application with a contact form to demonstrate Docker containerization. The complete source code is provided in Appendix A.

  1. Create the project structure:

    mkdir my-ecs-php-app
    cd my-ecs-php-app
    mkdir src
    
  2. Copy the application files from Appendix A:

    • Create src/index.html - the welcome page
    • Create src/contact_form.html - the contact form
    • Create src/process_contact_form.php - the form processor
    • Create src/style.css - the stylesheet

Concept Deep Dive

This simple application demonstrates both static content (HTML) and dynamic content (PHP). The htmlspecialchars() function prevents XSS attacks by sanitizing user input. In a container, this application will run identically across any platform that supports Docker.

Quick check: Your project should have the structure my-ecs-php-app/src/ with four files inside

Step 2: Write the Dockerfile

Create a Dockerfile that defines how to build your application container. The Dockerfile is like a recipe that tells Docker how to assemble your application with all its dependencies into a portable image.

  1. Create a file named Dockerfile in the project root (not in src):

    Dockerfile

    # Use official PHP image with CLI and Alpine for smaller size
    FROM php:8.2-cli-alpine
    
    # Set working directory
    WORKDIR /var/www/html
    
    # Copy application files
    COPY src/ .
    
    # Expose port 8000
    EXPOSE 8000
    
    # Start PHP's built-in web server
    CMD ["php", "-S", "0.0.0.0:8000"]
    

Concept Deep Dive

The Dockerfile has several important parts:

  • FROM php:8.2-cli-alpine - Uses Alpine Linux (tiny footprint, ~5MB vs ~400MB)
  • WORKDIR - Sets the working directory inside the container
  • COPY src/ . - Copies your application files into the container
  • EXPOSE 8000 - Documents which port the application uses
  • CMD - Starts PHP’s built-in web server on all interfaces (0.0.0.0)

Common Mistakes

  • Using 127.0.0.1 instead of 0.0.0.0 will prevent external connections
  • Forgetting the dot (.) in COPY src/ . will cause files to be copied incorrectly
  • Not exposing the port doesn’t prevent it from working but is bad documentation

Quick check: Dockerfile exists in project root alongside the src directory

Step 3: Build and Test Locally

Build the Docker image and test it on your local machine before publishing. This ensures everything works correctly before pushing to Docker Hub.

  1. Build the Docker image:

    docker build -t my-ecs-php-app:test .
    
  2. Run the container:

    docker run -p 8000:8000 my-ecs-php-app:test
    
  3. Test the application:

    • Open your browser to http://localhost:8000
    • You should see the welcome page
    • Click “Go to Contact Form”
    • Fill out and submit the form
    • Verify the PHP processing works
  4. Stop the container:

    • Press Ctrl+C in the terminal

Concept Deep Dive

The -p 8000:8000 flag maps port 8000 on your machine to port 8000 in the container. The format is -p HOST_PORT:CONTAINER_PORT. The dot (.) at the end of the build command tells Docker to use the current directory as the build context.

Common Mistakes

  • Forgetting the dot (.) at the end of docker build will cause an error
  • If port 8000 is already in use, change the first number: -p 8080:8000
  • Don’t forget to stop the container before the next step

Quick check: Application loads in browser and form submission works

Step 4: Build Multi-Architecture Image

Build an image that works on both Intel/AMD (amd64) and ARM (arm64) processors using Docker buildx. This allows your image to run on Intel Macs, Apple Silicon Macs, Intel servers, ARM servers, and Raspberry Pi devices.

  1. Login to Docker Hub:

    docker login
    

    Enter your Docker Hub username and password when prompted.

  2. Create a buildx builder:

    docker buildx create --name multiarch-builder --use --bootstrap
    
  3. Build for multiple architectures (replace YOUR_USERNAME with your Docker Hub username):

    docker buildx build \
      --platform linux/amd64,linux/arm64 \
      --tag YOUR_USERNAME/my-ecs-php-app:latest \
      --push \
      .
    

Concept Deep Dive

Docker buildx enables building images for different CPU architectures:

  • linux/amd64 - Intel and AMD processors (most servers and PCs)
  • linux/arm64 - ARM processors (Apple Silicon, AWS Graviton, Raspberry Pi)
  • --push - Automatically pushes to Docker Hub after building
  • The builder uses QEMU to emulate different architectures during the build

Multi-architecture images contain manifests that automatically select the correct image for each platform. When someone runs docker pull, they get the right version for their CPU.

Common Mistakes

  • Forgetting to replace YOUR_USERNAME will cause a permission error
  • The --push flag requires authentication with docker login first
  • Building for multiple platforms takes longer than single-platform builds

Quick check: Build completes without errors and image appears on Docker Hub

Step 5: Push to Docker Hub

Verify your multi-architecture image is available on Docker Hub and test pulling it from different platforms.

  1. Visit Docker Hub at https://hub.docker.com

  2. Navigate to your repository:

    • Click on “Repositories”
    • Find my-ecs-php-app
    • Click on it to view details
  3. Check the architecture tags:

    • You should see multiple architectures listed (amd64, arm64)
    • Click on “Tags” to see available versions
  4. Test pulling the image:

    docker pull YOUR_USERNAME/my-ecs-php-app:latest
    
  5. Run the pulled image:

    docker run -p 8000:8000 YOUR_USERNAME/my-ecs-php-app:latest
    
  6. Verify it works:

    • Visit http://localhost:8000
    • Test the application functionality

Success indicators:

  • Image appears in your Docker Hub repositories
  • Multiple architectures are listed (linux/amd64, linux/arm64)
  • Image can be pulled from Docker Hub
  • Application runs correctly from the pulled image
  • Anyone with Docker can now run your application

Final verification checklist:

  • ☐ Dockerfile created with correct syntax
  • ☐ Local build and test successful
  • ☐ Multi-architecture build completed
  • ☐ Image pushed to Docker Hub
  • ☐ Image visible in Docker Hub repository
  • ☐ Image can be pulled and run successfully

Common Issues

If you encounter problems:

“Cannot connect to Docker daemon”: Ensure Docker Desktop is running

“denied: requested access to the resource is denied”: Run docker login and ensure you’re using your correct Docker Hub username

“no builder instance selected”: Run docker buildx create --name multiarch-builder --use first

“failed to solve: failed to fetch”: Check your internet connection and proxy settings

“error: multiple platforms feature is currently not supported”: Update Docker Desktop to the latest version

Still stuck? Check https://docs.docker.com/buildx/working-with-buildx/ for buildx documentation

Summary

You’ve successfully containerized a PHP application and published it as a multi-architecture Docker image which:

Key takeaway: Docker containerization packages your application with all its dependencies, ensuring it runs the same everywhere. Multi-architecture builds enable your application to run on any platform, from development laptops to production servers, regardless of CPU architecture.

Going Deeper (Optional)

Want to explore more?

  • Add environment variables for configuration using ENV in Dockerfile
  • Implement multi-stage builds to reduce image size
  • Add health checks with HEALTHCHECK instruction
  • Set up automated builds with GitHub Actions
  • Explore Docker Compose for multi-container applications
  • Research container orchestration with Kubernetes

Done! 🎉

Excellent work! You’ve learned how to containerize applications with Docker and publish multi-architecture images to Docker Hub. This skill is essential for modern application deployment and enables your applications to run anywhere Docker is supported, from local development to cloud production environments.

Appendix A: Complete Application Source Code

Full source code for reference:

Application Structure

my-ecs-php-app/
├── Dockerfile
└── src/
    ├── index.html
    ├── contact_form.html
    ├── process_contact_form.php
    └── style.css

src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Welcome to Docker</title>
  <link rel="stylesheet" href="style.css">
</head>
<body class="landing-body">
  <div class="landing-container">
    <div class="landing-card">
      <h1>🚀 Welcome to Docker</h1>
      <p>Your PHP-enabled Docker container is running successfully!</p>
      <a href="contact_form.html" class="btn">Go to Contact Form</a>
    </div>
  </div>
</body>
</html>

src/contact_form.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Contact Form</title>
  <link rel="stylesheet" href="style.css">
</head>
<body class="form-body">
  <div class="form-container">
    <h1>Contact Form</h1>
    <form action="process_contact_form.php" method="POST">
      <label for="name">Name:</label>
      <input type="text" id="name" name="name" required>

      <label for="email">Email:</label>
      <input type="email" id="email" name="email" required>

      <label for="message">Message:</label>
      <textarea id="message" name="message" rows="5" required></textarea>

      <button type="submit">Send Message</button>
    </form>
    <p><a href="index.html">← Back to Home</a></p>
  </div>
</body>
</html>

src/process_contact_form.php

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $name = htmlspecialchars($_POST['name'] ?? '');
  $email = htmlspecialchars($_POST['email'] ?? '');
  $message = htmlspecialchars($_POST['message'] ?? '');
} else {
  header('Location: contact_form.html');
  exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Thank You</title>
  <link rel="stylesheet" href="style.css">
</head>
<body class="form-body">
  <div class="form-container">
    <h1 class="success-title">✅ Thank You!</h1>
    <div class="success">
      Your message has been received successfully!
    </div>
    <div class="form-data">
      <h3>Your submission:</h3>
      <p><strong>Name:</strong> <?php echo $name; ?></p>
      <p><strong>Email:</strong> <?php echo $email; ?></p>
      <p><strong>Message:</strong><br><?php echo nl2br($message); ?></p>
    </div>
    <p>
      <a href="index.html">← Back to Home</a> |
      <a href="contact_form.html">Send Another Message</a>
    </p>
  </div>
</body>
</html>

src/style.css

/* Global Styles */
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
}

/* Landing Page */
.landing-body {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.landing-container {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}
.landing-card {
  background: white;
  padding: 40px;
  border-radius: 10px;
  text-align: center;
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
.landing-card h1 {
  color: #333;
  margin-bottom: 20px;
}
.landing-card p {
  color: #666;
  margin-bottom: 30px;
}
.btn {
  display: inline-block;
  background: #007acc;
  color: white;
  padding: 15px 30px;
  text-decoration: none;
  border-radius: 5px;
  transition: background 0.3s;
}
.btn:hover {
  background: #005999;
}

/* Form Pages */
.form-body {
  background: #f5f5f5;
  padding: 20px;
}
.form-container {
  max-width: 500px;
  margin: 0 auto;
  background: white;
  padding: 30px;
  border-radius: 10px;
  box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.form-container h1 {
  text-align: center;
  color: #333;
  margin-bottom: 30px;
}
label {
  display: block;
  margin-bottom: 5px;
  margin-top: 15px;
  font-weight: bold;
  color: #555;
}
input, textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
  box-sizing: border-box;
}
button {
  width: 100%;
  background: #007acc;
  color: white;
  padding: 12px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
  margin-top: 20px;
}
button:hover {
  background: #005999;
}
.form-container p {
  text-align: center;
  margin-top: 20px;
}
.form-container a {
  color: #007acc;
  text-decoration: none;
}

/* Thank You Page */
.success-title {
  color: #28a745;
}
.success {
  background: #d4edda;
  border: 1px solid #c3e6cb;
  color: #155724;
  padding: 15px;
  border-radius: 5px;
  margin-bottom: 20px;
}
.form-data {
  background: #f8f9fa;
  padding: 15px;
  border-radius: 5px;
  margin-bottom: 20px;
}
.form-data strong {
  color: #333;
}

Dockerfile

# Use official PHP image with CLI and Alpine for smaller size
FROM php:8.2-cli-alpine

# Set working directory
WORKDIR /var/www/html

# Copy application files
COPY src/ .

# Expose port 8000
EXPOSE 8000

# Start PHP's built-in web server
CMD ["php", "-S", "0.0.0.0:8000"]

Appendix B: Automation Script

Advanced: Automated build and push script

For production workflows, you can automate the build and push process with a shell script. This script handles authentication, git-based versioning, and multi-architecture builds automatically.

scripts/build-and-push-dockerhub.sh

#!/bin/bash

# ============================================================================
# CONFIGURATION
# ============================================================================
APP_NAME="my-ecs-php-app"
PLATFORMS="linux/amd64,linux/arm64"

# Determine project root
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
if [ -f "${SCRIPT_DIR}/Dockerfile" ]; then
    PROJECT_ROOT="${SCRIPT_DIR}"
elif [ -f "${SCRIPT_DIR}/../Dockerfile" ]; then
    PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
else
    echo "Error: Dockerfile not found"
    exit 1
fi

# ============================================================================
# PHASE 1: DOCKER AUTHENTICATION
# ============================================================================
echo "Phase 1: Authenticating with Docker Hub..."

LOGIN_OUTPUT=$(docker login 2>&1)
echo "$LOGIN_OUTPUT"

if ! echo "$LOGIN_OUTPUT" | grep -q "Login Succeeded"; then
    echo "Docker login failed"
    exit 1
fi

DOCKER_USERNAME=$(echo "$LOGIN_OUTPUT" | grep -o '\[Username: [^]]*\]' | cut -d' ' -f2 | tr -d ']')
echo "Using username: ${DOCKER_USERNAME}"

# ============================================================================
# PHASE 2: TAG GENERATION
# ============================================================================
echo "Phase 2: Generating image tag..."

if git -C "${PROJECT_ROOT}" rev-parse HEAD >/dev/null 2>&1; then
    IMAGE_TAG=$(git -C "${PROJECT_ROOT}" rev-parse --short=7 HEAD)
    echo "Using git hash: ${IMAGE_TAG}"
    TAGS="--tag ${DOCKER_USERNAME}/${APP_NAME}:${IMAGE_TAG} --tag ${DOCKER_USERNAME}/${APP_NAME}:latest"
else
    IMAGE_TAG="latest"
    TAGS="--tag ${DOCKER_USERNAME}/${APP_NAME}:latest"
fi

# ============================================================================
# PHASE 3: BUILDER SETUP
# ============================================================================
echo "Phase 3: Setting up buildx builder..."

docker buildx create --name multiarch-builder --use --bootstrap 2>/dev/null || docker buildx use multiarch-builder

# ============================================================================
# PHASE 4: BUILD AND PUSH
# ============================================================================
echo "Phase 4: Building and pushing multi-architecture image..."

docker buildx build \
    --platform ${PLATFORMS} \
    ${TAGS} \
    --push \
    "${PROJECT_ROOT}"

# ============================================================================
# PHASE 5: VERIFICATION
# ============================================================================
echo "Phase 5: Verifying image..."

docker pull "${DOCKER_USERNAME}/${APP_NAME}:latest"

EXPOSED_PORT=$(grep -i "^EXPOSE" "${PROJECT_ROOT}/Dockerfile" | head -1 | awk '{print $2}')

echo ""
echo "Build completed successfully!"
echo "To run: docker run -p ${EXPOSED_PORT}:${EXPOSED_PORT} ${DOCKER_USERNAME}/${APP_NAME}:latest"

Script Features:

  • Automatic Docker Hub authentication check
  • Git-based versioning using commit hash
  • Multi-architecture build automation
  • Post-build verification
  • Automatic port detection from Dockerfile

Usage:

  1. Save the script to scripts/build-and-push-dockerhub.sh
  2. Make it executable: chmod +x scripts/build-and-push-dockerhub.sh
  3. Run it: ./scripts/build-and-push-dockerhub.sh