# syntax=docker/dockerfile:1
#
# quarry ships WITH zit, deliberately.
#
# 0001 §2 makes the zit CLI the entire store interface: every read and
# every write is a subprocess call. So zit's behaviour IS quarry's
# behaviour, and the two cannot be allowed to drift. On 2026-08-16 alone,
# three zit builds changed what quarry does — `frames` got 31% faster,
# a card-parsing fix changed which artifacts parse AT ALL (before it,
# sqlite's 794 tickets were invisible), and a new `stat -R` let quarry
# serve a bare store. An image that resolved zit at run time from
# whatever happened to be on PATH would reintroduce exactly the class of
# silent divergence that cost a day to find.
#
# Hence: ZIT_REF is a build argument, the built binary is copied in, and
# `quarry version` is recorded next to `zit version` at build time so a
# running container can answer "which zit is this?".
#
# docker build --build-arg ZIT_REF=<commit> -t quarry .
# ---- zit builder ---------------------------------------------------------
# Zig rather than Go: zit is a Zig program, which is why this stage cannot
# simply be another golang builder.
# The Go that go.mod names; TestGoPinsAgree keeps the two in step.
ARG GO_VERSION=1.27.1
FROM alpine:3.21 AS zit-builder
ARG ZIG_VERSION=0.16.0
# Naming is zig-<arch>-linux-<version>, NOT zig-linux-<arch>-<version>:
# the order changed in a recent Zig release, and the old form 404s.
RUN apk add --no-cache curl xz
RUN case "$(uname -m)" in \
aarch64) ZIG_ARCH=aarch64 ;; \
x86_64) ZIG_ARCH=x86_64 ;; \
*) echo "unsupported architecture $(uname -m)" >&2; exit 1 ;; \
esac && \
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-${ZIG_ARCH}-linux-${ZIG_VERSION}.tar.xz" \
-o /tmp/zig.tar.xz && \
mkdir -p /opt/zig && tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1 && \
ln -s /opt/zig/zig /usr/local/bin/zig
WORKDIR /src
# zit's source arrives as a NAMED BUILD CONTEXT rather than a clone:
#
# docker build --build-context zit=/path/to/zit .
#
# A clone would need credentials today — the GitHub mirror is private,
# and the two ways to supply them are both worse than this: a token
# baked into a layer, or an SSH agent forwarded into a builder. zit is
# a fossil repository whose public mirror lives at
# https://fossil.craftdesign.group/zit, so once that is the source of
# record a `fossil clone` step is a fine alternative.
#
# A named context is worth keeping regardless: it pins exactly the
# checkout being shipped rather than whatever a branch name means at
# build time, which is the same reason quarry pins the pair at all. The
# recorded `zit version` in /etc/quarry-build is what says which one an
# image actually carries.
COPY --from=zit . /src/zit
# --release=fast, not -Doptimize: zit's build.zig uses Zig 0.16's spelling.
RUN cd zit && zig build --release=fast && ./zig-out/bin/zit version
# ---- quarry builder ------------------------------------------------------
# CGO stays off: quarry's SQLite is ncruces/go-sqlite3 (WASM, no C) and
# Pikchr runs on wazero, so the whole binary is pure Go and static.
FROM golang:${GO_VERSION} AS quarry-builder
ARG VERSION=0.0.0-dev
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ENV CGO_ENABLED=0
RUN go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o /out/quarry ./cmd/quarry
# ---- runtime -------------------------------------------------------------
# busybox wget is present so compose and k8s health checks work, matching
# the notify image's posture.
FROM alpine:3.21
RUN apk add --no-cache ca-certificates && adduser -D -u 10001 quarry
COPY --from=zit-builder /src/zit/zig-out/bin/zit /usr/local/bin/zit
COPY --from=quarry-builder /out/quarry /usr/local/bin/quarry
# The pair, recorded together. `docker run <image> cat /etc/quarry-build`
# answers which zit a given quarry was shipped against without starting it.
RUN printf 'quarry: %s\nzit: %s\n' "$(quarry version)" "$(zit version)" > /etc/quarry-build && \
cat /etc/quarry-build
# The mount points must exist AND be owned by the runtime user before
# VOLUME declares them: docker creates a missing volume directory as
# root, and the server then cannot open its own database. Found by
# running the image, not by reading it.
RUN mkdir -p /repos /data && chown quarry:quarry /repos /data
USER quarry
# /repos holds the zit stores quarry serves; /data holds derived state
# (indexes, embedded NATS, the OLTP database) and is disposable by 0001
# §11 — wiping it and re-ingesting must converge to identical state.
VOLUME ["/repos", "/data"]
EXPOSE 8080
ENTRYPOINT ["quarry"]
CMD ["serve", "--addr", "0.0.0.0:8080", "--data", "/data"]