Docker Examples & Real-World Production Use Cases 🐳
A hands-on DevOps guide to practical containerization patterns. Master multi-stage image optimization, non-root security boundaries, persistent stateful database storage, custom user-defined bridge networks, and production container troubleshooting.
Published by AHP • cicdclouds.com
1. The Shift to Production Container Engineering
While running basic containers like docker run -d -p 80:80 nginx is sufficient for local development sandbox experiments, implementing Docker at enterprise scale requires rigorous discipline. In mission-critical environments, production containers must satisfy four non-negotiable operational standards:
- Minimal Attack Surface: Eliminating compilers, package managers, development headers, and shell interpreters from final production artifacts.
- Least-Privilege Process Execution: Running all application runtimes under dedicated unprivileged system users (non-root) to contain kernel escape vectors.
- Decoupled Persistence: Managing stateful data through independent Docker Named Volumes that survive container destruction, recreation, and scaling events.
- Deterministic Service Discovery: Isolating inter-container communication inside isolated bridge networks using embedded DNS resolution rather than legacy host port mappings.
2. Use Case 1: Multi-Stage Asset Compilation & Image Slimming
A common pitfall in frontend and compiled languages (such as Node.js, React, Go, and Java) is shipping the entire build toolchain (compilers, npm cache, git, build dependencies) into the release container. This practice balloons image sizes past 1.2 GB, increases deployment transfer latency across CI/CD runners, and introduces hundreds of unpatched Common Vulnerabilities and Exposures (CVEs).
The Solution: Multi-Stage Builds. By declaring multiple FROM directives, the build engine isolates compilation within an intermediate stage, copying only the compiled static binaries into a minimal, stripped-down production runtime layer.
Production Multi-Stage Dockerfile (React + Hardened Nginx)
# ---------------------------------------------------------------------
# STAGE 1: Compilation Environment (Disposable Build Container)
# ---------------------------------------------------------------------
FROM node:20-alpine AS build-stage
WORKDIR /app
# Cache package definitions independently of application source code
COPY package*.json ./
RUN npm ci --prefer-offline --no-audit
# Copy source assets and build static distribution bundles
COPY . .
RUN npm run build
# ---------------------------------------------------------------------
# STAGE 2: Minimal Production Runtime
# ---------------------------------------------------------------------
FROM nginx:1.27-alpine-slim AS production-stage
# Remove default static landing page assets
RUN rm -rf /usr/share/nginx/html/*
# Copy compiled production artifacts from build-stage only
COPY --from=build-stage /app/dist /usr/share/nginx/html
# Deploy custom high-performance Nginx configuration
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
# Security: Run non-privileged worker processes
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chmod -R 755 /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1
CMD ["nginx", "-g", "daemon off;"]
Architectural Comparison
| Evaluation Metric | Single-Stage Build | Multi-Stage Production Build |
|---|---|---|
| Final Image Footprint | ~1.15 GB (Includes node_modules & SDK) | ~24.8 MB (Static assets + Alpine Nginx) |
| Vulnerability Profile | Dozens of high/critical CVEs in package manager | Zero build-tool vulnerabilities |
| Deployment Network Transfer | Slow pull times across EKS / ECS nodes | Instantaneous sub-second image pull |
3. Use Case 2: Defense-in-Depth Non-Root User Hardening
By default, processes inside a container execute as user root (UID 0). While container runtimes employ Linux namespaces to isolate process trees, the container root UID is identical to the host kernel's root UID. If a remote code execution (RCE) vulnerability allows an attacker to break out of the container boundary, they immediately obtain full root privileges over the host machine.
Hardened Python 3.12 Flask / FastAPI Dockerfile
FROM python:3.12-slim
# Prevent Python from writing .pyc files and enable unbuffered logging
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /srv/api
# 1. Create a dedicated non-login system group and user with fixed UID/GID
RUN groupadd -r -g 10001 appgroup && \
useradd -r -u 10001 -g appgroup -s /sbin/nologin -d /srv/api appuser
# 2. Install dependencies separately for optimal layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# 3. Copy application codebase and assign strict ownership
COPY --chown=appuser:appgroup . .
# 4. Drop privileges: enforce execution as unprivileged user
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "main:app"]
4. Use Case 3: Stateful Multi-Container Stacks & Named Volumes
A common production architecture pairs a stateless application server with a persistent database. Storing database data directly inside a container's writable layer is fatal: any container crash, rebuild, or recreation destroys all transactional records.
The production pattern utilizes Docker Named Volumes for data persistence and a User-Defined Bridge Network for secure internal DNS resolution:
# 1. Create an isolated private custom bridge network
docker network create --driver bridge internal-tier
# 2. Provision a dedicated named volume for persistent database files
docker volume create mysql-production-data
# 3. Launch the MySQL Database Container on the private network
docker run -d \
--name db-backend \
--network internal-tier \
--restart unless-stopped \
--mount source=mysql-production-data,target=/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD="ProductionRootPassword123!" \
-e MYSQL_DATABASE="ecommerce_db" \
-e MYSQL_USER="appuser" \
-e MYSQL_PASSWORD="SecureAppPassword456!" \
mysql:8.0
# 4. Launch the Web Application referencing the DB by container name (DNS)
docker run -d \
--name web-frontend \
--network internal-tier \
--restart unless-stopped \
-p 8080:8000 \
-e DB_HOST="db-backend" \
-e DB_PORT="3306" \
-e DB_NAME="ecommerce_db" \
-e DB_USER="appuser" \
-e DB_PASSWORD="SecureAppPassword456!" \
cicdclouds/ecommerce-api:1.0.0
Architectural Note: Notice that MySQL does not expose Port 3306 to the host using-p 3306:3306. Because both containers reside in the sharedinternal-tiernetwork, the web frontend reaches MySQL over the internal bridge via the automatic DNS hostnamedb-backend:3306. The database is completely protected from external internet attacks.
5. Use Case 4: Disposable Ephemeral CLI Environments
Instead of polluting developer workstations or CI/CD runners with competing versions of Python, Terraform, AWS CLI, or database drivers, run them inside disposable ephemeral containers using the --rm flag. Once execution finishes, the container cleans itself up completely:
# 1. Run a complex Terraform plan without installing Terraform locally
docker run --rm -it \
-v $(pwd):/workspace \
-w /workspace \
-v ~/.aws:/root/.aws:ro \
hashicorp/terraform:1.7.0 plan
# 2. Execute a database backup dump to the host directory
docker run --rm \
--network internal-tier \
-v $(pwd)/backups:/backups \
mysql:8.0 \
mysqldump -h db-backend -u appuser -pSecureAppPassword456! ecommerce_db > ./backups/dump.sql
# 3. Run a zero-dependency static documentation server
docker run --rm -d \
--name doc-server \
-p 8080:80 \
-v $(pwd)/docs:/usr/share/nginx/html:ro \
nginx:alpine
6. Production Troubleshooting & Container Anti-Patterns
Diagnosing container failures in production environments requires understanding runtime signals, storage locks, and kernel resource boundaries.
1. The Zombie PID 1 Signal Trap
When specifying an execution command using the shell form (CMD python app.py), Docker starts the process via /bin/sh -c. The shell becomes Process ID 1 (PID 1). Because most shells do not forward standard UNIX termination signals (SIGTERM) to child processes, running docker stop will hang for 10 seconds before forcibly killing the container with SIGKILL.
Remedy: Always declare entrypoints in the Exec Form (e.g., CMD ["python", "app.py"]) or install an init system wrapper such as tini to ensure graceful connection draining.
2. Volume Permission Denied on Non-Root Containers
When mounting a host directory or newly initialized Docker volume to a non-root container, processes frequently fail with Permission Denied (errno 13) during write operations.
Remedy: Host mount paths must be pre-chowned on the host machine to match the numerical UID/GID of the container (e.g., sudo chown -R 10001:10001 ./data), or use Docker Named Volumes initialized with the correct permission boundaries.
3. Silent OOMKilled Containers (Exit Code 137)
Containers that suddenly disappear or crash during peak traffic spikes without generating application log stack traces are almost always victims of the Linux kernel Out-Of-Memory (OOM) Killer.
Diagnostic Command:
# Inspect container exit status code
docker inspect <container_id> --format='{{.State.ExitCode}} : OOMKilled={{.State.OOMKilled}}'
If OOMKilled=true and the exit code is 137, configure explicit memory reservations and ceiling limits via --memory="512m" --memory-reservation="256m" to prevent memory runaway.