es), and deterministic scaling without sacrificing security or observability.
Core Solution
Production-ready containerization requires a disciplined architecture: immutable artifacts, explicit resource boundaries, health-driven lifecycle management, and declarative orchestration. The following implementation demonstrates a multi-stage build, local development stack, and minimal Kubernetes deployment.
1. Production Dockerfile (Multi-Stage & Non-Root)
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Stage 2: Runtime
FROM node:18-alpine AS runtime
RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
2. Local Development Stack (docker-compose.yml)
version: "3.8"
services:
app:
build: .
ports: ["3000:3000"]
environment:
- NODE_ENV=development
- DB_HOST=postgres
depends_on:
postgres:
condition: service_healthy
deploy:
resources:
limits: { cpus: "0.5", memory: 256M }
reservations: { cpus: "0.25", memory: 128M }
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: changeme_in_prod
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
3. Kubernetes Deployment & Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: app
image: registry.example.com/app:latest
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secrets
resources:
requests: { cpu: "250m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
livenessProbe:
httpGet: { path: /health, port: 3000 }
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet: { path: /ready, port: 3000 }
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: web
ports:
- port: 80
targetPort: 3000
type: ClusterIP
Architecture Decisions:
- Multi-stage builds separate compilation from runtime, stripping dev dependencies and build tools.
- Non-root execution enforces least-privilege container isolation, mitigating kernel escape risks.
- Explicit resource requests/limits enable K8s scheduler accuracy and prevent OOMKilled cascades.
- Health/readiness probes decouple traffic routing from container startup, ensuring zero-downtime deployments.
- Config/Secret separation externalizes environment-specific data, enabling immutable image promotion across stages.
Pitfall Guide
- Running Containers as Root: Default Docker images often run as UID 0. This violates container isolation boundaries and amplifies kernel vulnerability impact. Always create a dedicated non-root user, set
USER in the Dockerfile, and drop Linux capabilities (--cap-drop=ALL).
- Ignoring Multi-Stage Builds & Layer Caching: Single-stage Dockerfiles bloat images with compilers, debug symbols, and package managers. Multi-stage builds produce minimal runtime artifacts. Additionally, copy dependency files before source code to maximize Docker layer cache hits during CI/CD.
- Hardcoding Configuration & Secrets: Embedding environment variables or credentials in images breaks immutability and leaks secrets in registries. Use externalized ConfigMaps, Secrets, or vault injection (e.g., HashiCorp Vault, AWS Secrets Manager) mounted as volumes or environment variables at runtime.
- Skipping Health Checks & Readiness Probes: Without probes, orchestrators cannot distinguish between a starting container and a healthy one. Traffic routes to unready pods, causing 502/503 errors. Implement
/health (liveness) and /ready (readiness) endpoints with appropriate timeouts and thresholds.
- Misconfiguring Resource Requests vs. Limits: Setting only limits causes scheduler starvation; setting only requests allows unbounded consumption. Always define both. Use
requests for scheduling guarantees and limits for burst protection. Monitor actual usage and tune via Vertical Pod Autoscaler (VPA) recommendations.
- Treating Kubernetes as a Docker Wrapper: K8s manages declarative state, not imperative commands. Applying
kubectl run or manual docker exec workflows bypasses reconciliation loops, causing drift. Embrace GitOps (ArgoCD/Flux), declarative manifests, and immutable deployments.
- Neglecting Image Scanning & Supply Chain Security: Unscanned images introduce CVEs, malware, and license violations. Integrate Trivy, Grype, or Snyk into CI pipelines. Enforce signed images (Cosign/Notary) and pull from private registries with vulnerability gating.
Deliverables
- ๐ Blueprint: Container-to-Orchestration Migration Path โ A phased architecture guide covering local dev โ CI/CD image promotion โ staging rollout โ production K8s deployment, including rollback strategies and traffic shifting patterns.
- โ
Checklist: Production-Ready Containerization Checklist โ 28-point validation covering image minimization, non-root enforcement, probe configuration, resource bounding, secret management, network policies, and observability hooks.
- โ๏ธ Configuration Templates: Production-grade
Dockerfile, docker-compose.yml, k8s-deployment.yaml, k8s-service.yaml, k8s-hpa.yaml, and k8s-networkpolicy.yaml with inline comments, environment variable mapping, and security hardening defaults.