Official guide for hardening Docker in production environments. docs.docker.com/security →
Never use latest in production. You need to know exactly what version is running and be able to roll back to any previous version.
# Bad: latest is a moving target — no reproducibility
docker pull myapp:latest
# Good: tag by git commit SHA (immutable, traceable)
GIT_SHA=$(git rev-parse --short HEAD)
docker build -t myapp:${GIT_SHA} .
docker push myapp:${GIT_SHA}
# Also tag as "latest" for convenience (not for deployment)
docker tag myapp:${GIT_SHA} myapp:latest
# In CI/CD: tag by version + sha
docker build -t myapp:v1.2.3-abc1234 .
A registry stores and serves your Docker images. In production you need a private registry — you don't want your images public.
| Registry | Best for | Notes |
|---|---|---|
| Docker Hub | Public open-source images | Free tier limits pulls. Private repos cost money. |
| AWS ECR | AWS deployments | Native integration with ECS/EC2. Pay per storage. |
| GitHub GHCR | GitHub Actions workflows | Free for public, integrated with GHCR token auth. |
| Google Artifact Registry | GCP deployments | Replaces old GCR. |
# Authenticate with AWS ECR
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin \
123456789.dkr.ecr.us-east-1.amazonaws.com
# Push to ECR
docker tag myapp:abc1234 \
123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:abc1234
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:abc1234
# Authenticate with GHCR (GitHub Container Registry)
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
docker tag myapp:abc1234 ghcr.io/username/myapp:abc1234
docker push ghcr.io/username/myapp:abc1234
By default, containers run as root. If an attacker exploits your app, they get root inside the container. Always use a non-root USER in your Dockerfile (covered in Lesson 06).
# Container can't write to filesystem (except explicit volumes)
docker run --read-only \
-v /tmp:/tmp \
-v /app/logs:/app/logs \
myapp:abc1234
# Prevent a runaway container from consuming all server resources
docker run \
--memory="512m" \
--cpus="0.5" \
myapp:abc1234
# In docker-compose.yml:
services:
api:
deploy:
resources:
limits:
memory: 512M
cpus: '0.50'
# Docker Scout (built-in)
docker scout quickview myapp:abc1234
docker scout cves myapp:abc1234
# Trivy (open source, use in CI/CD)
trivy image myapp:abc1234
Already covered in Lesson 06. Never skip this.
# Configure log rotation (prevents disk fill)
docker run \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp:abc1234
# In docker-compose.yml:
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
Docker's health check lets orchestrators know if your app is truly healthy, not just started. In production, always expose a /health endpoint in your API and reference it in the HEALTHCHECK instruction (covered in Lesson 06) and Compose file.