ZDDC/zddc/Containerfile
ZDDC cf4101b9e4 build(zddc-server): use tini as PID-1 entrypoint
Adds tini to the runtime image and routes ENTRYPOINT through it so
zddc-server runs as PID 2 with proper orphan reaping and signal
forwarding. Today zddc-server is a single-process server and the change
is invisible; the motivation is the upcoming render path that will
shell out to pandoc (which itself shells out to xelatex / lua filters /
dot) — any grandchild orphaned by a mid-run crash gets reparented to
PID 1, and a Go server is not the right thing to put in charge of
reaping subprocesses it never spawned.

tini is ~24KB and does exactly this one job. Putting it in the upstream
image (rather than each downstream consumer's Dockerfile) means every
deployment of codeberg.org/varasys/zddc-server gets the fix for free,
including the Burns & McDonnell prod chart wrapper that's about to land.

Cut a new release with `sh release-image.sh <version> stable` to
publish.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 10:02:57 -05:00

107 lines
5 KiB
Docker

# syntax=docker/dockerfile:1
#
# Multi-stage build with three useful targets:
#
# --target binaries — scratch image holding cross-compiled binaries.
# Use `podman build --target binaries -o dist/ .`
# to extract zddc-server-{linux,darwin,windows}-*
# to the host. No image published from this stage.
#
# --target server — alpine-based runtime. Default target. Published
# as codeberg.org/varasys/zddc-server:vX.Y.Z.
#
# Build context expectations (when targeting `server`):
# dist/web/index.html and dist/web/archive.html must exist —
# produced by `sh build.sh` from the repo root. The release flow
# (`release-image.sh` at the repo root) runs build.sh first and
# then invokes `podman build --target server`.
#
# ─── Stage 1: build ──────────────────────────────────────────────────────────
FROM docker.io/library/golang:1.24-alpine AS builder
WORKDIR /src
# git is required by go mod for VCS dependencies
RUN apk add --no-cache git
# Skip sum DB checks (allows building with empty/partial go.sum)
ENV GONOSUMDB=* GOPRIVATE=* GOPROXY=direct
# Copy source
COPY . .
# Build linux/amd64 (used by the runtime image and Linux hosts)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /out/zddc-server-linux-amd64 ./cmd/zddc-server
# Cross-compile for macOS (Intel and Apple Silicon)
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /out/zddc-server-darwin-amd64 ./cmd/zddc-server
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -trimpath -ldflags="-s -w" \
-o /out/zddc-server-darwin-arm64 ./cmd/zddc-server
# Cross-compile for Windows
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
-o /out/zddc-server-windows-amd64.exe ./cmd/zddc-server
# ─── Stage 2: export binaries ─────────────────────────────────────────────────
# Use `podman build --target binaries -o dist/ .` to extract binaries to the host.
# No base image needed — this stage only exists to hold the output files.
FROM scratch AS binaries
COPY --from=builder /out/ /
# ─── Stage 3: runtime (published image) ─────────────────────────────────────
FROM docker.io/library/alpine:3.20 AS server
LABEL org.opencontainers.image.title="zddc-server" \
org.opencontainers.image.description="HTTP server for ZDDC archives — ACL via .zddc files, virtual archive index, audit logging" \
org.opencontainers.image.source="https://codeberg.org/VARASYS/ZDDC" \
org.opencontainers.image.documentation="https://zddc.varasys.io/zddc-server.html" \
org.opencontainers.image.licenses="AGPL-3.0-only" \
org.opencontainers.image.vendor="VARASYS"
# wget is in the base image (busybox); explicitly install ca-certificates
# (outbound HTTPS for any future upstream auth check) and tini (PID-1
# orphan reaper + signal forwarder). zddc-server itself only spawns
# subprocesses transitively — e.g. once a future render path shells out
# to pandoc, which in turn shells out to xelatex / lua filters / dot —
# and any of those grandchildren orphaned by a mid-run crash get
# reparented to PID 1. Without an init that knows to wait(2) on them,
# they accumulate as zombies. tini is ~24KB and does exactly this.
RUN apk add --no-cache ca-certificates tini && rm -rf /var/cache/apk/*
# Non-root user. UID/GID are deliberately fixed so volume permissions are
# predictable across hosts.
RUN addgroup -S -g 1000 zddc && adduser -S -u 1000 -G zddc zddc
# Binary
COPY --from=builder /out/zddc-server-linux-amd64 /usr/local/bin/zddc-server
# Bundled landing + archive tools — useful for self-contained demos and as
# a fallback web root. Set ZDDC_ROOT=/opt/zddc-server/web to serve only
# these (no external data). For real archives, mount the data tree at
# /srv (the default ZDDC_ROOT below).
COPY dist/web/index.html /opt/zddc-server/web/index.html
COPY dist/web/archive.html /opt/zddc-server/web/archive.html
# Conventional mount point for the served archive. Operators mount their
# data here (Azure Files, NFS, hostPath, …). Override with ZDDC_ROOT.
VOLUME /srv
USER zddc
# Default config: data mount at /srv. Override at run time as needed.
# Other env vars (ZDDC_TLS_CERT, ZDDC_EMAIL_HEADER, ZDDC_CORS_ORIGIN, …)
# are intentionally not defaulted — see zddc/README.md.
ENV ZDDC_ROOT=/srv
EXPOSE 8443
# Liveness probe for `docker run` users. Kubernetes deployments override
# this with their own livenessProbe / readinessProbe.
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD wget --no-check-certificate -q --spider https://localhost:8443/ || exit 1
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/zddc-server"]