Image Roadmap — the road to 100 (demand-ranked)

Why demand, not stars#

For a hardened-container catalog, the metric that matters is "how often is this run as a container," not GitHub stars. They diverge sharply:

Image ⭐ stars 🐳 Docker pulls Reality
dive 54k 1.7M laptop image-inspector — rarely containerized
k9s 34k 169k terminal UI run locally — almost never a prod container
oauth2-proxy 15k 97M auth sidecar — run as a container everywhere
pomerium 5k 1.6B* identity-aware proxy — massive container use

So popular-but-local tools (k9s, stern, dive, age) are deprioritized, and high-pull proxies/apps (oauth2-proxy, pomerium, …) are promoted.

* Docker pull counts are noisy — cumulative since inception, inflated by CI/bot pulls, and some namespaces (bitnami/*) are being deprecated. Treat as order-of-magnitude, not precise.

License lens: 🟢 permissive (MIT/BSD/Apache/MPL/ISC) add freely · 🟡 AGPL/GPL ok (precedent: loki/tempo/mimir/minio/trufflehog) · 🔴 SSPL/BUSL/EULA avoid or fork.

Build effort: Go single-binary = the proven Batch-B crank (fast). C/Rust/C++ = heavier. Node/frontend = the bwrap frontend quagmire (defer, own effort). Controllers = multi-image, run in-cluster (high demand, more work).


Tier 1 — high demand × easy Go build (do next)#

Single static Go binaries and genuinely run as containers. Best impact-per-effort.

Image Upstream License 🐳 pulls Notes
[x] oauth2-proxy oauth2-proxy/oauth2-proxy 🟢 MIT 97M 15k k8s auth sidecar, ubiquitous, CG-gated (#393)
[x] flux (CLI) fluxcd/flux2 🟢 Apache-2.0 3.8M 8k GitOps, CG-gated — embeds install manifests (kustomize bundle at build)
[x] kustomize kubernetes-sigs/kustomize 🟢 Apache-2.0 12M 12k CI/CD standard, CG-gated — monorepo, kustomize/vX tag
[x] sops getsops/sops 🟢 MPL-2.0 22k secrets in CI, CG-gated
[x] crane google/go-containerregistry 🟢 Apache-2.0 4k registry ops, heavy CI use
[x] kubeseal bitnami/sealed-secrets 🟢 Apache-2.0 9k sealed-secrets CLI (canonical repo, not bitnami-labs 301)

Reclassified out of Tier 1 (not clean Go single-binaries — moved to "deferred"):

  • pomerium — huge demand (1.6B pulls) but //go:embeds an arch-specific Envoy binary as its data plane → Tier-3-complexity build, own effort.
  • cmctl — cert-manager's makefile-modules/klone build, no clean -X version injection → needs its own investigation.

Tier 2 — solid demand, easy Go CLIs#

Image Upstream License 🐳 pulls Notes
[x] helmfile helmfile/helmfile 🟢 MIT 5k declarative Helm, CD pipelines
[x] regctl regclient/regclient 🟢 Apache-2.0 2k registry client, CI
[x] stern stern/stern 🟢 Apache-2.0 5k multi-pod log tail (borderline: often local)
[x] kube-vip kube-vip/kube-vip 🟢 Apache-2.0 3k control-plane VIP + LB for bare metal — shipped; runs as root (NET_ADMIN/NET_RAW for netlink+ARP)

Tier 3 — high demand, heavier builds (deliberate; fills thin DB/proxy/app categories)#

Real container demand, but C/Rust/C++/Node — each is its own effort, not a crank. Sequence by effort. Fills the catalog's thinnest categories (databases, proxies, apps).

Image Upstream License Build 🐳 pulls Notes
[x] pgbouncer pgbouncer/pgbouncer 🟢 ISC C 20M 4k Postgres pooler (thin DB cat) — shipped; underscore tags (tag-rewrite)
[x] unbound NLnetLabs/unbound 🟢 BSD C 12M 5k DNS resolver — shipped; --sbindir=/usr/bin, builtin evloop, local-data smoke test
[ ] varnish varnishcache/varnish-cache 🟢 BSD C 21M 4k Deferred → see below. varnishd compiles VCL with cc at runtime → needs gcc+binutils+headers in prod (breaks the shell-less/minimal thesis).
[ ] apisix apache/apisix 🟢 Apache-2.0 C/Lua/OpenResty 37M 17k API gateway (harder: OpenResty)
[x] vaultwarden dani-garcia/vaultwarden 🟡 AGPL-3.0 Rust 304M 64k self-hosted Bitwarden — shipped
[ ] kvrocks apache/kvrocks 🟢 Apache-2.0 C++ 3.5M 4k Redis-on-RocksDB
[x] patroni patroni/patroni 🟢 MIT Python 9k Postgres HA — shipped; first Python-daemon pattern
[ ] woodpecker woodpecker-ci/woodpecker 🟢 Apache-2.0 Go+frontend 2.7M 7k CI server (has web UI)
[x] zipkin openzipkin/zipkin 🟢 Apache-2.0 JVM-jlink 60M 17k distributed tracing — shipped; Spring Boot fat jar EXPLODED so its 157 bundled jars are scannable/patchable

Deferred / heavier efforts (own project, not a batch)#

Image Reason
varnish varnishd compiles VCL → C → .so by invoking cc at runtime (VCC_CC="exec cc … -fpic -shared -o %o %s"), on every config load. A working prod image must therefore ship gcc + binutils + C headers + varnish's headers — a permanent compiler/attack-surface that breaks the "no compiler, minimum packages" thesis (Chainguard ships the toolchain for the same reason). Revisit only as a deliberate, documented exception, or pick a compiler-free HTTP-cache alternative.
kubescape Go, but a large build — needs ~40 G local disk freed (stale /tmp/bubblewrap-guest-*). Recipe already generated in batch-b; onboard once disk allows.
pomerium 1.6B pulls but //go:embeds an arch-specific Envoy binary (data plane). Needs an Envoy-fetch step + cross-arch handling — Envoy-image-class effort, not a clean crank.
cmctl cert-manager's makefile-modules/klone build; no clean -X version injection. Needs its own investigation of the version mechanism.
cert-manager (core) Multi-image (controller + webhook + cainjector + startupapicheck + acmesolver). High demand, in-cluster. Mirror the upstream image split — own effort after Tier 1.
flux (controllers) source/kustomize/helm/notification-controller, separate repos. Multi-image, in-cluster. Own effort.
uptime-kuma 166M pulls (MIT) but Node/yarn frontend → the bwrap frontend quagmire (see grafana). Backend-from-source + prebuilt frontend assets, own effort.
argocd Embeds a yarn-built React UI and repo-server needs git/helm/kustomize at runtime (fights distroless). Grafana-bucket effort.

k9s (terminal UI) · dive (image inspector) · age (local crypto CLI) · duckdb (embedded lib, not a server) · zig/erlang/lua (languages — official base images exist). Add later only on explicit demand.

Avoided — non-OSS licenses (🔴)#

MongoDB / Elasticsearch (SSPL) · CockroachDB / Dragonfly (BSL). Use permissive forks if a real need appears (e.g. kvrocks/valkey cover the Redis/BSL gap).

Existing 🔴 exposures to resolve (pre-existing, tracked separately)#

  • redis-slim declares SSPL-1.0 but 8.x is tri-licensed → re-declare AGPL-3.0-only.
  • consul 2.0.0 = BUSL-1.1 (no OSS fork) → keep-vs-drop decision pending.

Road to 100 — remaining batches (F–K), policy × usage × license#

Ordered by our proven build templates first (fastest impact-per-effort), then demand. Policy filter = buildable under the shell-less/minimal thesis: ✅ static-Go binary · clean C daemon (no runtime compiler — the varnish lesson) · JVM-jlink · interpreter · binary-repackage. Excluded from the 100-path: frontend-in-bwrap (grafana/argocd/uptime-kuma), runtime-compiler (varnish), tangled force-bump graphs, huge-disk C++ (clickhouse), and 🔴 SSPL/BUSL/EULA. This section is a completed build log — the 100 target was reached and the catalog now stands at 108.

Batch F — static Go (the crank) → 88 [DONE]#

Image Upstream License Build notes
external-dns kubernetes-sigs/external-dns v0.21.0 🟢 Apache main ., CGO=0, -X …/pkg/apis/externaldns.Version
velero vmware-tanzu/velero v1.18.2 🟢 Apache main ./cmd/velero, CGO=0, …/pkg/buildinfo.Version
kaniko GoogleContainerTools/kaniko v1.24.0 🟢 Apache main ./cmd/executor (binary executor → /kaniko/executor), …/pkg/version.version
step-ca smallstep/certificates v0.30.2 🟢 Apache main ./cmd/step-ca, CGO=0 (drops pkcs11 KMS), -X main.Version; server
skopeo containers/skopeo v1.23.0 🟢 Apache module go.podman.io/skopeo; CGO=0 + -tags containers_image_openpgp drops libgpgme/btrfs

Batch G — clean C daemon (pgbouncer/unbound template) → 93 [DONE]#

Image Upstream License Build notes
dnsmasq Wolfi dnsmasq 2.93 🟡 GPL-2.0-or-later apko-only — Wolfi packages it, so no melange recipe and no versions.yaml row (classified wolfi-rolling). Flag-driven: the package ships no config and apko cannot author file contents, so --conf-file= disables config lookup and --log-facility=- moves logs off syslog.
keepalived acassen/keepalived 2.4.3 🟡 GPL-2.0-or-later source-built; --disable-dbus --disable-snmp --with-init=none, --sbindir=/usr/bin (usrmerge). Needs CAP_NET_ADMIN/NET_RAW/NET_BROADCAST at runtime — still runs as nonroot 65532. github-tags needs strip-prefix: 'v', not strip-v (that key is only read by other source types).

Fills thin Infrastructure.

cassandra (🟢 wide-column DB, Java-17 + jamm agent) · solr (🟢 search, Java-21) · pulsar (🟢 messaging, Java-21) · flink (🟢 stream processing, Java-21) — all DONE. All Apache. Fills thin DB/search/messaging.

Batch I — Rust (qdrant/vector precedent, own effort each)#

vector (🟢 MPL-2.0, observability pipeline) · vaultwarden (🟡 AGPL-3.0, Bitwarden server; ~304M pulls) — both DONE.

Batch J — Erlang / Python#

patroni (🟢 MIT, Postgres HA — new Python-daemon pattern) — DONE. couchdb (🟢 Apache, Erlang — rabbitmq precedent) remaining.

Batch K — own-effort revisit#

cmctl (🟢 Apache — revisit klone build) · + one of {cert-manager core (multi-image) / pomerium (Envoy fetch) / apisix (OpenResty)}.

Batches H–K name more candidates than the 4 slots left to 100; the last slots go to whichever land first.

Beyond the 100-path (deliberate exceptions, not scheduled): varnish (runtime cc), grafana/argocd/uptime-kuma (frontend-in-bwrap), clickhouse/kubescape (disk-cap). See the deferred table above.

Wave 2 — beyond 108 (demand-ranked, re-surveyed 2026-08-30)#

Candidate survey after the 100 target was met. Same lens as above (license · container demand · use case), with one new input: many of these already have a Wolfi package, which moves them from "own effort" into the cheapest bucket we have (apko-only, dnsmasq/vector precedent — no melange recipe, no versions.yaml row, just an autoupdate-coverage.yaml classification).

Pull counts are Docker Hub pull_count read on 2026-08-30; treat as order-of-magnitude (cumulative, CI/bot-inflated).

Tier W1 — apko-only from an existing Wolfi package (cheapest crank)#

Verified present in the Wolfi x86_64 APKINDEX on 2026-08-30. (v) = versioned package ⇒ wolfi-versioned; (r) = unversioned/rolling ⇒ wolfi-rolling.

Image Wolfi package License 🐳 pulls Category Notes
[ ] wordpress wordpress (r) + wordpress-oci-entrypoint 🟡 GPL-2.0-or-later 1.5B Apps Largest single demand number in the survey. Pairs with our php/httpd; Wolfi ships the OCI entrypoint too. Fills the thinnest category (Apps, 6).
[ ] sonarqube sonarqube (r) 🟡 LGPL-3.0 1.2B K8s, CI & IaC Community Build. Heavier than it looks — bundles Elasticsearch and needs an external Postgres; smoke test = boot + /api/system/status.
[ ] nextcloud nextcloud-server-33 (v) 🟡 AGPL-3.0 1.0B Apps AGPL precedent already set (loki/tempo/mimir/minio/vaultwarden). PHP-FPM.
[ ] maven maven-3.9 (v) 🟢 Apache-2.0 767M Languages & Runtimes Build-tool image on top of our java. Near-zero effort, very high demand.
[ ] kong kong (r) + kong-entrypoint 🟢 Apache-2.0 359M Web Servers & Proxies The Wolfi package removes the OpenResty/Lua build problem that keeps apisix deferred. Fills a thin category (8).
[ ] neo4j neo4j-2025.12 (v) 🟡 GPL-3.0 322M Databases Graph DB — a category we have zero coverage of. GPL precedent: dnsmasq, keepalived.
[ ] gradle gradle-9 (v) 🟢 Apache-2.0 300M Languages & Runtimes Same shape as maven.
[ ] perl perl (r) 🟡 Artistic-1.0 / GPL-1.0+ 255M Languages & Runtimes Trivial; the last mainstream scripting runtime we're missing.
[ ] couchdb couchdb-3.3 (v) 🟢 Apache-2.0 205M Databases Already named in Batch J as an Erlang source build — the Wolfi package makes it a W1, not an own-effort.
[ ] argo-cd argo-cd-3.2 + -repo-server (v) 🟢 Apache-2.0 148M K8s, CI & IaC Re-opens a deferred item. Wolfi packaging removes the yarn-frontend build; the repo-server's runtime git/helm/kustomize needs still apply, so this is multi-image, not one.
[ ] rust rust-1.92 (v) 🟢 MIT OR Apache-2.0 143M Languages & Runtimes Toolchain image (builder-shaped, like our go).
[ ] erlang erlang-28 (v) 🟢 Apache-2.0 65M Languages & Runtimes Natural companion to rabbitmq.
[ ] meilisearch meilisearch (r) 🟢 MIT 51M Databases Rust search engine; complements opensearch/solr at a much smaller size.
[ ] temporal temporal (r) 🟢 MIT 47M Apps Durable-execution server. Wolfi also has temporal-ui-server-oci-entrypoint if we want the UI later.

Also packaged in Wolfi, parked deliberately: argo-workflows (🟢 Apache — take with argo-cd or not at all), harbor-2.14-* (🟢 Apache, but 5+ images — own effort like cert-manager), mattermost-11.4 (⚠️ mixed AGPL + Mattermost Source Available License in the same tree — needs a license read before onboarding), apisix-ingress-controller (🟢 Apache — note Wolfi packages the controller, not apisix itself, so the Tier-3 apisix entry above is unchanged).

Wave 3 survey — verified 2026-09-07#

Re-checked every W1/W2 candidate against the live Wolfi APKINDEX (120,016 packages) and a Chainguard free-tier probe, after the W1 table was found wrong twice: it listed a gitlab-runner-18.11 package that was actually gitlab-agent-* (a different product), and a bare-name APKINDEX grep misses versioned packages entirely. Method that actually works — check three things, not one:

grep -x  "P:<name>"        APKINDEX   # exact package name
grep -E  "^P:<name>-[0-9]"  APKINDEX   # versioned package (gitlab-runner-19.3)
grep -E  "^p:(.* )?<name>=" APKINDEX   # a DIFFERENT package that provides the name

The Chainguard probe (validated against known-free nginx/python/static, which all return 200) is an anonymous pull-token request against cgr.dev/v2/chainguard/<name>/manifests/latest.

Already free from Chainguard — dropped from the plan. These were the roadmap's "near-zero effort, very high demand" picks, and there is no reason to publish a second free hardened build of something already free elsewhere:

Image 🐳 pulls Why dropped
wordpress 1.5B cgr.dev/chainguard/wordpress returns 200 anonymously
maven 767M free
gradle 300M free
rust 143M free

Gated on Chainguard and packaged in Wolfi — the cheap, high-demand bucket. Pull counts read from Docker Hub on 2026-09-07:

Image 🐳 pulls License Category Note
sonarqube 1.3B 🟡 LGPL-3.0 K8s, CI & IaC Heavier than the packaging suggests: bundles Elasticsearch, needs an external Postgres
nextcloud 1.1B 🟡 AGPL-3.0 Apps nextcloud-server-33 (+ -apache2-config). PHP-FPM
kong 360M 🟢 Apache-2.0 Web Servers & Proxies The Wolfi package removes the OpenResty/Lua build that keeps apisix deferred
neo4j 323M 🟡 GPL-3.0 Databases Graph DB — a category with zero coverage today
perl 255M 🟡 Artistic/GPL Languages & Runtimes Trivial; the last mainstream scripting runtime missing
couchdb 205M 🟢 Apache-2.0 Databases
erlang 66M 🟢 Apache-2.0 Languages & Runtimes Companion to rabbitmq
meilisearch 52M 🟢 MIT Databases Rust search engine, much smaller than opensearch/solr
temporal 48M 🟢 MIT Apps Durable execution; fills the thinnest category
argo-cd · cert-manager · keda · karpenter · harbor · zot 🟢 Apache-2.0 K8s, CI & IaC All in Wolfi, all gated on Chainguard. No pull counts exist — they publish to quay/ghcr/registry.k8s.io, so demand cannot be ranked the way the rest of this table is

The finding that shapes the next wave: the source-built well has run dry at the top of the demand curve. Every remaining high-demand candidate is either Wolfi-packaged (so apko-only), blocked by a frontend build (alloy, authelia, woodpecker), or already free from Chainguard. What is left to build from source is genuinely hard and much lower demand: influxdb (1.2B, but that number is v1/v2 — current upstream is a Rust rewrite), kvrocks (3.5M, C++), apisix (37M, OpenResty), emqx (Erlang, licence read needed), timescaledb (split licence), pgadmin4 (Python + prebuilt frontend), fluentd (Ruby).

So Wave 3 is a decision, not a queue: continue insisting on source builds and accept far lower demand per unit of effort, or take the apko-only route for things Chainguard gates. Note that apko-only is not a weaker product here — the Wolfi package is free, Chainguard's image is not, so a hardened signed SBOM-attested rebuild of a gated image is exactly the gap this catalogue exists to fill. It also keeps full auto-update coverage via autoupdate-coverage.yaml, which check-autoupdate enforces.

Tier W2 — source-built Go (the proven crank; no Wolfi package)#

Image Upstream License 🐳 pulls Category Notes
[x] gitlab-runner gitlab-org/gitlab-runner 🟢 MIT 3.6B K8s, CI & IaC Shipped — source-built. Wolfi does ship it as the versioned gitlab-runner-19.3, which p:-provides the bare name, so provider-priority: 100 was mandatory; without it apko silently installed Wolfi's binary over ours. versions.yaml uses type: json against the GitLab tags API.
[x] buildkit moby/buildkit 🟢 Apache-2.0 1.8B K8s, CI & IaC Shipped — source-built. provider-priority: 100 needed too (Wolfi's buildkitd provides buildkit). buildkitd is the only CGO_ENABLED=1 build in the catalogue, to keep upstream's seccomp tag; libseccomp-static keeps it static. Runs as root — it refuses unprivileged uids, and the rootless path needs setuid newuidmap Wolfi does not ship setuid.
[ ] alloy grafana/alloy 🟢 Apache-2.0 388M Observability Blocked — frontend-in-bwrap, verified 2026-09-06. internal/web/ui/dist is NOT committed; assets_builtin.go is behind //go:build embedalloyui whose go:generate runs npm ci && npm run build, and the !embedalloyui fallback serves from a runtime filesystem path that will not exist in the image. So there is no UI-bearing build without npm in the sandbox. Demand is real (inherits EOL promtail 2.8B + grafana-agent 470M) — reconsider if we ever take on a frontend build pattern.
[ ] authelia authelia/authelia 🟢 Apache-2.0 81M Web Servers & Proxies Blocked — frontend-in-bwrap, verified 2026-09-06. internal/server/public_html contains only 3 files (index.html + the openapi pair); the React bundle is built from web/ by pnpm and is not in the tarball. Same blocker as alloy.
[x] syncthing syncthing/syncthing 🟢 MPL-2.0 346M (two repos) Infrastructure Shipped — confirmed the easiest build in this table. CGO_ENABLED=0 picks the pure-Go modernc.org/sqlite driver v2.x needs; the web GUI is .gitignore'd upstream and regenerated by script/genassets.go, which is plain Go — no npm step. Built -tags noupgrade like upstream's own container.
[x] nsq nsqio/nsq 🟢 MIT 63M Caches, Queues & Messaging Shipped — was indeed about an hour. Ships all nine upstream binaries for drop-in parity with nsqio/nsq; nsqadmin's web assets are committed pre-built and pulled in via go:embed. Note upstream is quiet (v1.3.0, Dec 2023), so its auto-update loop will be idle by design.

Tier W3 — heavier / needs a decision#

Image Why it's not W1/W2
influxdb 1.1B pulls on library/influxdb, but that number is v1/v2 (Go, MIT); current upstream is InfluxDB 3 Core in Rust (🟢 Apache-2.0), no Wolfi package, and our time-series slot is already served by prometheus/mimir/victoria-metrics/thanos. Worth it only if we want line-protocol coverage.
emqx 47M pulls, but the repo is NOASSERTION (Apache core + BSL-ish enterprise pieces) and it's a large Erlang build. License read required first.
timescaledb 123M pulls, but the tree is split Apache-2.0 / TSL (source-available) — an Apache-only build is possible and is the only version we could ship. Own effort.
pgadmin4 444M pulls (🟢 PostgreSQL license), Python + prebuilt frontend. Plausible via pip wheels; needs its own investigation.

Confirmed avoid (🔴 non-OSS) — checked in this survey#

terraform (492M) · vault (554M) · nomad · boundary · waypoint — all BUSL-1.1; already covered by our opentofu and openbao. elasticsearch / kibana / logstash — Elastic License; covered by opensearch. redpanda (35M) — BSL. n8n — Sustainable Use License. No change to the policy.

Suggested first PR out of this survey#

Six images, all W1, no new build template, spanning four categories: gitlab-runner · wordpress · maven · gradle · kong · perl — the two largest demand numbers we're missing, the two cheapest Languages entries, and the first addition to Web Servers & Proxies since oauth2-proxy.

Execution model#

  • One PR per tier group (~6–8 images). Each image: 10 registration points (see docs/onboarding.md) incl. a validated cron-enabled versions.yaml row — the check-autoupdate gate blocks the PR otherwise.
  • Build and prod+dev smoke-test every image locally before push (make <img>, make test-<img>, and assemble+test the -dev variant). Never push a failing image.
  • Registration inserts use Python (newline-safe), not bash $(...) (which strips newlines and glues YAML lines — same failure family as the batch-b dropped paren).

Was this page helpful?