09Deployment
Docker Security
Docker & Container Security — Instruction 09
Coverage
OWASP Docker Top 10, CWE-269, CWE-732 Container hardening, image security, runtime security
Dockerfile Checks
1. Never Run as Root
# 🔴 CRITICAL — Container runs as root by default
FROM node:20
COPY . .
RUN npm install
CMD ["node", "app.js"]
# 🟢 Create dedicated non-root user
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Create non-root user
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "app.js"]
2. No Secrets in Dockerfile / Image Layers
# 🔴 CRITICAL — Secrets baked into layers forever
RUN export API_KEY=sk_live_abc123 && npm run build
ENV DATABASE_URL=postgresql://root:password@localhost/mydb
# 🟢 Use ARG for build-time only (not stored in final image)
ARG BUILD_ARG
RUN echo "Build only: $BUILD_ARG"
# 🟢 Inject secrets at runtime via environment
docker run -e DATABASE_URL=$DATABASE_URL myapp
# Or use Docker secrets in Swarm/Kubernetes
3. .dockerignore
Check .dockerignore exists and excludes:
.env
.env.*
.git
node_modules
*.log
*.md
.DS_Store
coverage/
.nyc_output/
*.test.*
4. Pin Image Tags
# 🔴 :latest is unpredictable — could change anytime
FROM node:latest
FROM python:latest
# 🟢 Pin to specific digest or version
FROM node:20.11-alpine3.19
FROM python:3.12-slim
5. Use Minimal Base Images
# 🔴 Full OS image = large attack surface
FROM ubuntu:latest
FROM node:20
# 🟢 Minimal images
FROM node:20-alpine # much smaller, fewer packages
FROM node:20-slim # compromise between size and compatibility
# Or use distroless (no shell = no shell injection)
FROM gcr.io/distroless/nodejs20
6. Multi-Stage Builds
# 🟢 Build stage has tools, final image does not
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
USER node
CMD ["node", "server.js"]
7. Read-Only Filesystem
# docker-compose.yml
services:
app:
read_only: true
tmpfs:
- /tmp # only /tmp is writable
- /var/run
8. Drop Capabilities
# docker-compose.yml
services:
app:
cap_drop:
- ALL # drop all capabilities
cap_add:
- NET_BIND_SERVICE # only add what's needed
security_opt:
- no-new-privileges:true # prevent privilege escalation
docker-compose.yml Checks
9. No Privileged Mode
# 🔴 CRITICAL — Full host access
services:
app:
privileged: true
# 🟢 Never use privileged unless absolutely necessary
10. No Host Network
# 🔴 Container shares host network stack
network_mode: host
# 🟢 Use bridge network (default) or custom network
networks:
- app-network
11. Limit Resources
# Prevent DoS via resource exhaustion
services:
app:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
12. Health Checks
services:
app:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
13. Database Ports Not Exposed
# 🔴 Database accessible from host
services:
db:
ports:
- "5432:5432" # publicly accessible!
# 🟢 No port mapping for DB (only accessible within Docker network)
services:
db:
# No ports section — only accessible by other containers
app:
depends_on:
- db
environment:
DATABASE_URL: postgresql://user:pass@db:5432/mydb
Image Security
14. Scan Images for Vulnerabilities
Advise user to run:
# Using Docker Scout (built-in)
docker scout cves myimage:latest
# Using Trivy (free)
trivy image myimage:latest
# Using Snyk
snyk container test myimage:latest
15. Image Signature Verification
For production, images should be signed:
# Docker Content Trust
export DOCKER_CONTENT_TRUST=1