Files
noitu/Dockerfile
T
tiennm99 00d3dadad4 build: container health check, licence in the image, CI hardening
HEALTHCHECK via noitu-server -healthcheck; the root LICENSE ships next
to NOTICE and the CI image check requires it; .claude and .env files
stay out of the build context. CI uses go-version stable, runs
govulncheck and npm audit, checks out without persisted credentials, and
proto.yml moves off the archived buf-setup-action. dependabot.yml is
dropped; the audit steps are the dependency signal. deployment.md gains
the Coolify/Traefik recipe, the stop-grace rule, the hello deadline and
per-address room budget, and the suppressed-log counter.
2026-09-29 20:33:16 +07:00

95 lines
3.9 KiB
Docker

# One image: the binary, the built frontend, and the derived dictionary.
#
# The upstream dump is downloaded in a builder stage and never reaches the
# final image — only the few-MB database derived from it does. That
# derived database is CC BY-SA 4.0 while the code is Apache-2.0, so it is
# copied in as its own layer alongside its licence and attribution rather than
# being embedded in the binary.
# --- the frontend -----------------------------------------------------------
FROM node:24-alpine AS web
WORKDIR /src/web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ ./
RUN npm run build
# --- the binary -------------------------------------------------------------
FROM golang:1-alpine AS build
# What GET /version answers and the startup log line carries. .dockerignore
# deliberately keeps .git out of the build context — a stale copy should
# never ship — so git describe cannot run in here; a caller that wants a real
# version passes it in, the way `make image` does. Unset, this defaults to
# "dev", which is honest about an unstamped build.
ARG VERSION=dev
WORKDIR /src/server
COPY server/go.mod server/go.sum ./
RUN go mod download
COPY server/ ./
# CGO_ENABLED=0 is what makes a distroless static image possible, and it works
# because the SQLite driver is pure Go.
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.version=${VERSION}" -o /out/noitu-server ./cmd/noitu-server
RUN CGO_ENABLED=0 go build -trimpath -o /out/build-dictionary ./cmd/build-dictionary
# --- the dictionary ---------------------------------------------------------
FROM alpine:3 AS dict
# Fetched fresh, not pinned: Wikimedia regenerates the dump monthly and
# repoints `latest/`. The derived dictionary is the one thing in this image
# that cannot be rebuilt from the repository alone, so the builder records the
# SHA-256 of the file it read in the database's meta table. The Makefile uses
# the same URL for local builds, and a test asserts the two agree.
ARG DICT_URL=https://dumps.wikimedia.org/viwiktionary/latest/viwiktionary-latest-pages-articles.xml.bz2
# Set to 1 to build from the checked-in word sample instead of downloading the
# upstream dump. That produces a playable but tiny dictionary, and exists so
# the image itself can be smoke-tested without network access.
ARG FIXTURE_DICT=0
RUN apk add --no-cache curl
WORKDIR /work
COPY --from=build /out/build-dictionary /usr/local/bin/build-dictionary
COPY testdata/fixture-words.txt ./fixture-words.txt
RUN set -eu; \
mkdir -p /out; \
if [ "$FIXTURE_DICT" = "1" ]; then \
build-dictionary --words ./fixture-words.txt --out /out/noitu.db --min-words 150; \
else \
curl -fsSLR -o viwiktionary-latest-pages-articles.xml.bz2 "$DICT_URL"; \
build-dictionary --dump ./viwiktionary-latest-pages-articles.xml.bz2 --out /out/noitu.db; \
fi
# --- the image --------------------------------------------------------------
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /app
COPY --from=build /out/noitu-server /app/noitu-server
COPY --from=web /src/web/build /app/web
# The share-alike half of the image. data/LICENSE and data/ATTRIBUTION.md ship
# with the derived wordlist because CC BY-SA 4.0 applies to it wherever it is
# distributed, and a container image is distribution.
COPY --from=dict /out/noitu.db /app/data/noitu.db
COPY data/LICENSE /app/data/LICENSE
COPY data/ATTRIBUTION.md /app/data/ATTRIBUTION.md
COPY NOTICE /app/NOTICE
COPY LICENSE /app/LICENSE
ENV NOITU_ADDR=:8080 \
NOITU_DB_PATH=/app/data/noitu.db \
NOITU_WEB_DIR=/app/web
EXPOSE 8080
USER nonroot:nonroot
# The image has no curl or wget, so the health check is the binary itself:
# -healthcheck GETs /healthz on NOITU_ADDR and exits 0 or 1. It is liveness
# only; /readyz is the drain signal a load balancer polls.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["/app/noitu-server", "-healthcheck"]
ENTRYPOINT ["/app/noitu-server"]