132 lines
6.7 KiB
Docker
132 lines
6.7 KiB
Docker
# =============================================================================
|
|
# felhom-controller Dockerfile
|
|
# Multi-stage build: Go binary + minimal runtime
|
|
# Supports amd64 (N100 mini PCs) and arm64 (Raspberry Pi)
|
|
# =============================================================================
|
|
|
|
# --- Build stage ---
|
|
# R-972 (v0.322.0): a supported, patched Go, pinned by version AND digest (no moving tag). Go 1.24 had no security fixes
|
|
# left (go.dev/doc/devel/release#policy). The builder's Debian does not reach the runtime (a static CGO-free binary).
|
|
FROM golang:1.27.2-trixie@sha256:47667934874913a449b96faa7ef4777fa285d9ec29126ee80ed61c45b7cccf0e AS builder
|
|
|
|
ARG TARGETOS=linux
|
|
ARG TARGETARCH
|
|
|
|
WORKDIR /build
|
|
|
|
# Cache dependencies first
|
|
COPY go.mod ./
|
|
RUN go mod download || true
|
|
|
|
# Copy source
|
|
COPY . .
|
|
|
|
# Generate go.sum and ensure all deps are fetched
|
|
RUN go mod tidy
|
|
|
|
# R-208: the per-build values are declared HERE, just above the only step that reads them. A RUN's
|
|
# cache key includes the stage's ARG environment, so declared above `go mod download` a fresh VERSION
|
|
# re-ran the module download on every build (208 cache records, each used once, ~440 MB apiece).
|
|
# Pinned by TestR208_DockerfileVersionArgsSitBelowModuleDownload.
|
|
ARG VERSION=dev
|
|
ARG GIT_COMMIT=unknown
|
|
|
|
# Build static binary
|
|
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
|
|
-ldflags="-s -w \
|
|
-X main.Version=${VERSION} \
|
|
-X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
|
|
-X main.GitCommit=${GIT_COMMIT}" \
|
|
-o /build/felhom-controller \
|
|
./cmd/controller/
|
|
|
|
# --- restic: the official release binary, pinned by version AND checksum (R-1105, v0.323.0) ---
|
|
# Never again „whatever the OS ships": Debian 12 → 13 would have swapped restic 0.14.0 → 0.18.0 under the off-site code
|
|
# without anyone choosing it. The version below is a decision; moving it is a re-proof (07-backup-architecture.md §6.8).
|
|
# Three checks, each fails the build: restic's SHA256SUMS carries a good signature from restic's release key (vendored
|
|
# in third_party/restic/, fingerprint CF8F 18F2 8445 7597 3F79 D4E1 91A6 868B D3F7 A907 — no keyserver at build
|
|
# time); the download matches the published sum; and it matches the sum pinned HERE, so even a re-signed release
|
|
# cannot swap the binary. Pinned by TestR1105_ResticIsPinnedOutsideTheOS.
|
|
FROM debian:13.7-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f AS restic
|
|
ARG TARGETARCH
|
|
ARG RESTIC_VERSION=0.18.1
|
|
ARG RESTIC_SHA256_AMD64=680838f19d67151adba227e1570cdd8af12c19cf1735783ed1ba928bc41f363d
|
|
ARG RESTIC_SHA256_ARM64=87f53fddde38764095e9c058a3b31834052c37e5826d2acf34e18923c006bd45
|
|
COPY third_party/restic/restic-release-key.asc /tmp/restic-release-key.asc
|
|
RUN set -eu; \
|
|
apt-get update && apt-get install -y --no-install-recommends ca-certificates curl gnupg gpgv bzip2; \
|
|
case "${TARGETARCH}" in \
|
|
amd64) want="${RESTIC_SHA256_AMD64}" ;; \
|
|
arm64) want="${RESTIC_SHA256_ARM64}" ;; \
|
|
*) echo "no pinned restic checksum for architecture '${TARGETARCH}'" >&2; exit 1 ;; \
|
|
esac; \
|
|
f="restic_${RESTIC_VERSION}_linux_${TARGETARCH}.bz2"; \
|
|
url="https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}"; \
|
|
cd /tmp; \
|
|
curl -fsSL -o "$f" "$url/$f"; curl -fsSL -o SHA256SUMS "$url/SHA256SUMS"; curl -fsSL -o SHA256SUMS.asc "$url/SHA256SUMS.asc"; \
|
|
gpg --dearmor < /tmp/restic-release-key.asc > /tmp/restic-release-key.gpg; \
|
|
gpgv --keyring /tmp/restic-release-key.gpg SHA256SUMS.asc SHA256SUMS; \
|
|
grep " ${f}\$" SHA256SUMS | sha256sum -c -; \
|
|
echo "${want} ${f}" | sha256sum -c -; \
|
|
bunzip2 -c "$f" > /usr/local/bin/restic; chmod 0755 /usr/local/bin/restic; \
|
|
/usr/local/bin/restic version | grep -q "^restic ${RESTIC_VERSION} "
|
|
|
|
# --- Runtime stage ---
|
|
# Debian 13 (R-1105, v0.323.0), pinned by point release AND digest. It waited on Debian 12 until the off-site code's
|
|
# restic assumptions were re-proven on 0.18 (07-backup-architecture.md §6.8); restic itself no longer comes from here.
|
|
FROM debian:13.7-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f
|
|
|
|
# Install runtime dependencies — each one is a program the controller binary runs (R-1105 re-checked every exec):
|
|
# - docker-cli + compose plugin: every stack operation (below)
|
|
# - ca-certificates: for HTTPS (healthchecks pings, git, the hub)
|
|
# - curl: the image's HEALTHCHECK, and the docker repo key below
|
|
# - gnupg: dearmors docker's repo key below
|
|
# - git: catalog sync
|
|
# - util-linux: findmnt (mount reads)
|
|
# - rsync: data migration between storage paths
|
|
# - openssh-client: restic's sftp/rclone transports shell out to `ssh`; the off-site key probe
|
|
# v0.323.0 (R-1105) REMOVED, because the binary never runs them: postgresql-client and default-mysql-client (every
|
|
# dump and import runs INSIDE the app's own database container — `docker exec`, appbackup/dbdump.go), sqlite3,
|
|
# fdisk, e2fsprogs, parted (disk work moved to the host agent in v0.37.0), sshpass (the ssh-copy-id key install it
|
|
# fed was replaced by the hub's key registrar in v0.289.0). Fewer packages, fewer known holes.
|
|
RUN apt-get update && apt-get upgrade -y --no-install-recommends && apt-get install -y --no-install-recommends \
|
|
ca-certificates \
|
|
curl \
|
|
gnupg \
|
|
git \
|
|
util-linux \
|
|
rsync \
|
|
openssh-client \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
COPY --from=restic /usr/local/bin/restic /usr/local/bin/restic
|
|
|
|
# Install docker-cli (without daemon). The repo's codename comes from the image's own /etc/os-release, never a
|
|
# hard-coded name (a hard-coded `bookworm` would have kept Debian 12's packages on a Debian 13 base).
|
|
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg \
|
|
&& . /etc/os-release \
|
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] \
|
|
https://download.docker.com/linux/debian ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list \
|
|
&& apt-get update \
|
|
&& apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Create non-root user (but we'll run as root for Docker socket access)
|
|
# The Docker socket requires root or docker group membership
|
|
RUN mkdir -p /opt/docker/felhom-controller/data
|
|
|
|
COPY --from=builder /build/felhom-controller /usr/local/bin/felhom-controller
|
|
|
|
# Copy baked-in app assets (logos, screenshots)
|
|
# These are synced from the felhom.eu website repo before building.
|
|
# See: make sync-assets
|
|
COPY assets/ /usr/share/felhom/assets/
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD curl -f http://localhost:8080/api/health || exit 1
|
|
|
|
EXPOSE 8080
|
|
|
|
ENTRYPOINT ["/usr/local/bin/felhom-controller"]
|
|
CMD ["--config", "/opt/docker/felhom-controller/controller.yaml"] |