Overview of Custom Assembly Overlays
Note: Custom Assembly Overlays is in beta.
Standard Custom Assembly applies one customization to every tag in a repository. This fails for images that ship several language or runtime versions side by side, because a package built for one version can’t install on the others.
For example, the python image publishes tags for Python 3.11, 3.12, 3.13, and 3.14. The py3.13-typer package depends on Python 3.13. If you add it with standard Custom Assembly, every tag tries to install it, and the 3.11, 3.12, and 3.14 builds fail.
Custom Assembly Overlays let you choose which tags, and which repositories, receive a customization. For example, you can do the following:
- Add a package to specific tags, such as
3.13and3.13-dev. - Add debugging tools to every
-devtag and keep the other tags minimal. - Add a package to every tag, with the package name matched to each tag’s Python version.
- Reuse one customization, such as your organization’s internal certificates, across many repositories.
- Apply a customization to every repository in your organization or in a folder, including repositories created later.
This page explains the concepts. To create and manage customizations, see Managing Custom Assembly Overlays with chainctl or Managing Custom Assembly Overlays with Terraform.
Overlays and bindings
Custom Assembly Overlays split a customization into two resources:
- An overlay is a named, reusable set of customizations, such as packages, environment variables, annotations, user accounts, certificates, and runtime repositories. An overlay belongs to your organization, not to a repository, and on its own it changes nothing.
- A binding attaches one overlay to one repository, or to every repository under an organization or folder, and selects which tags the overlay applies to.
To apply an overlay to several repositories, create one binding for each repository, or create one all-repos binding that covers them all.
When you create, update, or delete a binding, or update an overlay, Chainguard rebuilds the affected tags without waiting for a new upstream release. An overlay update rebuilds the matching tags in every repository the overlay is bound to. As with standard Custom Assembly, a build normally takes less than 20 minutes, and Chainguard rebuilds the customized tags whenever their packages are updated.
Tag selectors
Each binding has exactly one tag selector, which chooses the tags the overlay applies to. In chainctl, you set it with one of the --all, --variant, or --tag flags on chainctl images overlays attach. A selector is one of three kinds:
| Selector | Matches | Example use |
|---|---|---|
| Exact | The tags you list by name, such as 3.13 and 3.13-dev. | Add a package that only works with one version. |
| Variant | Every tag of a variant. Only the dev variant is available, which matches every tag ending in -dev. | Add debugging tools to development images only. |
| All | Every tag in the repository. | Add certificates or packages that work with every tag. |
Variant and all selectors also match tags published after you create the binding. An exact selector matches only the tag names you list. Tags that no binding matches keep their uncustomized image.
Exact tags and shared digests
Several tags often point to the same image. For example, 3.13, 3.13.7, and 3.13.7-r0 might share one digest. An exact selector customizes only the tags you list, even when other tags share their digest. If you bind an overlay to 3.13 alone, 3.13 gets a customized image, and 3.13.7 and 3.13.7-r0 keep the original. To keep several tags identical, list all of them.
An exact selector matches tag names, not images. When 3.13 moves to a new release, the binding follows it, so the new 3.13 image is customized too.
Chainguard doesn’t check that an exact tag exists when you create the binding. A mistyped tag name matches nothing, so no build runs for it.
All-repos bindings
A binding normally attaches an overlay to one repository. An all-repos binding attaches it to every repository under an organization or a folder instead, including repositories created later. Use one to apply a customization, such as your organization’s internal certificates, everywhere at once.
An all-repos binding belongs to the organization or folder it covers, and it has a tag selector like any other binding. An overlay can have at most one all-repos binding per organization or folder. You can’t change an all-repos binding’s scope; to re-scope one, detach it and attach a new one.
Folders nest, so a repository can be covered by several all-repos bindings at once: one on the organization and one on each folder above it. For example, an organization can bind a certificates overlay to every repository, and also bind a hardening overlay to its golden-images folder. Repositories in golden-images receive both.
A repository can’t opt out of an all-repos binding. To vary a setting for one repository, bind another overlay to that repository directly: the repository’s own bindings take precedence, as described in the next section. All-repos bindings also don’t apply to repositories that use standard Custom Assembly; those keep their standard customization.
How overlapping bindings combine
A tag can match more than one binding. For example, latest-dev matches an all binding, a dev variant binding, and an exact binding that lists latest-dev. When a tag matches several bindings, Chainguard layers them first by scope, from broadest to most specific:
- The organization’s all-repos bindings.
- Each folder’s all-repos bindings, outer folders before nested ones.
- The repository’s own bindings.
Within each scope, bindings layer by selector kind:
- All bindings.
- Variant bindings.
- Exact bindings.
Packages and runtime repositories accumulate across layers. When two layers set the same environment variable, annotation, or other single value, the more specific layer wins: exact over variant, variant over all, and the repository’s own bindings over any all-repos binding.
For example, suppose a repository has the following bindings:
- An all binding whose overlay adds
curl. - A dev variant binding whose overlay adds
strace. - An exact binding on
latest-devwhose overlay addsgdb.
Chainguard adds these packages to each tag:
| Tag | Packages added |
|---|---|
latest-dev | curl, strace, gdb |
Other -dev tags | curl, strace |
| All other tags | curl |
Several bindings of the same kind
You can bind several overlays to one repository with the same kind of selector. For example, you can bind a certificates overlay and a packages overlay to a repository, both with an all selector.
Bindings of the same kind in the same scope have no precedence order, so their overlays must not contradict each other. Chainguard rejects a binding if it matches a tag that another binding of the same kind in the same scope also matches, and the two overlays set any of the following to different values:
- An environment variable
- An annotation
- A named certificate, runtime key, user, or group
- Another single value, such as the user the image runs as
Packages and runtime repositories never cause a binding conflict, because Chainguard combines them. Combined packages can still fail a build if the packages themselves are incompatible, for example if two of them install the same file. Chainguard also runs the conflict check when you update an overlay, against every repository the overlay is bound to.
Bindings in different scopes never conflict, because scopes have a precedence order. You can bind a given overlay to a repository only once.
Version templates in package names
Many packages include a language version in their name, such as py3.12-cryptography and py3.14-cryptography. To add the right package to every tag with one overlay, use the {{major}} and {{minor}} placeholders in the package name:
contents:
packages:
- py{{major}}.{{minor}}-cryptographyWhen Chainguard builds each tag, it replaces the placeholders with the major and minor version of the image’s main package. The 3.12 tags of the python image receive py3.12-cryptography, and the 3.14 tags receive py3.14-cryptography. When a tag moves to a new version, the package follows it.
Chainguard reads each image’s main package from its dev.chainguard.package.main label. To check the label, run the following crane command:
crane config cgr.dev/$ORGANIZATION/python:3.12 | jq -r '.config.Labels["dev.chainguard.package.main"]'python-3.12In this example, the main package is Python 3.12, so {{major}} becomes 3 and {{minor}} becomes 12.
{{major}} and {{minor}} are the only supported placeholders. If you create or update an overlay with any other {{...}} placeholder, Chainguard rejects it.
If an image has no main package, or its version has no major and minor components, Chainguard can’t fill in the placeholders. That tag’s build fails, and the build logs name the package.
Supported customizations
An overlay supports the same customizations as standard Custom Assembly, with the same validation rules:
- Packages (
contents.packages) - Custom runtime repositories (
contents.runtime_repositories) - Custom runtime keys (
contents.runtime_keyring) - Environment variables and annotations (
environmentandannotations) - User accounts and groups (
accounts) - Custom certificates (
certificates.additional) - Guarded Entrypoint (
guarded_entrypoint,fail_mode,preflight, andcommand_override). To see how these fields combine when several bindings match one tag, see How the fields combine across bindings.
Overlays don’t support Chainguard-managed certificate bundles (certificates.providers). If an overlay contains a field that overlays don’t support, Chainguard rejects the whole overlay instead of ignoring the field.
Limitations
Custom Assembly Overlays have the following limitations:
- One model per repository. A repository can use standard Custom Assembly or overlays, but not both. If a repository has one kind of customization, adding the other kind fails with the error
repository custom overlay and overlay binding not allowed. To move a repository from standard Custom Assembly to overlays, contact your Chainguard account team. - No Chainguard Console support. Manage overlays and bindings with
chainctl, Terraform, or the Chainguard API. The Console’s Custom Assembly editor manages standard Custom Assembly only. - A missing package fails the build. If a package in an overlay can’t be installed on a tag, that tag’s build fails. Chainguard doesn’t skip the package. The build logs name the package that failed.
- No removing base packages. As with standard Custom Assembly, an overlay can add to an image but can’t remove packages from the source image.
Permissions
Overlays and bindings use their own capabilities:
registry.overlays.listlets you view overlays and bindings. The built-inviewer,editor, andownerroles include it.registry.overlays.editlets you create, update, and delete overlays and bindings. The built-ineditorandownerroles include it.
To create a custom role with these capabilities, see Overview of roles and role-bindings in Chainguard.