PROJECT GUIDE · 03

Modernize the Application with Docker Containers

Package the ABC Retail web application into Docker containers. Write your first Dockerfile, build images, manage containers, configure volumes for persistent storage, and orchestrate multiple services with Docker Compose — all running on your Ubuntu VM.

Environment
Ubuntu VM in VirtualBox
Docker Engine on Linux
Difficulty
⭐⭐ Beginner–Intermediate
First containerization project
Course Module
Chapter 4
Virtualization & Containerization
Duration
2–3 Days
Docker + Compose + Registry

1. System Architecture – VM vs Container Comparison

Understanding the difference between virtual machines and containers is fundamental to modern cloud engineering. VMs virtualize hardware — each VM has its own full operating system kernel. Containers virtualize the operating system — they share the host kernel but have isolated user space. This makes containers much lighter, faster to start, and more portable than VMs.

VIRTUAL MACHINES (Before) Physical Server Hardware Host OS (your computer) + VirtualBox VM 1 Guest OS (1.5 GB) Binaries/Libraries Web App (10 MB) VM 2 Guest OS (1.5 GB) Binaries/Libraries DB App (25 MB) ~3 GB overhead per VM · Minutes to start "It works on my machine" problem remains 🐳 DOCKER CONTAINERS (After) Physical Server Hardware Host OS + Docker Engine (shared kernel) Container 1 App Libs (50 MB) 🌐 Web App (10 MB) Container 2 App Libs (80 MB) 🗄 Database (25 MB) Docker Volume (Persistent Data) ~50MB overhead · Seconds to start · Portable "Works everywhere" – same image on any machine
⚠ Prerequisite This project requires that you have completed Project 2. You must have a running Ubuntu 22.04 VM in VirtualBox. All commands in this project are run inside that VM's terminal.

2. Step-by-Step Action Items

PHASE 1 · STEP 1 Install Docker Engine on Ubuntu

Docker Engine is the core software that runs and manages containers. We will install the official Docker Engine from Docker's repository — this is more up-to-date than the version in Ubuntu's default package list.

1
Start your VM from Project 2 and log in. Open the terminal (or connect via SSH from your host machine).
2
Update the package index and install required packages:
sudo apt update sudo apt install -y ca-certificates curl gnupg lsb-release
These packages are prerequisites for Docker installation: ca-certificates allows Ubuntu to verify SSL certificates when downloading from the internet, curl downloads files from URLs, gnupg verifies cryptographic signatures (to ensure you're installing authentic Docker packages), and lsb-release provides information about the Linux distribution version.
3
Add Docker's official GPG key (this verifies Docker packages are genuine):
sudo mkdir -m 0755 -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
A GPG (GNU Privacy Guard) key is a cryptographic signature that proves Docker packages come from the real Docker company and haven't been tampered with. The curl -fsSL downloads the key silently, and gpg --dearmor converts it from ASCII armor format to binary format. Without this key, Ubuntu would refuse to install packages from Docker's repository as a security measure.
4
Add Docker's repository to Ubuntu's package sources:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
This command adds Docker's official repository to Ubuntu's list of software sources. The $(dpkg --print-architecture) automatically inserts your system's CPU architecture (amd64 for 64-bit x86), and $(lsb_release -cs) inserts your Ubuntu version codename (e.g., "jammy" for 22.04). This ensures you get the version of Docker built specifically for your system.
5
Update the package index to include Docker's repository and install Docker Engine:
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
This installs the Docker components: docker-ce is Docker Community Edition (the container engine), docker-ce-cli is the command-line interface for controlling Docker, containerd.io is the container runtime that actually starts and stops containers, docker-buildx-plugin provides advanced image building features, and docker-compose-plugin adds the ability to manage multi-container applications.
6
Verify Docker is installed and running:
sudo systemctl status docker
This shows whether the Docker service (called a "daemon" — a background process) is running. You should see Active: active (running) in green. Docker must be running as a background service for any Docker commands to work, just like how Nginx must be running to serve websites.
7
Add your user to the docker group so you don't need sudo for every Docker command:
sudo usermod -aG docker $USER newgrp docker
By default, only the root user can run Docker commands. Adding your user to the docker group grants permission to use Docker without typing sudo every time. The usermod -aG command appends your user to the group. The newgrp docker command applies the group change immediately in the current session without requiring a logout.
8
Test Docker with the Hello World container:
docker run hello-world
This command downloads a tiny test image from Docker Hub (Docker's public registry) and runs it as a container. The container simply prints a success message and exits. This proves that Docker can download images from the internet, create containers from them, and run them. If you see "Hello from Docker!" — Docker is fully functional. This is like running a "ping" test for your container environment.
9
Check Docker version information:
docker --version docker compose version
The docker --version command shows the installed Docker Engine version. The docker compose version (note: no hyphen in newer Docker versions) confirms Docker Compose is available. Write down these version numbers — they are part of your documentation deliverable.
PHASE 1 · STEP 2 Explore Docker – Images, Containers, and Registries

Before building our own image, explore how Docker works. These commands will help you understand the relationship between images (blueprints) and containers (running instances).

1
Pull an Ubuntu image from Docker Hub (this is like downloading a cloud VM image):
docker pull ubuntu:22.04
The docker pull command downloads a Docker image from a registry (Docker Hub by default). The image name format is imagename:tag where the tag specifies a version. ubuntu:22.04 downloads Ubuntu 22.04. This image is only ~29 MB — compared to the 1.5 GB Ubuntu ISO file — because it contains only the minimal OS components needed to run applications, not a full graphical desktop environment.
2
List all downloaded images:
docker images
The docker images command lists all Docker images stored locally on this machine. You should see the hello-world and ubuntu:22.04 images. The columns show: REPOSITORY (image name), TAG (version), IMAGE ID (unique identifier), CREATED (when the image was built), and SIZE. Images are the "blueprints" — they are read-only templates. Running an image creates a container (the living instance).
3
Run an interactive Ubuntu container to explore it:
docker run -it --name my-ubuntu-test ubuntu:22.04 /bin/bash
This command creates and starts a container from the ubuntu:22.04 image. The -it flags mean: -i (interactive — keep STDIN open) and -t (tty — allocate a terminal). --name my-ubuntu-test gives the container a memorable name. /bin/bash is the command to run inside the container — starting an interactive bash shell. You will see the prompt change to something like root@abc123456:/# — you are now inside the container!
4
Inside the container, run some commands to see that it is isolated:
ls / cat /etc/os-release hostname whoami
These commands explore the container's isolated environment: ls / lists the root directory (you'll see typical Linux directories), cat /etc/os-release shows the Ubuntu 22.04 OS information, hostname shows a randomly generated container ID as the hostname, and whoami shows you're running as root inside the container. The container has its own isolated filesystem, hostname, and user space — even though it shares the host system's kernel.
5
Exit the container by typing:
exit
Typing exit closes the bash shell inside the container, which causes the container to stop (since bash was the only process running in it). You are back in your Ubuntu VM's terminal. The container still exists in a "stopped" state — you can restart it or delete it.
6
List all containers (running and stopped):
docker ps -a
The docker ps command lists running containers. The -a flag (all) shows both running and stopped containers. You should see your my-ubuntu-test container with STATUS "Exited". The CONTAINER ID column shows the unique ID assigned to each container. Think of containers like processes — they can be started, stopped, and removed independently of their image.
7
Remove the test container to clean up:
docker rm my-ubuntu-test
The docker rm command removes (deletes) a stopped container. Once a container is removed, any changes made inside it are permanently lost (unless you saved data to a Docker Volume). The underlying image (ubuntu:22.04) remains — you can always create a new container from it. This is a key difference between images (permanent, shareable) and containers (temporary instances).
PHASE 2 · STEP 1 Create a Project Directory and Write Your First Dockerfile

A Dockerfile is a text file containing instructions to build a custom Docker image. Think of it as a recipe — each line is an instruction that adds a layer to the image. This is where containerization begins: packaging your application with all its dependencies.

1
Create the project directory structure:
mkdir -p ~/abc-retail-app/{app,nginx-config} cd ~/abc-retail-app
We create a dedicated project directory at ~/abc-retail-app (the tilde ~ represents your home directory). Inside it, we create app/ for the application code and nginx-config/ for the web server configuration. Good directory organization is essential in production projects — it makes the project understandable to other engineers and your future self.
2
Create the web application files:
cat > ~/abc-retail-app/app/index.html << 'EOF' <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ABC Retail – Containerized</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { background: #0f172a; color: #f8fafc; font-family: Arial, sans-serif; } .header { background: linear-gradient(135deg, #1e293b, #0c2545); padding: 48px 24px; text-align: center; border-bottom: 1px solid #334155; } h1 { color: #38bdf8; font-size: 32px; margin-bottom: 8px; } .badge { display: inline-block; background: rgba(52,211,153,0.15); border: 1px solid #34d399; color: #34d399; padding: 6px 16px; border-radius: 999px; margin: 12px 0; } .cards { display: flex; gap: 16px; flex-wrap: wrap; padding: 32px 24px; justify-content: center; } .card { background: #1e293b; border: 1px solid #334155; border-radius: 10px; padding: 20px; width: 200px; text-align: center; } .card h3 { color: #38bdf8; margin-bottom: 8px; } .card p { color: #94a3b8; font-size: 13px; } .footer { text-align: center; padding: 24px; color: #64748b; font-size: 12px; border-top: 1px solid #334155; margin-top: 20px; font-family: monospace; } </style> </head> <body> <div class="header"> <h1>🐳 ABC Retail Pvt. Ltd.</h1> <p style="color:#94a3b8">Enterprise E-Commerce Platform</p> <div class="badge">✅ Running Inside Docker Container</div> </div> <div class="cards"> <div class="card"><h3>🛒</h3><h3>Shop</h3><p>Browse 10,000+ products</p></div> <div class="card"><h3>📦</h3><h3>Orders</h3><p>Track your deliveries</p></div> <div class="card"><h3>👤</h3><h3>Account</h3><p>Manage your profile</p></div> <div class="card"><h3>📞</h3><h3>Support</h3><p>24/7 customer care</p></div> </div> <div class="footer">Containerized with Docker · Nginx Web Server · Ubuntu 22.04 LTS</div> </body> </html> EOF
This creates a complete, professional-looking HTML page for the ABC Retail website. It will be served from inside the Docker container. In a real project, this would be your compiled frontend application (React, Vue, Angular build output, etc.) or a backend application. The important thing is that ALL files this application needs will be packaged INSIDE the Docker image.
3
Create the Dockerfile: This is the most important file in this project.
nano ~/abc-retail-app/Dockerfile
4
In nano, type the complete Dockerfile:
# ============================================== # Dockerfile for ABC Retail Web Application # Base image: Official Nginx on Alpine Linux # ============================================== # INSTRUCTION 1: FROM - Choose the base image FROM nginx:alpine # INSTRUCTION 2: LABEL - Add metadata to the image LABEL maintainer="cloudadmin@abc-retail.com" LABEL version="1.0" LABEL description="ABC Retail Enterprise Web Application" # INSTRUCTION 3: RUN - Execute commands during image build RUN apk update && apk add --no-cache curl # INSTRUCTION 4: COPY - Copy files from host into the image COPY app/ /usr/share/nginx/html/ # INSTRUCTION 5: EXPOSE - Document which port the container listens on EXPOSE 80 # INSTRUCTION 6: HEALTHCHECK - Verify the container is healthy HEALTHCHECK --interval=30s --timeout=3s \ CMD curl -f http://localhost/ || exit 1 # INSTRUCTION 7: CMD - The default command to run when container starts CMD ["nginx", "-g", "daemon off;"]
Each instruction in a Dockerfile creates a new layer in the Docker image: FROM — every Dockerfile must start with a base image. We use nginx:alpine (Nginx on Alpine Linux, a tiny 5 MB Linux distribution) to keep the image small. LABEL — adds metadata like author and version. RUN — executes shell commands during build (here, updating Alpine's package list and installing curl for health checks). COPY — copies files from your local machine into the image. EXPOSE — documents that the container listens on port 80 (HTTP). HEALTHCHECK — tells Docker how to verify the container is working correctly. CMD — the command that runs when the container starts (Nginx in foreground mode).
5
Press Ctrl+O then Enter to save, then Ctrl+X to exit nano.
6
Create a .dockerignore file to exclude unnecessary files from the build:
cat > ~/abc-retail-app/.dockerignore << 'EOF' .git *.md *.log node_modules .env EOF
The .dockerignore file works like .gitignore — it lists patterns of files that should NOT be copied into the Docker image. Excluding .git (version control history), log files, node_modules (reinstalled inside the container), and .env files (may contain secrets) keeps the image small and prevents accidentally exposing sensitive information. A well-crafted .dockerignore can reduce image size by hundreds of MB in larger projects.
PHASE 2 · STEP 2 Build the Docker Image

Building a Docker image reads your Dockerfile and executes each instruction to create a layered, immutable image file. This is the "compilation" step in containerization.

1
Make sure you are in the project directory:
cd ~/abc-retail-app ls -la
Always verify you are in the correct directory before building. The ls -la command lists all files including hidden ones (like .dockerignore). You should see: Dockerfile, app/ directory, nginx-config/ directory, and .dockerignore. If any are missing, go back and create them before proceeding.
2
Build the Docker image:
docker build -t abc-retail:v1.0 .
The docker build command reads the Dockerfile and creates an image. The -t abc-retail:v1.0 flag "tags" (names and versions) the image as abc-retail with version v1.0. The . (dot) at the end tells Docker to look for the Dockerfile in the CURRENT directory. Watch the output — you will see each Dockerfile instruction executing in sequence. Each instruction creates a new layer that is cached for future builds.
3
List your images to see the newly built image:
docker images
You should now see abc-retail with tag v1.0 in the list. Notice that the image size is very small — around 15–25 MB — because we built on the Alpine Linux base. Compare this to the full Ubuntu VM which was 20 GB. This is the power of containers: everything needed to run the application is included, but nothing unnecessary is included.
4
Inspect the image to see its layers:
docker history abc-retail:v1.0
The docker history command shows all the layers that make up the image, from bottom (base) to top (your instructions). Each row corresponds to a Dockerfile instruction. The SIZE column shows how much each layer added to the image. Understanding image layers is important for optimization — large layers (e.g., a 500 MB RUN command) should be analyzed and reduced if possible.
PHASE 2 · STEP 3 Run the Application Container

Now you will run your custom image as a container and verify the ABC Retail website is accessible.

1
Run the container in detached mode (background):
docker run -d \ --name abc-retail-web \ -p 8080:80 \ --restart unless-stopped \ abc-retail:v1.0
Breaking down each flag: -d runs the container in "detached" mode (in the background, so it doesn't block your terminal), --name abc-retail-web gives it a memorable name for easy management, -p 8080:80 maps port 8080 on the HOST (your Ubuntu VM) to port 80 INSIDE the container (where Nginx listens) — this is called "port publishing", and --restart unless-stopped automatically restarts the container if it crashes or if the Docker service restarts, except when manually stopped. This restart policy is the Docker equivalent of systemctl enable.
2
Check that the container is running:
docker ps
The docker ps command (without -a) shows only RUNNING containers. You should see abc-retail-web with STATUS "Up X seconds" and PORTS showing 0.0.0.0:8080->80/tcp. The arrow (->) shows the port mapping: traffic arriving on port 8080 of the host is forwarded to port 80 inside the container. The 0.0.0.0 means it listens on all network interfaces.
3
Test the website from inside the VM:
curl http://localhost:8080
This sends an HTTP request to port 8080 on the local machine. Docker forwards it to port 80 inside the container, where Nginx serves the HTML file. You should see the HTML content of your ABC Retail website. The fact that the website is served through Docker port mapping rather than directly through Nginx on the VM is the fundamental difference between the old deployment (Project 2) and the containerized deployment (this project).
4
Access from your host browser: Open your browser on your main computer and go to http://[VM-IP]:8080 (use the same VM IP from Project 2 but with port 8080). Take a screenshot — this is a deliverable.
5
View container logs:
docker logs abc-retail-web
The docker logs command retrieves all output that the container has written to stdout and stderr since it started. For Nginx, this shows access logs (every HTTP request received) and error logs. In a production environment, these logs would be collected by a centralized logging system (like ElasticSearch or AWS CloudWatch). You should see entries for the curl request and browser requests you just made.
6
Check the container's health status:
docker inspect --format='{{.State.Health.Status}}' abc-retail-web
The docker inspect command returns detailed JSON information about a container. The --format flag uses Go templating to extract just the health status field. You should see healthy — which means the HEALTHCHECK instruction in your Dockerfile successfully ran the curl command and got a response from Nginx. The health check runs every 30 seconds (as configured). A container that fails health checks would show "unhealthy" — this triggers alerts in production monitoring systems.
PHASE 2 · STEP 4 Configure Docker Volumes for Persistent Data

By default, any data written inside a container is lost when the container is removed. Docker Volumes solve this by storing data outside the container filesystem in a special Docker-managed location on the host.

1
Create a named Docker volume:
docker volume create abc-retail-data
The docker volume create command creates a named volume — a Docker-managed storage location on the host filesystem (usually in /var/lib/docker/volumes/). Named volumes are more portable and manageable than "bind mounts" (which directly map host directories). Think of Docker volumes as the equivalent of cloud block storage (EBS volumes) — they exist independently of any specific container.
2
List all volumes:
docker volume ls
This lists all Docker volumes on the system. You should see abc-retail-data with DRIVER local. The local driver stores data on the local filesystem. In cloud environments, you would use volume drivers that connect to cloud storage services (like AWS EFS or Azure Files), allowing multiple containers to share the same volume across different servers.
3
Run a new container that uses the volume for persistent log storage:
docker stop abc-retail-web docker rm abc-retail-web docker run -d \ --name abc-retail-web \ -p 8080:80 \ --restart unless-stopped \ -v abc-retail-data:/var/log/nginx \ abc-retail:v1.0
We stop and remove the old container, then create a new one with an additional flag: -v abc-retail-data:/var/log/nginx. This mounts the abc-retail-data Docker volume to the /var/log/nginx directory inside the container (where Nginx writes its logs). Now, even if the container is removed and recreated, the log files persist in the volume. This is the key difference between persistent and ephemeral container storage.
4
Verify the volume is being used:
docker inspect abc-retail-web | grep -A 5 "Mounts"
This command inspects the container and searches for the "Mounts" section in the JSON output. You should see that the abc-retail-data volume is mounted at /var/log/nginx inside the container. The output shows the source path on the host (in /var/lib/docker/volumes/) and the destination path inside the container — this confirms the volume is correctly connected.
PHASE 3 · STEP 1 Build Multi-Container App with Docker Compose

Real applications require multiple services — a web server, a database, a cache. Docker Compose lets you define and run multi-container applications using a single YAML configuration file. This is much more manageable than running multiple separate docker run commands.

1
Stop the running container (Compose will manage it instead):
docker stop abc-retail-web docker rm abc-retail-web
2
Create the Docker Compose file:
nano ~/abc-retail-app/docker-compose.yml
3
Type the complete Docker Compose configuration:
# ============================================== # Docker Compose for ABC Retail Platform # Version: 3.8 (Compose file format) # ============================================== version: '3.8' services: # ---- Web Server Service ---- web: build: context: . dockerfile: Dockerfile image: abc-retail:v1.0 container_name: abc-retail-web ports: - "8080:80" # HOST_PORT:CONTAINER_PORT volumes: - web-logs:/var/log/nginx # Persistent log storage restart: unless-stopped networks: - abc-retail-net healthcheck: test: ["CMD", "curl", "-f", "http://localhost/"] interval: 30s timeout: 5s retries: 3 # ---- Database Service (MariaDB) ---- database: image: mariadb:10.11 container_name: abc-retail-db environment: MYSQL_ROOT_PASSWORD: "SecureRootPass@2024" MYSQL_DATABASE: "abc_retail" MYSQL_USER: "retailuser" MYSQL_PASSWORD: "RetailPass@2024" volumes: - db-data:/var/lib/mysql # Persistent database storage networks: - abc-retail-net restart: unless-stopped healthcheck: test: ["CMD", "healthcheck.sh", "--su-mysql", "--connect", "--innodb_initialized"] interval: 30s timeout: 10s retries: 5 # ---- Networks ---- networks: abc-retail-net: driver: bridge # Isolated network for container communication # ---- Volumes ---- volumes: web-logs: # Nginx access and error logs driver: local db-data: # MariaDB database files driver: local
This Docker Compose file defines two services: web (the Nginx-based web server from your Dockerfile) and database (MariaDB, an open-source MySQL-compatible database). Key concepts: services — each named block under "services" is a container; networks — both containers are on the same abc-retail-net network, allowing them to communicate by service name (e.g., the web container can reach the database at hostname "database"); volumes — named volumes for persistent data; environment — environment variables passed to the container at startup (used to configure MariaDB). In production, passwords would be stored in Docker Secrets or a vault rather than in this file.
4
Save and exit nano: Ctrl+O, Enter, Ctrl+X.
5
Start all services with Docker Compose:
cd ~/abc-retail-app docker compose up -d
The docker compose up command reads docker-compose.yml, creates the network and volumes defined in it, and starts all services in the correct order. The -d flag runs everything in detached (background) mode. Docker Compose is smart about dependencies — if service A depends on service B, it starts B first. Watch the output: you'll see Docker pulling the MariaDB image, creating the network, creating volumes, and starting both containers.
6
Check all services are running:
docker compose ps
The docker compose ps command shows the status of all services defined in your Compose file. You should see both abc-retail-web and abc-retail-db with STATUS "Up". The database may show "Up (health: starting)" initially — wait about 30 seconds for MariaDB to fully initialize, then run the command again. It should show "Up (healthy)".
7
View logs from all services:
docker compose logs --tail=20
This shows the last 20 log lines from all services combined, with each line prefixed by the service name. You can see Nginx startup messages and MariaDB initialization messages together. This is invaluable for debugging multi-container applications — when something goes wrong, you can see logs from all services in one place rather than checking each container separately.
8
Test database connectivity from the web container:
docker exec abc-retail-web ping database -c 3
The docker exec command runs a command inside an already-running container. Here we run ping database -c 3 inside the web container, pinging the "database" service by name (-c 3 means send 3 pings). This works because Docker Compose creates a shared network where containers can reach each other by their service names. You should see ping responses — proving that the web and database containers can communicate. This simulates a real application architecture where the web server connects to the database.
PHASE 3 · STEP 2 Tag Your Image and Push to Docker Hub (Registry)

Docker Hub is the world's largest container registry — it's where Docker images are stored and shared. Pushing your image to Docker Hub means it can be pulled and run on ANY machine with Docker, anywhere in the world.

1
Create a free Docker Hub account: Go to https://hub.docker.com → click "Sign Up" → fill in your username, email, and password → verify your email.
2
Log in to Docker Hub from your VM terminal:
docker login
This command prompts you to enter your Docker Hub username and password. After successful login, Docker saves a token to ~/.docker/config.json so you don't need to log in again for future push/pull operations. In production pipelines, a service account token or robot account is used instead of personal credentials.
3
Tag the image with your Docker Hub username:
docker tag abc-retail:v1.0 YOUR_DOCKERHUB_USERNAME/abc-retail:v1.0 docker tag abc-retail:v1.0 YOUR_DOCKERHUB_USERNAME/abc-retail:latest
Replace YOUR_DOCKERHUB_USERNAME with your actual Docker Hub username. The docker tag command creates an alias for an existing image with a new name. We create two tags: v1.0 (specific version) and latest (always points to the most recent version). In production, never rely only on latest — versioned tags allow you to roll back to a specific version if the latest has a bug.
4
Push the image to Docker Hub:
docker push YOUR_DOCKERHUB_USERNAME/abc-retail:v1.0 docker push YOUR_DOCKERHUB_USERNAME/abc-retail:latest
The docker push command uploads the image to Docker Hub. Docker uploads each layer separately — if any layer was previously pushed, it is skipped (only changed layers are uploaded). After pushing, anyone with internet access can run your container by pulling this image. Go to https://hub.docker.com/r/YOUR_USERNAME/abc-retail in your browser — you should see your image listed there!
5
Verify by pulling the image fresh (as if you were on a different machine):
docker pull YOUR_DOCKERHUB_USERNAME/abc-retail:v1.0 docker run -d -p 9090:80 YOUR_DOCKERHUB_USERNAME/abc-retail:v1.0
First we pull the image as a fresh download (simulating a different machine), then run it on port 9090. If you access http://[VM-IP]:9090 in your browser and see the ABC Retail website — you have successfully demonstrated that your containerized application is portable and can be deployed on any Docker host worldwide. This is the fundamental value proposition of containers.

3. Container Build & Deployment Pipeline

This diagram shows the Docker container lifecycle — from source code to running container to registry.

📝 WRITE CODE index.html Dockerfile 🔨 BUILD IMAGE docker build Layers created TEST IMAGE docker run Health check 🏷 TAG IMAGE docker tag v1.0 + latest PUSH REGISTRY docker push Docker Hub 🚀 DEPLOY ANYWHERE Any Docker host Cloud or local ✓ One image · Any environment · Consistent behavior guaranteed

4. Complete Deliverable Assets & Templates

Dockerfile — Production-Ready ABC Retail Container Image
# ============================================== # Dockerfile — ABC Retail Web Application v1.0 # Build: docker build -t abc-retail:v1.0 . # Run: docker compose up -d # ============================================== # Multi-stage build: Stage 1 — Build stage (not in final image) FROM alpine:3.18 AS builder WORKDIR /build COPY app/ . # In a real app, you'd run: npm install && npm run build # Stage 2 — Production image (slim) FROM nginx:1.25-alpine # Metadata labels LABEL maintainer="cloudadmin@abc-retail.com" LABEL version="1.0.0" LABEL org.opencontainers.image.title="ABC Retail Web" # Install health check dependency RUN apk add --no-cache curl # Create non-root user for security RUN adduser -D -H -u 1001 webuser # Copy built application from builder stage COPY --from=builder /build/ /usr/share/nginx/html/ # Set correct permissions RUN chown -R webuser:webuser /usr/share/nginx/html # Document listening port EXPOSE 80 # Health check every 30s HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD curl -f http://localhost/ || exit 1 # Start Nginx in foreground CMD ["nginx", "-g", "daemon off;"]
docker-compose.yml — Complete Multi-Service Stack
version: '3.8' services: web: build: . image: abc-retail:v1.0 container_name: abc-retail-web ports: ["8080:80"] volumes: ["web-logs:/var/log/nginx"] networks: [abc-retail-net] restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost/"] interval: 30s timeout: 5s retries: 3 database: image: mariadb:10.11 container_name: abc-retail-db environment: MYSQL_ROOT_PASSWORD: "SecureRoot@2024" MYSQL_DATABASE: abc_retail MYSQL_USER: retailuser MYSQL_PASSWORD: "RetailPass@2024" volumes: ["db-data:/var/lib/mysql"] networks: [abc-retail-net] restart: unless-stopped networks: abc-retail-net: {driver: bridge} volumes: web-logs: {driver: local} db-data: {driver: local}

5. Deliverables Summary

📄 Files to Submit

  • Dockerfile (production-ready)
  • docker-compose.yml
  • .dockerignore
  • app/index.html (website)
  • Screenshot: docker images output
  • Screenshot: docker compose ps
  • Screenshot: website in browser (port 8080)
  • Screenshot: Docker Hub showing your pushed image
  • README.md explaining deployment steps

✅ Verification Checklist

  • Docker Engine installed and running
  • docker run hello-world succeeds
  • Dockerfile is correct (all 7 instructions present)
  • Image built with docker build
  • Container running and accessible at port 8080
  • Health check shows "healthy" status
  • Docker volume created and mounted
  • Docker Compose starts both web + database
  • Web container can ping database by name
  • Image pushed to Docker Hub registry

6. Why We Did This & What It Accomplishes

Strategic Intent & Operational Impact

Why We Did This

What This Accomplishes