DockerDevOpsContainersSecurity
Docker Best Practices for Production Deployments
Essential Docker best practices for building secure, efficient, and production-ready container images — covering multi-stage builds, image optimization, security hardening, and container runtime configuration.
October 22, 2025·Phan Minh Anh
Docker in Production: The Basics Aren't Enough
Running docker run myapp works in development. Production requires hardened images, minimal attack surfaces, and efficient resource usage.
1. Multi-Stage Builds
Separate the build environment from the runtime image:
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Production stage
FROM node:20-alpine AS production
RUN addgroup -g 1001 -S nodejs \
&& adduser -S nextjs -u 1001
WORKDIR /app
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
USER nextjs
EXPOSE 3000
CMD ["node", "dist/server.js"]
Result: runtime image with no build tools, compilers, or development dependencies.
2. Run as Non-Root
Never run containers as root:
# Create a dedicated user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Set ownership
COPY --chown=appuser:appuser . .
# Switch to non-root
USER appuser
3. Use Specific Image Tags
# Bad: unpredictable, changes without warning
FROM node:latest
# Bad: still unpredictable
FROM node:20
# Good: fully reproducible
FROM node:20.15.1-alpine3.20
4. Minimize Layers and Image Size
# Bad: multiple RUN commands = multiple layers
RUN apt-get update
RUN apt-get install -y curl wget
RUN rm -rf /var/lib/apt/lists/*
# Good: single layer, clean up in same RUN
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl wget \
&& rm -rf /var/lib/apt/lists/*
5. Use .dockerignore
# .dockerignore
node_modules
.git
.env
*.log
coverage/
.DS_Store
Dockerfile
README.md
6. Health Checks
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
7. Resource Limits in docker-compose
services:
web:
image: myapp:v1.0.0
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
restart: unless-stopped
read_only: true
security_opt:
- no-new-privileges:true
Security Scanning
Always scan images before pushing to production:
# Trivy (recommended)
trivy image myapp:v1.0.0
# Docker Scout (built into Docker Desktop)
docker scout cves myapp:v1.0.0
Quick Wins Summary
| Practice | Impact |
|---|---|
| Multi-stage builds | Smaller images, no build tools in prod |
| Non-root user | Reduced blast radius if compromised |
| Pinned tags | Reproducible, predictable builds |
| Health checks | Self-healing with orchestrators |
| Resource limits | Prevent noisy neighbor issues |
| Image scanning | Catch CVEs before deployment |