# syntax=docker/dockerfile:1 # ── build ─────────────────────────────────────────────────────────────────── # templ output (*_templ.go) and the vendored htmx asset are committed, so a # plain `go build` produces a self-contained binary — no codegen, no CDN. FROM golang:1.26 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . # CGO off + static linking so the binary runs in a distroless/scratch image with # no libc. Trim symbols/DWARF to shrink the layer. RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags='-s -w' -o /out/tapir ./cmd/tapir # ── ffmpeg ────────────────────────────────────────────────────────────────── # Audio upload probes files with ffprobe and converts them with ffmpeg (ADR-033, # #34 option A). Static builds, so they run in the distroless image below. # # Pinned by version AND sha256: a changed upstream file fails the build instead of # shipping. The checksum proves these are the bytes that were reviewed (2026-10-02; # the publisher's md5 matched too). It does NOT prove authenticity: the publisher # signs nothing we can check. amd64 only (the cluster is one amd64 node). # # These builds are GPLv3 (--enable-gpl --enable-version3). The service is private and # the image is not distributed; the licence text is copied into the image anyway. # # The release URL moves on when upstream releases a newer version, so the pinned # version is tried in releases/ and then old-releases/. If neither has it the build # fails; vendoring the verified tarball is the durable fix. FROM golang:1.26 AS ffmpeg ARG FFMPEG_VERSION=7.0.2 ARG FFMPEG_SHA256=abda8d77ce8309141f83ab8edf0596834087c52467f6badf376a6a2a4c87cf67 # xz is not in the golang image (found by building it: `tar: xz: Cannot exec`). RUN set -eu; \ apt-get update; \ apt-get install -y --no-install-recommends xz-utils; \ rm -rf /var/lib/apt/lists/*; \ mkdir -p /out; \ for dir in releases old-releases; do \ curl -fsSL -o /out/ffmpeg.tar.xz "https://johnvansickle.com/ffmpeg/${dir}/ffmpeg-${FFMPEG_VERSION}-amd64-static.tar.xz" && break; \ done; \ echo "${FFMPEG_SHA256} /out/ffmpeg.tar.xz" | sha256sum -c -; \ tar -xJf /out/ffmpeg.tar.xz -C /out --strip-components=1 --wildcards '*/ffmpeg' '*/ffprobe' '*/GPLv3.txt'; \ rm /out/ffmpeg.tar.xz # ── runtime ───────────────────────────────────────────────────────────────── # distroless static + nonroot: no shell, no package manager, runs as uid 65532. # ca-certificates are bundled, which the OIDC/HTTPS clients need. FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /out/tapir /tapir COPY --from=ffmpeg /out/ffmpeg /out/ffprobe /usr/local/bin/ COPY --from=ffmpeg /out/GPLv3.txt /usr/share/doc/ffmpeg/GPLv3.txt # Where the service finds them. Uploads stay OFF until TAPIR_UPLOADS_ENABLED is set # (a deployment decision, not an image one). ENV TAPIR_FFMPEG_BIN=/usr/local/bin/ffmpeg \ TAPIR_FFPROBE_BIN=/usr/local/bin/ffprobe EXPOSE 8080 USER nonroot:nonroot ENTRYPOINT ["/tapir"] CMD ["serve"]