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
- Exercise Steps
- Common Issues
- Summary
- Going Deeper (Optional)
- Appendix A: Complete Application Source Code
- Appendix B: Automation Script
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
- Create the Application Files
- Write the Dockerfile
- Build and Test Locally
- Build Multi-Architecture Image
- 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.
Create the project structure:
mkdir my-ecs-php-app cd my-ecs-php-app mkdir srcCopy 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
- Create
ℹ 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.
Create a file named
Dockerfilein the project root (not insrc):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 containerCOPY src/ .- Copies your application files into the containerEXPOSE 8000- Documents which port the application usesCMD- Starts PHP’s built-in web server on all interfaces (0.0.0.0)⚠ Common Mistakes
- Using
127.0.0.1instead of0.0.0.0will prevent external connections- Forgetting the dot (
.) inCOPY 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
srcdirectory
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.
Build the Docker image:
docker build -t my-ecs-php-app:test .Run the container:
docker run -p 8000:8000 my-ecs-php-app:testTest 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
- Open your browser to
Stop the container:
- Press
Ctrl+Cin the terminal
- Press
ℹ Concept Deep Dive
The
-p 8000:8000flag 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 ofdocker buildwill 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.
Login to Docker Hub:
docker loginEnter your Docker Hub username and password when prompted.
Create a buildx builder:
docker buildx create --name multiarch-builder --use --bootstrapBuild for multiple architectures (replace
YOUR_USERNAMEwith 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_USERNAMEwill cause a permission error- The
--pushflag requires authentication withdocker loginfirst- 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.
Visit Docker Hub at https://hub.docker.com
Navigate to your repository:
- Click on “Repositories”
- Find
my-ecs-php-app - Click on it to view details
Check the architecture tags:
- You should see multiple architectures listed (amd64, arm64)
- Click on “Tags” to see available versions
Test pulling the image:
docker pull YOUR_USERNAME/my-ecs-php-app:latestRun the pulled image:
docker run -p 8000:8000 YOUR_USERNAME/my-ecs-php-app:latestVerify it works:
- Visit
http://localhost:8000 - Test the application functionality
- Visit
✓ 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 loginand ensure you’re using your correct Docker Hub username“no builder instance selected”: Run
docker buildx create --name multiarch-builder --usefirst“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:
- ✓ Runs consistently across different operating systems
- ✓ Works on both Intel/AMD and ARM processors
- ✓ Can be distributed globally through Docker Hub
- ✓ Simplifies deployment and scaling
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
ENVin Dockerfile- Implement multi-stage builds to reduce image size
- Add health checks with
HEALTHCHECKinstruction- 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:
- Save the script to
scripts/build-and-push-dockerhub.sh- Make it executable:
chmod +x scripts/build-and-push-dockerhub.sh- Run it:
./scripts/build-and-push-dockerhub.sh