| name | dockerfile |
| description | Docker best practices for multi-stage Alpine builds — security patches, image pinning, and layer optimization. Use when creating, reviewing, or updating Dockerfiles. |
Dockerfile — Alpine Multi-Stage Builds
Base Image Pinning
There are two valid strategies. We use Option A because we don't run Renovate/Dependabot and the apk upgrade pattern covers the security gap.
Option A: Pin minor, float patch (our default)
ARG RUNNER_IMAGE="alpine:3.21"
Combined with apk upgrade --no-cache in the runtime stage, this automatically picks up security patches without manual digest tracking.
Option B: Pin exact + automation (industry standard for larger teams)
# Pin exact tag or SHA digest for full reproducibility
ARG RUNNER_IMAGE="alpine:3.21.6"
# Or even stricter:
ARG RUNNER_IMAGE="alpine@sha256:a8560b36..."
Requires Renovate or Dependabot to automatically open PRs when new versions are released. Best for teams that need deterministic builds and have the automation to stay patched.
What to avoid
# ❌ Floating major — surprise breaking changes
ARG RUNNER_IMAGE="alpine:latest"
Builder images
For compound images (e.g. Elixir + Erlang + Alpine), pin the toolchain versions:
ARG ELIXIR_VERSION=1.15.7
ARG OTP_VERSION=26.2.1
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-alpine-3.21"
Security Patches
Always add apk upgrade --no-cache early in the runtime stage. This pulls security patches even when Docker Hub hasn't rebuilt the base image tag yet.
FROM ${RUNNER_IMAGE}
RUN apk upgrade --no-cache && apk add --no-cache libstdc++ openssl ncurses-libs