# syntax=docker/dockerfile:1.7 # ---- Build stage -------------------------------------------------------- # Cross-compiles: # * the server binary for the image's TARGETARCH (linux/amd64 or arm64), # * three agent binaries (linux/amd64, linux/arm64, windows/amd64) that # the running server hands out via /agent/binary. # Pure-Go SQLite (modernc.org/sqlite) means CGO stays off; static binaries # run on distroless/static. FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS build WORKDIR /src ENV CGO_ENABLED=0 \ GOFLAGS="-trimpath" # Cache module downloads in a separate layer. COPY go.mod go.sum* ./ RUN go mod download COPY . . ARG VERSION=dev ARG COMMIT=none ARG DATE=unknown ARG TARGETOS ARG TARGETARCH ENV VERSION_PKG="gitea.dcglab.co.uk/steve/restic-manager/internal/version" ENV LDFLAGS="-s -w \ -X ${VERSION_PKG}.Version=${VERSION} \ -X ${VERSION_PKG}.Commit=${COMMIT} \ -X ${VERSION_PKG}.Date=${DATE}" # Server: built for the image's runtime arch. RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ go build -ldflags="${LDFLAGS}" \ -o /out/restic-manager-server \ ./cmd/server # Empty /data skeleton so the runtime image carries an existing, # nonroot-owned mount point. Docker copies that ownership onto a # named volume the first time it's created, which avoids the # "permission denied" trap on /data/secret.key when the operator # uses a default `volumes: { rm-data: {} }` declaration. RUN mkdir -p /out/data # Agents: identical across image arches — an arm64 server image still # ships an amd64 agent binary for amd64 endpoints to download. RUN mkdir -p /out/agent-binaries && \ GOOS=linux GOARCH=amd64 \ go build -ldflags="${LDFLAGS}" \ -o /out/agent-binaries/restic-manager-agent-linux-amd64 \ ./cmd/agent && \ GOOS=linux GOARCH=arm64 \ go build -ldflags="${LDFLAGS}" \ -o /out/agent-binaries/restic-manager-agent-linux-arm64 \ ./cmd/agent && \ GOOS=windows GOARCH=amd64 \ go build -ldflags="${LDFLAGS}" \ -o /out/agent-binaries/restic-manager-agent-windows-amd64.exe \ ./cmd/agent # ---- Runtime stage ------------------------------------------------------ FROM gcr.io/distroless/static-debian12:nonroot LABEL org.opencontainers.image.source="https://gitea.dcglab.co.uk/steve/restic-manager" LABEL org.opencontainers.image.licenses="PolyForm-Noncommercial-1.0.0" USER nonroot:nonroot WORKDIR / # Server binary on PATH. COPY --from=build /out/restic-manager-server /usr/local/bin/restic-manager-server # Image-baked bundled assets (P5-03). Read-only; the /agent/binary and # /install/* handlers fall back here when /... is empty, so a # fresh container Just Works without first-run staging. Operators can # still drop a custom build under /agent-binaries/ to # override per-host. COPY --from=build --chmod=0755 /out/agent-binaries/ /opt/restic-manager/dist/agent-binaries/ COPY --chmod=0755 deploy/install/install.sh /opt/restic-manager/dist/install/install.sh COPY --chmod=0644 deploy/install/install.ps1 /opt/restic-manager/dist/install/install.ps1 COPY --chmod=0644 deploy/install/restic-manager-agent.service /opt/restic-manager/dist/install/restic-manager-agent.service # Pre-created data dir owned by nonroot so a fresh named volume # inherits the right ownership. COPY --from=build --chown=nonroot:nonroot /out/data /data EXPOSE 8443 ENTRYPOINT ["/usr/local/bin/restic-manager-server"]