A monorepo for OCMv2 components.
Packages cloud-native applications (container images, Helm charts and their configuration) as Open Component Model components and publishes them to ghcr. It builds no application code; images and charts come from their own source repositories.
Warning
OCM v2. The predecessor opendefensecloud/ocm-components is pinned to the legacy v1 CLI and its commands do not work here. Layout and conventions are kept the same so components can move over with little churn.
| Component | Wraps | Docs |
|---|---|---|
opendefense.cloud/dependency-controller |
controller + webhook images, Helm chart | README |
opendefense.cloud/quota-controller |
controller + webhook images, Helm chart | README |
opendefense.cloud/krop-controller |
controller image, Helm chart | README |
opendefense.cloud/solution-arsenal |
SolAr apiserver, controller-manager, renderer, UI + etcd images, Helm chart | README |
opendefense.cloud/solution-arsenal-discovery |
SolAr discovery image, Helm chart | README |
opendefense.cloud/arc |
ARC apiserver, controller-manager + etcd images, Helm chart | README |
opendefense.cloud/argo-workflows |
controller, server, executor images, Helm chart | README |
opendefense.cloud/flux |
Flux controller + CLI images, Helm chart | README |
opendefense.cloud/zot |
zot + alpine images, Helm chart | README |
opendefense.cloud/ocm-demo |
example nginx image, Helm chart | README |
One directory per component, discovered automatically:
<component>/
├── component-constructor.yaml # OCM descriptor — the only place a version lives
├── values.yaml.tpl # image localization, rendered by ocm-kit
├── minimal-values.yaml # dev/test configuration
├── production-values.yaml # production HA configuration
└── README.md
Everything else is shared: Makefile (all commands), .ocmconfig (credentials,
signer and verifier), airgap.ocmconfig (by-value bundle), renovate.json (version bumps),
.github/workflows/ (release, Scorecard, commit and workflow linting).
Create a directory with a component-constructor.yaml — the release workflow
discovers it, there is no matrix to update. Then follow the conventions above
and see CONTRIBUTING.md.
make setup # install the pinned OCM v2 CLI into ./bin
make validate COMPONENT=<name> # build into a throwaway CTF, publishes nothing
make help # every targetTargets take COMPONENT=<name> and REGISTRY=<ref>; OCM_VERSION is derived
from the wrapped chart. The Makefile pulls common.mk from
dev-kit for repo-settings and
update-action-pins.
make publish is descriptor-only: the descriptor is pushed, images and
charts stay as references to where they already live in ghcr. Copying them by
value would duplicate bytes inside the same registry.
Consumers build their own self-contained bundle when they need one (make airgap does the same from a local build):
ocm transfer component-version --config .ocmconfig --config airgap.ocmconfig \
ghcr.io/opendefensecloud//opendefense.cloud/dependency-controller:<version> \
ctf::./bundle --recursiveairgap.ocmconfig holds a localblob uploader, which copies every resource
into the bundle. It must not go into .ocmconfig: the CLI merges that file
into every call, so make publish would turn into a by-value upload. --config
replaces the .ocmconfig lookup rather than adding to it, so pass
.ocmconfig explicitly. Otherwise the credentials are gone. The
--copy-resources/--upload-as flags are deprecated since CLI 0.21.
A dangling reference cannot be published: OCM resolves every external resource
at build time to compute its digest, so make validate already fails if a
wrapped image or chart is missing. make resolve-check re-validates the whole
graph after publishing.
Published paths keep the source image path under the target repository, so the
chart lands at <REGISTRY>/opendefensecloud/charts/<component>. The doubled
opendefensecloud is expected.
The same holds for a by-value transfer into a registry, which needs an oci
uploader in place of airgap.ocmconfig: an artifact lands at
<target>/<source path without host>, and the OCI uploader cannot change that.
quay.io/coreos/etcd:v3.6.11 ends up at <target>/coreos/etcd:v3.6.11, and
docker.io/library/alpine:3.24 at <target>/library/alpine:3.24. This flat
layout is deliberate and has one risk: two sources that
differ only in their host land on the same path. make lint-constructors
fails when that happens. Several components sharing one identical reference,
like etcd in solution-arsenal and arc, is fine.
SBOMs are type: sbom resources in the constructor, one per image and
platform, linked to their image by the ocm.software/artifact-references label
(ocm-project#1225).
The label is signing: true, and the resource digest is in the descriptor, so the
signature covers both the link and the SBOM content.
The documents are the CycloneDX release assets of the upstream release, fetched
with Wget/v1. The URL uses ${OCM_VERSION}, so a Renovate bump moves the
SBOMs along. This assumes the image and chart versions match. An upstream release without them fails make validate, so they
cannot be dropped silently.
make sbom COMPONENT=<name> # SBOMs of every image into ./sbomsocm download resource --sbom prefers a linked resource. Without one, it falls back
to the BuildKit SPDX attestation in the image index. Both survive make airgap.
Every component links SBOMs for each of its images. Charts get none: they contain no binaries.
A component's version is the version of the artifact it wraps. Chart 0.4.0
means component 0.4.0 read from the helmChart resource (make version).
There is no second version line to keep in sync. A v prefix on the chart tag
is dropped, so ARC's chart v0.3.2 means component 0.3.2.
Components moved from ocm-components share their repository with old
versions. opendefense.cloud/{solution-arsenal,solution-arsenal-discovery,arc,flux,argo-workflows,zot,ocm-demo}
already hold v26.x versions released by ocm-components under its repo-wide
tag. They sort above the chart-derived versions published here (v26.5.8 > 0.4.0), so anything that resolves "latest" or a range picks the stale ones. Pin
an explicit version.
Renovate bumps the chart/image → merge to main → published + signed → tagged
Nothing is tagged by hand and there is no release PR. A push to main
publishes every component whose version is not in the registry yet, so the
workflow is safe to re-run. Pull requests validate but publish nothing.
The tag <component>/v<version> is a convenience — it lets you check out the
packaging state of a release. It triggers nothing and is an unsigned ref.
The descriptor is not a substitute: a sources entry is not covered by the
signature (measured — two builds differing only in the commit produce an
identical signed digest, and OCM warns about exactly this). What does bind the
release to a commit is the Sigstore certificate, whose extensions record the
workflow run and its revision.
Important
A packaging-only fix has no version of its own and ships with the next upstream release.
Sigstore keyless. There are no signing secrets: permissions: id-token: write
provides an ambient OIDC token that the signing handler forwards to cosign, and
Fulcio issues a short-lived certificate bound to the workflow identity — which is
also what ties a release to its commit.
The signature is stored inside the component descriptor, not as a separate
registry artifact, so GitHub's package page shows no signature badge. Check it
with make verify.
.ocmconfig holds the registry credentials, the signer, and the
verifier. CLI 0.21 removed --verifier-spec. Earlier versions ignored a
verifier: key in the config, so older docs tell you the opposite.
Warning
Registry credentials are likewise not optional: without an explicit
DockerConfig/v1 entry OCM requests a push token anonymously and ghcr answers
403. A public pull works either way, which makes the omission easy to miss.
Verification pins who may have signed — without both constraints a keyless signature proves only that somebody signed:
# .ocmconfig, signing.config.ocm.software/v1alpha1 → verifier
certificateOIDCIssuer: https://token.actions.githubusercontent.com
certificateIdentity: https://github.com/opendefensecloud/ocm/.github/workflows/release.yml@refs/heads/mainSigning does not work offline; locally you need SIGSTORE_ID_TOKEN set.
Resolvers are not configured — no component declares componentReferences.
OCM v2 changes enough that v1 commands do not carry over — resource copying is
an uploader config instead of a flag, --version is gone, and v2
signatures do not verify with v1 tooling. The measured list is in
CLAUDE.md.
The Apache 2.0 LICENSE covers this repository's own content — component constructors, values files, the Makefile, workflows and documentation.
It does not cover the packaged applications. Each wrapped image and Helm
chart keeps the licence of the project that produces it; see the component's
README.md for which, and that project's repository for the terms.
Publishing here is descriptor-only, so this repository redistributes no
third-party artifacts — it references them where they already live. A consumer
who builds a by-value bundle (make airgap) does redistribute
them, and the upstream licences apply to that copy.