Complete dockerfile toolkit with generation and validation capabilities
74
92%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Risky
Do not use without reviewing
This guide provides comprehensive security best practices for writing secure Dockerfiles. Security should be a primary consideration when containerizing applications.
Problem: Using :latest tag can lead to unpredictable builds and security vulnerabilities.
# Bad - Unpredictable, may pull vulnerable versions
FROM node:latest
# Good - Specific version
FROM node:20-alpine
# Better - Specific version with digest for reproducibility
FROM node:20-alpine@sha256:2c6c59cf4d34d4f937ddfcf33bab9d8bbad8658d1b9de7b97622566a52167f5bPrinciple: Smaller images have fewer attack vectors.
# Large attack surface (>1GB)
FROM ubuntu:22.04
# Medium attack surface (~200MB)
FROM node:20
# Small attack surface (~50MB)
FROM node:20-alpine
# Minimal attack surface (~2MB for Go apps)
FROM gcr.io/distroless/static-debian12Recommended Base Images:
# Use Trivy to scan base images
trivy image node:20-alpine
# Use Snyk
snyk container test node:20-alpine
# Use Docker Scout
docker scout cves node:20-alpineProblem: Running as root gives attackers full system access if container is compromised.
# Bad - Runs as root (default)
FROM node:20-alpine
COPY . /app
CMD ["node", "server.js"]
# Good - Creates and uses non-root user
FROM node:20-alpine
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
COPY --chown=nodejs:nodejs . /app
USER nodejs
CMD ["node", "server.js"]Alpine Linux:
RUN addgroup -g 1001 -S appgroup && \
adduser -S appuser -u 1001 -G appgroup
USER appuserDebian/Ubuntu:
RUN useradd -m -u 1001 appuser
USER appuserDistroless (built-in nonroot user):
FROM gcr.io/distroless/static-debian12
USER nonroot:nonroot# Copy with ownership
COPY --chown=appuser:appuser . /app
# Ensure executables have correct permissions
RUN chmod +x /app/entrypoint.sh# Bad - Hardcoded secrets
ENV API_KEY=sk_live_abc123
ENV DATABASE_PASSWORD=password123
# Good - Use runtime environment variables
ENV API_KEY=""
ENV DATABASE_PASSWORD=""# Mount secrets during build (never stored in image layers)
# syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) && \
echo "Configuring with API key..." && \
# Use API_KEY here
rm -f /run/secrets/api_keyBuild command:
docker build --secret id=api_key,src=.env .# Bad - Secret remains in image history
FROM alpine
RUN echo "SECRET_KEY=abc123" > /app/config
RUN rm /app/config # Secret still in previous layer!
# Good - Use multi-stage builds or build secrets
FROM alpine
RUN --mount=type=secret,id=config \
cat /run/secrets/config > /app/config && \
process_config && \
rm /app/config# Bad - Unpinned versions
RUN apt-get install -y curl git
# Good - Pinned versions
RUN apt-get install -y \
curl=7.81.0-1ubuntu1.16 \
git=1:2.34.1-1ubuntu1.11Node.js:
# Use package-lock.json or yarn.lock
COPY package*.json ./
RUN npm ci # Uses lock filePython:
# Pin versions in requirements.txt
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txtGo:
# go.sum ensures reproducible builds
COPY go.mod go.sum ./
RUN go mod download# Alpine (apk)
RUN apk add --no-cache curl
# Debian/Ubuntu (apt)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Node.js (npm)
RUN npm ci && npm cache clean --force
# Python (pip)
RUN pip install --no-cache-dir -r requirements.txt# Bad - Installs unnecessary packages
RUN apt-get install -y \
curl \
wget \
vim \
sudo \
ssh
# Good - Only essential packages
RUN apt-get install -y --no-install-recommends \
curl \
ca-certificates# Build stage - can include build tools
FROM golang:1.21-alpine AS builder
RUN apk add --no-cache git make
COPY . .
RUN make build
# Production stage - minimal runtime
FROM alpine:3.21
COPY --from=builder /app/binary /app/binary
CMD ["/app/binary"]# Install, use, and remove in same layer
RUN apk add --no-cache --virtual .build-deps \
gcc \
musl-dev \
&& pip install --no-cache-dir -r requirements.txt \
&& apk del .build-deps# Don't include SSH, telnet, or other services
# Use docker exec for debugging instead# Document exposed ports
EXPOSE 8080
# Don't expose unnecessary ports
# Bad: EXPOSE 22 (SSH)
# Bad: EXPOSE 3306 (MySQL in app container)# Good - Use port > 1024 (doesn't require root)
EXPOSE 8080
# Avoid privileged ports (< 1024)
# EXPOSE 80 # Requires root# GitHub Actions example
- name: Scan Docker image
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:latest
format: 'sarif'
output: 'trivy-results.sarif'# Rebuild images regularly to get security patches
FROM node:20-alpine # Alpine releases security updates frequently# Use gitleaks or trufflehog
docker run -v $(pwd):/path zricethezav/gitleaks:latest detect --source=/path
# Use hadolint to detect some secret patterns
hadolint Dockerfile:latest)