check / check (push) Successful in 49s
bin/entrypoint.sh, still running as root, now creates DATA_DIR if missing and gives it and /data to the netwatch user with mode 750 before starting the backend as that user. An empty host directory owned by root, or one holding files from another uid, works with no step on the host, so the README no longer tells the operator to create or chown it. The image no longer sets that ownership at build time. Model: opus-5-5
98 lines
3.7 KiB
Docker
98 lines
3.7 KiB
Docker
# 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.
|
|
# bin/entrypoint.sh starts and watches both.
|
|
|
|
# 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
|
|
# node:22-alpine as of 2026-02-22
|
|
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS frontend
|
|
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
|
|
|
|
# Runtime stage
|
|
# nginx:stable-alpine as of 2026-02-22
|
|
FROM nginx@sha256:15e96e59aa3b0aada3a121296e3bce117721f42d88f5f64217ef4b18f458c6ab
|
|
|
|
# netwatch-server runs as this user, which owns the report directory.
|
|
# nginx keeps the image's own arrangement: its main process runs as
|
|
# root, its worker processes as the nginx user.
|
|
RUN addgroup -g 1000 -S netwatch && \
|
|
adduser -u 1000 -S netwatch -G 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 --from=builder /src/netwatch-server /usr/local/bin/netwatch-server
|
|
COPY bin/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
|
|
# bin/entrypoint.sh creates DATA_DIR at start and gives it and /data to
|
|
# the netwatch user, whatever is mounted there.
|
|
ENV DATA_DIR=/data/reports
|
|
VOLUME /data
|
|
|
|
# The default public port; PORT changes it.
|
|
EXPOSE 8080
|
|
|
|
# Requests the backend's health check through nginx, on the port from
|
|
# PORT, so it fails unless both answer. upaas reads the result 60
|
|
# seconds after a deploy and fails the deploy unless it is healthy.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD wget -q -O /dev/null "http://127.0.0.1:${PORT:-8080}/.well-known/healthcheck"
|
|
|
|
# The nginx image stops its container with SIGQUIT; the entrypoint
|
|
# acts on TERM and INT.
|
|
STOPSIGNAL SIGTERM
|
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|