Docker Compose is widely used as a local development orchestrator, while Kubernetes is often assumed to be the only choice for production. However, for lean engineering teams and single-node staging or production instances, Kubernetes introduces significant operational overhead and maintenance complexity.
A properly hardened Docker Compose configuration can provide secure, resilient container isolation with minimal maintenance.
The Five Vulnerabilities of Default Containers
Out-of-the-box container runtimes have several common security risks:
UID 0 (root) by default. If a remote code execution vulnerability is exploited, the attacker has root privileges inside the container..env files leaves secrets exposed in process listings and commit histories.The Production-Hardened compose.yaml
Here is a hardened, production-ready Docker Compose specification:
services:
api:
image: ghcr.io/aebibtech/production-api:v1.4.2
container_name: production_api
restart: unless-stopped
# 1. Run as unprivileged user
user: "10001:10001"
# 2. Prevent privilege escalation
security_opt:
- no-new-privileges:true
# 3. Read-only root filesystem with ephemeral /tmp
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m
# 4. Strict resource quotas
deploy:
resources:
limits:
cpus: '2.00'
memory: 1024M
reservations:
cpus: '0.50'
memory: 256M
# 5. Native health check
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
interval: 15s
timeout: 3s
retries: 3
start_period: 10s
# 6. Logging limits (prevents disk fill-up)
logging:
driver: "json-file"
options:
max-size: "20m"
max-file: "5"
# 7. Secure secrets handling
secrets:
- db_password
environment:
- DATABASE_URL_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txtDockerfile Hardening Best Practices
Your Compose file can only secure what your container image permits. Pair it with a multi-stage Dockerfile:
# Build stage
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production minimal runner
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root system group and user
RUN addgroup -S appgroup -g 10001 && adduser -S appuser -u 10001 -G appgroup
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]With these protections applied, your application runs inside a secure, unprivileged sandbox with bounded memory limits, automated health-checking restarts, and protected production secrets.