Docker Guidelines
Core Principles
- Minimal images - Include only what's needed
- Reproducible builds - Pin versions, use multi-stage
- Security - Don't run as root, scan for vulnerabilities
- Layer optimization - Order commands for caching
- Health checks - Verify container health
Dockerfile Best Practices
Multi-Stage Builds
# Stage 1: Build
FROM {{BASE_IMAGE}} AS builder
WORKDIR /app
# Copy package files first (better caching)
COPY package*.json ./
RUN npm ci
# Copy source and build
COPY . .
RUN npm run build
# Stage 2: Production
FROM {{BASE_IMAGE}} AS production
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Copy only production dependencies
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# Copy built artifacts
COPY --from=builder /app/dist ./dist
# Use non-root user
USER nodejs
# Expose port
EXPOSE {{APP_PORT}}
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:{{APP_PORT}}/health || exit 1
# Start application
CMD ["node", "{{BUILD_OUTPUT}}/{{ENTRY_POINT}}"]
Layer Optimization
# Bad - busts cache on any file change
COPY . .
RUN npm install
# Good - dependencies cached separately
COPY package*.json ./
RUN npm ci
COPY . .
Minimize Image Size
# Use alpine images
FROM {{BASE_IMAGE}}
# Clean up in same layer
RUN apk add --no-cache python3 make g++ && \
npm ci && \
apk del python3 make g++
# Use .dockerignore
# .dockerignore:
# node_modules
# .git
# *.md
# .env*
# coverage
# dist
Security
Non-Root User
# Create and use non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
# Set ownership
COPY --chown=appuser:appgroup . .
# Switch to non-root user
USER appuser
No Secrets in Images
# Bad - secret in image history
ARG API_KEY
ENV API_KEY=$API_KEY
# Good - use runtime secrets
# Pass at runtime: docker run -e API_KEY=xxx myapp
# Or use Docker secrets
docker secret create api_key ./secret.txt
docker service create --secret api_key myapp
Health Checks
Application Health Check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:{{APP_PORT}}/health || exit 1
Known Gotchas
Build Context
# .dockerignore is crucial for performance
# Exclude:
node_modules
.git
*.md
.env*
coverage
dist
.DS_Store
Cache Invalidation
# ARG invalidates cache for subsequent layers
ARG CACHEBUST=1
RUN npm ci # Runs every time if CACHEBUST changes
# Better: Use BuildKit cache mounts
RUN --mount=type=cache,target=/root/.npm npm ci
Signal Handling
# PID 1 problem - use dumb-init or tini
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "{{BUILD_OUTPUT}}/{{ENTRY_POINT}}"]
# Or use exec form (doesn't use shell)
CMD ["node", "{{BUILD_OUTPUT}}/{{ENTRY_POINT}}"] # Good - node is PID 1
CMD node {{BUILD_OUTPUT}}/{{ENTRY_POINT}} # Bad - shell is PID 1
Environment vs ARG
# ARG - build-time only
ARG NODE_VERSION=20
# ENV - runtime available
ENV NODE_ENV=production
# ARG to ENV pattern
ARG VERSION
ENV APP_VERSION=$VERSION
Layer Caching with COPY
# Specific files first for better caching
COPY package.json package-lock.json ./
RUN npm ci
# Then rest of source
COPY . .
Additional References