victorzhuk/skills

z-go-dockerfile

Multi-stage Dockerfile authoring for Go services — a deps layer cached separately from source, BuildKit cache mounts, CGOENABLED=0 static builds, and the scratch vs distroless vs alpine call.

Quelltext ansehen

Verwendung

  1. Installationsbefehl kopieren und das Quellpaket vor der Installation in der eigenen Agentenumgebung prüfen.
  2. Benötigte Werkzeuge, Zugangsdaten und Hosts im Original prüfen.
  3. Eingabe und gewünschtes Ergebnis beschreiben und die Ausgabe mit den Quellen abgleichen.
npx skills add https://github.com/victorzhuk/skills --skill z-go-dockerfile

Vor dem Start

  • Die Installation umfasst weder Modellabo noch API-Kontingent oder Dateizugriffsrechte.
  • Sterne und Installationen zeigen Aktivität, keine unabhängige Qualitäts- oder Sicherheitsprüfung.
Originales Skill-Dokument

Aus dem Quell-Repository gerendert; Überschriften, Beispiele, Code, Tabellen, Links und Bilder bleiben erhalten.

Go Dockerfile

Multi-stage build: a deps stage that caches modules, a build stage that compiles a static binary, and a minimal final stage that only carries the binary. Every layer earns its place — no shell, no package manager, no root user in the shipped image unless a specific need demands it.

Final image choice

BaseCerts & tzdataShell / exec debuggingReach for it when
gcr.io/distroless/static-debian13:nonrootbundled (ca-certificates + tzdata)none in :nonroot; swap to the :debug tag (busybox shell) to triagedefault for a CGO_ENABLED=0 Go binary
scratchnone — COPY both from the builder yourselfnone, ever — no :debug variant existsabsolute minimal footprint is a hard requirement and you're fine hand-rolling certs/tzdata
alpineapk add --no-cache ca-certificates tzdata (neither ships by default)ash shell + apk built inyou need a shell for an entrypoint script, kubectl exec triage, or a musl-linked CGO build

distroless/static is the sane default: it already carries what scratch forces you to reinvent, and it's smaller than alpine with a strictly smaller attack surface (no shell, no package manager). Reach for scratch only when a security policy mandates zero non-binary content, and for alpine only when something in the running container genuinely needs a shell.

Reference Dockerfile

dockerfile
# syntax=docker/dockerfile:1

FROM golang:1.26-bookworm AS deps
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
    go mod download

FROM deps AS build
ARG VERSION=dev
ARG GIT_COMMIT=none
ARG BUILD_DATE=
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    CGO_ENABLED=0 GOOS=linux go build -trimpath \
    -ldflags="-s -w -X main.Version=${VERSION} -X main.CommitHash=${GIT_COMMIT} -X main.BuildDate=${BUILD_DATE}" \
    -o /out/app ./cmd/app

FROM gcr.io/distroless/static-debian13:nonroot
COPY --from=build /out/app /app
USER 65532:65532
ENTRYPOINT ["/app"]

Numeric UID, not the nonroot name: a Pod with securityContext.runAsNonRoot: true fails to start against a string USER — Kubernetes can't verify non-root status from a name, only a number, and 65532 is the exact UID the :nonroot tag already sets up.

go.mod/go.sum land in their own COPY before the rest of the source, so go mod download only reruns when dependencies actually change — editing a .go file never invalidates that layer.

BuildKit cache mounts

--mount=type=cache,target=... needs the # syntax=docker/dockerfile:1 directive on line one; without it the mount flag is silently unsupported. Two mounts cover the two Go caches:

  • /go/pkg/mod — GOMODCACHE, the default under the official golang image's GOPATH=/go.
  • /root/.cache/go-build — GOCACHE, the compiler's build-artifact cache (builder stage runs as root, hence /root).

These caches persist across builds on the same daemon/builder — a cold CI runner still pays the full cost, but a warm local Docker daemon or a CI cache-mount backend (e.g. --cache-from/--cache-to in docker buildx build) turns a go build layer into an incremental one. This is the default accelerator, not optional hardening: skip it and every source-touching rebuild redownloads and recompiles the entire module graph.

Version stamping

-X main.Version / main.CommitHash / main.BuildDate mirror the -ldflags convention in [[z-go-makefile]] — same three variable names in package main, so a local make build and the image produced by this Dockerfile report identical version strings. Pass all three at build time: docker build --build-arg VERSION=$(git describe --tags --always --dirty) --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) --build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) .; the pipeline step that computes and passes them belongs in [[z-go-ci]].

-s -w strips the symbol table and DWARF debug info — smaller binary, but dlv can't symbolicate it. If a container image genuinely needs live delve debugging, build a second, unstripped target for that image; don't drop -s -w from the production build to accommodate a debugging session that happens rarely.

ca-certificates and tzdata on scratch

scratch starts from nothing: no /etc/ssl/certs, no /usr/share/zoneinfo, no /etc/passwd. Any outbound TLS call or time.LoadLocation fails without an explicit copy from the builder:

dockerfile
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo

distroless/static already bundles both — this is the main reason it beats scratch for anything that makes an HTTPS call or handles time zones.

Healthcheck reality

Docker's HEALTHCHECK CMD instruction needs a shell or an exec-able binary in the image, and it's meaningless in Kubernetes — the kubelet drives livenessProbe/readinessProbe/startupProbe from the Pod spec and never reads the image's HEALTHCHECK metadata. Define probes there (httpGet, grpc, or exec against a tiny health binary), not in the Dockerfile. A Dockerfile HEALTHCHECK only matters for docker run/Compose/Swarm, and on scratch/distroless:nonroot there's no shell for the shell form anyway.

.dockerignore essentials

.git
bin/
dist/
*.md
.env
.env.*
.github/
testdata/
compose.override.yml

Don't ignore vendor/ if the module actually vendors dependencies — the build stage needs it. Ignoring .git matters most: without it, every commit invalidates Docker's build-context cache even when no tracked file changed.

Multi-arch

BuildKit auto-populates TARGETOS/TARGETARCH for cross-compiling the same Dockerfile to linux/arm64 and linux/amd64:

dockerfile
FROM --platform=$BUILDPLATFORM golang:1.26-bookworm AS build
ARG TARGETOS
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /out/app ./cmd/app

--platform=$BUILDPLATFORM pins the builder stage to the host's own architecture — Go then cross-compiles natively via GOOS/GOARCH with no QEMU emulation of the build step. Drop that pin and buildx --platform linux/arm64 on an amd64 host emulates the entire builder under QEMU instead. The docker buildx build --platform linux/amd64,linux/arm64 invocation and the CI job that runs it belong in [[z-go-ci]]; this Dockerfile only needs the platform pin and the two ARGs to be cross-compile-ready.

Local Compose stack

Commit a compose.yml for local dev — one file, docker compose up gives the service a real database and cache:

yaml
services:
  app:
    build:
      context: .
      args:
        VERSION: ${VERSION:-dev}
        GIT_COMMIT: ${GIT_COMMIT:-none}
        BUILD_DATE: ${BUILD_DATE:-}
    env_file: [.env]
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app"]
      interval: 5s
      timeout: 3s
      retries: 5
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

Expose the 9090 management port only on explicit opt-in, in a companion file named compose.mgmt.yml — do NOT call it compose.override.yml, that exact name auto-loads and would publish 9090 on every plain up:

yaml
services:
  app:
    ports: ["9090:9090"]

A plain docker compose up keeps 9090 private — no host access; docker compose -f compose.yml -f compose.mgmt.yml up adds it. An optional otel profile adds an otel-collector service for local trace inspection. The build.args names (VERSION/GIT_COMMIT/BUILD_DATE) mirror the stamping vars in [[z-go-makefile]].

Production Compose is not this file: it uses the [[z-selfhost-deploy]] patterns — pinned image tag, no build:, restart: unless-stopped. This compose is local dev only.

Container healthcheck: prefer the app's own subcommand — test: ["CMD", "/app", "healthcheck"] — or fall back to wget -qO- http://localhost:8080/healthz (see [[z-go-observability]]). Minimal images (scratch, distroless:nonroot) ship no curl and usually no wget; a shell-form HEALTHCHECK fails outright there.

Do not

  • Copy the full source tree before go mod download — it collapses the deps-cache layer into the source layer, so every code change re-downloads every module.
  • Ship alpine or a full golang image to production because a shell "might be useful someday" — that's exactly the attack surface distroless removes.
  • Add a Dockerfile HEALTHCHECK as a substitute for a Kubernetes probe — the kubelet never reads it.
  • Run the final stage as root — distroless/static-debian13:nonroot and an explicit USER nonroot:nonroot/numeric UID exist precisely so you don't have to.
  • Strip -s -w from every build "just in case delve is needed" — keep it in prod, build a separate debug target for the rare session that needs symbols.

Verify

sh
DOCKER_BUILDKIT=1 docker build -t app:local .
docker run --rm app:local  # exits/serves without a missing-cert or missing-tzdata error
docker inspect app:local --format '{{.Config.User}}'   # 65532:65532, not root/empty
docker history app:local --no-trunc | head             # deps layer cached, no shell/package manager baked in
aus demselben Repository

Weitere Skills

Alle Skills
victorzhuk
Community

z-accessibility

Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible".

Installationen
1
GitHub Stars
0
Aktualisiert
22. Okt.
victorzhuk
Community

z-algo-core

DS/algorithm selection judgment for production code, not competitive tricks — hash map for membership/dedup, slice+sort beats a tree until proven, heap for top-k, two-pointer/sliding-window for scans, BFS/DFS/topo-sort for dependency shapes. Triggers on Big-O, hash map vs tree. Does not cover profiling/tuning; see [[z-go-performance]].

Installationen
1
GitHub Stars
0
Aktualisiert
22. Okt.
victorzhuk
Community

z-bdd

BDD acceptance contracts for humans and agents across UI, API, and CLI products. Use for Gherkin, Given-When-Then, E2E acceptance, or scenario evidence. Define repeatable scenarios with explicit assertions and required outcomes. Test selection belongs to [[z-testing-strategy]]. Go harness wiring belongs to [[z-go-bdd]].

Installationen
1
GitHub Stars
0
Aktualisiert
22. Okt.
victorzhuk
Community

z-codegraph

Use an existing CodeGraph integration for call graphs, impact analysis, affected tests, or route discovery. Discover installed tools and index freshness; follow host LSP precedence and verify current source. Do not initialize indexes without user authorization.

Installationen
1
GitHub Stars
0
Aktualisiert
22. Okt.