multi-stage-builds — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited multi-stage-builds (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use multi-stage Docker builds to produce minimal, secure production images by separating build-time tooling from runtime dependencies.
Separate TypeScript compilation and dependency installation from the runtime image.
# ---------- Stage 1: Install dependencies ----------
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# ---------- Stage 2: Build ----------
FROM deps AS builder
COPY tsconfig.json ./
COPY src/ src/
RUN npm run build && npm prune --omit=dev
# ---------- Stage 3: Production ----------
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["dist/index.js"]Result: ~150MB image instead of ~1GB with full Node.js + dev dependencies.
Go compiles to a single static binary, making scratch or distroless ideal.
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /server ./cmd/server
FROM gcr.io/distroless/static-debian12
COPY --from=builder /server /server
USER nonroot:nonroot
ENTRYPOINT ["/server"]Result: ~10-20MB image with zero OS packages and no shell for attackers to exploit.
Isolate Python dependencies in a virtualenv, then copy only the venv to the runtime image.
FROM python:3.12-slim AS builder
WORKDIR /app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM python:3.12-slim
WORKDIR /app
RUN groupadd -r app && useradd -r -g app app
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /app /app
ENV PATH="/opt/venv/bin:$PATH"
USER app
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Use BuildKit cache mounts to persist package manager caches across builds, avoiding repeated downloads.
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
FROM rust:1.77-alpine AS rust-builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
COPY src/ src/
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
cargo build --release && \
cp target/release/myapp /usr/local/bin/myappUse build args to customize builds without separate Dockerfiles.
ARG BUILD_ENV=production
FROM node:20-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./
FROM base AS deps-production
RUN npm ci --omit=dev
FROM base AS deps-development
RUN npm ci
FROM deps-${BUILD_ENV} AS deps
FROM node:20-alpine AS final
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .Build with: docker build --build-arg BUILD_ENV=development -t myapp:dev .
Use named stages with --target for different environments from a single Dockerfile.
FROM node:20-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM base AS development
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS test
COPY . .
RUN npm run lint && npm test
FROM base AS builder
COPY . .
RUN npm run build && npm prune --omit=dev
FROM gcr.io/distroless/nodejs20-debian12 AS production
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["dist/index.js"]docker build --target development -t myapp:dev .
docker build --target test -t myapp:test .
docker build --target production -t myapp:prod .alpine, slim, distroless, or scratch. A full Debian base adds 100-200MB of unnecessary packages.npm ci, every source code change invalidates the dependency cache.--target and build args instead of maintaining separate Dockerfiles for dev, test, and prod.| Base Image | Size | Shell | Use Case |
|---|---|---|---|
scratch | 0 MB | No | Go static binaries |
distroless/static | ~2 MB | No | Static binaries, max security |
distroless/cc | ~20 MB | No | C/C++ apps needing libc |
distroless/nodejs20 | ~130 MB | No | Node.js production |
alpine | ~7 MB | Yes | When you need a shell |
debian-slim | ~80 MB | Yes | When you need apt packages |
# Enable BuildKit (required for cache mounts)
export DOCKER_BUILDKIT=1
# Build specific target
docker build --target production -t myapp:prod .
# Check final image size
docker images myapp:prod~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.