10 Docker Best Practices for Production-Ready Applications
10 Docker Best Practices for Production-Ready Applications
Docker has revolutionized how we build and deploy applications. Here are 10 essential best practices to ensure your containers are production-ready.
1. Use Official Base Images
Always start with official images from Docker Hub.
# Good ✅ FROM node:20-alpine # Bad ❌ FROM some-random-user/node
Why? Official images are:
- Regularly updated
- Security patched
- Well documented
- Optimized for size
2. Minimize Layer Count
Each RUN, COPY, and ADD creates a new layer. Combine commands when possible.
# Good ✅ RUN apt-get update && apt-get install -y curl git && apt-get clean && rm -rf /var/lib/apt/lists/* # Bad ❌ RUN apt-get update RUN apt-get install -y curl RUN apt-get install -y git
3. Use .dockerignore
Prevent unnecessary files from bloating your image.
# .dockerignore node_modules npm-debug.log .git .env .DS_Store *.md dist coverage
4. Run as Non-Root User
Never run containers as root in production.
# Create user RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 # Switch to user USER nextjs # Run application CMD ["node", "server.js"]
5. Use Multi-Stage Builds
Reduce final image size dramatically.
# Build stage FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Production stage FROM node:20-alpine AS runner WORKDIR /app COPY /app/dist ./dist COPY /app/node_modules ./node_modules CMD ["node", "dist/server.js"]
6. Set Proper Health Checks
Enable automatic container health monitoring.
HEALTHCHECK CMD node healthcheck.js || exit 1
7. Use Specific Image Tags
Never use 'latest' in production.
# Good ✅ FROM node:20.10.0-alpine3.18 # Bad ❌ FROM node:latest
8. Optimize Layer Caching
Place frequently changing files at the end.
# Copy package files first (rarely change) COPY package*.json ./ RUN npm ci # Copy source code last (changes frequently) COPY . . RUN npm run build
9. Set Resource Limits
Prevent containers from consuming all resources.
# docker-compose.yml services: app: image: myapp:1.0 deploy: resources: limits: cpus: '0.5' memory: 512M reservations: cpus: '0.25' memory: 256M
10. Scan for Vulnerabilities
Regularly scan images for security issues.
# Using Docker Scout docker scout cves myapp:1.0 # Using Trivy trivy image myapp:1.0 # Using Snyk snyk container test myapp:1.0
Complete Example
Here's a production-ready Dockerfile incorporating all best practices:
# Multi-stage build FROM node:20.10.0-alpine3.18 AS builder # Set working directory WORKDIR /app # Copy package files COPY package*.json ./ # Install dependencies RUN npm ci --only=production && npm cache clean --force # Copy source COPY . . # Build application RUN npm run build # Production stage FROM node:20.10.0-alpine3.18 AS runner # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init # Create non-root user RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 # Set working directory WORKDIR /app # Copy built files COPY /app/dist ./dist COPY /app/node_modules ./node_modules COPY /app/package.json ./ # Switch to non-root user USER nextjs # Expose port EXPOSE 3000 # Health check HEALTHCHECK CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))" # Use dumb-init to handle signals ENTRYPOINT ["dumb-init", "--"] # Start application CMD ["node", "dist/server.js"]
Conclusion
Following these Docker best practices will result in:
- 🔒 More secure containers
- 🚀 Faster builds and deployments
- 💾 Smaller image sizes
- 🛡️ Better reliability
- 📊 Easier debugging
Start implementing these today for production-ready containers! 🐋