Rework Dockerfile.backend to the mandated Go multistage lint-stage pattern #17

Open
opened 2026-08-09 03:38:14 +02:00 by clawbot · 0 comments
Collaborator

Problem

Dockerfile.backend does not follow the Go Dockerfile pattern that REPO_POLICIES.md mandates, and it drags the entire git history into the build context to do it. Verified on main at fbfe1df.

Current shape: a single builder stage based on golang:1.25-alpine that apk adds git make gcc musl-dev, go installs golangci-lint from source, copies .git, then runs make check and make build.

Divergences from policy

  1. No separate lint stage. Policy: "Dockerfiles must use a separate lint stage for fail-fast feedback. Go repos use a multistage build where linting runs in an independent stage based on the golangci/golangci-lint image (pinned by hash). This stage runs make fmt-check and make lint before the full build begins." There is no AS lint stage at all.

  2. No BuildKit stage dependency. Policy requires COPY --from=lint /src/go.sum /dev/null in the build stage to force BuildKit to complete linting before compiling. Absent.

  3. golangci-lint is compiled from source inside the build. RUN CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@9f61b0f... builds the linter on every cache miss. The policy pattern uses the prebuilt golangci/golangci-lint image directly, which "includes both Go and the linter, so there is no need to install the linter separately." This is the single largest contributor to build time.

  4. COPY .git /repo/.git. The whole history is copied into the image solely so backend/Makefile's VERSION := $(shell git describe --always --dirty) resolves. Policy's pattern uses ARG VERSION=dev and -ldflags "-X main.Version=${VERSION}" instead. The .git copy also blocks adding .git to .dockerignore (see #15).

  5. Static linking via CGO. backend/Makefile builds with -linkmode external -extldflags -static, which is why gcc and musl-dev are installed. The policy pattern is CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}", which needs no C toolchain and produces a static binary anyway.

Definition of done

  • Dockerfile.backend has a lint stage FROM golangci/golangci-lint@sha256:... (pinned by digest, with a # golangci/golangci-lint:v2.12.2, YYYY-MM-DD comment above it) that runs make fmt-check and make lint.
  • The lint stage's linter version agrees with issue #14 (v2.12.2). If #14 has landed, match it; if not, this issue must not regress whatever #14 established. Coordinate ordering rather than duplicating the change.
  • The build stage contains COPY --from=lint /src/go.sum /dev/null (or the equivalent for the chosen paths) so BuildKit cannot run the stages in parallel and skip a lint failure. Verify this actually works: introduce a deliberate lint error, confirm docker build -f Dockerfile.backend . fails, revert.
  • The build stage runs make test and builds with CGO_ENABLED=0 go build -trimpath and -ldflags="-s -w -X main.Version=${VERSION}", driven by ARG VERSION=dev.
  • gcc and musl-dev are no longer installed; backend/Makefile's -linkmode external -extldflags -static is removed. Confirm the resulting binary is still static and runs in the alpine runtime stage.
  • COPY .git /repo/.git is gone. Version comes from ARG VERSION, and backend/Makefile tolerates git describe being unavailable (no build failure when .git is absent).
  • Every FROM is pinned by @sha256: digest with a version-and-date comment above it, per the hash-pinning policy.
  • docker build -f Dockerfile.backend . succeeds and completes in under 5 minutes.
  • Root make check and cd backend && make check both pass.
  • TODO.md updated in the same commit.
  • Commit title ends with (closes #N).

Implementation requirements

  • Follow the reference Dockerfile in REPO_POLICIES.md closely; deviate only where this repo genuinely differs, and note any deviation in the PR description.
  • The runtime stage stays minimal (alpine + ca-certificates), keeps EXPOSE 8080, and keeps the existing entrypoint behaviour.
  • Do not change application behaviour, routes, or Go logic in this commit.
  • make targets and script/ entrypoints only for verification.
  • No attribution trailers in the commit message.
## Problem `Dockerfile.backend` does not follow the Go Dockerfile pattern that `REPO_POLICIES.md` mandates, and it drags the entire git history into the build context to do it. Verified on `main` at `fbfe1df`. Current shape: a single `builder` stage based on `golang:1.25-alpine` that `apk add`s `git make gcc musl-dev`, `go install`s golangci-lint from source, copies `.git`, then runs `make check` and `make build`. ### Divergences from policy 1. **No separate lint stage.** Policy: "Dockerfiles must use a separate lint stage for fail-fast feedback. Go repos use a multistage build where linting runs in an independent stage based on the `golangci/golangci-lint` image (pinned by hash). This stage runs `make fmt-check` and `make lint` before the full build begins." There is no `AS lint` stage at all. 2. **No BuildKit stage dependency.** Policy requires `COPY --from=lint /src/go.sum /dev/null` in the build stage to force BuildKit to complete linting before compiling. Absent. 3. **golangci-lint is compiled from source inside the build.** `RUN CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@9f61b0f...` builds the linter on every cache miss. The policy pattern uses the prebuilt `golangci/golangci-lint` image directly, which "includes both Go and the linter, so there is no need to install the linter separately." This is the single largest contributor to build time. 4. **`COPY .git /repo/.git`.** The whole history is copied into the image solely so `backend/Makefile`'s `VERSION := $(shell git describe --always --dirty)` resolves. Policy's pattern uses `ARG VERSION=dev` and `-ldflags "-X main.Version=${VERSION}"` instead. The `.git` copy also blocks adding `.git` to `.dockerignore` (see #15). 5. **Static linking via CGO.** `backend/Makefile` builds with `-linkmode external -extldflags -static`, which is why `gcc` and `musl-dev` are installed. The policy pattern is `CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}"`, which needs no C toolchain and produces a static binary anyway. ## Definition of done - [ ] `Dockerfile.backend` has a `lint` stage `FROM golangci/golangci-lint@sha256:...` (pinned by digest, with a `# golangci/golangci-lint:v2.12.2, YYYY-MM-DD` comment above it) that runs `make fmt-check` and `make lint`. - [ ] The lint stage's linter version agrees with issue #14 (v2.12.2). If #14 has landed, match it; if not, this issue must not regress whatever #14 established. Coordinate ordering rather than duplicating the change. - [ ] The build stage contains `COPY --from=lint /src/go.sum /dev/null` (or the equivalent for the chosen paths) so BuildKit cannot run the stages in parallel and skip a lint failure. **Verify this actually works**: introduce a deliberate lint error, confirm `docker build -f Dockerfile.backend .` fails, revert. - [ ] The build stage runs `make test` and builds with `CGO_ENABLED=0 go build -trimpath` and `-ldflags="-s -w -X main.Version=${VERSION}"`, driven by `ARG VERSION=dev`. - [ ] `gcc` and `musl-dev` are no longer installed; `backend/Makefile`'s `-linkmode external -extldflags -static` is removed. Confirm the resulting binary is still static and runs in the `alpine` runtime stage. - [ ] `COPY .git /repo/.git` is gone. Version comes from `ARG VERSION`, and `backend/Makefile` tolerates `git describe` being unavailable (no build failure when `.git` is absent). - [ ] Every `FROM` is pinned by `@sha256:` digest with a version-and-date comment above it, per the hash-pinning policy. - [ ] `docker build -f Dockerfile.backend .` succeeds and completes in under 5 minutes. - [ ] Root `make check` and `cd backend && make check` both pass. - [ ] `TODO.md` updated in the same commit. - [ ] Commit title ends with ` (closes #N)`. ## Implementation requirements - Follow the reference Dockerfile in `REPO_POLICIES.md` closely; deviate only where this repo genuinely differs, and note any deviation in the PR description. - The runtime stage stays minimal (alpine + `ca-certificates`), keeps `EXPOSE 8080`, and keeps the existing entrypoint behaviour. - Do not change application behaviour, routes, or Go logic in this commit. - `make` targets and `script/` entrypoints only for verification. - No attribution trailers in the commit message.
clawbot added this to the 1.0.0 milestone 2026-08-09 03:38:14 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/netwatch#17