Containers are the foundation of modern deployment. Building production-ready Docker images requires attention to security, performance, and maintainability. This guide covers essential practices for container excellence.
Optimized Multi-Stage Builds
# Production-ready Node.js Dockerfile
# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
# Install dependencies only when needed
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build application
RUN corepack enable pnpm && pnpm build
# Prune dev dependencies
RUN pnpm prune --prod
# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app
# Security: Create non-root user
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 appuser
# Set environment
ENV NODE_ENV=production
ENV PORT=3000
# Copy only production files
COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=builder --chown=appuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:nodejs /app/package.json ./
# Switch to non-root user
USER appuser
# Expose port
EXPOSE 3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"
# Start application
CMD ["node", "dist/index.js"]Security Hardening
# Security-focused Dockerfile
FROM node:20-alpine AS base
# Update packages and install security updates
RUN apk update && apk upgrade && \
# Remove unnecessary packages
apk del --purge apk-tools && \
# Remove caches
rm -rf /var/cache/apk/* /tmp/*
# Don't run as root
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Set restrictive permissions
RUN chmod 755 /app
USER appuser
# Prevent privilege escalation
# In docker-compose.yml or k8s manifest:
# security_context:
# run_as_non_root: true
# run_as_user: 1001
# read_only_root_filesystem: true
# allow_privilege_escalation: false
# capabilities:
# drop:
# - ALL# docker-compose.yml with security options
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: true
tmpfs:
- /tmp
user: "1001:1001"
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40sImage Optimization
# Optimized Python Dockerfile
FROM python:3.12-slim AS base
# Prevent Python from writing pyc files and buffering stdout/stderr
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
FROM base AS builder
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
FROM base AS production
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Create non-root user
RUN useradd --create-home --shell /bin/bash appuser
WORKDIR /home/appuser/app
# Copy application code
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "app:app"]Scanning and Testing
# Scan images for vulnerabilities
# Using Trivy (recommended)
trivy image --severity HIGH,CRITICAL myapp:latest
# Using Snyk
snyk container test myapp:latest
# Using Docker Scout
docker scout cves myapp:latest
# Lint Dockerfiles
hadolint Dockerfile
# Scan for secrets in image
trufflehog docker --image myapp:latest
# Check image size and layers
docker history myapp:latest
dive myapp:latest # Interactive layer explorerBest Practices Summary
Docker Best Practices Checklist
Build:
- Use multi-stage builds to minimize image size
- Pin specific base image versions
- Order layers from least to most frequently changed
- Use .dockerignore to exclude unnecessary files
Security:
- Never run as root in production
- Scan images for vulnerabilities regularly
- Use read-only root filesystem where possible
- Drop all capabilities and add only what's needed
Performance:
- Use slim/alpine base images when possible
- Combine RUN commands to reduce layers
- Leverage BuildKit cache mounts
- Use HEALTHCHECK for proper orchestration
Conclusion
Production-ready containers require attention to security, size optimization, and proper configuration. These practices ensure your containers are secure, efficient, and maintainable in production environments.
Need help with container strategy? Contact Jishu Labs for expert DevOps consulting and container optimization.
About David Kumar
David Kumar is the DevOps Lead at Jishu Labs with extensive experience in containerization and cloud infrastructure.