Back to Blog

Docker Best Practices: Building Production-Ready Containers

DevOps Team
March 25, 2025
7 min read
Cloud & DevOps

Docker Best Practices: Building Production-Ready Containers

Docker has revolutionized application deployment, but building production-ready containers requires following best practices. Here’s a comprehensive guide to creating secure, efficient Docker images.

Dockerfile Optimization

Use Multi-Stage Builds

Reduce image size and improve security:

# Build stage
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Production stage
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

Minimize Layers

Each instruction creates a layer—combine commands when possible:

# ❌ Bad - multiple layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# ✅ Good - single layer
RUN apt-get update && \
    apt-get install -y curl git && \
    rm -rf /var/lib/apt/lists/*

Use .dockerignore

Exclude unnecessary files:

# .dockerignore
node_modules
npm-debug.log
.git
.env
*.md
.vscode
coverage
.DS_Store

Base Image Selection

Use Official Images

# ✅ Official image
FROM node:18-alpine

# ❌ Unknown source
FROM some-random-user/node

Choose Minimal Base Images

# Full image: 900MB
FROM node:18

# Slim image: 200MB
FROM node:18-slim

# Alpine image: 120MB
FROM node:18-alpine

Pin Specific Versions

# ❌ Bad - latest changes unexpectedly
FROM node:latest

# ✅ Good - reproducible builds
FROM node:18.17.0-alpine3.18

Security Best Practices

Don’t Run as Root

FROM node:18-alpine

# Create non-root user
RUN addgroup -g 1001 appgroup && \
    adduser -D -u 1001 -G appgroup appuser

WORKDIR /app
COPY --chown=appuser:appgroup . .

# Switch to non-root user
USER appuser

CMD ["node", "index.js"]

Scan for Vulnerabilities

# Use Docker Scout
docker scout cves my-image:latest

# Or Trivy
trivy image my-image:latest

Use Secret Management

# ❌ Bad - secrets in image
ENV API_KEY=secret123

# ✅ Good - secrets at runtime
# docker run -e API_KEY=$API_KEY my-image

Minimize Attack Surface

# Remove unnecessary packages
RUN apk add --no-cache python3 && \
    apk del apk-tools

# Don't include development tools in production

Performance Optimization

Layer Caching Strategy

Order instructions from least to most frequently changed:

# 1. Base image (rarely changes)
FROM node:18-alpine

# 2. System dependencies (rarely changes)
RUN apk add --no-cache python3 make g++

# 3. Application dependencies (changes occasionally)
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 4. Application code (changes frequently)
COPY . .

CMD ["node", "index.js"]

Use BuildKit

Enable advanced caching and parallel builds:

# Enable BuildKit
export DOCKER_BUILDKIT=1
docker build -t my-app .

# Or in docker-compose
COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build

Optimize Node.js Images

FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production && \
    npm cache clean --force

# Copy source
COPY . .

# Use production mode
ENV NODE_ENV=production

CMD ["node", "index.js"]

Resource Management

Set Resource Limits

# docker-compose.yml
services:
  app:
    image: my-app
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Health Checks

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js || exit 1

Logging and Monitoring

Log to STDOUT/STDERR

// Don't write logs to files inside container
console.log('Application started');
console.error('Error occurred');

Add Labels

LABEL maintainer="team@example.com"
LABEL version="1.0"
LABEL description="Production web application"
LABEL org.opencontainers.image.source="https://github.com/user/repo"

Development vs Production

Use Different Dockerfiles

# Dockerfile.dev
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

# Dockerfile.prod
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "index.js"]

Docker Compose for Development

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development

CI/CD Integration

GitHub Actions Example

name: Docker Build and Push

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2

      - name: Login to DockerHub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: user/app:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

Common Mistakes to Avoid

  1. ❌ Running containers as root
  2. ❌ Using latest tag in production
  3. ❌ Not using .dockerignore
  4. ❌ Installing unnecessary packages
  5. ❌ Storing secrets in images
  6. ❌ Not setting resource limits
  7. ❌ Ignoring security scans
  8. ❌ Poor layer caching strategy

Quick Checklist

  • ✅ Use official, minimal base images
  • ✅ Pin specific versions
  • ✅ Use multi-stage builds
  • ✅ Run as non-root user
  • ✅ Add .dockerignore
  • ✅ Scan for vulnerabilities
  • ✅ Set health checks
  • ✅ Add resource limits
  • ✅ Use BuildKit
  • ✅ Optimize layer caching

Conclusion

Building production-ready Docker images requires attention to security, performance, and maintainability. Follow these best practices to create containers that are secure, efficient, and easy to manage in production environments.

DockerDevOpsContainersSecurityBest Practices

Related Articles

Cloud & DevOps

Building Cloud-Native Applications: Best Practices for 2025

Explore modern cloud-native architecture patterns and best practices for building scalable, resilient applications in the cloud...

August 15, 2025
8 min
Read More
Security

Implementing ISO 27001: Lessons from Real Projects

What we learned helping companies achieve ISO 27001 certification and build robust security management systems...

October 15, 2025
5 min
Read More
Security & Compliance

Web3 Security: Protecting Smart Contracts and DApps

Essential security practices for blockchain developers building smart contracts and decentralized applications...

July 28, 2025
9 min
Read More