agentsclimarketplace

Infra docker best practices

Skill ivanshamaev/de-agent-skills/group_skills/infra_dataops_group_skills/infra_docker_best_practices

Docker best practices — multi-stage builds (builder/runtime separation), layer cache optimization (dependency install before source copy), minimal base images (distroless/alpine/slim), security hardening (non-root USER, read-only FS, no SUID binaries), .dockerignore patterns, BuildKit secrets for private registries, image vulnerability scanning (Trivy), COPY vs ADD, CMD vs ENTRYPOINT patterns, data engineering Dockerfiles (dbt/Spark/Python ETL)From its SKILL.md

Install
npx -y skills add ivanshamaev/de-agent-skills --skill infra_docker_best_practices

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 15 stars15 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.9 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it

Docker Best Practices

When to Use

  • Building production Docker images for data pipelines
  • Reducing image size to speed up CI/CD and reduce registry costs
  • Hardening images against CVE exposure
  • Debugging slow Docker builds (cache invalidation)
  • Reviewing Dockerfiles in code review

Multi-Stage Builds

Pattern: Builder + Runtime Stage

# Stage 1: Build dependencies (heavy — compile, install)
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .

RUN pip install --user --no-cache-dir -r requirements.txt

# Stage 2: Runtime (lean — only what's needed to run)
FROM python:3.11-slim AS runtime

WORKDIR /app

# Copy only installed packages from builder
COPY --from=builder /root/.local /root/.local

# Copy application code
COPY src/ .

# Security: non-root user
RUN useradd -m -u 1001 appuser
USER appuser

ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1

CMD ["python", "main.py"]

Result: Runtime image excludes pip cache, build tools, and intermediate files.

Data Engineering: dbt Image

FROM python:3.11-slim AS builder

ARG DBT_VERSION=1.7.0
ARG DBT_ADAPTER=dbt-trino

RUN pip install --user --no-cache-dir \
    dbt-core==${DBT_VERSION} \
    ${DBT_ADAPTER}==1.7.0

# ─────────────────────────────────────────
FROM python:3.11-slim AS runtime

# Install git (needed for dbt deps)
RUN apt-get update && apt-get install -y --no-install-recommends git \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /root/.local /root/.local

# Non-root user
RUN useradd -m -u 1001 dbt && mkdir -p /dbt && chown dbt:dbt /dbt
USER dbt
WORKDIR /dbt

ENV PATH=/root/.local/bin:$PATH
ENV DBT_PROFILES_DIR=/dbt/profiles

ENTRYPOINT ["dbt"]
CMD ["--help"]

Layer Cache Optimization

Rule: Copy Dependencies Before Source Code

# BAD: any code change invalidates pip install cache
COPY . /app
RUN pip install -r /app/requirements.txt

# GOOD: cache pip install separately from source changes
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
COPY src/ /app/src/        # only invalidates from here

Combine RUN Commands for apt-get

# BAD: update and install in separate layers
RUN apt-get update
RUN apt-get install -y curl wget git

# GOOD: single layer, clean up cache in same RUN
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        curl \
        git \
        ca-certificates \
    && rm -rf /var/lib/apt/lists/*   # removes cached package lists

BuildKit Cache Mounts (Fastest for Dependencies)

# syntax=docker/dockerfile:1
FROM python:3.11-slim

# Cache pip packages across builds (host cache, not in image)
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --no-cache-dir -r requirements.txt

# Cache apt packages
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && apt-get install -y git

Enable with: DOCKER_BUILDKIT=1 docker build .


Minimal Base Images

Base ImageSizeUse Case
python:3.11-slim~50MBMost Python apps
python:3.11-alpine~25MBWhen musl libc is OK
gcr.io/distroless/python3~15MBProduction, no shell
ubuntu:22.04~70MBWhen system packages needed
scratch0MBStatic Go/Rust binaries
# Distroless: no shell, no package manager — minimal attack surface
FROM python:3.11-slim AS builder
COPY requirements.txt .
RUN pip install --prefix=/install --no-cache-dir -r requirements.txt

FROM gcr.io/distroless/python3-debian11
COPY --from=builder /install /usr/local
COPY src/ /app
WORKDIR /app
CMD ["/app/main.py"]

Security Hardening

Non-Root User

# Create and use a non-root user
RUN groupadd -r appgroup && useradd -r -g appgroup -u 1001 appuser

# Set ownership on necessary directories
RUN mkdir -p /data && chown appuser:appgroup /data

USER appuser    # all subsequent RUN/CMD/ENTRYPOINT run as this user

Read-Only Root Filesystem

# In Kubernetes: enforce in pod spec
securityContext:
  readOnlyRootFilesystem: true
# Add writable emptyDir for /tmp if needed
volumeMounts:
- name: tmp
  mountPath: /tmp
volumes:
- name: tmp
  emptyDir: {}

BuildKit Secrets (Private PyPI / npm)

# syntax=docker/dockerfile:1
FROM python:3.11-slim

# Secret never stored in image layer
RUN --mount=type=secret,id=pip_token \
    pip install --extra-index-url \
    https://$(cat /run/secrets/pip_token)@pkgs.company.com/simple/ \
    my-private-package
# Build with secret
docker build \
  --secret id=pip_token,env=PIP_TOKEN \
  -t myimage .

.dockerignore

# .dockerignore — exclude everything not needed for the build
.git
.gitignore
.env
*.env
__pycache__/
*.pyc
*.pyo
.pytest_cache/
.mypy_cache/
.tox/
dist/
build/
*.egg-info/
docs/
tests/
*.md
*.log
.DS_Store
node_modules/
.venv/
venv/

CMD vs ENTRYPOINT

# ENTRYPOINT: fixed executable, CMD: default args
ENTRYPOINT ["python", "-m", "gunicorn"]
CMD ["--workers=4", "--bind=0.0.0.0:8080", "app:app"]

# Override CMD at runtime:
# docker run myimage --workers=8 --bind=0.0.0.0:8080 app:app

# Use exec form (not shell form) — proper signal handling
CMD ["python", "main.py"]        # exec form: PID 1 receives SIGTERM
# NOT: CMD python main.py        # shell form: /bin/sh -c wraps it, signal lost

Spark ETL Image Example

# syntax=docker/dockerfile:1
FROM openjdk:11-jre-slim AS base

ARG SPARK_VERSION=3.5.0
ARG HADOOP_VERSION=3

# Install Spark
RUN apt-get update && apt-get install -y --no-install-recommends \
        curl procps tini \
    && rm -rf /var/lib/apt/lists/* \
    && curl -fsSL "https://archive.apache.org/dist/spark/spark-${SPARK_VERSION}/spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz" \
       | tar -xz -C /opt/ \
    && ln -s "/opt/spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}" /opt/spark

ENV SPARK_HOME=/opt/spark
ENV PATH=$SPARK_HOME/bin:$PATH

# Python layer
FROM base AS python

RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-pip \
    && rm -rf /var/lib/apt/lists/*

COPY --mount=type=cache,target=/root/.cache/pip requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Runtime
FROM python AS runtime

RUN useradd -m -u 1001 spark
USER spark
WORKDIR /app

COPY --chown=spark:spark src/ .

ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/opt/spark/bin/spark-submit", "--help"]

Image Scanning in CI

# GitHub Actions: scan with Trivy
- name: Build image
  run: docker build -t $IMAGE_NAME .

- name: Scan with Trivy
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE_NAME }}
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'             # fail CI on CRITICAL/HIGH

- name: Upload Trivy results to GitHub Security tab
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: 'trivy-results.sarif'

Image Size Audit

# Analyze image layers
docker history myimage:latest

# Using dive (interactive layer explorer)
dive myimage:latest

# Check final image size
docker images myimage:latest --format "{{.Size}}"

Anti-Patterns

  1. COPY . . before dependency install — every code change busts the pip/npm cache; copy lockfiles first.
  2. RUN apt-get update in separate layer — stale cache causes apt-get install to fail; always combine in one RUN.
  3. Running as root — if the container is compromised, attacker has root; always set USER nonroot.
  4. :latest tag in production — non-deterministic; use specific digests or version tags.
  5. Including .git and secrets in build context — add to .dockerignore; .env files often contain credentials.
  6. Shell form for CMD/ENTRYPOINTCMD python app.py makes shell PID 1, which doesn't forward signals; use exec form.

References

  • Docker Dockerfile best practices: docs.docker.com/develop/develop-images/dockerfile_best-practices/
  • BuildKit: docs.docker.com/build/buildkit/
  • Distroless: github.com/GoogleContainerTools/distroless
  • Trivy: trivy.dev/docs/
  • Related skills: [[infra-kubernetes-security-audit]], [[github-actions-dataops]], [[docker-data-environments]]

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.