# syntax=docker/dockerfile:1 # ---- deps: module download, cached until go.mod/go.sum change ---------------- FROM golang:1.25-alpine AS deps WORKDIR /src COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod go mod download # ---- test: `docker build --target test .` runs the test suite ---------------- FROM deps AS test COPY . . RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \ go vet ./... && go test ./... # ---- manual: an on-demand renderer for the PDF manual ----------------------- # Not part of the app image, so deploying the app never waits for it. Running # this image renders docs/manual.md to /out/manual.pdf (typeset with WeasyPrint, # then coffee-stained, hole-punched and photocopied; see docs/pdf/). The file is # replaced atomically. Use scripts/render-manual.sh (compose volume) or # scripts/manual-pdf.sh (a local file). FROM alpine:3.20 AS manual RUN apk add --no-cache weasyprint py3-markdown py3-pillow py3-numpy font-liberation poppler-utils WORKDIR /manual COPY docs/pdf ./pdf COPY docs/manual.md . ENTRYPOINT ["python3", "pdf/render.py", "manual.md", "/out/manual.pdf"] # ---- build: static binaries (SQLite and Postgres drivers are pure Go) -------- FROM deps AS build COPY . . ENV CGO_ENABLED=0 RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build \ go build -trimpath -ldflags="-s -w" -o /out/ ./cmd/server ./cmd/daily ./cmd/asm # ---- final: small runtime image ---------------------------------------------- FROM alpine:3.20 RUN addgroup -S -g 10001 halcyon && adduser -S -u 10001 -G halcyon halcyon \ && mkdir /data && chown halcyon:halcyon /data COPY --from=build /out/server /out/daily /out/asm /usr/local/bin/ USER halcyon WORKDIR /data # Where the world lives: "sqlite:" or "postgres://user:pass@host/db". # The default is a SQLite file on the /data volume; override it at run time, # e.g. -e DATABASE_URL=postgres://... (the -db flag also overrides it). ENV DATABASE_URL=sqlite:/data/halcyon.db # The rendered PDF manual is read from here on every request. Compose mounts the # volume the manual renderer writes to; if the file is absent /manual.pdf is a 404. ENV MANUAL_PDF=/manual/manual.pdf VOLUME /data EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ CMD wget -q -O /dev/null http://127.0.0.1:8080/info || exit 1 # Default: the API and website. Run one day of the belt with: # docker run --rm -e DATABASE_URL=... daily CMD ["server"]