forgejo-mcp-broker/Containerfile

73 lines
2.8 KiB
Text
Raw Permalink Normal View History

feat(deploy): rootless podman + Quadlet deployment (forgejo-mcp-broker-8yd) Adds a multi-stage Containerfile, Quadlet unit, and operator walkthrough for a production deploy. The broker spawns forgejo-mcp per session, so the image bundles both binaries — broker built from this repo, forgejo-mcp pinned via FORGEJO_MCP_VERSION build-arg (default 2.18.0). Image stages: 1. golang:alpine compiles the broker with ldflags-stamped buildinfo 2. golang:alpine clones forgejo-mcp at the pinned tag and compiles it 3. distroless static-nonroot copies both binaries; uid 65532 Persistent state via the named volume `fjmcp-state` mounted at /data. SQLite WAL + SHM sidecars live alongside broker.db on the same volume, so a container swap or image upgrade preserves all OAuth clients, issued tokens, and refresh-token history. Verified end-to-end: podman run --rm -d -v fjmcp-test-state:/data ... fjmcp-broker:test curl /healthz # store: ok, broker.db created podman stop fjmcp-test podman run --rm -d -v fjmcp-test-state:/data ... fjmcp-broker:test curl /healthz # store: ok, same broker.db ls volume → broker.db, broker.db-shm, broker.db-wal all present Quadlet unit (deploy/podman/fjmcp-broker.container) drops into ~/.config/containers/systemd/, reads secrets from a 0600 env file outside the unit, publishes :8080 on loopback for Caddy to front. Makefile gains `image` and `image-run` targets. README links to the new docs/deploy-podman.md walkthrough. Closes forgejo-mcp-broker-8yd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:42:09 +02:00
# Multi-stage Containerfile for fjmcp-broker.
#
# Stage 1: build fjmcp-broker (this repo).
# Stage 2: build forgejo-mcp at a pinned version. The broker spawns it as
# a subprocess per session, so it must live in the final image.
# Stage 3: distroless static-nonroot. CGO_ENABLED=0 in both build stages
# keeps the final image free of glibc/musl entanglements and
# makes the image entirely static.
#
# Build labels and /etc/build-info wire BUILD_DATE and GIT_REVISION so
# operators can correlate a running container back to a commit.
ARG BUILD_DATE="unknown"
ARG GIT_REVISION="unknown"
ARG FORGEJO_MCP_VERSION="2.18.0"
# ---------- Stage 1: broker build ----------
FROM docker.io/library/golang:1.26-alpine AS broker-build
WORKDIR /src
RUN apk add --no-cache git
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG BUILD_DATE
ARG GIT_REVISION
ENV CGO_ENABLED=0
RUN go build \
-trimpath \
-ldflags "-s -w \
-X kode.naiv.no/olemd/forgejo-mcp-broker/internal/buildinfo.Version=${GIT_REVISION} \
-X kode.naiv.no/olemd/forgejo-mcp-broker/internal/buildinfo.GitRevision=${GIT_REVISION} \
-X kode.naiv.no/olemd/forgejo-mcp-broker/internal/buildinfo.BuildDate=${BUILD_DATE}" \
-o /out/fjmcp-broker \
./cmd/broker
# ---------- Stage 2: forgejo-mcp build ----------
FROM docker.io/library/golang:1.26-alpine AS mcp-build
WORKDIR /src
RUN apk add --no-cache git
ARG FORGEJO_MCP_VERSION
RUN git clone --depth=1 --branch v${FORGEJO_MCP_VERSION} \
https://codeberg.org/goern/forgejo-mcp.git . \
&& CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/forgejo-mcp .
# ---------- Stage 3: runtime ----------
FROM gcr.io/distroless/static-debian12:nonroot
ARG BUILD_DATE
ARG GIT_REVISION
ARG FORGEJO_MCP_VERSION
LABEL org.opencontainers.image.title="fjmcp-broker"
LABEL org.opencontainers.image.description="OAuth 2.1 broker that fronts forgejo-mcp for MCP clients."
LABEL org.opencontainers.image.source="https://kode.naiv.no/olemd/forgejo-mcp-broker"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.revision="${GIT_REVISION}"
LABEL org.opencontainers.image.licenses="MIT"
LABEL net.naiv.fjmcp.forgejo-mcp-version="${FORGEJO_MCP_VERSION}"
COPY --from=broker-build /out/fjmcp-broker /usr/local/bin/fjmcp-broker
COPY --from=mcp-build /out/forgejo-mcp /usr/local/bin/forgejo-mcp
# Persistent volume for the SQLite store (clients, tokens, audit data).
# Operators mount a named podman volume here so state survives container
# replacement. SQLite WAL writes auxiliary files (.db-wal, .db-shm) next
# to the main file — the volume contains them all.
VOLUME ["/data"]
WORKDIR /data
# Distroless nonroot user is uid 65532. Volumes inherit ownership from the
# host; document the chown step in deploy-podman.md.
USER 65532:65532
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/fjmcp-broker"]