Public base and debug container images for Conduktor products, published nightly with fresh packages updates. Every image use apko builds, is signed keyless with cosign, ships with an SPDX SBOM attached as a Sigstore attestation, and carries a SLSA build-provenance attestation.
| Image | Purpose | Reference |
|---|---|---|
base-os |
Minimal Wolfi (glibc) OS layer + conduktor UID/GID 10001 account. No language runtime. |
ghcr.io/conduktor/base-os:latest |
base-jre-25 |
base-os + OpenJDK 25 JRE + GNU userland. Source for Conduktor Console and Gateway images. |
ghcr.io/conduktor/base-jre-25:latest |
base-monitoring |
Conduktor's Prometheus + Cortex builds and a patched supervisord, no JVM. Source for the Console monitoring image. |
ghcr.io/conduktor/base-monitoring:latest |
conduktor-debug |
Sidecar debug toolkit: network / TLS / LDAP / Kafka / JVM tools. Deploy alongside a running Conduktor pod. | conduktor/conduktor-debug:latest (Docker Hub)ghcr.io/conduktor/conduktor-debug:latest |
Multi-arch: linux/amd64 + linux/arm64 in a single OCI index per tag.
conduktor-debug is published to both Docker Hub and GHCR in the same
build, so the two references resolve to the same digest and either one
verifies with the commands below. Conduktor docs and Helm charts reference the
Docker Hub name. The base images are GHCR-only.
Nightly scans of each :latest image, refreshed on every push.
Each badge reads <n>C / <n>H / <n>M / <n>L — findings at critical, high,
medium and low severity. Click one for the full listing: every finding with
its CVE ID, package, installed version and fix, refreshed by the same run.
| Image | Trivy | Grype |
|---|---|---|
base-os |
||
base-jre-25 |
||
base-monitoring |
||
conduktor-debug |
Trivy runs with --ignore-unfixed, so it lists only what a rebuild can clear.
Grype includes unfixed findings and every severity down to negligible. The two
disagreeing is expected; that is why both are here.
Pull requests get the same per-severity table as a comment, from the same counting code, so a PR and a badge never report different numbers.
The raw scan JSON and SBOMs are attached as workflow artifacts on each nightly run (30-day retention).
docker pull ghcr.io/conduktor/base-jre-25:latest
# pin to an immutable tag in production:
docker pull ghcr.io/conduktor/base-jre-25:2026.07.31
docker pull ghcr.io/conduktor/base-jre-25:git-2f3fd50
# the debug sidecar is on Docker Hub as well (same digest):
docker pull conduktor/conduktor-debug:latestAvailable tag streams per image:
| Tag | Meaning |
|---|---|
latest |
Most recent successful nightly. Moves. |
nightly |
Alias of latest. Moves. |
YYYY.MM.DD |
Immutable build for that UTC date. |
git-<short-sha> |
Immutable build from that commit. |
All three attestations are keyless — they are bound to the GitHub Actions workflow that produced the image, not to a private key. To trust an image you assert who built it, not that a secret was known.
The identity is build.yml, not nightly.yml:
Fulcio's SAN names the workflow containing the signing job, not the one that
called it. Only a publishing run on main can hold it — pr.yml never signs.
IMAGE=ghcr.io/conduktor/base-jre-25:latest
# ...or the Docker Hub reference for the debug sidecar — same identity, because
# each registry's copy is signed natively by the same workflow:
# IMAGE=docker.io/conduktor/conduktor-debug:latest
# 1. Signature (cosign keyless, Fulcio issuer)
cosign verify \
--certificate-identity-regexp='^https://github\.com/conduktor/container-images/\.github/workflows/build\.yml@refs/heads/main$' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
"${IMAGE}"
# 2. SPDX SBOM attestation
cosign verify-attestation \
--type=spdxjson \
--certificate-identity-regexp='^https://github\.com/conduktor/container-images/\.github/workflows/build\.yml@refs/heads/main$' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
"${IMAGE}" \
| jq -r '.payload' | base64 -d | jq '.predicate' > sbom.spdx.json
# 3. SLSA build-provenance (verifiable with gh CLI too)
gh attestation verify "oci://${IMAGE}" --repo conduktor/container-images
# 4. apko lock — the exact package set this digest was built from
cosign verify-attestation \
--type=https://conduktor.io/apko-lock/v1 \
--certificate-identity-regexp='^https://github\.com/conduktor/container-images/\.github/workflows/build\.yml@refs/heads/main$' \
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
"${IMAGE}" \
| jq -r '.payload' | base64 -d | jq '.predicate' > apko.lock.jsonScan the extracted SBOM against your own policy:
grype sbom:./sbom.spdx.json
trivy sbom ./sbom.spdx.jsonThe lock is apko lock output: every package with its version, architecture and
.apk URL, as resolved when the image was built. Extract it for two tags to see
what a nightly changed — usually the answer to "why did the CVE count move":
jq -r '.contents.packages[] | "\(.architecture) \(.name) \(.version)"' \
apko.lock.json | sort > new.txt
# ...same for the older tag, then:
diff old.txt new.txtDiff the package list, not the whole file: for conduktor-debug and
base-monitoring the lock also embeds a per-build melange signing key. And the
.apk URLs stop resolving once Wolfi drops those versions, so pin a digest to
keep an exact image — the lock is a record, not a rebuild input.
# Console / Gateway
FROM ghcr.io/conduktor/base-jre-25:latest
COPY --chown=10001:10001 --chmod=0755 ./bin /opt/conduktor/bin
USER 10001
CMD ["/opt/conduktor/bin/run.sh"]# Console monitoring — prometheus, promtool and cortex are already on PATH,
# and supervisord is pre-patched for arbitrary UIDs (see the image README).
FROM ghcr.io/conduktor/base-monitoring:latest
COPY --chown=0:0 --chmod=0755 ./monitoring-helper /opt/conduktor/bin/
USER 10001
CMD ["/opt/conduktor/scripts/run.sh"]The image default user stays root so downstream RUN steps can
chown/chmod freely; switch to USER 10001 in your final layer.
conduktor-debug is meant to run next to a live Conduktor JVM pod, sharing
its process namespace so the JDK tools can attach to the target process.
Sketch for a Helm values override:
podSpec:
shareProcessNamespace: true
containers:
- name: debug
image: conduktor/conduktor-debug:latest
command: ["sleep", "infinity"]
securityContext:
runAsUser: 10001 # same UID as the target JVM — see below
runAsGroup: 10001Then kubectl exec -it <pod> -c debug -- bash and run jcmd <pid> GC.heap_info, jstack <pid>, openssl s_client -connect kafka:9093,
ldapsearch -H ldaps://ldap:636, kcat -b kafka:9092 -L, …
jcmd, jstack, jmap, jinfo and jfr use the HotSpot attach mechanism,
a unix socket whose peer credentials the target JVM checks: it accepts the
attach when the caller is root or when the caller's effective UID and GID
both match its own. Since every Conduktor image runs as conduktor 10001,
running the sidecar as runAsUser: 10001 is enough — no root, and no
SYS_PTRACE, which the attach path never uses.
Add capabilities only for the tools that genuinely need them:
| Tool | Needs |
|---|---|
jcmd / jstack / jmap / jinfo / jfr |
matching UID/GID (or root) — nothing else |
strace, jstack -F, jmap -F, jhsdb |
SYS_PTRACE (these really do ptrace(2)) |
tcpdump |
NET_RAW |
The image itself has no run-as and so defaults to root, which keeps every
tool working out of the box for ad-hoc use. Override it with runAsUser in
production namespaces where root sidecars are disallowed.
Gateway pods predating the UID change run as 1001; the image ships that
legacy gateway account too, so use runAsUser: 1001 to match those.
Requires Docker (for the apko container fallback and to docker load the
result). A native apko binary is used if it's on PATH.
./scripts/image-build.sh base-os
./scripts/image-build.sh base-jre-25
./scripts/image-build.sh debug
# or via Make:
make build IMAGE=base-jre-25
make build-allThen docker run --rm conduktor/base-jre-25:local java -version.
The repo ships a Nix flake.nix that pins every tool the
Makefile and CI call — apko, cosign, trivy, grype, syft, crane,
yamllint, actionlint, shellcheck, gitleaks, pre-commit, jq, yq.
nix develop # drop into the dev shell
make help # list every dev target
make precommit-install # install the git hooksNon-Nix users can install the same tools via their OS package manager; the
Makefile only cares that they're on PATH.
Pull requests are welcome. The PR workflow
builds every affected image on amd64 and runs Trivy + Grype against the
tarball. Scanning is report-only: the counts land in the job summary and a
sticky PR comment, and no severity blocks the merge. Before pushing, run
make lint and
make precommit-run — the same checks run in CI, and gitleaks will catch
accidentally-staged secrets.
- Package additions or removals go in
images/<image>/apko.yaml. Explain why the package is needed in a trailing comment — the config is the contract with downstream product Dockerfiles. - Wolfi package names sometimes differ from Debian/Ubuntu (
openldap-clientsvsldap-utils,nmap-ncatvsnetcat-openbsd, etc.). Search packages.wolfi.dev before adding. - The nightly workflow assumes GHCR pushes are enabled via the built-in
GITHUB_TOKEN(packages: write). No extra secrets are required for signing, provenance, or the CVE badges — the badges are plain JSON files, and the reports they link to plain Markdown, that thepublish-badgesjob commits to the dedicatedbadgesbranch after every nightly (main is protected). - Every third-party GitHub Action is pinned to a commit SHA; Dependabot
(
.github/dependabot.yml) opens PRs to bump them weekly.