Complete dockerfile toolkit with generation and validation capabilities
94
94%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Advisory
Suggest reviewing before use
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)