TL;DR
Docker containers share the host kernel, so security matters. Use non-root users, minimal base images, multi-stage builds, and never put secrets in images. Scan images for vulnerabilities and don't run containers with --privileged. These prompts cover essential container security.
Secure Dockerfile
Review and improve my Dockerfile for security.
Secure Dockerfile template:
Use specific version, not
FROM node:20-alpine AS builder
Set working directory
WORKDIR /app
Copy dependency files first (cache optimization)
COPY package*.json ./
Install dependencies
RUN npm ci --only=production
Copy application code
COPY . .
Build application
RUN npm run build
Production stage - minimal image
FROM node:20-alpine AS production
Create non-root user
RUN addgroup -g 1001 appgroup &&
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
WORKDIR /app
Copy only necessary files from builder
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
Switch to non-root user
USER appuser
Expose port (documentation)
EXPOSE 3000
Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
Run application
CMD "node", "dist/index.js"
Non-Root User
Configure my Docker container to run as non-root user.
Why: Root in container = root on host (in some exploits). Running as non-root limits damage from container escapes.
For Node.js (Alpine):
RUN addgroup -g 1001 appgroup &&
adduser -u 1001 -G appgroup -s /bin/sh -D appuser
USER appuser
For Node.js (Debian):
RUN groupadd -g 1001 appgroup &&
useradd -u 1001 -g appgroup -m appuser
USER appuser
For Python: RUN useradd -m -u 1001 appuser USER appuser
For Go (scratch image):
Copy user from builder or use numeric ID
COPY --from=builder /etc/passwd /etc/passwd USER 1001
Set file ownership: COPY --chown=appuser:appgroup . .
or
RUN chown -R appuser:appgroup /app
Common issues:
- Can't bind to port 80/443 (use 3000+ or set capabilities)
- Can't write to /app (set ownership correctly)
- npm/yarn cache issues (set HOME or cache dir)
Never use --privileged or --cap-add=ALL: These flags give containers nearly full host access. If you need specific capabilities, add only what's needed (--cap-add=NET_BIND_SERVICE for port 80).
Handle Secrets Securely
Handle secrets securely in my Docker setup.
NEVER do this: ENV API_KEY=secret123 # Visible in image layers COPY .env /app/.env # Secrets baked into image ARG DB_PASSWORD # ARG values visible in history
Instead:
- Runtime environment variables:
docker run -e API_KEY=secret myapp
or
docker run --env-file .env myapp # .env not in image - Docker Secrets (Swarm/Kubernetes):
docker secret create api_key ./secret.txt
In compose:
secrets:- api_key
Access at /run/secrets/api_key
- Mount secrets as volumes: docker run -v ./secrets:/run/secrets:ro myapp
- Use secret managers:
Fetch at runtime from AWS Secrets Manager, Vault, etc.
const secret = await secretsManager.getSecret('api-key'); - Multi-stage builds for build-time secrets:
syntax=docker/dockerfile:1.4
RUN --mount=type=secret,id=npm_token
NPM_TOKEN=$(cat /run/secrets/npm_token) npm install
Check for leaked secrets: docker history --no-trunc myimage | grep -i secret
Minimal Base Images
Reduce my Docker image attack surface.
Image comparison (approximate sizes):
- node:20 ~1GB (Debian, full toolchain)
- node:20-slim ~200MB (Debian minimal)
- node:20-alpine ~130MB (Alpine Linux)
- distroless ~20MB (No shell, minimal)
Recommendations:
- Use Alpine for most apps:
FROM node:20-alpine
Smaller, fewer CVEs, but uses musl libc
- Use Distroless for maximum security:
FROM gcr.io/distroless/nodejs20
No shell, no package manager, no attack surface
- Use slim for compatibility:
FROM node:20-slim
When Alpine compatibility issues arise
Multi-stage build pattern: FROM node:20 AS builder
Build with full toolchain
RUN npm ci && npm run build
FROM gcr.io/distroless/nodejs20
Run with minimal image
COPY --from=builder /app/dist /app CMD "app/index.js"
Scan for vulnerabilities: docker scout cves myimage
or
trivy image myimage
Pro tip: Run vulnerability scans in CI/CD. Tools like Trivy, Snyk, and Docker Scout can catch known CVEs before deployment. Block deployments with critical vulnerabilities.
Is Alpine safe to use?
Yes, Alpine is widely used and regularly updated. It has fewer packages installed by default, meaning fewer potential vulnerabilities. Watch for musl libc compatibility issues with some npm packages.
How do I update base images for security patches?
Rebuild regularly with --no-cache or use automated image rebuilding. Pin to specific versions (node:20.10.0-alpine) for reproducibility, but update those pins when security patches release.
Scan Your Docker Images
Check your containers for security misconfigurations and vulnerabilities.
Start Free Scan