CtrlK
BlogDocsLog inGet started
Tessl Logo

pantheon-ai/dockerfile-toolkit

Complete dockerfile toolkit with generation and validation capabilities

94

Quality

94%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

docker_best_practices.mdvalidator/references/

Docker Best Practices Reference

This document summarizes official Docker best practices based on current recommendations from Docker documentation and industry standards.

General Principles

1. Create Ephemeral Containers

  • Containers should be as stateless and ephemeral as possible
  • Should be able to stop, destroy, and recreate with minimal setup
  • Align with Twelve-Factor App methodology

2. Understand Build Context

  • Use .dockerignore to exclude unnecessary files
  • Keep context size minimal for faster builds
  • Don't include secrets or sensitive data in context

3. Use Multi-Stage Builds

  • Separate build dependencies from runtime
  • Dramatically reduce final image size
  • Improve security by minimizing attack surface

4. One Concern Per Container

  • Each container should address a single concern
  • Makes containers more reusable and easier to scale
  • Simplifies debugging and updates

Dockerfile Instructions Best Practices

FROM

Use specific tags, not :latest

# Bad
FROM node:latest

# Good
FROM node:21-alpine

# Better
FROM node:21-alpine@sha256:abc123...

Choose minimal base images

  • Alpine Linux: ~5 MB base (vs ~80 MB for Ubuntu)
  • Distroless: No shell, package manager (minimal attack surface)
  • Scratch: Absolutely minimal (for static binaries)

Prefer official images

  • Look for "Official Image" or "Verified Publisher" badges
  • Official images are maintained and regularly updated

RUN

Chain commands to reduce layers

# Bad - creates 4 layers
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN curl -sL https://example.com/script.sh | bash

# Good - creates 1 layer
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    vim \
    && rm -rf /var/lib/apt/lists/* \
    && curl -sL https://example.com/script.sh | bash

Clean up in same layer

# Package manager cache must be removed in same RUN
RUN apt-get update && apt-get install -y \
    package1 \
    package2 \
    && rm -rf /var/lib/apt/lists/*

# For Alpine
RUN apk add --no-cache package1 package2

Use --no-install-recommends for apt

RUN apt-get install -y --no-install-recommends package

Pin package versions

# For apt
RUN apt-get install -y package=1.2.3-1

# For apk
RUN apk add package=1.2.3-r0

# For pip
RUN pip install package==1.2.3

Sort multi-line arguments

RUN apt-get update && apt-get install -y \
    curl \
    git \
    vim \
    wget \
    && rm -rf /var/lib/apt/lists/*

Use pipefail for pipes

RUN set -o pipefail && wget -O - https://example.com | wc -l > /number

COPY vs ADD

Prefer COPY over ADD

# Use COPY for files and directories
COPY app.py /app/

# Only use ADD for auto-extraction or remote URLs
ADD https://example.com/file.tar.gz /tmp/

Use COPY --chown to avoid extra layer

# Bad - creates extra layer
COPY app.py /app/
RUN chown user:user /app/app.py

# Good - single layer
COPY --chown=user:user app.py /app/

WORKDIR

Use absolute paths

# Bad
WORKDIR app

# Good
WORKDIR /app

Don't use RUN cd

# Bad
RUN cd /app && npm install

# Good
WORKDIR /app
RUN npm install

USER

Don't run as root

# Create user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Or for Alpine
RUN addgroup -g 1001 -S appuser && adduser -S appuser -u 1001

# Switch to user
USER appuser

Use high UID (>10000) for better security

RUN useradd -u 10001 -m appuser
USER appuser

CMD and ENTRYPOINT

Use exec form for proper signal handling

# Bad - shell form (doesn't handle signals)
CMD python app.py

# Good - exec form
CMD ["python", "app.py"]

Combine ENTRYPOINT and CMD

# ENTRYPOINT defines the executable
ENTRYPOINT ["python"]

# CMD provides default arguments (can be overridden)
CMD ["app.py"]

EXPOSE

Document ports even though it doesn't publish

EXPOSE 8080
EXPOSE 443

HEALTHCHECK

Add health checks for services

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

LABEL

Add metadata

LABEL org.opencontainers.image.authors="team@example.com"
LABEL org.opencontainers.image.version="1.0.0"
LABEL org.opencontainers.image.description="Application description"

Build Optimization

Layer Caching

Order instructions from least to most frequently changing

# 1. Base image (rarely changes)
FROM node:21-alpine

# 2. System packages (rarely change)
RUN apk add --no-cache curl

# 3. Dependencies (change occasionally)
COPY package*.json ./
RUN npm ci

# 4. Source code (changes frequently)
COPY . .

Multi-Stage Builds

Separate build and runtime

# Build stage
FROM node:21 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Runtime stage
FROM node:21-alpine
COPY --from=builder /app/dist /app
CMD ["node", "/app/index.js"]

BuildKit Features

Enable modern features

# syntax=docker/dockerfile:1

# Use cache mounts
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

# Use secret mounts (secrets not in final image)
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
    aws s3 cp s3://bucket/file .

Security Best Practices

1. Scan Images

docker scan myimage:tag
# or
trivy image myimage:tag

2. Use Minimal Base Images

  • Fewer packages = fewer vulnerabilities
  • Alpine, distroless, or scratch

3. Don't Store Secrets in Images

# Bad
ENV DATABASE_PASSWORD=secret123

# Good - use runtime config or secrets
# Pass at runtime: docker run -e DATABASE_PASSWORD=...

4. Run as Non-Root

USER appuser

5. Use Read-Only Filesystem

docker run --read-only myimage

6. Limit Capabilities

docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myimage

Common Anti-Patterns

❌ Using :latest tag

  • Unpredictable
  • Not reproducible
  • Can break without warning

❌ Not cleaning package cache

# Missing cleanup increases image by hundreds of MB
RUN apt-get update && apt-get install -y package
# Missing: && rm -rf /var/lib/apt/lists/*

❌ Running as root

  • Security risk
  • Violates principle of least privilege

❌ Installing unnecessary packages

# Bloated image
RUN apt-get install -y vim nano emacs curl wget

❌ Using ADD instead of COPY

  • ADD has implicit behavior
  • Can extract archives unexpectedly

❌ Multiple FROM in non-multi-stage context

  • Creates confusion
  • Use multi-stage builds properly

Resources

  • Official Docker Best Practices
  • Dockerfile Reference
  • Docker Security Best Practices
  • Multi-Stage Builds

tile.json