Sources — Git, OCI, Helm, Bucket

How source-controller fetches, verifies, and republishes external content as an in-cluster artifact, and how GitRepository, OCIRepository, HelmRepository, and Bucket differ in auth, verification, and scale characteristics.

Overview

Sources are the boundary

Sources are the boundary between the outside world and the cluster. In Flux v2 one component, source-controller, owns that boundary entirely, and its contract explains most of the architecture on top of it.

The narrow contract

source-controller acquires content, optionally verifies its authenticity, strips what should not be deployed, packages the rest as a gzip-compressed TAR and serves it at an in-cluster HTTP URL. status.artifact records the url, digest, revision, size and timestamp.

It does nothing else: it does not apply manifests, does not know what objects are inside, and does not care who consumes it.

  • Credentials live in one place — kustomize-controller and helm-controller never open a Git connection or authenticate to a registry
  • Fetching happens once — No matter how many Kustomizations reference the same repository
  • Independent scaling and security — The fetch path and the apply path are separate

Four source kinds

All are served by the same controller under apiVersion source.toolkit.fluxcd.io/v1. They differ in authentication, how they express a version, and what they can prove about the content.

  • GitRepository — Clones a repository at a ref — branch@sha1:
  • OCIRepository — Pulls an OCI artifact from a registry — tag@sha256:
  • HelmRepository — Describes a chart repo: HTTP/S (index cached) or OCI (pointed at) — Index-based
  • Bucket — Reads objects from S3-compatible storage — SHA256 over object keys and etags

The revision is the load-bearing abstraction

> [!IMPORTANT] Source interval governs delivery latency; consumer interval governs drift correction. Confusing the two is the most common Flux tuning mistake.

Verification: from plumbing to supply chain control

  • GitRepository — GPG or SSH signatures on head commit, tag object, or both, using public keys from a Secret — The commit the content was authored from
  • OCIRepository — cosign or notation, including cosign keyless with OIDC issuer and subject regexes — The exact bytes that will be applied

If verification fails, no artifact is produced, so nothing downstream can apply unsigned content.

> [!TIP] Keyless cosign can assert an issuer of the GitHub Actions token endpoint and a subject matching your own repository. That is materially stronger than Git signature checking.

The scale story: author in Git, deliver over OCI

  • Polling from hundreds of clusters every minute — Git provider API quotas are not designed for it — Registries are built for this read pattern
  • Immutability — Branches move — Artifacts are digest-addressed
  • Promotion — Move commits between branches — Promote tags between environments
  • Generated manifests — Must be committed to Git — Can be CI output that never existed in Git

Key concepts

Artifact
A gzip-compressed TAR of the fetched content, stored on the source-controller volume and served at an in-cluster HTTP URL. status.artifact carries url, revision, digest, path, size, and lastUpdateTime. Consumers fetch this over plain HTTP and never contact the upstream themselves.
Revision
The identity of the current content. GitRepository uses branch@sha1:, OCIRepository uses tag@sha256:, Bucket uses a SHA256 digest over object keys and etags. The controller compares the freshly computed revision to status.artifact.revision and does nothing at all when they match.
spec.ref precedence (GitRepository)
branch, tag, semver, name, commit — later overrides earlier, so commit wins outright. name accepts any Git reference such as refs/heads/main or refs/pull/420/head. If nothing is set the controller defaults to the master branch. semver uses Masterminds constraint syntax over tags.
spec.verify
Signature verification gate. On GitRepository it checks GPG or SSH signatures with mode HEAD, Tag, or TagAndHEAD against keys in a Secret. On OCIRepository it uses provider cosign or notation, with matchOIDCIdentity issuer and subject regexes for cosign keyless. Failure means no artifact is produced.
sparseCheckout
A GitRepository list of directories to clone instead of the whole repository. On a monorepo this cuts clone time, network transfer, source-controller memory, and artifact size at once. Reported back in status.observedSparseCheckout so you can confirm the running configuration.
spec.ignore and .sourceignore
Exclusion rules in gitignore pattern format. Defaults already drop .git metadata, media and archive extensions, CI configuration, and Flux v1 files. A .sourceignore file in the content root can overrule the defaults; spec.ignore in the CRD overrides both and is preferable because it is reviewable in the manifest rather than hidden in the fetched tree.
HelmRepository type: oci
A static data container describing an OCI registry and its credentials. It fetches nothing, produces no artifact, and ignores interval, timeout, and suspend; its existence is its readiness. type: default is the opposite — it polls index.yaml on interval (default 1m) and caches it as an artifact.
layerSelector
An OCIRepository field that picks a specific OCI layer by mediaType, with operation extract or copy. This is how a single artifact can carry multiple payloads and Flux can pull just the one it needs, for example the Helm chart content layer out of a multi-layer image.

When to use it

  • GitRepository when the repository contains the manifests you deploy and the cluster count is small enough that polling the Git provider is not a quota problem
  • OCIRepository when manifests are generated in CI, when you need cosign or notation signatures over the exact deployed bytes, or when a large cluster fleet makes Git polling expensive
  • HelmRepository with type: default for classic HTTP chart repositories, and type: oci for charts hosted in a container registry such as GHCR or ECR
  • Bucket when the delivery artifact is produced by a pipeline that already writes to S3, GCS, or Azure Blob, or when the environment has no reachable Git host but does have object storage
  • sparseCheckout plus spec.ignore on any monorepo large enough that source-controller memory or artifact storage becomes a concern
  • spec.verify on every source that feeds production, so an unsigned commit or unsigned artifact produces no artifact at all rather than a failed apply

Interview questions

  • Explain the artifact model. What does source-controller actually produce and how does a Kustomization consume it?
  • Compare GitRepository and OCIRepository as the delivery mechanism. When would you move a fleet off Git?
  • Walk through GitRepository authentication options and their tradeoffs.
  • How do you guarantee that what Flux applies is what you signed?
  • A GitRepository has interval 1h and its Kustomization has interval 1m. What actually happens when someone merges?

Sign up free to read the full topic

More DevOps topics

Start free on Cariara · Coding problems · System design studio