main's tree, with next merged on 26 October, as one commit onto prod, on sneak's order. Model: opus-5-5 Co-authored-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #198.
This commit is contained in:
+143
-55
@@ -1,66 +1,154 @@
|
||||
# The one image netwatch ships: nginx serves the built frontend and
|
||||
# passes /api/ and /.well-known/healthcheck to netwatch-server, the Go
|
||||
# backend, which runs in the same container on loopback only.
|
||||
# passes /api/, /.well-known/healthcheck and /metrics to netwatch-server,
|
||||
# the Go backend, which runs in the same container on loopback only.
|
||||
# bin/entrypoint.sh starts and watches both.
|
||||
#
|
||||
# The lint and test phases are the gates: `make lint` (script/lint)
|
||||
# builds the lint stage alone and `make test` (script/test) the test
|
||||
# stage alone, and the two build stages, frontend-build and builder,
|
||||
# depend on both, so the image cannot be built unless they pass. Each
|
||||
# phase covers the frontend as well, through a copy from a node stage.
|
||||
# Inside them each tool is invoked directly, never through make or
|
||||
# script/, whose lint and test are themselves docker builds.
|
||||
#
|
||||
# In every stage ARG CACHEBUST comes right after the steps that install
|
||||
# system packages and download Go's modules, or right after FROM in a
|
||||
# stage with neither, and every step that lints, tests or compiles
|
||||
# comes after it, as does yarn install in the node stages. script/
|
||||
# passes a new value on every build, which reruns every RUN step after
|
||||
# it. Docker keeps the layers above it until the base image, their own
|
||||
# lines or go.mod and go.sum change. The steps that compile mount Go's
|
||||
# build cache, and golangci-lint run also mounts golangci-lint's; both
|
||||
# survive from build to build.
|
||||
|
||||
# Lint stage — fast feedback on formatting and lint issues. The
|
||||
# golangci/golangci-lint image ships Go, gofmt, make and the linter, so
|
||||
# nothing is installed here. The root make lint builds this stage alone.
|
||||
# golangci/golangci-lint:v2.12.2 (2026-08-10)
|
||||
FROM golangci/golangci-lint@sha256:5cceeef04e53efe1470638d4b4b4f5ceefd574955ab3941b2d9a68a8c9ad5240 AS lint
|
||||
|
||||
WORKDIR /src
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ .
|
||||
RUN make fmt-check
|
||||
RUN make lint
|
||||
|
||||
# Backend build stage
|
||||
# golang:1.25-alpine (2026-02-27)
|
||||
FROM golang:1.25-alpine@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4ced AS builder
|
||||
|
||||
RUN apk add --no-cache make
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Force BuildKit to run the lint stage before proceeding. BuildKit runs
|
||||
# stages in parallel by default; without this no-op copy a lint failure
|
||||
# would not gate compilation.
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ .
|
||||
|
||||
RUN make test
|
||||
|
||||
# make build is a shim around backend/script/build, the one definition
|
||||
# of the build command:
|
||||
# CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.Version=... -X main.Buildarch=..."
|
||||
# That script reads VERSION from the environment, so it is handed over
|
||||
# there rather than as a make variable.
|
||||
ARG VERSION=dev
|
||||
RUN VERSION="${VERSION}" make build
|
||||
|
||||
# Frontend stage
|
||||
# Frontend lint stage: eslint with the rules in eslint.config.js. The
|
||||
# lint phase below runs it.
|
||||
# node:22-alpine as of 2026-02-22
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS frontend
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS frontend-lint
|
||||
ARG CACHEBUST
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
RUN apk add --no-cache git make
|
||||
COPY . .
|
||||
# make frontend-check is the frontend half of make check (test + lint +
|
||||
# fmt-check); its test step is the production yarn build, so this both
|
||||
# produces dist/ and gates the image on lint/fmt-check/test regressions.
|
||||
# This node stage has neither Go nor Docker; the lint and builder stages
|
||||
# above gate the backend half.
|
||||
RUN make frontend-check
|
||||
RUN yarn eslint .
|
||||
|
||||
# Runtime stage
|
||||
# Lint phase: golangci-lint over the Go packages with .golangci.yml,
|
||||
# and eslint through the copy from frontend-lint at the end. The
|
||||
# golangci/golangci-lint image ships Go and the linter.
|
||||
# golangci/golangci-lint:v2.14.0, 2026-09-24
|
||||
FROM golangci/golangci-lint@sha256:ad862ba6b3798cbe0fd9fd7408d498fd74fbd2623a92406b2fd3898faf0bf98f AS lint
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ARG CACHEBUST
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/root/.cache/golangci-lint \
|
||||
golangci-lint run --config .golangci.yml ./...
|
||||
# Nothing is wanted from frontend-lint; the copy is what makes this
|
||||
# phase run it.
|
||||
COPY --from=frontend-lint /app/yarn.lock /dev/null
|
||||
|
||||
# Frontend test stage: the unit tests in test/unit/. The test phase
|
||||
# below runs it, and the frontend-build stage starts from it. The tests
|
||||
# print a dot each; if any fails, they run again with every test listed,
|
||||
# and the step fails even if that run passes. NODE_OPTIONS chooses the
|
||||
# reporter because yarn adds its arguments after the test files, where
|
||||
# node would take a reporter option for one more file.
|
||||
# node:22-alpine as of 2026-02-22
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS frontend-test
|
||||
ARG CACHEBUST
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN NODE_OPTIONS=--test-reporter=dot timeout 90 yarn --silent run test || \
|
||||
{ echo "--- Rerunning with every test listed for details ---"; \
|
||||
NODE_OPTIONS=--test-reporter=spec timeout 90 yarn --silent run test; \
|
||||
exit 1; }
|
||||
|
||||
# Test phase: the backend's tests with the race detector and coverage,
|
||||
# and the frontend's through the copy from the frontend-test stage at
|
||||
# the end. -race needs cgo and so a C compiler, which the Debian Go image
|
||||
# ships and the alpine one does not. -timeout 90s is a backstop above
|
||||
# the 60-second cap on the suite. The rerun with -v only shows details:
|
||||
# the step fails however it ends, because the first run already failed.
|
||||
# -count=1 because the mounted cache also holds test results, which go
|
||||
# test would otherwise replay.
|
||||
# golang:1.25.7-trixie, 2026-10-07
|
||||
FROM golang@sha256:2b174ffcf56c7ad0c47d30d2630693265639ddf2a5141149c2da34db921791b4 AS test
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ARG CACHEBUST
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
go test -count=1 -timeout 90s -race -cover ./... || \
|
||||
{ echo "--- Rerunning with -v for details ---"; \
|
||||
go test -count=1 -timeout 90s -race -v ./...; exit 1; }
|
||||
# Nothing is wanted from the frontend-test stage; the copy is what makes
|
||||
# this phase run its tests.
|
||||
COPY --from=frontend-test /app/yarn.lock /dev/null
|
||||
|
||||
# Frontend build stage: the production build into dist/, which the
|
||||
# runtime stage serves, on the yarn packages and source of the
|
||||
# frontend-test stage. Nothing is wanted from the two phases; the copies
|
||||
# are what make BuildKit build them first, so this stage cannot run
|
||||
# unless lint and test passed.
|
||||
FROM frontend-test AS frontend-build
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
COPY --from=test /src/go.sum /dev/null
|
||||
# vite.config.js reads the commit for the page's footer with git. git is
|
||||
# installed again on every image build, although it comes above
|
||||
# ARG CACHEBUST, because this stage starts from frontend-test, whose
|
||||
# steps run again on every build.
|
||||
RUN apk add --no-cache git
|
||||
ARG CACHEBUST
|
||||
RUN yarn build
|
||||
|
||||
# Backend build stage. Nothing is wanted from the two phases; the copies
|
||||
# are what make BuildKit build them first, so nothing after them can
|
||||
# run unless lint and test passed.
|
||||
# golang:1.25-alpine (2026-02-27)
|
||||
FROM golang@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4ced AS builder
|
||||
RUN apk add --no-cache git
|
||||
WORKDIR /src
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
ARG CACHEBUST
|
||||
COPY --from=lint /src/go.sum /dev/null
|
||||
COPY --from=test /src/go.sum /dev/null
|
||||
# A tar-stream context keeps the sender's file owners, which git refuses.
|
||||
RUN git config --system --add safe.directory /src
|
||||
COPY . .
|
||||
|
||||
# The same go build as in script/build, which builds the binary on the
|
||||
# host.
|
||||
#
|
||||
# The version is the VERSION build argument when one is given, otherwise
|
||||
# `git describe --tags --always` on the .git in the build context: the
|
||||
# tag on a tagged commit, tag-N-gHASH on a commit after one, the short
|
||||
# commit when no tag is reachable. With .git present, a version that is
|
||||
# still empty, dev or unknown fails the build: git is missing or could
|
||||
# not read the checkout.
|
||||
ARG VERSION
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
version="${VERSION:-$(git describe --tags --always)}"; \
|
||||
if [ -e .git ]; then \
|
||||
case "$version" in ""|dev|unknown) \
|
||||
echo "version is '$version' although .git is present" >&2; \
|
||||
exit 1 ;; \
|
||||
esac; \
|
||||
fi; \
|
||||
CGO_ENABLED=0 go build -trimpath \
|
||||
-ldflags "-s -w -X main.Version=$version" \
|
||||
-o netwatch-server ./cmd/netwatch-server/
|
||||
|
||||
# Runtime stage, and the last one: a plain `docker build .` builds it
|
||||
# and the stages it copies from, the two phases included.
|
||||
# nginx:stable-alpine as of 2026-02-22
|
||||
FROM nginx@sha256:15e96e59aa3b0aada3a121296e3bce117721f42d88f5f64217ef4b18f458c6ab
|
||||
ARG CACHEBUST
|
||||
|
||||
# netwatch-server runs as this user, which owns the report directory.
|
||||
# nginx keeps the image's own arrangement: its main process runs as
|
||||
@@ -71,9 +159,9 @@ RUN addgroup -g 1000 -S netwatch && \
|
||||
# At start-up the nginx image renders every template here into
|
||||
# conf.d; bin/entrypoint.sh says how.
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY nginx.conf /etc/nginx/templates/netwatch.conf.template
|
||||
COPY security-headers.conf /etc/nginx/security-headers.conf
|
||||
COPY --from=frontend /app/dist /usr/share/nginx/html
|
||||
COPY configs/nginx.conf /etc/nginx/templates/netwatch.conf.template
|
||||
COPY configs/security-headers.conf /etc/nginx/security-headers.conf
|
||||
COPY --from=frontend-build /app/dist /usr/share/nginx/html
|
||||
COPY --from=builder /src/netwatch-server /usr/local/bin/netwatch-server
|
||||
COPY bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
|
||||
|
||||
Reference in New Issue
Block a user