Status: 108 images — the 100 target is met. This is the source-of-truth plan for growing the catalog. The tier tables below stay useful as the demand-ranked backlog; the "road to 100" batch section near the end is kept as a build log of how the first 100 were reached, not as remaining work. It supersedes the batch order from earlier sessions, which was ordered by build ease and GitHub popularity. This version is ordered by actual container demand first, then build effort.
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).
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-Xversion injection → needs its own investigation.
| ✓ | 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) |
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 |
| [ ] | 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 |
| 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.
MongoDB / Elasticsearch (SSPL) · CockroachDB / Dragonfly (BSL). Use permissive forks if a
real need appears (e.g. kvrocks/valkey cover the Redis/BSL gap).
redis-slimdeclares SSPL-1.0 but 8.x is tri-licensed → re-declare AGPL-3.0-only.consul2.0.0 = BUSL-1.1 (no OSS fork) → keep-vs-drop decision pending.
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.
| 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 |
| 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.
vector (🟢 MPL-2.0, observability pipeline) · vaultwarden (🟡 AGPL-3.0, Bitwarden server; ~304M pulls) — both DONE.
patroni (🟢 MIT, Postgres HA — new Python-daemon pattern) — DONE. couchdb (🟢 Apache, Erlang — rabbitmq precedent) remaining.
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.
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).
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 (apisix-ingress-controller (🟢 Apache — note Wolfi packages the controller,
not apisix itself, so the Tier-3 apisix entry above is unchanged).
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.
| ✓ | 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. |
| [ ] | 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 | 🟢 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. |
| 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. |
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.
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.
- One PR per tier group (~6–8 images). Each image: 10 registration points
(see
docs/onboarding.md) incl. a validatedcron-enabledversions.yamlrow — thecheck-autoupdategate blocks the PR otherwise. - Build and prod+dev smoke-test every image locally before push (
make <img>,make test-<img>, and assemble+test the-devvariant). 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).