Image-mode install: helmdeck without a Go toolchain
The friction
Through v0.11.0, installing helmdeck required:
- Docker Engine + Compose v2
go ≥ 1.26(the control plane's Go binary)node ≥ 20(the Management UI Vite bundle)make(build orchestration)openssl,curl, ~6 GB disk
The go ≥ 1.26 requirement is the killer. Distro packages lag (Debian ships 1.22; even Trixie is still on 1.23). Operators evaluating helmdeck on a fresh VM had to install Go from upstream before they could try anything — and many didn't.
The fix isn't subtle: ship pre-built images and let operators pull them.
The fix
v0.12.0 closes #134 step 1 by splitting deploy/compose/compose.yaml into two layers:
Base (compose.yaml) — image-mode:
services:
control-plane:
image: ghcr.io/tosin2013/helmdeck:${HELMDECK_VERSION:-latest}
sidecar-warm:
command: ["docker pull ghcr.io/tosin2013/helmdeck-sidecar:${HELMDECK_VERSION:-latest}"]
Overlay (compose.build.yaml) — source-build:
services:
control-plane:
build:
context: ../..
dockerfile: deploy/docker/control-plane.Dockerfile
Operators choose at compose up time:
# Image-mode (no Go toolchain needed):
docker compose -f deploy/compose/compose.yaml up -d
# Source-build (contributors):
docker compose -f deploy/compose/compose.yaml \
-f deploy/compose/compose.build.yaml up -d
scripts/install.sh exposes both as a single flag:
./scripts/install.sh --image-mode # pull pre-built images
./scripts/install.sh # default = source build
--image-mode implies --no-build AND skips the host Go / Node / make preflight checks. The image-mode install only needs Docker + openssl + curl.
How it composes
Compose's deep-merge has a non-obvious behaviour that makes this work: when a service has BOTH build: and image: set, Compose picks build: (and tags the resulting image with the image: reference). So the overlay doesn't have to repeat the image tag — it inherits from the base.
The same ${HELMDECK_VERSION:-latest} substitution applies in both modes:
- Image-mode +
HELMDECK_VERSION=0.12.0→ pullsghcr.io/tosin2013/helmdeck:0.12.0from GHCR - Image-mode (no var) → pulls
:latest(handy for evaluation; pin in production) - Source-build +
HELMDECK_VERSION=dev→ builds locally, tags asghcr.io/tosin2013/helmdeck:dev
Operators pin a specific release in .env.local for reproducible deploys:
echo "HELMDECK_VERSION=0.12.0" >> deploy/compose/.env.local
The v1.0 implication
This isn't just a Compose-quality-of-life win. The v1.0-rc1 Helm chart will reuse the same versioned-tag convention:
# Helm values.yaml — coming with v1.0-rc1
image:
controlPlane: ghcr.io/tosin2013/helmdeck
tag: "0.12.0" # or 1.0.0, 1.0.1, ...
So the Compose-to-Helm mental model becomes "swap the orchestrator, keep the tag pin." Operators upgrading a Compose install to a Helm install in v1.0 don't have to relearn the deployment story; they're already pinned to a versioned tag.
#134 step 1 is a hard prerequisite for v1.0.0-rc1 specifically because the Helm chart can't reuse ghcr.io/tosin2013/helmdeck:dev (Compose's prior default tag) — the chart needs versioned tags that match what's published at every release.
Upgrade path
The upgrade howto (docs/howto/upgrade-helmdeck.md) now has two paths for §2 "In-place Compose-stack upgrade":
- Path A (image-mode):
git checkout v0.12.0, bumpHELMDECK_VERSIONin.env.local, run./scripts/install.sh --image-mode. ~1 minute on warm Docker cache. - Path B (source-build):
git checkout v0.12.0,make sidecars,make install. ~3 minutes on warm cache.
Operators on production should already be on path A (or about to be after v0.12.0 — the path A upgrade is one-shot from any prior tag).
What's NOT in this PR
A few things the plan deliberately left for a future ship:
- The Helm chart itself. That's #134 step 2 and ships with v1.0-rc1 (planning).
- arm64 sidecar image. Still blocked on Marp's amd64-only upstream tarball.
- Production hardening — NetworkPolicies, RBAC, KEDA scaling, gVisor toggles all show up in v1.0-rc1 or v1.0 GA.
- A separate
helmdeck-garage-initimage. The garage-init helper still builds locally on every install (~5 seconds, no Go toolchain dependency). Lazy on purpose: pulling it back into image-mode would mean publishing yet another image at every release.
Numbers
A clean Debian 12 VM with stock packages:
$ git clone https://github.com/tosin2013/helmdeck
$ cd helmdeck
$ ./scripts/install.sh --image-mode
... (90 seconds: pulls control-plane, sidecar, garage, garage-init helper builds)
✓ helmdeck is up
URL: http://localhost:3000
Username: admin
Password: s3cr3t-passw0rd-not-actually-this
No apt install golang-go. No nodesource.com curl-bash. No make build cache. The fastest path from "clean VM" to "logged-in admin UI" yet.
Source build is still the right path for contributors — local changes need to compile from the working copy, and you want the layer cache. Image-mode is the right path for evaluation, demos, and any production install that wants reproducibility over freshness.
