# ============================================================================= # 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/ # --- Runtime stage --- # The runtime stays on Debian 12 (its current point release, 12.13, with every fixed package) ON PURPOSE: Debian 13 # ships restic 0.18.0, and the off-site engine's measured behaviour is restic 0.14.0's (a missing source path is a # warning, the --json progress shape, the forget day-buckets, restore without a lock — offbox_*.go). Moving to # Debian 13 is its own change with that re-proof (R-1105). The binary itself is built on Go 1.27.2 above. FROM debian:12.13-slim@sha256:67b30a61dc87758f0caf819646104f29ecbda97d920aaf5edc834128ac8493d3 # Install runtime dependencies: # - docker-cli: for "docker compose" commands # - ca-certificates: for HTTPS (healthchecks pings, git) # - postgresql-client: for pg_dump # - default-mysql-client: for mysqldump # - sqlite3: for SQLite backup # - git: for stack sync from Gitea # - curl: for health pings and debugging # - util-linux: lsblk, blkid, mount (storage init) # - fdisk: sfdisk (partition table writing — separate from util-linux on Debian) # - e2fsprogs: mkfs.ext4 (filesystem formatting) # - rsync: for data migration between storage paths # - parted: partprobe (partition table re-read after sfdisk) # - restic: encrypted off-box (NAS) backup over SFTP (Part B; version pinned by the Debian release) # - openssh-client: restic's sftp backend shells out to `ssh` (the SFTP transport); also ssh-copy-id/sftp # for the SLICE-2 offsite apply-bridge key install # - sshpass: feeds the one-time storage-box password to ssh-copy-id -s -f (offsite apply-bridge) # R-972: `apt-get upgrade` first — the Debian security fixes newer than the base image's point release (gnutls, pcre2, # libcap2 were fixable on 12.13 on 2026-10-16). Within Debian 12 only, so restic stays 0.14.0. RUN apt-get update && apt-get upgrade -y --no-install-recommends && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ gnupg \ git \ postgresql-client \ default-mysql-client \ sqlite3 \ util-linux \ fdisk \ e2fsprogs \ rsync \ parted \ restic \ openssh-client \ sshpass \ && rm -rf /var/lib/apt/lists/* # Install docker-cli (without daemon) RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] \ https://download.docker.com/linux/debian bookworm 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"]