feat(skills): pull skills from OCI registries via init container#259
Merged
Conversation
Adds OCI as a SkillSource alongside the existing ConfigMap-backed
skills. Knowledge-work agents need a way to ship specialized,
versioned expertise (financial analysis, web research, report
templates) that isn't best modeled as YAML manifests glued to a
team CR. OCI artifacts give us a versioning, signing, and
distribution story that already exists in every cluster.
Reconciler
- New per-skill init container ("pull-skill-{name}") runs `oras pull`
into an emptyDir mounted at /var/claude-skills/{name}/. The
existing runner entrypoint already copies that path into
~/.claude/skills/{name}/, so the main container needs no change.
- ConfigMap skills continue to work unchanged; both source types
can coexist on the same teammate.
- spec.imagePullSecrets are propagated to the Pod (kubelet pulls)
and additionally projected as a `.dockerconfigjson` → config.json
Secret volume on the init containers, with DOCKER_CONFIG=/auth/.docker
so ORAS picks creds up natively. The first listed pull secret is
used; multi-registry deployments combine credentials into a single
dockerconfigjson.
- Default puller image is ghcr.io/oras-project/oras:v1.2.0,
overridable per-cluster via the --skill-puller-image manager flag
for air-gapped or mirror deployments.
API
- AgentTeamSpec.imagePullSecrets — list of LocalObjectReference; the
same Secrets the kubelet sees for image pulls also flow into the
ORAS init container's auth mount.
- SkillSource: dropped the "not yet implemented" TODO on the OCI
field and documented re-pull semantics.
- SkillSpec gains a CEL XValidation rule enforcing exactly one of
configMap or oci.
Docs
- docs/how-to/skill-authoring.md walks through the full lifecycle:
authoring a SKILL.md directory, packaging with `oras push`,
referencing from an AgentTeam (public + private registries), and
the practical notes on digest pinning + air-gapped clusters.
- Wired into mkdocs nav under a new "Author skills" how-to group.
Sample
- config/samples/oci-skills-team.yaml exercises both a public
registry skill and a private one through imagePullSecrets.
Tests
- internal/controller coverage rose to 86.2%. Five new
buildAgentPod cases cover: OCI emptyDir + init container, private
registry auth wiring (DOCKER_CONFIG + Secret volume + dockerconfigjson
key/path projection), ConfigMap path unchanged, mixed sources on
one teammate, and SkillPullerImage override.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: amcheste <13696614+amcheste@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds OCI as a
SkillSourcealongside the existing ConfigMap-backedskills. Knowledge-work agents need a way to ship versioned, signed,
shareable expertise (financial analysis templates, research playbooks,
output formats) — OCI artifacts give us that distribution story
without inventing a new one.
ConfigMap-backed skills keep working unchanged. Both source types can
coexist on the same teammate.
How it works
Per-skill init container (`pull-skill-{name}`) runs `oras pull`
into a per-skill emptyDir mounted at `/var/claude-skills/{name}/`.
The existing runner entrypoint already copies that path into
`~/.claude/skills/{name}/` before launching Claude Code, so the
main container needs no change.
For private registries, list a `kubernetes.io/dockerconfigjson`
Secret under `spec.imagePullSecrets`. The operator both (a)
propagates it to the pod for kubelet pulls, and (b) projects its
`.dockerconfigjson` into the init container at
`/auth/.docker/config.json` with `DOCKER_CONFIG=/auth/.docker` so
ORAS picks credentials up natively. First listed secret wins —
multi-registry deployments combine credentials into a single
dockerconfigjson.
API
documented re-pull semantics (no shared cache between pods → pin to
digests for cheap registry short-circuit)
Operator flag
`--skill-puller-image` overrides the default
`ghcr.io/oras-project/oras:v1.2.0`, for air-gapped clusters that
mirror to an internal registry.
Tests
`internal/controller` rose to 86.2% coverage. Five new
`buildAgentPod` cases cover: OCI emptyDir + init container, private
registry auth wiring (DOCKER_CONFIG + Secret volume + dockerconfigjson
projection), ConfigMap path unchanged, mixed sources on one teammate,
and the puller image override.
Docs
`docs/how-to/skill-authoring.md` walks the full lifecycle: SKILL.md
authoring, `oras push`, referencing from an AgentTeam (public +
private), digest pinning, air-gapped clusters. Wired into mkdocs nav.
Sample
`config/samples/oci-skills-team.yaml` — a research team pulling
one public skill and one private skill via imagePullSecrets.
Test plan
🤖 Generated with Claude Code