# Two binaries, one image, Alpine in both stages, matching git-forge's own # Containerfile. # # forge/ is vendored by ./fetch-forge before a build and is not committed. FROM alpine:3.23 AS build RUN apk add --no-cache go WORKDIR /src COPY forge/ ./forge/ COPY go.mod ./syncd/ COPY *.go ./syncd/ # Static, so the runtime image needs no Go. buildvcs is off because neither tree # carries a .git this build should read. RUN cd forge && CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags "-s -w" -o /git-forge . RUN cd syncd && CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags "-s -w" -o /syncd . FROM alpine:3.23 # git-daemon is not the daemon here: in Alpine that subpackage is where # git-http-backend lives, and without it every clone and push answers 500. # tini reaps what git leaves: a detached gc is reparented to PID 1 and would # otherwise stay a zombie. RUN apk add --no-cache git git-daemon cgit highlight ca-certificates tini COPY --from=build /git-forge /usr/local/bin/git-forge COPY --from=build /syncd /usr/local/bin/syncd # Which git-forge this is; syncd logs it at startup. COPY --from=build /src/forge/.rev /etc/git-forge.rev # Non-root, and the datadir is credential material, so 0700 and one owner. RUN adduser -D -H -u 1000 forge \ && install -d -o forge -g forge -m 0700 /var/lib/git-forge USER forge ENV HOME=/tmp # No VOLUME: this disk is ephemeral by design and the durable copy is in the # bucket. 8081 is syncd; git-forge listens on loopback only. EXPOSE 8081 ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/syncd"]