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.

Vedi sorgente

Come usare

  1. Copia il comando e controlla il pacchetto prima di installarlo nel tuo ambiente agente.
  2. Controlla strumenti, credenziali e ambienti richiesti nel documento originale.
  3. Descrivi input e risultato desiderato; confronta l’output con le fonti.
npx skills add https://github.com/victorzhuk/skills --skill z-go-dockerfile

Prima di iniziare

  • L’installazione non include abbonamenti, quote API o accesso ai file.
  • Stelle e installazioni indicano attività, non un test indipendente di qualità o sicurezza.
Documento Skill originale

Contenuto dal repository con titoli, esempi, codice, tabelle, link e immagini preservati.

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
dallo stesso repository

Altri Skills

Tutti gli 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".

installazioni
1
GitHub Stars
0
Aggiornato
22 ott
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]].

installazioni
1
GitHub Stars
0
Aggiornato
22 ott
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]].

installazioni
1
GitHub Stars
0
Aggiornato
22 ott
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.

installazioni
1
GitHub Stars
0
Aggiornato
22 ott