# Chainguard Academy - Full Content Generated: October 16, 2026 at 2:05 PM UTC ## License Documentation: CC BY-SA 4.0 | Code Examples: Apache 2.0 --- ## Full Documentation ## General Resources ### Chainguard Community Guidelines! URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/guidelines.md Last Modified: September 28, 2026 This space is for developers, engineers, and security pros who are building and learning together to secure the software supply chain—from distroless containers to CVE-free deployments. We’re glad you’re here. To keep this space productive and respectful, please follow these community guidelines: ## 1. Be kind and respectful to one another. * Assume good intent. We all come from different backgrounds, skill levels, and communication styles. * Encourage curiosity and questions — no gatekeeping or shaming. ## 2. Stay on topic when posting! * Use the relevant channels. * Ask questions, share tips, post relevant resources. * For memes or off-topic convos, head to #memes or #overheard — we love a good laugh. ## 3. Help each other (there are no dumb questions, either!) * Don't be afraid to ask “basic” questions—someone else is probably wondering the same. * Share what worked for you. Bonus points for linking docs, examples, or GitHub issues. * Be constructive when offering feedback or corrections. ## 4. Respect privacy & security. * Don’t share private customer data, credentials, or anything sensitive. * If you see something that violates these guidelines, DM the @chainguard-team. ## 5. This is a Community, Not Support * While Chainguard team members may participate, this Slack isn’t a replacement for official support and is outside of our formal SLAs. * If you are interested in Enterprise Support from Chainguard, we recommend getting in touch with our Sales team to discuss options tailored to your needs. ## 6. By Being Here, You Agree To: * Follow these guidelines. * Help us keep this a welcoming space. * Remember: we're building something better—together. Got questions about the guidelines or ideas for improvement? DM the @chainguard-team — we're always listening and here to help! --- ## Chainguard ### Guarded Entrypoint for Custom Assembly URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/guarded-entrypoint/overview.md Last Modified: October 9, 2026 Tags: Chainguard Containers, Custom Assembly, Configuration, Procedural > **Note**: Guarded Entrypoint is in beta. To use it, contact Chainguard customer support to enable it for your organization. Guarded Entrypoint lets a Custom Assembly image run startup logic without a derived image build. You declare the logic as part of your Custom Assembly configuration. Chainguard builds it into the image and signs the result. This page explains what Guarded Entrypoint is and how to turn it on. The other pages in this section cover the details: * [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/) * [Guarded Entrypoint examples](/chainguard/containers/custom-assembly/guarded-entrypoint/examples/) * [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/) * [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/) ## What Guarded Entrypoint is Many applications need to do some work before they start. They read secrets into environment variables, wait for a database to accept connections, or change the command they run. A Chainguard image gives you one way to change its entrypoint: build a new image on top of it. That derived image is no longer the image Chainguard signs and rebuilds. Scanners report the difference, and the image does not pick up Chainguard's rebuilds unless you rebuild it too. Guarded Entrypoint removes the need for the derived build. When you turn it on for a Custom Assembly repo, Chainguard sets the image's entrypoint to a small binary that Chainguard builds, `/usr/bin/guarded-entrypoint`. When the container starts, the binary does the following: 1. Resolves secret references in the container's environment. 2. Runs the preflight checks you configured. 3. Starts your application as its child process. The binary forwards signals to your application and exits with your application's exit code. The image you deploy is the image Chainguard built, with your startup settings stored in its configuration. ## Prerequisites Before you start, you need the following: * A Custom Assembly repo. Refer to the [Custom Assembly overview](/chainguard/containers/custom-assembly/overview/) to create one. * A role that lets you edit Custom Assembly repos. Refer to the [Custom Assembly permissions requirements](/chainguard/containers/custom-assembly/overview/#custom-assembly-permissions-requirements). * The latest [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/). Run `chainctl update` to update it. An older `chainctl` drops the Guarded Entrypoint keys when it reads a repo, so a teammate who edits the repo with an older version can turn the feature off without noticing. Update every copy of `chainctl` that edits the repo. * Guarded Entrypoint enabled for your organization. It's a beta feature, so contact Chainguard customer support to enable it. Until then, the API rejects `guarded_entrypoint` with the error in [API errors](#api-errors). The examples on this page use the following environment variables. Set them to match your organization and repo: ```shell export ORGANIZATION=example.com export REPO=my-custom-python ``` ## Turn on Guarded Entrypoint with chainctl A repo's Custom Assembly configuration is a YAML manifest. Guarded Entrypoint adds four keys to it: | Key | Meaning | | --- | --- | | `guarded_entrypoint` | Set to `true` to wrap the image's entrypoint. Every other key in this table requires it. | | `fail_mode` | `closed` (the default) or `open`. Sets what happens when a secret reference can't be resolved. | | `preflight` | A list of checks to run before the application starts. | | `command_override` | A command to run in place of, or in front of, the image's own command. | Secret references go in the existing `environment` key. For details on each key, refer to [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/). To turn it on interactively, open the repo's manifest in your editor: ```shell chainctl images repos build edit --repo $REPO --parent $ORGANIZATION ``` Add the keys you need. The following manifest turns on Guarded Entrypoint and resolves one secret from Vault: ```yaml guarded_entrypoint: true environment: VAULT_ADDR: https://vault.example.com:8200 VAULT_K8S_ROLE: orders-service DB_PASSWORD: cg+vault://secret/data/orders#db_password ``` Keep the keys that are already in the manifest. Applying a manifest replaces the repo's stored configuration, so removing a key from the manifest removes it from the repo. Save and close the editor. `chainctl` shows a diff and asks you to confirm. After you confirm, Chainguard rebuilds the repo's images with the wrapper as their entrypoint. To apply a manifest without an editor, put it in a file and use `apply`: ```shell chainctl images repos build apply -f build.yaml --repo $REPO --parent $ORGANIZATION --yes ``` The `--yes` flag skips the confirmation prompt. To preview the change first, use `--dry-run` in place of `--yes`. The command prints the diff and exits with a non-zero status when it finds a change to apply. In a pipeline, pass `--yes` to apply or `--dry-run` to preview, and pass `--parent` so `chainctl` doesn't prompt you to choose a group. A structured `--output` format on its own doesn't suppress the confirmation prompt. `chainctl` checks the manifest before it sends anything to the API. For example, it rejects `preflight` without `guarded_entrypoint`. For more on `edit` and `apply`, refer to [Using chainctl to manage Custom Assembly resources](/chainguard/containers/custom-assembly/custom-assembly-chainctl/). ### Check the result To list the builds, run the following command: ```shell chainctl images repos build list --repo $REPO --parent $ORGANIZATION ``` When Chainguard refuses to wrap an image, or when two bindings conflict, Chainguard records the build as a failure and the `Reason` column shows why. The column is empty for an ordinary build failure. To read a reason in full, run `chainctl images repos build logs --repo $REPO --parent $ORGANIZATION` and select the failed build. For the reasons that relate to Guarded Entrypoint, refer to [Entrypoints the wrapper refuses](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/#entrypoints-the-wrapper-refuses). To confirm that a rebuilt image uses the wrapper, check its entrypoint. The first element is `/usr/bin/guarded-entrypoint`. What follows depends on the image. For an image with a command, it is that command. For a shell fragment, it is `/bin/sh -c` and the fragment. For a service bundle, it is `/bin/s6-svscan /sv`. An image that has only a CMD has the wrapper alone: ```shell crane config cgr.dev/$ORGANIZATION/$REPO:latest | jq '.config.Entrypoint' ``` ### Turn off Guarded Entrypoint To remove the wrapper from the image, edit the manifest and delete `guarded_entrypoint`, `fail_mode`, `preflight`, and `command_override`. The API rejects the other three keys when `guarded_entrypoint` is not set. Also remove any `cg+...` values from `environment`. Without the wrapper, they ship as literal strings. The next rebuild produces an image with its original entrypoint. To bypass the wrapper on a running container without a rebuild, refer to [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/). ## Turn on Guarded Entrypoint with the API The Chainguard API accepts the same four fields. They are `guardedEntrypoint`, `failMode`, `preflight`, and `commandOverride` on the repo's `customOverlay`. For general guidance on authenticating and calling the API, refer to [Using the Chainguard API](/platform/api/api-v2-tutorial/). The API's enum fields take enum names. `failMode` is `FAIL_MODE_CLOSED` or `FAIL_MODE_OPEN`. A `commandOverride` `mode` is `MODE_DEFAULT`, `MODE_PREPEND`, or `MODE_OVERRIDE`. A preflight `onFailure` is `ON_FAILURE_FAIL` or `ON_FAILURE_CONTINUE`. The following request turns on Guarded Entrypoint for a repo, with a fail-open setting and one preflight check: ```shell export TOKEN=$(chainctl auth token) export API=https://console-api.enforce.dev export REPO_UID=YOUR_REPO_UID curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/registry/v2/repos/$REPO_UID" \ -d '{ "customOverlay": { "guardedEntrypoint": true, "failMode": "FAIL_MODE_OPEN", "preflight": [ { "tcp": "db.internal:5432", "timeout": "60s", "interval": "1s", "onFailure": "ON_FAILURE_FAIL" } ], "environment": { "CONSUL_HTTP_ADDR": "https://consul.example.com:8501", "FEATURE_FLAGS_URL": "cg+consul://apps/web/feature-flags-url" } } }' ``` The request merges into the repo's stored overlay. The API builds an update mask from the fields in the request body, so only the fields you send change: * A field you leave out keeps its stored value. You can't turn Guarded Entrypoint off by leaving its fields out. * The request replaces `environment` and `preflight` as a whole. The example request sets the repo's environment variables to the two it lists and removes the others, so include every variable you want to keep. To replace the whole overlay with exactly what you send, add `?update_mask=custom_overlay` to the URL. Use this form to turn Guarded Entrypoint off, with a body that leaves out the four fields. The API validates the request with the rules in [API errors](#api-errors). ## Use Guarded Entrypoint with Custom Assembly Overlays An overlay can carry the same four fields. This lets you apply Guarded Entrypoint to some of a repo's tags, or to many repos at once. Refer to the [overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/overview/) for overlays, bindings, and tag selectors. Guarded Entrypoint has its own enrollment, separate from Custom Assembly Overlays. Contact Chainguard customer support to enable Guarded Entrypoint. Setting the fields on a repo with `chainctl images repos build edit`, as described earlier on this page, needs only Guarded Entrypoint. A repo uses its own configuration or overlay bindings, not both. The overlay examples that follow use a different repo from the one you configured with `build edit`. Attaching an overlay to a repo that has its own configuration fails with the error `repository custom overlay and overlay binding not allowed`. Setting a configuration on a repo that has bindings fails the same way. Write the overlay as a YAML file in the same shape as a repo manifest, create the overlay from it, and bind it to tags: ```shell cat > startup.yaml <` is `custom_overlay` when you set the fields on a repo, and `config` when you set them on an overlay. On the overlay path, the API adds the text `Invalid argument: config:` and a space to the start of each `InvalidArgument` message. For `FailedPrecondition` errors on either path, the API adds `Precondition failed:` and a space, except where the table shows the message without it. In a message, `[i]` is the index of the entry in the list, starting at 0. | Trigger | Code | Message | | --- | --- | --- | | `guarded_entrypoint: true` on an organization that doesn't have Guarded Entrypoint enabled. Contact Chainguard customer support to get access. | `PermissionDenied` | `using .guarded_entrypoint is not allowed` | | `preflight` set without `guarded_entrypoint` | `InvalidArgument` | `.preflight requires guarded_entrypoint` | | `command_override` set without `guarded_entrypoint` | `InvalidArgument` | `.command_override requires guarded_entrypoint` | | `fail_mode` set without `guarded_entrypoint` | `InvalidArgument` | `.fail_mode requires guarded_entrypoint` | | More than 32 preflight entries | `InvalidArgument` | `.preflight: at most 32 entries` | | Preflight entry with neither or both of `tcp` and `path` | `InvalidArgument` | `.preflight[i]: rpc error: code = InvalidArgument desc = exactly one of tcp or path is required` | | Preflight `timeout` or `interval` is not a Go duration | `InvalidArgument` | `.preflight[i]: rpc error: code = InvalidArgument desc = timeout "abc": time: invalid duration "abc"` | | Preflight `timeout` or `interval` is negative | `InvalidArgument` | `.preflight[i]: rpc error: code = InvalidArgument desc = timeout "-5s" must be non-negative` | | Preflight value contains `,` or `=` | `InvalidArgument` | `.preflight[i]: rpc error: code = InvalidArgument desc = tcp "a:1,b:2" must not contain ',' or '='` (the message names the field that holds the character) | | `command_override.mode` is a number that isn't a declared mode | `InvalidArgument` | `.command_override: rpc error: code = InvalidArgument desc = mode "99" must be "default", "prepend", or "override"` | | `prepend` or `override` with an empty `command` | `InvalidArgument` | `.command_override: rpc error: code = InvalidArgument desc = mode "prepend" requires a non-empty command` | | `command` entry contains a NUL byte | `InvalidArgument` | `.command_override: rpc error: code = InvalidArgument desc = command[i] contains a NUL byte` | | `command` entry has a `${` with no closing `}` | `InvalidArgument` | `.command_override: rpc error: code = InvalidArgument desc = command[i] has an unterminated ${ reference` | | `command` entry has a `${...}` reference with an invalid variable name | `InvalidArgument` | `.command_override: rpc error: code = InvalidArgument desc = command[i] has an invalid variable name in a ${...} reference` | | `fail_mode` is a number that isn't a declared mode | `InvalidArgument` | `.fail_mode must be one of "closed" or "open", got "99"` | | `environment` key starts with `GUARDED_` | `InvalidArgument` | `environment variable "GUARDED_DISABLE" uses reserved prefix 'GUARDED_'` | | `environment` key starts with `CHAINGUARD_` | `InvalidArgument` | `environment variable "CHAINGUARD_X" uses reserved prefix 'CHAINGUARD_'` | | Version 1 repo API: `sync_config.apko_overlay.environment` key starts with `GUARDED_` | `InvalidArgument` | `sync_config.apko_overlay.environment: variable "..." uses reserved prefix 'GUARDED_'` | | Overlay or binding path: organization is not enrolled in Custom Assembly Overlays | `FailedPrecondition` | `Precondition failed: this organization is not enrolled in Custom Assembly Overlays. Contact your Chainguard account team to enroll.` | | Overlay or binding path: the repo has its own configuration, or a repo with bindings gets one | `FailedPrecondition` | `repository custom overlay and overlay binding not allowed` | | Overlay path: `config` sets a field that overlays don't support | `InvalidArgument` | `config may set only contents.packages, contents.runtime_repositories, contents.runtime_keyring, environment, annotations, accounts, certificates.additional, guarded_entrypoint, command_override, preflight, and fail_mode` | | Overlay path: `config` sets nothing | `InvalidArgument` | `config must set at least one customization field` | | Binding path: two bindings of one kind that match the same tag set different `fail_mode` or `command_override` values | `FailedPrecondition` | `Precondition failed: overlay config does not merge commutatively with co-matching binding(s): binding "..." (overlay "...", selector ALL) on fields [fail_mode]` | | Overlay update: the new config conflicts with a co-matching binding on a repo the overlay is bound to | `FailedPrecondition` | `Precondition failed: overlay config update does not merge commutatively with co-bound overlay(s): binding "..." and binding "..." (overlay "...") on repo "..." conflict on fields [fail_mode]`, or `Precondition failed: overlay config update conflicts with the overlay of a co-matching binding outside your visible scope or beyond the inspected repos` | The binding and overlay conflict errors also carry the violation type `OVERLAY_BINDING_CONFLICT`. The message names the bindings and the fields that conflict. A `command_override` conflict lists `command_override` in the fields. The enum fields take enum names in JSON. A request with an enum name that doesn't exist fails when the API parses it, before the checks in the table run. A preflight `onFailure` value that isn't declared is treated as `fail`. The `custom_overlay` and `config` prefixes show up in the message text only. In JSON requests, the fields are `customOverlay` and `config`. ## Learn more * [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/) * [Guarded Entrypoint examples](/chainguard/containers/custom-assembly/guarded-entrypoint/examples/) * [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/) * [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/) * [Overview of Chainguard Custom Assembly](/chainguard/containers/custom-assembly/overview/) --- ### Find Chainguard replacements with chainctl images discover URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-tools/images-discover.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Migration, chainctl `chainctl images discover` reads the image references in a directory, such as a repository checkout, and reports the Chainguard container image that replaces each upstream image. For each replacement, it also tells you whether your organization can pull it today. Use it to size a migration before you start, or to find what's left after one. The command only reports. It doesn't edit any files. To get replacements suggested on pull requests as images are introduced, enable [Guardener Container Image Suggestions](/chainguard/guardener/github/image-suggestions/). To have Guardener open a pull request that replaces the images a repository already uses, enable [Guardener Container Image Migration](/chainguard/guardener/github/image-migration/). To convert a Dockerfile, use the [Dockerfile Converter](/chainguard/containers/migration/migration-tools/dockerfile-conversion/). ## Prerequisites - `chainctl` [installed](/platform/chainctl-usage/how-to-install-chainctl/) and authenticated with `chainctl auth login`. If `chainctl images discover --help` reports an unknown command, update `chainctl`. - Access to the Chainguard organization whose images you want to check, with a role that grants the `groups.list`, `repo.list`, and `tag.list` capabilities. If your identity can access more than one organization, pass `--parent` with the organization name or ID. Without it, the command asks you to choose in a terminal and exits with an error in CI. ## Scan a repository From the root of a repository, run: ```shell chainctl images discover ``` To scan another directory, pass its path: ```shell chainctl images discover ./services ``` The command reads the following files, skipping `.git`, `node_modules`, `vendor`, `.terraform`, and `testdata` directories: - **Dockerfiles**: every `FROM` instruction, in every build stage, with `ARG` defaults substituted. The command skips `FROM scratch`, references to earlier build stages, and references that depend on an `ARG` with no default. - **YAML and JSON**: Kubernetes manifests, Compose files, Helm values, and any other `image` value, including Helm-style `image` mappings split into `registry`, `repository`, and `tag` keys. A mapping with an empty tag uses the chart's `appVersion`. Helm values are read as written, not rendered, so a reference computed inside a chart template isn't seen. - **Terraform, shell scripts, and Makefiles**: lines about an image, such as an `image` argument or a `docker run` command. Images used only in `COPY --from` or `RUN --mount` instructions aren't reported. ## Read the results For each reference, the output shows the file, the current image, and up to three Chainguard replacements, best first: ``` ---------------------|------------------|---------------------------------------|------------------ FILE | CURRENT IMAGE | CHAINGUARD IMAGE | STATUS ---------------------|------------------|---------------------------------------|------------------ app/Dockerfile | python:3.12-slim | cgr.dev/my-org/python-fips:3.12 | entitled | | cgr.dev/my-org/python:3.12 | entitled ---------------------|------------------|---------------------------------------|------------------ app/Dockerfile | node:22 | cgr.dev/my-org/node:22 | entitled | | cgr.dev/my-org/node-fips:22 | available to add ---------------------|------------------|---------------------------------------|------------------ deploy/compose.yaml | nginx:1.29 | cgr.dev/my-org/nginx:1.31.6 | entitled | | cgr.dev/my-org/nginx-fips:1.31.6 | available to add | | cgr.dev/my-org/nginx-otel-fips:1.31.6 | available to add ---------------------|------------------|---------------------------------------|------------------ deploy/compose.yaml | redis:7.4 | cgr.dev/my-org/redis-fips:7.4 | available to add | | cgr.dev/my-org/redis:7.4 | available to add | | cgr.dev/my-org/redis-sentinel:7.4 | available to add ---------------------|------------------|---------------------------------------|------------------ ``` In a narrow terminal, the same results print as a list instead of a table. Each replacement has a status: | Status | Meaning | What to do | | --- | --- | --- | | `entitled` | Your organization has this image and the tag, ready to pull. | Switch the reference to the suggested image. | | `entitled, not ready` | Your organization has this image, but the suggested tag isn't in your repository yet. | Wait for the tag to sync, or choose another tag. | | `available to add` | Chainguard publishes this image, but your organization doesn't have it. | [Add the image to your organization](/chainguard/containers/troubleshooting/container-version-troubleshooting/#the-container-isnt-in-your-organizations-catalog), then switch. | | `no maintained tag for ` | Chainguard publishes this image, but no maintained tag matches the one you asked for. | Choose a maintained tag. Use `--all-candidates` to list them. | | `on chainguard` | The reference already uses a Chainguard image. | No action needed. | | `not available` | Chainguard doesn't publish a replacement. | Nothing to move to. Keep the image or find an alternative. | Whether adding an image is self-service depends on your subscription. Refer to [The container isn't in your organization's catalog](/chainguard/containers/troubleshooting/container-version-troubleshooting/#the-container-isnt-in-your-organizations-catalog) for the steps and the roles each one needs. References with no replacement are listed too, so the output shows everything a migration would leave behind. ## Check a single image To check one image without scanning files, pass `--image`. You can repeat it: ```shell chainctl images discover --image nginx:1.29 --image redis:7.4 ``` To see every matching variant and all of its maintained tags, pass one image to `--all-candidates`: ```shell chainctl images discover --all-candidates nginx:1.29 ``` ## How replacements are chosen `chainctl images discover` matches an image to a Chainguard container image by catalog alias first. Otherwise, it tries progressively shorter suffixes of the repository path, so `registry.example.com/cache/dotnet/sdk` can match `dotnet/sdk`, and a path ending in `nginx` can match `nginx`. A match identifies the Chainguard offering for that software, not an image with identical contents, so test the replacement before you ship it. To match on image contents instead, use [Image Matcher](/chainguard/containers/migration/migration-tools/image-matcher/). The suggested tag follows the reference you wrote: - A maintained tag stays as written. - An untagged reference becomes `latest`, the tag Docker uses anyway. - A digest-pinned reference stays digest-pinned once the image is in your organization. Until then, the suggestion uses a tag, because the digest isn't known yet. - A variant tag, such as `python:3.12-slim`, resolves to the same version without the variant suffix. - When the requested version isn't maintained, the suggestion moves to the newest maintained version on the same minor line, then the same major line, never to an older line. - When no version of that major line is maintained, the suggestion falls back to `latest`. Check a `latest` suggestion before you switch, because it can be a different major version. Images your organization already has rank ahead of images it doesn't. Within each group, FIPS variants rank first by default. To rank standard images first, pass `--fips=false`: ```shell chainctl images discover --fips=false ``` ## Use the results in scripts Pass `-o json` for machine-readable output. Each result includes the file, line, build stage, requested tag, how the match was made, and every ranked candidate. ```shell chainctl images discover -o json ``` For every flag, refer to the [`chainctl images discover` reference](/platform/chainctl/chainctl-docs/chainctl_images_discover/). --- ### Overview of Custom Assembly Overlays URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/overlays/overview.md Last Modified: October 9, 2026 Tags: Chainguard Containers, Conceptual, Custom Assembly

Note: Custom Assembly Overlays is in beta.

Standard [Custom Assembly](/chainguard/containers/custom-assembly/overview/) 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.13` and `3.13-dev`. * Add debugging tools to every `-dev` tag 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, refer to [Manage Custom Assembly Overlays with chainctl](/chainguard/containers/custom-assembly/overlays/chainctl/) or [Manage Custom Assembly Overlays with Terraform](/chainguard/containers/custom-assembly/overlays/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](#all-repos-bindings) 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 several all-repos bindings can cover a repository 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: 1. The organization's all-repos bindings. 1. Each folder's all-repos bindings, outer folders before nested ones. 1. The repository's own bindings. Within each scope, bindings layer by selector kind: 1. All bindings. 1. Variant bindings. 1. 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-dev` whose overlay adds `gdb`. 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: ```yaml contents: packages: - py{{major}}.{{minor}}-cryptography ``` When 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](https://github.com/google/go-containerregistry/tree/main/cmd/crane) command: ```shell crane config cgr.dev/$ORGANIZATION/python:3.12 | jq -r '.config.Labels["dev.chainguard.package.main"]' ``` ```output python-3.12 ``` In 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](/chainguard/containers/custom-assembly/overview/#custom-runtime-repositories) (`contents.runtime_repositories`) * [Custom runtime keys](/chainguard/containers/custom-assembly/overview/#custom-runtime-keys) (`contents.runtime_keyring`) * [Environment variables and annotations](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#adding-custom-annotations-and-environment-variables) (`environment` and `annotations`) * [User accounts and groups](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_apply/) (`accounts`) * [Custom certificates](/chainguard/containers/custom-assembly/custom-assembly-certs/) (`certificates.additional`) * [Guarded Entrypoint](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/) (`guarded_entrypoint`, `fail_mode`, `preflight`, and `command_override`). To learn how these fields combine when several bindings match one tag, refer to [How the fields combine across bindings](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/#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.list` lets you view overlays and bindings. The built-in `viewer`, `editor`, and `owner` roles include it. * `registry.overlays.edit` lets you create, update, and delete overlays and bindings. The built-in `editor` and `owner` roles include it. To create a custom role with these capabilities, refer to [Overview of roles and role-bindings in Chainguard](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). ## Learn more * [Manage Custom Assembly Overlays with chainctl](/chainguard/containers/custom-assembly/overlays/chainctl/) * [Manage Custom Assembly Overlays with Terraform](/chainguard/containers/custom-assembly/overlays/terraform/) * [Overview of Chainguard Custom Assembly](/chainguard/containers/custom-assembly/overview/) * [Custom Assembly FAQs](/chainguard/containers/custom-assembly/faq/) --- ### Chainguard Checks overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/overview.md Last Modified: October 15, 2026 Tags: Chainguard Checks, Overview, GitHub Chainguard Checks runs your repository's CI on Chainguard's infrastructure instead of on GitHub Actions runners. You define your checks, such as linting and tests, in a `.chainguard/ci.yaml` file in your repository. On every pull request, Chainguard Checks runs each check in its own microVM, built on Chainguard OS, and reports the results on the pull request as GitHub checks. CI runners usually hold long-lived secrets and can reach the whole internet while they run untrusted code, such as a pull request's changes or a compromised dependency. In Chainguard Checks, each check runs in a fresh VM that has no long-lived credentials, and no network access unless the check lists the hosts it needs.

Note: Chainguard Checks is in beta. Available to organizations that turn it on with chainctl develop init, which comes with a free monthly compute allowance, or that arrange access with their Chainguard account team.

## How Checks works Chainguard Checks reaches your repositories through the Chainguard App, a GitHub App that you install on your GitHub organization or personal account. When you link that account to your Chainguard organization, each repository that has a `.chainguard/ci.yaml` file, or a `.chainguard/ci/` directory, opts in to Chainguard Checks. When someone opens or updates a pull request, the following happens: 1. Chainguard Checks plans a run against the commit GitHub would create by merging the pull request into its base branch, so each run tests the change together with the latest base branch. 2. It reads the configuration from that merged commit, so a pull request can add checks or change them. 3. Each check runs in its own microVM with the packages your configuration file declares. 4. It reports a summary check named **Chainguard Checks (presubmit)** on the pull request, plus one GitHub check for each of your checks. Each new push to the pull request cancels the run for the previous push and starts a new one. Chainguard Checks can also run checks on each new commit on your default branch, for work that should run after a change merges. Refer to [Use Chainguard Checks on GitHub](/chainguard/checks/github/) for details.

Note: Attestations for Checks runs are planned. With them, Chainguard Checks will verify an earlier passing run instead of running your checks again. Until then, Chainguard Checks runs your checks on every push to a pull request.

Here's a minimal `.chainguard/ci.yaml` file for a Go repository: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go] checks: vet: cmd: go vet ./... network: egress: [proxy.golang.org, sum.golang.org] test: cmd: go test ./... network: egress: [proxy.golang.org, sum.golang.org] ``` This file installs Go in every check's VM and defines two checks, `vet` and `test`. Each check runs in a fresh VM with no network access unless it asks for some, so both list the Go module proxy and checksum database under `network.egress` to download the modules your code depends on. Refer to the [configuration reference](/chainguard/checks/configuration/) for every option. ## Isolation by default Every check starts from the same minimal base and gets only what you declare: | Resource | What a check gets | | -------- | ----------------- | | Packages | BusyBox, GNU tar, and the packages your configuration file lists. A check has no `git`, package manager, or language toolchain unless you list it. | | Network | No network access unless the check lists hosts under `network.egress`. Chainguard Checks always blocks cloud metadata endpoints, loopback addresses, and private IP ranges. | | Credentials | No long-lived credentials. Your repository's code is already in the VM, so most checks need none. A check that must authenticate to Google Cloud or to Chainguard can request a [workload identity](/chainguard/checks/identity-and-trust/) for short-lived access. | | Caches | None unless the check declares one. During the beta, a Go check can reuse build output from earlier runs through cache pools that your default branch defines. Refer to [Caches](/chainguard/checks/variations-services-caches/#caches). | By default, Chainguard Checks refuses a connection to a host the check doesn't list, and the check keeps running. With `policy: destroy`, Chainguard Checks stops the check's VM instead, and the check fails. For the security model of Chainguard Checks and Chainguard Workspaces side by side, including trust rings and the run lifecycle, refer to [How Chainguard Checks and Chainguard Workspaces isolate your code](/get-started/checks-and-workspaces/how-it-works/). ## Use with Chainguard Workspaces [Chainguard Workspaces](/chainguard/workspaces/overview/) reads the same `.chainguard/ci.yaml` file. In a Workspaces session, the `run-ci` command runs your checks against the session's working tree, uncommitted changes included, so you or a coding agent can find failures before pushing. The `run-ci` command runs each check in the session's own environment, which can have different packages and network access than the check declares. Its result predicts the pull request's result, but only Chainguard Checks, which runs each check in a fresh VM, decides what GitHub shows. You can use Chainguard Checks without Chainguard Workspaces, and the other way around. Refer to [Run checks before you push](/chainguard/checks/run-checks/). ## Availability and limits {#availability-and-limits} Chainguard Checks is offered as a Technology Preview, as described in the [Chainguard Master Service and License Agreement](https://www.chainguard.dev/legal/master-service-and-license-agreement). Technology Previews aren't recommended for production use. Don't use a Technology Preview to process personal data, or any data subject to legal or regulatory compliance requirements, unless Chainguard specifically authorizes it. If you turn on the [requested checks agent](/chainguard/checks/github/#what-the-agent-reads), Chainguard Checks sends pull request content to a model, so don't turn it on for a repository whose pull requests contain such data. During the beta, Chainguard Checks has the following limits: - It works with repositories on github.com only. - It doesn't run on GitHub merge queues, so don't require **Chainguard Checks (presubmit)** on a branch that uses one. Refer to [Require checks before merging](/chainguard/checks/github/#require-checks-before-merging). - It doesn't run checks on a pull request, or on a commit to your default branch, that changes 3,000 or more files. The summary check fails with **Run creation failed** instead, so split a change that large into smaller pull requests. Refer to [Troubleshoot Chainguard Checks](/chainguard/checks/troubleshooting/#the-summary-check-says-the-run-couldnt-be-created-or-failed). - A run can take up to 50 minutes from when it starts. Checks still running or waiting to start at that point fail. `chainctl develop init` turns on the *Starter allowance*: 500 vCPU-hours of Checks each month, separate from the Workspaces allowance. The allowance resets on the first day of each month (UTC) and comes with the following limits: - Your organization can have 10 runs queued or running at a time, including runs after merging. - Each run can use up to 4 VMs and 16 vCPUs at once, so up to four of its checks run at the same time. - Each check's VM can have up to 4 vCPUs and 16 GB of memory. Chainguard counts each check's vCPUs for as long as its VM runs. A check with the [default resources](/chainguard/checks/configuration/#resources) uses 2 vCPU-hours for each hour it runs, and a `medium` check uses 4. Chainguard doesn't show your usage during the beta. When the allowance is used up, Chainguard Checks doesn't start new runs until it resets, and runs already in progress finish. Refer to [Troubleshoot Chainguard Checks](/chainguard/checks/troubleshooting/#the-summary-check-says-the-run-couldnt-be-created-or-failed) for what a pull request shows then. If your organization arranged access with its Chainguard account team, its allowance and limits can differ. ## Get help To ask questions or get support, use the #checks-questions channel in the [Chainguard community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw). If you aren't a member yet, the link invites you to join. ## Next steps - [Get started with Chainguard Checks](/chainguard/checks/getting-started/) to sign up and run your first checks on a pull request. - [Chainguard Checks configuration reference](/chainguard/checks/configuration/) to learn the `.chainguard/ci.yaml` format. - [Use Chainguard Checks on GitHub](/chainguard/checks/github/) to require checks before merging. - [Workload identities and trust](/chainguard/checks/identity-and-trust/) to give a check short-lived Google Cloud or Chainguard credentials. - [chainctl checks command reference](/chainguard/checks/chainctl-checks-reference/) for every `chainctl checks` command and flag. --- ### Chainguard Workspaces overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/overview.md Last Modified: October 15, 2026 Tags: Chainguard Workspaces, Overview, AI Chainguard Workspaces moves development, and the coding agents that do it, off your laptop. The `chainctl develop` command opens a *session*: a Linux microVM in Chainguard's cloud with your repository checked out, the tools your repository needs, and, by default, Claude Code. You work in it from your own terminal. A coding agent that runs on your laptop can read your SSH keys, cloud credentials, and browser sessions, and every package it installs runs with the same access. In a session, the agent works in a separate VM that holds no long-lived credentials. When the agent pushes to GitHub or calls Claude, the request leaves the VM without a credential, and your laptop or Chainguard adds one on the way out.

Note: Chainguard Workspaces is in beta. Available to organizations that turn it on with chainctl develop init, which comes with a free monthly allowance, or that arrange access with their Chainguard account team.

## How a session works When you run `chainctl develop` in a git checkout, `chainctl` creates a microVM and syncs your directory into it at `/work`. The sync includes tracked files, untracked files that `.gitignore` doesn't exclude, and the repository's history, so the session starts on exactly the tree you have, pushed or not. It leaves out your `.git/config` file and your `.git/hooks` directory. Submodules arrive as empty directories, and they're also empty in a session that you open from a repository URL. By default, you get a `bash` shell in the session as `root`. When your checkout has a github.com remote, the session is on one GitHub repository, as described in [Choose the session's repository](/chainguard/workspaces/manage-sessions/#choose-the-sessions-repository). Other directories sync differently: - A checkout with no github.com remote syncs with its history, but it has no remotes in the session, and the session gets no GitHub credential. - A directory that isn't a git checkout syncs every file in it, including dotfiles such as `.env`, and the session gets no GitHub credential. The session's packages come from Chainguard OS. If your repository has a `.chainguard/develop.yaml` file, the session installs the packages that file declares. Otherwise, it installs the packages your `.chainguard/ci.yaml` file declares. If the repository has neither file, the session opens a default environment with a shell, `git`, `gh`, Claude Code, `chainctl`, and other everyday tools. Refer to [Configure a session's environment](/chainguard/workspaces/session-environment/) for details. Sessions keep running after you detach, then park: - When you detach, the session keeps running for about 10 minutes. You can also ask it to keep running for longer, up to 24 hours at a time. - After that, the session *parks*: Chainguard saves its memory and disk to a snapshot and stops the VM, so it uses no compute. - When you return, the session resumes from the snapshot in seconds, with your processes, files, and shell as you left them. - A parked session expires 5 days after it parks. Each session is its own VM. You can open several sessions on the same repository, for example to give each of several agents its own copy of the code, without worktrees or duplicate dependency directories on your laptop. ## How sessions get credentials No long-lived credential enters a session. Instead, the session reaches each service through a local hostname, such as `http://github/` or `http://claude/`. Outside the VM, Chainguard or `chainctl` on your laptop adds the credential to each request. Depending on the service, the credential comes from your laptop, such as your Claude Code login, or from your Chainguard organization, such as the Chainguard App's access to your repositories. By default, a session can reach the following: - Claude, with the same credential Claude Code uses on your laptop. - The session's own GitHub repository, for fetch and push, and the other github.com repositories that your checkout's remotes point to, where the Chainguard App is installed. To use those, add them as remotes in the session. - `cgr.dev`, as the Chainguard platform rather than as you. - Your Chainguard identity, limited to the read, Chainguard Checks, and Model Context Protocol (MCP) capabilities you hold in the session's organization, while you're attached and for up to an hour after you detach. When the session asks for anything else, such as fetching another private repository, `chainctl` asks you on your terminal. You can approve requests ahead of time, or take access back, at any point. Refer to [Manage session credentials](/chainguard/workspaces/credentials/) for details. Claude Code can keep working after you close your terminal, for as long as its credential lasts, as described in [Keep Claude Code working while you're away](/chainguard/workspaces/coding-agents/#keep-claude-code-working-while-youre-away). A service that you set to use a credential from your laptop, such as `github` or `cgr.dev`, works only while you're attached or while the terminal you detached from keeps serving it. ## Network access A session reaches the open internet by default, the same as a laptop, so package managers, `git`, and build tools work without configuration. Chainguard always blocks cloud metadata endpoints, loopback addresses, and private IP ranges. To restrict a session, open it on a check in your `.chainguard/ci.yaml` file that has a `network.egress` allowlist. A session honors the allowlist the same way a check does, and refuses connections to hosts outside it while it keeps running. Unlike Chainguard Checks, though, a session whose environment has no `network` key reaches the open internet. That includes every session that uses a `.chainguard/develop.yaml` file, which can't have a `network` key. Refer to [Network access](/chainguard/workspaces/session-environment/#network-access) for details. For the security model of Chainguard Workspaces and Chainguard Checks side by side, including the session lifecycle, refer to [How Chainguard Checks and Chainguard Workspaces isolate your code](/get-started/checks-and-workspaces/how-it-works/). ## Use with Chainguard Checks Chainguard Workspaces and [Chainguard Checks](/chainguard/checks/overview/) read the same `.chainguard/ci.yaml` file. In a session, the `run-ci` command runs your repository's checks against the working tree in `/work`, uncommitted changes included, so you or an agent can find failures before pushing. Chainguard Checks runs the same checks on each pull request and reports the results to GitHub. After you push, most `chainctl checks` commands, such as `status`, `logs`, and `rerun`, work in the session as you, with the permissions you hold in the organization. In a Starter organization, they don't work for the `limited_owner` role, as described in [Your Chainguard identity in a session](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session). The `run-ci` command runs each check in the session's own environment, which can have different packages and network access than the check declares. It skips checks that only a pull request can run, such as a check that needs a [workload identity](/chainguard/checks/identity-and-trust/). Refer to [How run-ci differs from a pull request run](/chainguard/checks/run-checks/#how-run-ci-differs-from-a-pull-request-run). For the checks it runs, its result predicts the pull request's result, but only Chainguard Checks, which runs each check in a fresh VM, decides what GitHub shows. You can use Chainguard Workspaces without Chainguard Checks, and the other way around. ## Availability and limits {#availability-and-limits} Chainguard Workspaces is offered as a Technology Preview, as described in the [Chainguard Master Service and License Agreement](https://www.chainguard.dev/legal/master-service-and-license-agreement). Technology Previews aren't recommended for production use. Don't use a Technology Preview to process personal data, or any data subject to legal or regulatory compliance requirements, unless Chainguard specifically authorizes it. During the beta, Chainguard Workspaces has the following limits: - GitHub features, including pushing from a session, work only with repositories on github.com. - By default, each session has 4 vCPUs, 16 GB of memory, and a 50 GB disk. Unless Chainguard has raised your organization's limit, a session can't have more vCPUs or memory than that, but its disk can be up to 100 GB. Refer to [Resources](/chainguard/workspaces/session-environment/#resources) to change a session's size. The `/tmp` directory is held in memory and is limited to a quarter of the session's memory, 4 GB by default, so put large files under `/var/tmp` instead. - A session can't forward ports to your laptop. - `chainctl develop init` turns on a free monthly allowance, which resets on the first day of each month (UTC) and includes 500 vCPU-hours of Workspaces. Chainguard Checks has its own allowance. Each user can have one session at a time, including a parked one, and the organization can have three. An allowance arranged with your Chainguard account team can differ. A session counts its vCPUs against the allowance for every hour its VM runs, whether it's busy or idle. A session with the default 4 vCPUs uses 4 vCPU-hours an hour, including the roughly 10 minutes it keeps running after you detach, and a parked session uses none. A 4-vCPU session created with `--keep always` and left running uses the month's allowance in about 5 days. Chainguard doesn't show your usage during the beta. When the allowance is used up, you can't create a session or resume a parked one until the allowance resets, but sessions that are already running aren't stopped. Refer to [Your organization's monthly compute allowance is used up](/chainguard/workspaces/troubleshooting/#your-organizations-monthly-compute-allowance-is-used-up) for details. ## Get help To ask questions or get support, use the #workspaces-questions channel in the [Chainguard community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw). If you aren't a member yet, the link invites you to join. ## Next steps - [Get started with Chainguard Workspaces](/chainguard/workspaces/getting-started/) to sign up and open your first session. - [Manage sessions](/chainguard/workspaces/manage-sessions/) to learn how to detach from, return to, and delete sessions. - [Use Claude Code in a session](/chainguard/workspaces/coding-agents/) to run an agent in a session. --- ### How Guarded Entrypoint works URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works.md Last Modified: October 7, 2026 Tags: Chainguard Containers, Custom Assembly, Conceptual, Reference > **Note**: Guarded Entrypoint is in beta. To use it, contact Chainguard customer support to enable it for your organization. This page describes what the Guarded Entrypoint binary does when a container starts. The page calls the binary the wrapper. To turn Guarded Entrypoint on, refer to [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/). ## What the wrapper does When you turn on Guarded Entrypoint, Chainguard rebuilds the image with `/usr/bin/guarded-entrypoint` as the first element of its entrypoint. The image's original entrypoint follows it. Chainguard stores your settings in environment variables in the image configuration. The names of these variables start with `GUARDED_`. `docker inspect` shows them. The `GUARDED_` prefix is reserved, and the API rejects it in your own `environment` keys. On every start, the wrapper runs these steps in order: 1. **Check for the escape hatch.** If `GUARDED_DISABLE` is set, the wrapper starts the original entrypoint and does nothing else. An empty value, `0`, `false`, `no`, and `off` don't count as set. Refer to [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/). 1. **Resolve secret references.** The wrapper replaces every environment value of the form `cg+BACKEND://REF` with the secret it names. 1. **Resolve the command.** The wrapper applies `command_override`, if you set one, and expands `${VAR}` in it. 1. **Run preflight checks.** The wrapper waits for the TCP endpoints and paths you listed. 1. **Start your application.** The application runs as the wrapper's child process. While the application runs, the wrapper forwards every signal it can catch to the application and reaps orphaned processes. When the application exits, the wrapper exits with the same exit code. If a signal kills the application, the wrapper exits with 128 plus the signal number. The wrapper writes its own messages as JSON, one object per line, to standard error. Set `GUARDED_ENTRYPOINT_LOG` on the container to `debug`, `info`, `warn`, or `quiet` to change the level. The `quiet` level also hides the fail-open warning. The wrapper never logs a resolved secret value. It replaces any value it resolved or expanded into the command with `***` in its log lines. It doesn't scrub a value that only a preflight target expanded. ### Runtime overrides of the entrypoint The wrapper runs only when the container starts with the image's entrypoint. A Kubernetes `command:` field or `docker run --entrypoint` replaces the whole ENTRYPOINT, wrapper included. With such an override, no secret references resolve and no checks run. To pass your own arguments, use the Kubernetes `args:` field. If you must set `command:`, make `/usr/bin/guarded-entrypoint` its first element. ### What the wrapper doesn't do * It doesn't resolve references in the container's arguments. It resolves only environment values. Kubernetes expands `$(VAR)` in `args` before the wrapper runs, so `--password=$(DB_PASSWORD)` reaches the application as the literal `cg+...` reference. Have the application read secrets from its environment, or pass them through `command_override`, which expands after resolution. * It doesn't renew or revoke Vault leases. It reads each secret once, at startup. * It doesn't run code that you upload. It runs your application and, if you set `command_override`, the command you list. * It doesn't connect to Chainguard. Refer to [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/). ## Secret references A secret reference is an environment variable whose value has the form `cg+BACKEND://REF`. Put references in the `environment` key of the repo's manifest. The wrapper also resolves references in variables that you set in your pod spec or `docker run` command. The `cg+` prefix is reserved. A value without it, such as a bare `vault://` or `consul://` URI, passes through to your application as a plain string. A value with the prefix must resolve. A malformed reference or an unknown backend always stops the container before the application starts. A failed lookup or a secret that contains a NUL byte stops it too, unless you set [fail mode](#fail-mode) to `open`. | Backend | Reference | Reads | | --- | --- | --- | | Vault | `cg+vault://PATH[?version=N]#KEY` | One field, `KEY`, of the secret at the Vault API path `PATH` | | Consul | `cg+consul://KEY` | The whole value of one key in the Consul key-value store | | Google Secret Manager | `cg+gsm://projects/P/secrets/S/versions/V` | One version of a secret. `V` is a version number, `latest`, or an alias. | The wrapper makes up to three attempts for each request, with backoff. It retries connection errors and the HTTP statuses 429, 500, 502, 503, and 504. For Vault, it also retries 412. Resolution has a budget of 30 seconds in total. ### Vault references `PATH` is the path of the Vault API request, as in `envconsul` and Vault Agent templates. For a KV version 2 mount, the path includes `/data/`. The wrapper does not look up mounts or insert `/data/`. `#KEY` is required. | Reference | Reads | | --- | --- | | `cg+vault://secret/data/app#password` | KV version 2 mount `secret`, secret `app`, latest version | | `cg+vault://secret/data/app?version=3#password` | The same secret, version 3 | | `cg+vault://kv/app#password` | KV version 1 mount `kv`, secret `app` | A string value is set as is. A number, boolean, null, object, or array is set as its compact JSON text. A key that the secret lacks is an error. The wrapper reads its Vault settings from the container's environment. They must be literal values, not references. | Variable | Meaning | | --- | --- | | `VAULT_ADDR` | Required. For example, `https://vault.example.com:8200`. An `http://` address sends the token in the clear. | | `VAULT_TOKEN` | A token to read with. It takes precedence over Kubernetes authentication. | | `VAULT_K8S_ROLE` | Without `VAULT_TOKEN`, log in with the Kubernetes auth method as this role. | | `VAULT_K8S_MOUNT` | The Kubernetes auth mount path. The default is `kubernetes`. | | `VAULT_K8S_TOKEN_PATH` | The service account token to log in with. The default is `/var/run/secrets/kubernetes.io/serviceaccount/token`. | | `VAULT_NAMESPACE` | The Vault Enterprise or HCP namespace. | | `VAULT_CACERT` | A PEM bundle, at most 1 MiB, that verifies Vault's certificate in place of the system roots. | With neither `VAULT_TOKEN` nor `VAULT_K8S_ROLE`, resolution fails with `no Vault credentials`. Use Kubernetes authentication where you can. A `VAULT_TOKEN` in the image or the pod spec is a long-lived secret that anyone with access to the spec can read. The wrapper doesn't follow redirects, so `VAULT_ADDR` must name the active Vault node or a load balancer in front of it. Auth methods other than tokens and Kubernetes aren't supported. With an `https://` `VAULT_ADDR`, the wrapper honors `HTTPS_PROXY` and `NO_PROXY` from the container's environment. These must be literal values too. It never uses a proxy for an `http://` address. ### Consul references `KEY` is one or more segments separated by `/`. A segment can contain letters, digits, and `. _ ~ @ : + = , -`. A key that ends in `/`, an empty segment, a `.` or `..` segment, a query, and a fragment are all invalid. A key stored with no value resolves to an empty string. The wrapper reads its Consul settings from the container's environment, as the `consul` command does. They must be literal values. | Variable | Meaning | | --- | --- | | `CONSUL_HTTP_ADDR` | `HOST:PORT`, `http://HOST:PORT`, or `https://HOST:PORT`. The default is `127.0.0.1:8500`. | | `CONSUL_HTTP_SSL` | Set to `true` to use HTTPS with a bare `HOST:PORT`. | | `CONSUL_HTTP_TOKEN` | The token to read with. Without a token, the read is anonymous. | | `CONSUL_HTTP_TOKEN_FILE` | A file that holds the token. It wins over `CONSUL_HTTP_TOKEN`. | | `CONSUL_CACERT` | A PEM file of CAs to trust in place of the system roots. Mount the file in the container. | The wrapper sends the token only in the `X-Consul-Token` header, doesn't follow redirects, and doesn't use a proxy. It doesn't support `unix://` addresses, `CONSUL_CAPATH`, client certificates, `CONSUL_TLS_SERVER_NAME`, `CONSUL_HTTP_SSL_VERIFY`, `CONSUL_HTTP_AUTH`, or Consul Enterprise namespaces and partitions. ### Google Secret Manager references The wrapper authenticates with Application Default Credentials. `/versions/latest` is the default version. The wrapper supports only global secrets. A regional secret, `projects/P/locations/L/secrets/S`, is an invalid reference. A service account needs only secret access. ## Fail mode `fail_mode` sets what happens when a secret reference can't be resolved. It takes one of two values. | Value | When a reference can't be resolved | | --- | --- | | `closed` | The container stops with exit code 121 before your application starts. This is the default. | | `open` | Your application starts anyway. The variable keeps its literal `cg+...` value, and the wrapper logs one warning for it. | With `open`, a lookup that fails, missing credentials, a secret that contains a NUL byte, and the 30-second budget running out all leave the reference in place. The warning looks like the following: ```json {"time":"...","level":"WARN","msg":"secret unresolved, continuing","src":"guarded-entrypoint","name":"DB_PASSWORD","fail_mode":"open","error":"DB_PASSWORD: secret resolution timed out after 30s (context deadline exceeded)"} ``` An unresolved variable is not a secret. Your application sees the literal `cg+...` reference, and anyone who can read the image configuration or the pod spec knows that value. Don't choose `open` for a variable that your application uses as a password, token, or other credential. During an outage the application would start with a known value as its credential. `open` covers a store that can't serve a reference. It doesn't cover a reference that can never resolve. These configuration errors still stop the container with exit code 121: * A malformed reference * An unknown backend, such as a `cg+gms://` typo * A reference that the backend rejects by its shape * A `${VAR}` in the command that names a variable that is unset or that `open` left unresolved A repo that sets `fail_mode: open` has `GUARDED_FAIL_MODE=open` in its image environment. A `closed` repo's image carries no setting. ## Preflight checks A preflight check waits for a dependency before your application starts. Each entry in `preflight` has the following keys: | Key | Meaning | Default | | --- | --- | --- | | `tcp` | A `host:port` to wait for a TCP connection to. | None | | `path` | A filesystem path to wait for. | None | | `timeout` | The total time to wait for this check, as a Go duration such as `30s` or `2m`. A value of `0` means the default, not forever. | `30s` | | `interval` | The pause between attempts, as a Go duration. | `500ms` | | `on_failure` | `fail` stops the container with exit code 122. `continue` logs a warning and moves on. | `fail` | Set exactly one of `tcp` and `path` in each entry. The wrapper expands `${VAR}` in both from the resolved environment. A target that expands to nothing, or to an empty host, fails at once. An overlay or repo can hold at most 32 entries. Values can't contain `,` or `=`. The checks run in order, after secret resolution. Because a preflight target is not scrubbed from logs, don't use a variable that holds a secret in one. A preflight target expands a variable that `open` left unresolved to its literal `cg+...` value. Don't name such a variable in a target. ## Command override `command_override` changes what the wrapper starts. It has two keys: `mode` and `command`. The wrapper receives the image's own arguments, which are its original ENTRYPOINT followed by its CMD, or the arguments that you pass when you run the container. The `mode` sets how `command` combines with them. | Mode | What the wrapper starts | | --- | --- | | `default` | `command`, only when the wrapper receives no arguments at all, which means the image has no ENTRYPOINT or CMD of its own and nothing is passed at run time. Otherwise, the arguments it received. | | `prepend` | `command` followed by the container's arguments. | | `override` | `command` alone. The wrapper drops the image's ENTRYPOINT and CMD, and any arguments passed at run time. | `default` is the mode when you leave `mode` out. The `prepend` and `override` modes require a non-empty `command`. The image's ENTRYPOINT and CMD are the same in every mode. The mode only changes what the wrapper starts. To replace both an ENTRYPOINT and a CMD, use `override`. The wrapper looks up the first element of `command` on the container's `PATH`. Use an absolute path when the lookup matters. An empty `command` with `command_override` set is a setting. It means "default mode, no command". When you use Custom Assembly Overlays, it cancels the override from a broader binding. Chainguard stores `command` in the image configuration, where it is visible to anyone who can pull the image. Don't put a secret in it as a literal. Use a `${VAR}` reference, as the next section describes. Also refer to [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/). ## Variable expansion The wrapper expands variables in each `command` entry and in preflight `tcp` and `path` values. It expands from the environment after secret references resolve. * `${NAME}` expands to the value of `NAME`. A name starts with a letter or `_` and continues with letters, digits, or `_`. * `$$` is a literal `$`. * Any other `$` stays as it is. `$NAME` is not expanded. * A `${NAME}` whose variable is not set stops the container with exit code 121 in a command, and fails the check in a preflight. It never expands to an empty string. A variable that is set and empty expands to nothing. * The wrapper never expands the container's own arguments. The wrapper doesn't support shell forms such as `${NAME:-default}`. The API rejects a `${...}` reference with an invalid name in `command_override`. It doesn't check preflight targets. A preflight target such as `${REDIS_HOST:-redis}:6379` passes `chainctl` and the API, and then every container start exits with code 120, whatever `on_failure` says. To pass a `$` to a shell that runs in the image, write `$$`. For example, the following command override passes `${PORT:-8080}` to `sh` unchanged. It needs an image that includes a shell: ```yaml guarded_entrypoint: true command_override: mode: override command: - sh - -c - exec my-app --port $${PORT:-8080} ``` In a command, the wrapper never expands a variable that `open` left unresolved. An expanded value is visible in the application's command line to anything that can read the process's `/proc/PID/cmdline`. The wrapper can't hide it there. ## Supported and refused entrypoints The wrapper wraps these entrypoints: * **A command.** * **A shell fragment.** The wrapper runs `/bin/sh -c` with the fragment, so the fragment expands the resolved values. * **A service bundle.** The wrapper runs in front of the supervisor and resolves the environment once for every service. ### Service bundles and command override For a service bundle, `command_override` changes what runs in place of the supervisor: * `override` runs your command instead of the supervisor, so the image's services don't start. * `prepend` runs your command with the supervisor's command, `/bin/s6-svscan /sv`, as its arguments. Your command must run them itself, for example with `exec`. * `GUARDED_DISABLE` starts the supervisor directly, as in the unwrapped image. ### Shell fragments and exec A shell fragment that starts your application without `exec` keeps the shell between the wrapper and your application. On shutdown the shell receives SIGTERM and exits, and your application is stopped without a chance to shut down gracefully. Without Guarded Entrypoint, the shell is PID 1 and ignores SIGTERM, so the container runs until the stop timeout and is killed. Neither case shuts down gracefully. To give your application a graceful shutdown, start it with `exec`, for example `exec my-app --port 8080`. ### Init systems aren't supported Images whose entrypoint is an init system that must run as PID 1, such as systemd (`/sbin/init`) or s6-overlay's `/init`, aren't supported. An image that uses an s6 service bundle, which the wrapper runs in front of, is a different case and is supported. Under the wrapper, systemd exits at start and s6-overlay's shutdown is cut short. Don't turn on Guarded Entrypoint for these repos. If it's already on, set `GUARDED_DISABLE` on the deployment. ### Refused builds Chainguard refuses to build a wrapped image in two cases. The image's environment sets `GUARDED_DISABLE`, or the repo pins the wrapper package to a release that is too old for the repo's settings. The build then fails with a message that names the reason. For how a refusal appears in `chainctl`, refer to [Entrypoints the wrapper refuses](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/#entrypoints-the-wrapper-refuses). ## Learn more * [Guarded Entrypoint examples](/chainguard/containers/custom-assembly/guarded-entrypoint/examples/) * [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/) * [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/) --- ### Manage Custom Assembly Overlays with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/overlays/chainctl.md Last Modified: October 9, 2026 Tags: Chainguard Containers, Procedural, Custom Assembly, chainctl

Note: Custom Assembly Overlays is in beta.

This guide shows how to use `chainctl` to apply Custom Assembly customizations to some of a repository's tags, or to every repository in your organization or a folder. You create an overlay that holds the customizations, then bind it with a tag selector. For an explanation of overlays, bindings, and tag selectors, refer to [Overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/overview/). ## Prerequisites Before you start, you need the following: * [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) version 0.2.379 or later. Run `chainctl update` to update it. * A role with the `registry.overlays.edit` capability, such as the built-in `editor` or `owner` role. * A repository in your organization with no standard Custom Assembly customization. A repository can't use both. To move a repository from standard Custom Assembly, contact your Chainguard account team. The examples in this guide use the following environment variables. Set them to match your organization and repository: ```shell export ORGANIZATION=example.com export REPO=python ``` The examples add Python packages to the `python` repository, but the same commands work for any repository. ## Add a package to specific tags This example adds `py3.13-typer` to the Python 3.13 tags only, so the other Python versions keep building. 1. Create an overlay that adds the package: ```shell chainctl images overlays create typer --parent $ORGANIZATION --package py3.13-typer ``` ```output created overlay: typer (45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/1f4fcff90a5f0a02) ``` To add more than one package, repeat `--package` or separate the names with commas. 1. Bind the overlay to the tags that should receive it. Pass `--tag` once for each tag: ```shell chainctl images overlays attach \ --overlay typer \ --repo $REPO \ --parent $ORGANIZATION \ --tag 3.13 --tag 3.13-dev ``` ```output attached overlay "typer" to repo 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80 (binding 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/9b8a7c6d5e4f3a21, selector EXACT [3.13 3.13-dev]) ``` Every `attach` command takes exactly one tag selector: `--tag` for specific tags, `--variant` for every tag of a variant, or `--all` for every tag. The next sections show the other two selectors. Chainguard starts rebuilding `3.13` and `3.13-dev` with the overlay applied. It customizes only the tags you list, even if other tags such as `3.13.7` or `3.13.7-r0` point to the same image. To keep those tags identical, list them too. To check on the builds, refer to [Check the results](#check-the-results). ## Add packages to every `-dev` tag To bind an overlay to every tag ending in `-dev`, use `--variant dev` instead of `--tag`. The following commands create an overlay with two debugging tools and bind it to the repository's `-dev` tags: ```shell chainctl images overlays create debug-tools --parent $ORGANIZATION --package strace,gdb chainctl images overlays attach \ --overlay debug-tools \ --repo $REPO \ --parent $ORGANIZATION \ --variant dev ``` The binding also applies to `-dev` tags published after you create it, so new versions get `strace` and `gdb` without any further changes. ## Add a package to every tag To bind an overlay to every tag in a repository, use `--all`. The following commands create an overlay that adds `curl` and bind it to every tag: ```shell chainctl images overlays create curl --parent $ORGANIZATION --package curl chainctl images overlays attach \ --overlay curl \ --repo $REPO \ --parent $ORGANIZATION \ --all ``` As with a variant binding, an all binding also applies to tags published after you create it. ## Add the matching package version to every tag Some packages include a language version in their name, so no single package name works on every tag. The `{{major}}` and `{{minor}}` placeholders solve this: Chainguard replaces them with each tag's version when it builds the tag. The following commands add the `cryptography` package that matches each tag's Python version: ```shell chainctl images overlays create cryptography --parent $ORGANIZATION \ --package 'py{{major}}.{{minor}}-cryptography' chainctl images overlays attach \ --overlay cryptography \ --repo $REPO \ --parent $ORGANIZATION \ --all ``` The `3.12` tags receive `py3.12-cryptography`, the `3.14` tags receive `py3.14-cryptography`, and so on. Quote the package name so that your shell doesn't interpret the braces. For details on how Chainguard fills in the placeholders, refer to [Version templates in package names](/chainguard/containers/custom-assembly/overlays/overview/#version-templates-in-package-names). ## Add other customizations The `--package` flag sets only packages. To set environment variables, annotations, certificates, user accounts, or runtime repositories, write the overlay as a YAML file and pass it with `-f`. The file uses the same format as [`chainctl images repos build apply`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#applying-packages-non-interactively). The following commands write an overlay that adds an internal certificate authority and an environment variable, then create the overlay from the file: ```shell cat > internal-ca.yaml < -----END CERTIFICATE----- environment: REQUESTS_CA_BUNDLE: /etc/ssl/certs/ca-certificates.crt EOF chainctl images overlays create internal-ca --parent $ORGANIZATION -f internal-ca.yaml ``` If you pass both `-f` and `--package`, `chainctl` uses the file and ignores `--package`. For certificates, runtime repositories, and runtime keys, you can skip the YAML file and pass files or URLs directly with the following flags. They merge into whatever `--package` or `-f` defines: * `--with-certificates`: a comma-separated list of files to read custom certificates from. * `--with-runtime-repositories`: a comma-separated list of runtime APK repository URLs to write to `/etc/apk/repositories` in the image. * `--with-runtime-keys`: a comma-separated list of files to read APK signing public keys from. Each file becomes a key in `/etc/apk/keys` named after the file's basename, which must match the filename referenced by the repository's `APKINDEX` signature (`.SIGN.RSA256.`). For example, the following command creates an overlay that carries certificates from a PEM bundle, with no YAML file: ```shell chainctl images overlays create internal-ca --parent $ORGANIZATION --with-certificates ca-bundle.pem ``` An overlay belongs to your organization, so you can bind it to many repositories. The following loop binds `internal-ca` to every tag of three repositories: ```shell for repo in python node go; do chainctl images overlays attach --overlay internal-ca --repo $repo --parent $ORGANIZATION --all done ``` Each repository gets its own binding, and each binding starts a rebuild of that repository. To cover every repository without naming them, use an [all-repos binding](#apply-an-overlay-to-every-repository) instead. To attach several overlays in one command, separate their names with commas. Each overlay gets its own binding with the same selector: ```shell chainctl images overlays attach --overlay internal-ca,cryptography --repo $REPO --parent $ORGANIZATION --all ``` ## Apply an overlay to every repository To apply an overlay to every repository in your organization, pass `--all-repos` instead of `--repo`: ```shell chainctl images overlays attach \ --overlay internal-ca \ --all-repos \ --parent $ORGANIZATION \ --all ``` The binding also covers repositories created after you run the command, so new repositories get the customization automatically. As with any binding, choose the tags with `--all`, `--variant`, or `--tag`. To cover one folder instead of the whole organization, name the folder in `--parent`: ```shell chainctl images overlays attach \ --overlay hardening \ --all-repos \ --parent $ORGANIZATION/golden-images \ --all ``` An overlay can have one all-repos binding per organization or folder. The binding's scope is fixed: to move it, detach it and attach a new one. Repositories that use standard Custom Assembly keep their standard customization and don't receive all-repos bindings. For how all-repos bindings at several scopes combine with a repository's own bindings, refer to [How overlapping bindings combine](/chainguard/containers/custom-assembly/overlays/overview/#how-overlapping-bindings-combine). You can bind several overlays to one repository with the same kind of selector, as long as they don't set the same field to different values. For example, you can bind both `internal-ca` and `cryptography` to the `python` repository with `--all`. If two overlays conflict, `attach` fails. For example, binding a second overlay that sets `REQUESTS_CA_BUNDLE` to a different value returns an error that names the existing binding and the conflicting field: ```output Error: attaching overlay: rpc error: code = FailedPrecondition desc = Precondition failed: overlay config does not merge commutatively with co-matching binding(s): binding "45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/b7d24dbd7193c219" (overlay "internal-ca", selector ALL) on fields [environment["REQUESTS_CA_BUNDLE"]] ``` To resolve the conflict, change one of the overlays so that they agree, or bind them with selectors that don't match the same tags. ## List overlays and bindings To list your organization's overlays, their customizations, and the bindings for each overlay, run the following command: ```shell chainctl images overlays list --parent $ORGANIZATION ``` ```output overlay: debug-tools (id: 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/2a3b4c5d6e7f8091) packages: strace, gdb bindings: - repo: python (id: 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80) selector: VARIANT(DEV) id: 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/4d5e6f708192a3b4 overlay: typer (id: 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/1f4fcff90a5f0a02) packages: py3.13-typer bindings: - repo: python (id: 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80) selector: EXACT [3.13 3.13-dev] id: 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/9b8a7c6d5e4f3a21 ``` Each binding's `id` is its binding ID. You need it to change or remove the binding. To get output you can process with tools such as `jq`, add `-o json`. ## Change an overlay To change an overlay's customizations, run `update` with the overlay's name or ID. The packages or file you pass replace the overlay's existing customizations completely, so include everything the overlay should contain. > **Note**: `--package` replaces the whole overlay, not only its packages. If you created the overlay from a file, for example with certificates or environment variables, running `update` with `--package` removes those customizations. To keep them, update the file and pass it with `-f`. The `--with-certificates`, `--with-runtime-repositories`, and `--with-runtime-keys` flags behave differently: passed on their own, they merge into the overlay's existing customizations instead of replacing them. The following command replaces the packages in the `typer` overlay: ```shell chainctl images overlays update typer --package py3.13-typer,py3.13-rich ``` ```output updated overlay: typer (45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/1f4fcff90a5f0a02) ``` To change other customizations, pass a YAML file with `-f`: ```shell chainctl images overlays update internal-ca -f internal-ca.yaml ``` To rename an overlay, pass `--name`: ```shell chainctl images overlays update debug-tools --name dev-debug-tools ``` Bindings refer to overlays by ID, so renaming an overlay doesn't affect its bindings. Chainguard rebuilds the matching tags in every repository the overlay is bound to. ## Change which tags a binding applies to To change a binding's tag selector, run `update-binding` with the binding's ID. Set `BINDING_ID` to the `id` shown for the binding in `chainctl images overlays list`: ```shell export BINDING_ID=45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/9b8a7c6d5e4f3a21 ``` The following command replaces the binding's selector, adding `3.13.7` to the tags it applies to: ```shell chainctl images overlays update-binding $BINDING_ID --tag 3.13 --tag 3.13-dev --tag 3.13.7 ``` ```output updated overlay binding 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/9b8a7c6d5e4f3a21 (selector EXACT [3.13 3.13-dev 3.13.7]) ``` Chainguard rebuilds the affected tags. Newly matched tags receive the overlay, and tags that no longer match are rebuilt without it. Any other matching overlays still apply. The selector is the only part of a binding you can change. To bind a different overlay, or to move a binding to another repository, remove the binding and create a new one. ## Remove a customization To remove an overlay from a repository, run `detach` with the binding ID: ```shell chainctl images overlays detach $BINDING_ID ``` ```output detached overlay binding 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/7c3e5a1b2d4f6e80/9b8a7c6d5e4f3a21 ``` Chainguard rebuilds the tags that the binding matched without the detached overlay. Any other matching overlays still apply. To delete an overlay, detach all of its bindings first. Then run `delete` with the overlay's name or ID: ```shell chainctl images overlays delete typer ``` ```output deleted overlay 45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/1f4fcff90a5f0a02 ``` If the overlay is still bound to a repository, `delete` fails and lists the bindings to detach. If more than one overlay you can access has the same name, pass the overlay's ID instead, shown next to its name in `chainctl images overlays list`. ## Check the results Bindings and overlay updates start builds automatically. To list the builds for a repository and the tags each build produced, run the following command: ```shell chainctl images repos build list --repo $REPO --parent $ORGANIZATION ``` The following command shows a build's logs, including the configuration Chainguard built it with. Select a build when prompted: ```shell chainctl images repos build logs --repo $REPO --parent $ORGANIZATION ``` If a package can't be installed on a tag, that tag's build fails and the logs name the package. The failure doesn't affect other tags. For more on these commands, refer to [Retrieving information about Custom Assembly containers](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#retrieving-information-about-custom-assembly-containers). ## Learn more * [Overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/overview/) * [Manage Custom Assembly Overlays with Terraform](/chainguard/containers/custom-assembly/overlays/terraform/) * [Using chainctl to manage Custom Assembly resources](/chainguard/containers/custom-assembly/custom-assembly-chainctl/) --- ### Adding a package to a Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/building-and-modifying/adding-packages.md Last Modified: October 13, 2026 Tags: Chainguard Containers, Custom Assembly, Procedural Chainguard's container images ship with only the packages their application needs, so sooner or later you'll want one that isn't there. [Custom Assembly](/chainguard/containers/custom-assembly/overview/) is the supported way to add it. You declare the package you want, Chainguard builds the image on its own infrastructure, and Chainguard rebuilds that image whenever the package is updated. You can drive Custom Assembly from the Chainguard Console, interactively with `chainctl`, or non-interactively with `chainctl` from a pipeline. This page helps you pick an approach, then covers the three steps that apply whichever one you pick: finding the package name, adding the package, and confirming that it reached the finished image. ## Choose an approach The following table compares the available approaches: | Approach | Use it when | What you need | | --- | --- | --- | | [Custom Assembly in the Console](#add-the-package) | You want to browse the packages your organization can add and apply the change in a few clicks. | A Console account with a role that has the `repo.update` capability. | | [Custom Assembly with `chainctl`, interactively](#add-the-package) | You work from a terminal and want to review a diff before it applies. | `chainctl`, installed and authenticated. | | [Custom Assembly with `chainctl`, non-interactively](#add-the-package) | You keep image configuration in version control or apply it from CI/CD. | `chainctl` and a YAML build configuration file. | | [Custom Assembly with the Chainguard API](/chainguard/containers/custom-assembly/custom-assembly-api-demo/) | You're building your own tooling around Custom Assembly. | An API client and a Chainguard token. | | [`apk add` in a Dockerfile](/chainguard/containers/using-and-deploying/using-containers/#extending-chainguard-base-containers) | You build on a `-dev` variant or on `wolfi-base`, and you're prepared to pin package versions and image digests yourself. | A Dockerfile and a container image that includes `apk`. | | [`apk` with `chroot` in a multi-stage build](/chainguard/containers/building-and-modifying/install-apks-in-distroless-variants/) | You need a package in a distroless image and Custom Assembly doesn't fit your workflow. | A multi-stage Dockerfile. | Custom Assembly is the recommended approach because Chainguard's build pipeline resolves the packages you add against the packages already in the base image, then rebuilds the image when any of them change. Adding packages with `apk add` in your own Dockerfile moves that work to you: without pinned package versions and image digests, a package update can conflict with an older dependency in the base image and break your build until a new base image is released. For the longer version of this argument, see [Why use Custom Assembly for adding packages](/chainguard/containers/custom-assembly/overview/#why-use-custom-assembly-for-adding-packages).

Note: Custom Assembly is available to organizations with access to production Chainguard Containers. If you use Chainguard’s free container images, take one of the Dockerfile approaches.

## Before you begin The Custom Assembly approaches on this page share these prerequisites: * Access to production Chainguard Containers. * A role with the `repo.update` capability, to customize an existing image in place, or the `repo.create` capability, to save the result as a new image. Of Chainguard's three default roles — `viewer`, `editor`, and `owner` — only `owner` has both. For a custom role you can create instead, see [Custom Assembly permissions requirements](/chainguard/containers/custom-assembly/overview/#custom-assembly-permissions-requirements). * For the `chainctl` approaches, [`chainctl` installed](/platform/chainctl-usage/how-to-install-chainctl/) and [authenticated](/platform/chainctl-usage/authentication-options/). Custom Assembly adds packages to an image; it can't remove the packages the source image already contains. You can, however, remove packages you added in an earlier build. ## Find the package name You can add only the packages your organization is entitled to, which are the packages that appear in the Chainguard Containers you already have access to. Package names often carry a version stream — `python-3.14` rather than `python` — so confirm the exact name before you add it. Look it up in the Console, or with `apk` from inside a running container.

Open the image in the Chainguard Console, click Customize image, then use the Filter packages box. The list holds every package your organization can add. If the package you want isn’t listed, open a Chainguard support ticket.

## Add the package The three procedures below all produce the same result. Pick the tab that matches how you work.
  1. In the Chainguard Console, open the image you want to customize.
  2. Click Customize image, then select the packages to add.
  3. Click Continue, then choose Create a new image or Customize current image.
  4. Click Preview changes and review the package list.
  5. Click Apply changes.

For the full walkthrough, including how to edit or remove customizations later, see Using the Chainguard Console to manage Custom Assembly resources.

## Confirm the package is in the image Custom Assembly builds run on Chainguard's infrastructure and normally finish in under 20 minutes, so your change won't reach the registry immediately. 1. Check that the build succeeded: ```shell chainctl images repos build list --repo $CONTAINER ``` ```output START TIME | COMPLETION TIME | RESULT | TAGS -------------------------------|-------------------------------|---------|------------------------------- Wed, 09 Sep 2026 12:46:56 CDT | Wed, 09 Sep 2026 12:47:13 CDT | Success | 26-full, 26.8-full, latest-full Wed, 09 Sep 2026 12:45:22 CDT | Wed, 09 Sep 2026 12:46:13 CDT | Success | 26-dev, 26.8-dev, latest-dev ``` The Console shows the same information on the image's **Builds** tab. Builds stay listed for 24 hours. 2. Pull the image: ```shell docker pull cgr.dev/$ORGANIZATION/$CONTAINER:latest ``` 3. Check for the package. If the image includes `apk`, query it directly. `apk info -e` prints the package name when the package is installed and exits with a non-zero status when it isn't: ```shell docker run --rm --entrypoint apk \ cgr.dev/$ORGANIZATION/$CONTAINER:latest-dev info -e bash ``` ```output bash ``` Distroless images have no `apk`, so read the image's SBOM instead. This command lists the apk packages in the image: ```shell cosign download attestation \ --platform linux/amd64 \ --predicate-type https://spdx.dev/Document \ cgr.dev/$ORGANIZATION/$CONTAINER:latest \ | jq -r '.payload' | base64 -d \ | jq -r '.predicate.packages[] | select(.externalRefs[]?.referenceLocator? // "" | startswith("pkg:apk/")) | .name' ``` ```output ca-certificates-bundle gdbm glibc-2.44 ld-linux-2.44 python-3.14 ``` The Console shows the same list on the image's **SBOM** tab. For more ways to read this data, see [Retrieving Chainguard Container SBOMs](/chainguard/containers/security-and-compliance/retrieve-image-sboms/). ## If the build fails A Custom Assembly build reports failure only after it finishes. Retrieve the logs for a build with the `logs` subcommand, which prompts you to pick a build report: ```shell chainctl images repos build logs --repo $CONTAINER ``` In the Console, click a row on the image's **Builds** tab to open the same logs. Builds fail for a few recurring reasons: * Two packages install the same file, and Custom Assembly can't resolve the conflict. * A package is newer than the base image and was built against a newer version of `glibc`. * The build ran longer than an hour and timed out. * The package isn't one your organization is entitled to. For more on these cases, see [Custom Assembly troubleshooting](/chainguard/containers/custom-assembly/faq/#custom-assembly-troubleshooting). If you can't resolve a failure, [contact Chainguard support](https://www.chainguard.dev/contact?utm=docs). ## Learn more * [Overview of Chainguard Custom Assembly](/chainguard/containers/custom-assembly/overview/) covers what Custom Assembly can change, its limitations, and how the CVE remediation SLA applies to customized images. * [Custom Assembly FAQs](/chainguard/containers/custom-assembly/faq/) answers questions about entitlements, FIPS, and support boundaries. * [Overview of Chainguard's package repositories](/chainguard/containers/building-and-modifying/packages/package-model/) explains where the packages you can add come from. * [Adding custom certificates with Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/) covers embedding internal CA certificates in an image. --- ### Troubleshoot container and version availability URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/troubleshooting/container-version-troubleshooting.md Last Modified: October 8, 2026 Tags: Chainguard Containers You need a container image, or a particular version of one, and you can't pull it. The right next step depends on why it's missing, and there are five distinct reasons. This guide helps you tell them apart and resolve each one. First, distinguish between the public container registry and your organization's registry: * The [Chainguard Containers Directory](https://images.chainguard.dev/) at `images.chainguard.dev` is public. It lists every container image Chainguard builds, along with the tags, sizes, and metadata for each. Anyone can browse it. * Your organization's registry at `cgr.dev/$ORGANIZATION/` holds only the images your organization has access to. This is what your teams pull from. Browsing an image in the Directory doesn't mean your organization can pull it. For a fuller explanation of that distinction and how it relates to your subscription, see [Onboard your teams](/get-started/onboard-your-teams/). ## Find your situation You identify several of these situations from a label in the Console. On a container's **Tags** tab, the **Pull URL** column shows the pull URL for a version when your organization can pull that version. When it can't, the column shows a status label instead, and that label describes the repository rather than the version on that row. A label where you expected a URL means the version isn't available to you. Look up the image in the Directory, then match what you see to the following table: | What you find | Go to | | --- | --- | | The image is in the Directory, but the Console shows **Unavailable to organization**, **Add to organization for access**, or **Request image for access** | [The container isn't in your organization's catalog](#the-container-isnt-in-your-organizations-catalog) | | The image isn't in the Directory at all | [Chainguard doesn't build the container](#chainguard-doesnt-build-the-container) | | The version is listed and the Console shows **Available in organization**, but the pull fails | [The version shows as available but won't pull](#the-version-shows-as-available-but-wont-pull) | | The image is in the Directory, but not the version you need | [The version you need isn't listed](#the-version-you-need-isnt-listed) | | The version is listed with a pause icon, an **Expired** status, or an end-of-life date that has passed | [The version has reached end of life](#the-version-has-reached-end-of-life) | ## The container isn't in your organization's catalog Chainguard builds the image, but your organization hasn't added it yet. What you do next depends on your subscription. **Catalog customers.** A Catalog subscription covers the whole catalog, but only a subset of images is loaded into your organization's registry at any time. Administrators add more as teams need them. If you have the `owner` role, you can add the image yourself from the Console: go to **Images**, then click **Add image** on the **Organization** tab, or **Add to org** on the **Chainguard catalog** tab. The image appears in your catalog after a few minutes. Self-serve adds require a role with the `repo (create, list, update)` capabilities, and the `registry.entitlement (list)` capability is useful for understanding what your organization is entitled to. The `owner` role is the only built-in role that carries all of them. For the full walkthrough and the role details, see [Chainguard container catalog pricing](/chainguard/containers/reference/pricing/) and [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). If you don't have the `owner` role, ask an administrator in your organization to add the image. **Per-image customers.** A per-image subscription covers a specific licensed set of images rather than the whole catalog. You can browse everything in the Directory, but only your licensed images are permitted for builds, deployments, and production workloads. Ask your administrators to start a request; once they approve it, they add the image to your organization's registry. Questions about what your organization is licensed for go to your account team, meaning the customer success manager or solutions architect assigned to your organization. ## Chainguard doesn't build the container If the image doesn't appear in the Directory, Chainguard doesn't build it yet, and you can ask for it. Submit the request from the **Requests** section of the Chainguard Console, where you can also see what Chainguard is already building and upvote requests from other customers. Chainguard prioritizes requests by demand, so upvoting an existing request helps more than filing a duplicate. Submitting requests requires membership in a [verified organization](/platform/administration/iam-organizations/verified-orgs/). The form asks for the resource type, the resource's existing public name, and a link to the upstream open source repository. Some requests can't be fulfilled. Chainguard won't build resources from proprietary code, won't build projects that no longer receive upstream updates, and can't always produce a FIPS variant. For the full process and the current limitations, see [Requesting new Chainguard resources](/chainguard/containers/reference/request-resources/). ## The version shows as available but won't pull The **Pull URL** column reads **Available in organization**, but `docker pull` on that exact tag returns not found. That label is about the repository: your organization has the container, and at least one of its versions is active. It says nothing about the version on the row where you read it. The column falls back to a label only when it has no pull URL to show for that row, so seeing one tells you this version has no pull URL for you. Two things cause that. ### The container is still syncing When a container is added to an organization, its tags take a few minutes to reach that organization's registry. While the sync runs, an upload icon appears alongside the filters above the version list. Hover over it to see which stage it's in: * `A new update for this image has been queued and will begin shortly.` * `This image is currently being updated with newly built tags.` Either message means the sync hasn't finished. Wait a few minutes, then reload the page. The icon appears only in your organization's view of the container, so not seeing it doesn't rule out a sync in progress. If the icon persists for more than a couple of hours, treat this as the next case. ### The tag isn't in your organization's registry When a container is added to an organization, only its actively supported tags come across. An organization that has carried a container for a long time also holds records of older tags, from back when those tags were the supported ones. Those records outlive the images they name. A superseded tag can still appear in the Console with a pull URL beside it, and still be listed by `chainctl`, while pulling it returns `MANIFEST_UNKNOWN`. A tag's presence in a listing is a weaker signal than whether that tag is still active. So check against the active tags. This command reads your default organization: ```shell chainctl images tags list --repo=$IMAGE --active-only ``` If the tag you want isn't in that output, treat it as unavailable, whatever the Directory or the Console shows for it. Drop `--active-only` to see everything your organization holds, including tags that are no longer maintained. Some of those will fail to pull. What you do next depends on which kind of tag it is: * The tag is actively maintained and missing from your organization. [Open a support request](#open-a-support-request). * The tag has been superseded within its stream. Use that stream's current tag, or pin the exact build you need by digest, as described in [The version you need isn't listed](#the-version-you-need-isnt-listed). ## The version you need isn't listed This is the most common case, and often the version you're asking for isn't the one you need. Before you request anything, it's worth understanding how Chainguard versions its container images, because that determines what's available. ### How Chainguard maintains versions Chainguard actively maintains the latest patch release of each supported upstream version stream, not every patch that stream has ever released. If Python maintains 3.11, 3.12, and 3.13 upstream, Chainguard maintains all three streams; within each one, only the current patch is rebuilt. So `python:3.13` tracks the newest patch of the 3.13 stream, which was `3.13.9` when this was written. Chainguard's container images also use *floating tags*. A tag points at the most recent build within its version stream rather than at a fixed image, so a tag's contents change as Chainguard rebuilds it. No tag stays pinned to one specific upstream patch release. That combination explains most missing-version reports. If you need `7.79.2` and the stream has moved on to `7.80.1`, the supported path is the stream tag, which gives you that stream's latest patch with current security fixes applied. Requesting the older patch tag gets you an image that is no longer rebuilt and will accumulate CVEs. Two related points follow from how tags float: * A newer epoch tag supersedes the previous one. Once `1.14.5-r4` exists, `1.14.5-r3` stops being updated. Epoch tags aren't a locking mechanism. * To list what's actually maintained for an image, run `chainctl image repo list --repo=$IMAGE -o json | jq -r '.items[].activeTags'`. See [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/) for more on tags, epochs, and how to inspect them. ### Pin to a specific build with a digest If your reason for wanting a specific patch version is reproducibility, use a digest instead of a tag. A digest is a content-addressed reference to one exact build, and it never changes: ```shell docker pull cgr.dev/$ORGANIZATION/node@sha256:ede7ef4ca485553f5313f7a02ad3537db1fe337079fc7cfb879f44cf709326db ``` Retrieve the digest for a tag with `crane`: ```shell crane digest --full-ref cgr.dev/$ORGANIZATION/node:latest ``` A digest identifies one build and keeps identifying that same build even after the tag that pointed to it has moved on. Note the tradeoff: a pinned digest also stops receiving patches, so pair digest pinning with a process for updating the pin. Refer to [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/) for the full workflow. ### If an actively maintained tag is missing from your organization When the Directory shows a tag as actively maintained but that tag isn't available in your organization's registry, that's worth reporting. [Open a support request](#open-a-support-request) with the image name and the exact tag. The Console makes the same point from the image's **Tags** tab. Below the tag table is a link labeled **Looking for older tags?**, which explains that only actively supported tags are available when an image is added to your organization, and asks you to try a supported tag before opening a request. ## The version has reached end of life When an upstream project stops maintaining a version, Chainguard generally stops patching it. New builds are no longer published, and vulnerabilities accumulate in that version over time. Chainguard doesn't build or maintain end-of-life versions, so requesting one isn't a path forward. Move to an actively maintained version instead. Chainguard also doesn't retroactively build versions that had already reached end of life before Chainguard began maintaining an image. If an upstream project released a version years before the image entered the catalog, that version was never built and can't be added. ### Check the end-of-life date Use `chainctl` to see the end-of-life date and grace period end date for each release track: ```shell chainctl package versions list python --show-active ``` ```output VERSION | EOL DATE | EOL GRACE PERIOD END DATE ---------|------------|--------------------------- 3.10 | 2026-10-31 | 2027-05-01 3.11 | 2027-10-31 | 2028-05-01 3.12 | 2028-10-31 | 2029-05-01 3.13 | 2029-10-31 | 2030-05-01 3.14 | 2030-10-31 | 2031-05-01 ``` This output shows the EOL date that Chainguard uses for each version stream. For many projects, that date comes from [endoflife.date](https://endoflife.date). For projects that endoflife.date doesn't cover, it comes from the project's published support policy or upstream release data. If a source doesn't reflect the project's open source releases, such as when endoflife.date tracks a vendor's commercial support window, Chainguard sets the date manually, and that date takes precedence. If the date differs from the one on the upstream project's own site, the two are using different sources or define support differently. For details, see [How Chainguard determines EOL dates](/chainguard/containers/concepts/lifecycle-and-eol/versions/#how-chainguard-determines-eol-dates). Several signals tell you a version is no longer being rebuilt: * In the Console, an inactive tag appears in gray text with a pause icon beside its name. Hovering the icon shows the message `This tag is no longer considered active.` You may [open a support request](#open-a-support-request) if you want to request an inactive tag. Specify all the tags you need in a single request if requesting more than one to save time. Note that inactive versions are not supported and may contain CVEs and/or functional bugs that we are not able to fix. This is why we don't add them automatically. * On the **Organization** tab under **Images**, the **Status** column shows **Expired** for images your organization no longer has access to. * The **Vulnerabilities** tab reports that a tag is no longer being scanned. Scanning stops because the image contents have stopped changing. None of these mean the image was deleted. They mean the version has been superseded, and you should move to the current version of that stream. ### Continue past end of life with the EOL Grace Period Sometimes you can't upgrade on Chainguard's schedule, whether because a version reaches end of life ahead of your release cycle or because a later version has a problem that blocks you. For these cases, the End-of-Life Grace Period gives eligible containers up to six more months of new builds after the primary package reaches end of life. Eligibility depends on several requirements, and you can query grace period status through the Chainguard API. See the [EOL Grace Period overview](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for the requirements and the API. ## Open a support request Open a support request when: * An actively maintained tag appears in the Directory but is missing from your organization. * A version isn't end of life upstream but doesn't appear in the Directory. Chainguard may not have it in the build matrix yet, or it may be a recent upstream release still moving through the build pipeline. Support can confirm whether a build is planned. * A tag that used to be available in your organization has disappeared unexpectedly. Include the image name, the exact tag you need, and the date you need it by. For the full list of what to include and the portal's prerequisites, see [Get support](/get-started/get-support/). Questions about your subscription or entitlements, including whether a particular image is covered, go to your account team rather than the support portal. --- ### Getting started with Guardener URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/guardener/github/getting-started.md Last Modified: October 15, 2026 Tags: GitHub, Getting Started This guide walks you through everything you need to start using Guardener on your GitHub repositories: 1. [Install the Chainguard App](#step-1-install-the-chainguard-app) on your GitHub organization. 2. [Link your Chainguard organization to your GitHub organization](#step-2-link-your-chainguard-organization-to-github). 3. [Verify the link](#step-3-verify-the-link) and enable your first feature. Once the app is installed, Guardener can respond on your public repositories. Linking connects that activity to your Chainguard organization and unlocks the features that require one, such as private repository coverage — there is no separate entitlement step.

Note: Guardener is in beta. Available to organizations that have installed and linked the Chainguard App.

## Prerequisites Before you begin, make sure you have the following: - **`chainctl` installed and authenticated.** If you haven't installed it yet, follow the [chainctl installation guide](/platform/chainctl-usage/how-to-install-chainctl/), then run `chainctl auth login`. - **A Chainguard organization** where you are an owner, or where you otherwise hold the `guardener.association.manage` capability. This capability is required to link a GitHub organization to your Chainguard group. Refer to the [Built-in roles and capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/) for more information on roles. - **Owner (admin) access to the GitHub organization** you want to link. Installing the app and authorizing the link both require GitHub organization ownership. The `chainctl guardener` commands identify your Chainguard organization by its group name — you don't need to look up a group ID. Pass the name with `--group`, or omit the flag entirely and `chainctl` will prompt you to select from the organizations you have access to. ## Step 1: Install the Chainguard App Guardener acts on your repositories through the **Chainguard App**, a single hardened GitHub App that also backs other Chainguard products. Install it on the GitHub organization whose repositories you want Guardener to manage. 1. Go to the [Chainguard App page](https://github.com/apps/chainguard). 2. Select **Install** (or **Configure** if it is already installed on another account). 3. Choose the GitHub organization to install it on. 4. Choose which repositories Guardener can access. You can grant access to **All repositories** or select specific repositories. You can change this selection later in your GitHub organization settings. 5. Review the requested permissions and confirm the installation. ### Permissions Guardener requests Guardener requests the minimum GitHub permissions needed to operate: | Permission | Access | Why it's needed | | ---------- | ------ | --------------- | | Contents | Read & write | Read repository files (workflows, signatures, configuration) and push migration pull request branches. | | Pull requests | Read & write | Receive pull request events, read diffs, and post review comments and pull requests. | | Workflows | Read & write | Read and update GitHub Actions workflow files during Actions migration. | | Checks | Write | Publish check runs that report Guardener's results. | Installing the app does **not** change any repository on its own. Each feature stays disabled until you opt in with a configuration file, as described in [Configuration](/chainguard/guardener/github/configuration/). ## Step 2: Link your Chainguard organization to GitHub Linking associates your GitHub organization with a Chainguard group so Guardener knows which Chainguard organization your GitHub activity belongs to. Run `chainctl guardener github link`, passing your GitHub organization login and your Chainguard group name: ```shell chainctl guardener github link \ --github-org \ --group ``` If you omit `--group`, `chainctl` prompts you to select the Chainguard organization to link. A browser window opens to authorize with GitHub. This step proves that you own the GitHub organization. The Chainguard App must already be installed on the organization (refer to [Step 1](#step-1-install-the-chainguard-app)) for the link to succeed. > **Note**: Linking requires that you are an owner of the Chainguard group (specifically, that you hold the `guardener.association.manage` capability) **and** an owner of the GitHub organization. If you need to link your own user account rather than an organization, pass your GitHub username to `--github-org`. ## Step 3: Verify the link After linking, confirm that Guardener is active on your repositories: - Open a repository that Guardener can access and confirm the Chainguard App appears under the repository's or organization's installed GitHub Apps. - Add your first configuration file (for example, `.chainguard/actions.yaml`) as described in [Configuration](/chainguard/guardener/github/configuration/), then open a pull request to see Guardener respond. ## Unlinking a GitHub organization To remove the association between a GitHub organization and a Chainguard group, use `chainctl guardener github unlink`: ```shell chainctl guardener github unlink \ --github-org \ --group ``` When you pass `--group` and hold the `guardener.association.manage` capability on that group, the organization is unlinked using your Chainguard credentials with no browser involved. Otherwise, `chainctl` falls back to the GitHub authorization flow to prove you own the organization. Either way, you must be logged in to Chainguard. Unlinking stops Guardener's Chainguard-connected features, such as private repository coverage; features enabled on public repositories keep working while the app is installed. To fully remove Guardener, also uninstall the Chainguard App from your GitHub organization settings. ## Command reference For the complete set of flags and options, refer to the `chainctl` reference: - [`chainctl guardener`](/platform/chainctl/chainctl-docs/chainctl_guardener/) - [`chainctl guardener github`](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - [`chainctl guardener github link`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_link/) - [`chainctl guardener github unlink`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_unlink/) ## Next steps - **[App connections](/chainguard/guardener/github/app-connections/)** — Inspect, change, and remove the connections between your Chainguard organization and your GitHub organizations. - **[Configuration](/chainguard/guardener/github/configuration/)** — Learn the `.chainguard/` configuration model and how to enable features per repository. - **[Hardened Actions](/chainguard/guardener/github/actions-security/)** — Recommend and migrate GitHub Actions to hardened, SHA-pinned equivalents. - **[Container Image Suggestions](/chainguard/guardener/github/image-suggestions/)** — Get Chainguard container image suggestions on the images a pull request adds or changes. - **[Container Image Migration](/chainguard/guardener/github/image-migration/)** — Replace the images a repository already uses through one migration pull request. - **[Commit Verification](/chainguard/guardener/github/commit-verification/)** — Require cryptographically signed commits in pull requests. --- ### Chainguard Actions overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/actions/overview.md Last Modified: October 13, 2026 Tags: Chainguard Actions, Overview Chainguard Actions are a set of hardened drop-in replacements for popular GitHub Actions. Each action preserves the same inputs and outputs as the upstream version, but has been examined and revised to better protect your CI/CD pipelines from supply chain attacks. The only change in your workflow configuration is the name of the action in the `uses:` line. The catalog holds more than 1,000 hardened actions. Coverage spans GitHub first-party (`actions/*`), cloud-provider (`aws-actions/*`, `azure/*`, `google-github-actions/*`), Docker, HashiCorp, and security tools actions (Trivy, Grype, CodeQL, Semgrep), as well as a growing catalog of community actions. Each hardened action: - Is pulled from the upstream source at a pinned commit, then reviewed by a static ruleset and an AI-powered analysis pass - Has every internal `uses:` and container image reference pinned to an immutable SHA digest - Ships with a `HARDENING.md` report documenting exactly what was checked and fixed - Ships with a signed SLSA provenance attestation recording the upstream source and the ruleset version applied (releases published before signing began don't carry one) - Is re-reviewed and re-hardened whenever upstream publishes a new version or Chainguard adds a new rule Chainguard Actions protect against common threats including tag hijacking, dependency confusion, `pull_request_target` abuse, and secret exfiltration. This page provides enough to get you started. Browse the [Chainguard Actions organization](https://github.com/chainguard-actions) to find a specific action and read its hardening report. Chainguard Actions hardens workflows that stay on GitHub Actions. To move a repository's pull request checks off GitHub Actions runners instead, refer to [Migrate from GitHub Actions to Chainguard Checks](/chainguard/checks/migrate-from-github-actions/). ## What hardening checks and fixes Every action in the catalog goes through the same two-stage review. A deterministic static pre-pass runs first and catches patterns mechanically. An AI-powered analysis pass then evaluates the action against the policy ruleset. Findings from either stage carry an ID that appears in the action's `HARDENING.md` report, so you can trace any change back to the check that produced it. ### Policy checks | Check | Finding IDs | Severity | What it catches | | ----- | ----------- | -------- | --------------- | | Unpinned uses | `unpinned-uses` | High | A `uses:` reference or a `runs.image:` container reference pointing at a mutable tag or branch instead of an immutable commit SHA or image digest. The `docker://` prefix is optional, so `image: ghcr.io/example/tool:latest` is a finding too. | | Script injection | `script-injection` | High | Expressions such as `${{ inputs.name }}` interpolated directly into a `run:` block, where the shell can parse attacker-controlled text as commands. | | Unsafe shell | `unsafe-shell` | High | Remote content piped straight into an interpreter, such as `curl ... \| bash`. | | Hardcoded credentials | `hardcoded-credentials` | High | Literal secrets assigned to names containing `password`, `secret`, `token`, `api_key`, or `aws_secret`. | | GitHub environment injection | `github-env-injection` | High | Untrusted values written to `$GITHUB_ENV`, `$GITHUB_PATH`, or `$GITHUB_OUTPUT` without newline sanitization, which lets an attacker inject variables into later steps. | | Suspicious run content | `suspicious-run-content` | High | Malicious patterns in `run:` blocks, including obfuscated execution, process memory access, dynamic evaluation, credential file access, outbound exfiltration, reverse shells, persistence, and environment secret scraping. | | Permissions | `permissions`, `missing-permissions`, `broad-permissions` | Medium | Workflows that leave `GITHUB_TOKEN` at its default permissions, or that set `read-all` or `write-all` instead of specific scopes. | ### Static pre-pass checks The static pre-pass complements the analysis pass by catching patterns the analysis may miss. It reports one finding per occurrence, so a report can list the same ID many times, each with its own location. `static-inline-injection` is the most common finding in the catalog for that reason. | Finding ID | Severity | What it catches | | ---------- | -------- | --------------- | | `static-inline-injection` | High | A single expression interpolated directly into a `run:` block. The finding names the expression and the step it appears in, and the fix moves the value into an `env:` map. | | `static-unsanitized-env-write` | Medium | An unsanitized write to a GitHub environment file. | | `invalid-yaml` | High | An action or workflow file that could not be parsed. | Both stages read the action definition (`action.yml` or `action.yaml`) and every workflow under `.github/workflows/` in the action's own repository. Neither inspects built or vendored output: `dist/`, `vendor/`, and `node_modules/` are out of scope, as are the action's test fixtures. Findings are fixed in place and the pipeline re-evaluates its own work, so a single hardening run can take several iterations before an action passes. Both the findings and the per-iteration notes are recorded in `HARDENING.md`. ## Transitive dependencies Hardening the top-level action is only part of the problem. Actions pull in other actions and container images behind the scenes, and those transitive dependencies are a common path into a pipeline. Chainguard hardens the references it can see in the action definition: - **Container images are pinned to digests.** When an action runs a registry image (`runs.using: docker` with a `docker://` image), the pipeline resolves the tag to its digest and rewrites the reference as `image:tag@sha256:`. A retagged image can't change what the action pulls. - **Nested actions are pinned, and swapped for hardened copies where they exist.** Every nested `uses:` reference is pinned to an immutable commit SHA, with the original tag kept as a comment. In composite actions, when Chainguard has hardened the nested action, the reference points at the hardened copy instead of upstream. For example, the hardened `actions/upload-pages-artifact` calls `chainguard-actions/actions-upload-artifact`, not `actions/upload-artifact`. - **The action's own workflows are reviewed too.** Both stages of the review cover the workflows under `.github/workflows/` in the action's repository, not only the action definition. - **Missing dependencies can be onboarded.** When a hardened action depends on an action that isn't in the catalog yet, [request that action](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml) and Chainguard hardens and publishes it. ### How nested actions are swapped for hardened copies When Chainguard hardens and publishes an action, every hardened action that depends on it goes back through hardening so its `uses:` reference can point at the hardened copy. Three limits apply: - It covers composite actions only. Node and Docker actions have no `uses:` steps to rewrite. - It applies only when a hardened copy exists for that exact upstream commit. - It never rewrites a reference when the correct copy is ambiguous. Coverage grows as more of the catalog is hardened, so read the `action.yml` on the version branch you plan to use to see what it references today. ### What hardening doesn't change Hardening doesn't change the packages an action bundles or installs. A JavaScript action ships the same `dist/` bundle as its upstream release, and an action built from a Dockerfile uses the same base image as upstream. Packages and binaries an action downloads while it runs are outside what the review inspects. To see the full dependency graph for your own repository, including actions reached through other actions, use the `--recursive` flag described in [View the actions you are currently using](#view-the-actions-you-are-currently-using-in-a-repository). ## Prerequisites To follow this guide, you need: - `chainctl` **v0.2.261** or later, installed and authenticated. Refer to [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't have it yet. - An active Chainguard organization. - Owner access on the organization. ## Set up Chainguard Actions Setting up has two parts: entitle your organization, then choose how you migrate your workflows. ### Step 1: Create the Actions entitlement Authenticate using `chainctl`: ```shell chainctl auth login ``` Create the Chainguard Actions entitlement for your organization: ```shell chainctl actions entitlements create ``` The output confirms the entitlement: ```output Enabled Actions product for org chainguard.edu ($ENTITLEMENT_ID) [entitlement id: $ENTITLEMENT_ID] ``` Confirm your entitlement: ```shell chainctl actions entitlements list ``` ```output ID | CREATED ------------------------------------------|------------------------- $ENTITLEMENT_ID | 2026-06-18 17:33:24 UTC ``` ### Step 2: Install the Chainguard App The [Chainguard App](/chainguard/guardener/github/getting-started/) is the recommended way to adopt Chainguard Actions across more than a repository or two. Once you install it and link it to your Chainguard organization, Guardener: - Inventories the actions your workflows use across every repository it can access - Comments on pull requests that introduce unhardened actions, so your workflows don't drift back - Opens and maintains a pull request that swaps in Chainguard hardened equivalents, once you enable migration To set it up: 1. Install the [Chainguard App](https://github.com/apps/chainguard) on your GitHub organization. 2. Link your Chainguard organization to your GitHub organization with `chainctl guardener github link`. 3. Add a `.chainguard/actions.yaml` file to each repository you want Guardener to work on, or once to your organization's `.github` repository to apply it to every repository. Both of the last two steps matter. Installing the app changes no repository on its own, and the Actions feature stays inert until a `.chainguard/actions.yaml` applies to the repository, either in the repository itself or through the [org-level `.github` configuration](/chainguard/guardener/github/configuration/#organization-level-configuration-with-the-github-repository). Once it does, pull request recommendations are on by default, but automated migration pull requests need `migrate.enabled: true` set explicitly: ```yaml enabled: true migrate: enabled: true ``` If installing an app in your organization needs an administrator's approval, they will be asked to approve a specific set of GitHub permissions. [Permissions Guardener requests](/chainguard/guardener/github/getting-started/#permissions-guardener-requests) lists each one and why it's needed, so you can take that to them before you start. Refer to [Getting started with Guardener](/chainguard/guardener/github/getting-started/) for the installation and linking steps, and to [Hardened Actions](/chainguard/guardener/github/actions-security/) for the configuration reference, the migration options, and the on-demand migration command. The Chainguard App is in beta. It runs in production and is supported, but its features and configuration may still change. If you'd rather not install a GitHub App, you can migrate with the [cg-actions](https://github.com/chainguard-dev/cg-skills/tree/main/skills/cg-actions) skill or by hand. Both approaches are covered in [Configure your workflows to use Chainguard Actions](#configure-your-workflows-to-use-chainguard-actions). ## Basic usage (quick start) To use a Chainguard hardened action, edit your workflow's YAML configuration file and change the `uses:` line to match the location in `chainguard-actions`: ```yaml - uses: chainguard-actions/@ # ``` Repository names are prefixed with the upstream organization, so `tj-actions/changed-files` becomes `tj-actions-changed-files`. This keeps two different sources of a `changed-files` action from clashing in the Chainguard Actions organization. Search the Chainguard Actions repository, find the action you want to use, and then use the name you find there. > **Note:** Don't reference a hardened action with `@main`. The main branch of each repository holds only metadata (`README.md`, `LICENSE_CHAINGUARD`, and `source.json`). The hardened action itself lives on the version branches, so a reference to `@main` fails to resolve. Pin to the commit SHA that a version tag resolves to, and keep the tag as a comment. [Choose how to reference an action](#choose-how-to-reference-an-action) explains why, and [Replace the `uses:` line in each workflow](#replace-the-uses-line-in-each-workflow) shows how to look up the SHA. ## Choose how to reference an action Pin to a commit SHA, and pair the pin with Dependabot or Renovate: ```yaml - uses: chainguard-actions/actions-checkout@ # v4 ``` A commit SHA is the only immutable reference in the catalog. Tags are mutable by design, and not just the floating major version. Chainguard re-hardens published versions in place and moves the tag when it does, including fully qualified patch tags, so a single upstream release can be re-hardened several times with the same tag pointing somewhere new each time. Both `@v4` and `@v4.3.1` resolve to whatever was published most recently. Pinning on its own isn't enough, though. A pin with no tooling behind it is the one configuration that strands you: you stay on that build, and stop receiving re-hardening, until someone updates the SHA by hand. Dependabot and Renovate both track a pinned SHA against its tag and open a pull request when the tag moves, so you receive every re-hardening as a change your own CI validates before it reaches a live workflow. That gives you more control than a mutable tag does, and costs you nothing in freshness. Pinning is also the standard Chainguard applies to the actions it hardens. The `unpinned-uses` check fails any `uses:` reference on a tag, so if you run an actions linter against your own repository, referencing a hardened action by tag will register a finding. Use the canonical repository name in the reference. Some catalog repositories answer to an older name through a GitHub rename redirect — `chainguard-actions/checkout` reaches `chainguard-actions/actions-checkout`, for example — but a redirect isn't something to depend on in a pinned workflow. ## Configure your workflows to use Chainguard Actions You can save some time by using the optional [cg-actions](https://github.com/chainguard-dev/cg-skills/tree/main/skills/cg-actions) skill, a Claude Code skill for auditing GitHub Actions usage and migrating to Chainguard hardened actions. Following the steps in this section will achieve a similar result. ### Inventory the actions you currently use. Run this from the root of your repository to get a deduplicated list of every `uses:` line across every workflow: ```shell grep -rhE "uses:\s*[^@]+@" .github/workflows/ | sort -u ``` For a more thorough inventory that also follows composite actions, use [`chainctl actions discover`](#view-the-actions-you-are-currently-using-in-a-repository). ### Check the Chainguard Actions catalog for each action. Browse [the Chainguard Actions repository](https://github.com/chainguard-actions) or use the GitHub search UI. Match by organization and action name — for example, if you use `tj-actions/changed-files`, search for `org:chainguard-actions tj-actions-changed-files`. If the action isn't in the catalog, [open an issue](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml) to request it. ### Replace the `uses:` line in each workflow. Change the `uses:` line to match the location in `chainguard-actions`. Pin to the commit SHA and preserve the original tag as a comment so Dependabot, Renovate, and human reviewers can track upgrades: ```yaml # Before - uses: tj-actions/changed-files@v47 ``` ```yaml # After - uses: chainguard-actions/tj-actions-changed-files@ # v47 # originally - uses: tj-actions/changed-files@v47 ``` To find the SHA digest for a specific release, use the `gh` CLI: ```shell gh api repos/chainguard-actions/tj-actions-changed-files/commits/v47 --jq '.sha' ``` ```output 4b4bd2ed96c7629e1c911f97f2390b91e1362735 ``` For the short SHA digest: ```shell gh api repos/chainguard-actions/tj-actions-changed-files/commits/v47 --jq '.sha[:7]' ``` ```output 4b4bd2e ``` The resulting `uses:` line with the full SHA digest: ```yaml - uses: chainguard-actions/tj-actions-changed-files@4b4bd2ed96c7629e1c911f97f2390b91e1362735 # v47 ``` Run the command rather than copying the digest shown here. Because Chainguard re-hardens a published version in place and moves its tag, the SHA a version tag resolves to changes each time that version is re-hardened. ### Update your allowed-actions list. If your GitHub organization or repository restricts which actions can run (**Settings > Actions > General > Allow select actions**), add `chainguard-actions/*` to the allowed patterns. Without this, workflows fail with a policy error on first run. > **Note:** It's a good idea to remove allowed actions that are no longer being used. ### Commit, open a PR, and verify that CI passes. The action's inputs, outputs, and behavior are almost always identical to the upstream version, so no other workflow changes are typically needed. However, read the `HARDENING.md` file for each hardened action before migrating. In rare cases, the hardening process requires a change to inputs, outputs, or behavior — those changes are documented in this file. If something breaks, [file an issue](https://github.com/chainguard-actions/.github/issues/new?template=action-issue.yml) with a reproducer. ## View the actions you are currently using in a repository Use `chainctl` to scan every workflow and composite action in a repository and list the actions and container images they reference: ```shell chainctl actions discover $GIT_ORGANIZATION/$REPO ``` ```output scanning $GIT_ORGANIZATION/$REPO for workflows and actions ACTION | REQUESTED | USED BY -------------------------------------|-----------|--------- actions/checkout | v4 | 1 chainguard-actions/actions-checkout | v6.0.2 | 1 2 actions, 0 container images ``` The command needs a GitHub token, which it reads from `$GITHUB_TOKEN` or from `gh auth token`. The target can be a local directory (the current directory by default), an `owner/repo` pair, or a single action reference such as `actions/checkout@v4`. By default, `discover` lists only the actions your workflows reference directly. Add `--recursive` to follow each referenced action into its own definition and resolve the full transitive dependency graph: ```shell chainctl actions discover $GIT_ORGANIZATION/$REPO --recursive ``` A recursive scan makes many GitHub API calls, so it caches responses and stops after `--timeout` (five minutes by default). Refer to [`chainctl actions discover`](/platform/chainctl/chainctl-docs/chainctl_actions_discover/) for the full set of flags. ## View the actions currently available While you can search the [Chainguard Actions repository](https://github.com/chainguard-actions) directly in GitHub, you can also use `chainctl` to find an action. ```shell chainctl actions catalog list --upstream-owner=$OWNER ``` For example, to list all the actions from the `tj-actions` source: ```shell chainctl actions catalog list --upstream-owner=tj-actions ``` This example returns a list of all actions in the Chainguard Actions repository that originate from the `tj-actions` upstream source. To list the catalog entries available to a specific organization rather than the whole public catalog, use [`chainctl actions list`](/platform/chainctl/chainctl-docs/chainctl_actions_list/): ```shell chainctl actions list --parent $ORGANIZATION ``` ## What ships in each hardened action Each hardened action's repository has a main branch and one branch per hardened version. The hardened action lives on the version branches; the main branch holds only metadata. The main branch of each repository contains: - `README.md` — a pointer to the action and its upstream source - `LICENSE_CHAINGUARD` — the Chainguard license for the hardened variant - `source.json` — a manifest naming the upstream owner, repository, version, and commit, along with the policy SHAs applied Each version branch contains: - `HARDENING.md` — the authoritative, per-action record of what was checked, what was fixed, and how, including the policy SHA that pins the exact ruleset applied - `action.yml` or `action.yaml` — the hardened action definition, preserving upstream inputs and outputs with fixes applied - `attestations/provenance.intoto.jsonl` — a signed [SLSA provenance](https://slsa.dev/provenance/v1) attestation naming the upstream repository and commit, the ruleset version, the build times, and a SHA-256 digest for every file in the hardened action - `LICENSE_CHAINGUARD` — the Chainguard license for the hardened variant - The upstream action's own files, including its license and any documentation you can adapt for the hardened version Because `HARDENING.md` and the attestation are per-version, read them on the version branch you plan to use rather than on the main branch. Nearly every version branch in the catalog carries an attestation. A small number of older releases were published before Chainguard began signing them, so if a version branch has no `attestations/` directory, treat that release as unverifiable rather than as verified. Chainguard doesn't publish a customer-facing verification procedure yet. Verification requires the signing key's fingerprint, which isn't published, so there is no complete recipe to follow today. Tooling for this is planned. In the meantime the attestation is still useful as a record: it names the upstream commit the release was built from and the ruleset version that was applied. ## The continuous re-hardening process Chainguard Actions are continuously re-hardened: - When upstream publishes a new version, the pipeline re-runs and publishes a new hardened version - When the hardening ruleset is updated, every action in the catalog is re-evaluated against the new ruleset and re-hardened as needed - The `HARDENING.md` report is regenerated on every hardening run, with its own policy SHA pinning the exact set of rules that were applied Because the policy SHA is computed over the ruleset itself, any change to a rule produces a new SHA, which is what triggers the catalog-wide re-evaluation. ## Request a new action or report an issue To request an action that isn't in the catalog, [open a new action issue](https://github.com/chainguard-actions/.github/issues/new?template=new-action.yml). If an action isn't working as expected, [open an action issue](https://github.com/chainguard-actions/.github/issues/new?template=action-issue.yml) with the action reference, a description of the problem, and steps to reproduce. ## Learn more - [Chainguard Actions telemetry and privacy](/chainguard/actions/telemetry/) - [Hardened Actions with Guardener](/chainguard/guardener/github/actions-security/) - [Chainguard Actions product page](https://www.chainguard.dev/actions) - For other questions, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). --- ### Chainguard Agent Skills overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/agent-skills/overview.md Last Modified: October 9, 2026 Tags: Agent Skills, Overview Chainguard Agent Skills is a catalog of hardened AI agent skills that Chainguard reviews, scopes, and publishes with a full audit trail. It lets teams extend their AI agents without extending their attack surface.

Note: Chainguard Agent Skills is in beta. Available to Chainguard Containers customers who sign up for the beta program. You can sign up by visiting the Chainguard Agent Skills product page and clicking Join the beta.

## What is an agent skill? An agent skill is a small, modular instruction set — typically a single `SKILL.md` file — that extends what an AI agent can do. Agents such as Claude Code use skills to perform tasks like browser automation, database management, and code generation. Skills are the newest class of third-party software dependency, much like npm packages or container images. Like any dependency, a skill you install runs in your environment with whatever permissions and shell access its author gave it. ## The problem with skill registries Community skill registries are growing quickly, but most have no review process, no permission scoping, no integrity verification, and no audit trail. A skill can ship with broad tool permissions, unrestricted shell access, or a vague description that causes an agent to invoke it in the wrong context. Recent supply chain attacks have used malicious skills to direct agents into installing credential-stealing malware. Every skill installed without review is an unaudited dependency with arbitrary permissions running where your agent runs. ## How Chainguard hardens skills Chainguard applies the same model it brings to container images and language libraries — hardened defaults, continuous updates, and [verifiable provenance](/chainguard/agent-skills/verifying-skills/) — to agent skills: - **Ingest and review.** Chainguard pulls popular skills from community registries and reviews each one against a security and quality ruleset. - **Target real attack vectors.** The ruleset addresses how attackers exploit the agent-skill trust relationship, including unrestricted shell access, overly broad tool permissions, and vague descriptions that enable mis-invocation. - **Harden with an audit trail.** An automated agentic pipeline applies fixes one at a time, committing each change individually. Every published skill links to a pull request with a full diff showing what changed and why. - **Reconcile continuously.** Rather than scanning once, the catalog runs a persistent loop that compares each skill against the current rules. When an upstream source changes or a new rule is added, affected skills are re-evaluated and re-hardened, so the catalog doesn't go stale. The security work happens upstream, before you or your agent ever touches the skill. To install a hardened skill, you just need to add its `SKILL.md` to your agent; there's no new toolchain or configuration required. ## Public catalog and private registries Chainguard serves hardened skills from public and private registries at `skills.cgr.dev`: - **The public catalog**, maintained by Chainguard at `skills.cgr.dev/public`. This is the hardened catalog described above. Anyone can pull from it, and the skills in it are reviewed and re-hardened on an ongoing basis. Public skills are namespaced by their upstream source (`public////`). - **Your organization's private registry**, available to customers with access, at `skills.cgr.dev//`. Upload original skills to `uploads.cgr.dev//` and submit them with `chainctl skills harden`. Hardened results are published under `skills.cgr.dev//users//`, scoped to your organization. To interact with either of these registries, use the [`chainctl skills` commands](/platform/chainctl/chainctl-docs/chainctl_skills/). ## Next steps To install and run a skill hardened by Chainguard, check out our guide on [Getting started with the Chainguard Agent Skills public catalog](/chainguard/agent-skills/public-registry/). Alternatively, to publish, push, and run skills in your organization's private registry, refer to our guide on [Getting started with the Chainguard Skills Registry](/chainguard/agent-skills/skills-registry/). To harden your own skills, track jobs, and review their reports before installing, follow [Getting started with skill hardening](/chainguard/agent-skills/skill-hardening/). --- ### How to set up pull through from Chainguard's registry to Amazon ECR URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/ecr-pull-through.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Registry In March 2026, AWS [announced support](https://aws.amazon.com/about-aws/whats-new/2026/03/amazon-ecr-pull-through-cache-chainguard/) for using Amazon Elastic Container Registry (ECR) as a pull through cache for Chainguard's registry. By configuring a pull through cache rule, you can pull Chainguard Containers through your own ECR private registry. ECR caches each image on the first pull and checks the upstream registry for a newer version at most once every 24 hours, which reduces your workloads' direct dependency on Chainguard's registry. This tutorial outlines how to configure a pull through cache rule for [Chainguard's registry](/chainguard/containers/registry/overview/) with [Amazon ECR](https://docs.aws.amazon.com/AmazonECR/latest/userguide/what-is-ecr.html). Unlike some other registries, ECR treats Chainguard as an upstream that requires authentication. This means you store a Chainguard pull token in AWS Secrets Manager and reference it from a cache rule. This guide scopes that rule to your organization's private namespace, so it caches your [Production containers](/chainguard/containers/concepts/container-categories/#production-containers) and lets you pull them with short image paths. ## Prerequisites To complete this tutorial, you need the following: * An AWS account with permissions to create ECR pull through cache rules and AWS Secrets Manager secrets. Refer to the AWS guide on [IAM permissions for pull through cache](https://docs.aws.amazon.com/AmazonECR/latest/userguide/pull-through-cache-iam.html) for details. * The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed and configured, if you plan to follow the command line examples. You can complete every step in the [Amazon ECR console](https://console.aws.amazon.com/ecr/) instead. * Docker installed on your local machine. Refer to [the official documentation](https://docs.docker.com/engine/install/) to set this up. * `chainctl`, Chainguard's command-line interface tool, installed on your local machine. If you haven't already installed it, follow our [`chainctl` installation guide](/platform/chainctl-usage/how-to-install-chainctl/). To pull Production Containers, you also need permissions to pull images from your organization's private Chainguard registry. At minimum, you must be granted the `registry.pull` role, though other built-in roles like `owner`, `editor`, or `viewer` also work. Refer to our [Built-in roles and capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/#pull-token-creator-roles) for more details. If you don't already have access to Production Containers, you can [contact our sales team](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). ## Creating a Chainguard pull token Because ECR authenticates to Chainguard's registry on your behalf, you must supply it with credentials. Chainguard [pull tokens](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) are longer-lived tokens designed for environments that don't support OIDC, such as CI systems, Kubernetes clusters, or registry mirroring tools like ECR. First, log in with `chainctl`: ```sh chainctl auth login ``` Then create a pull token: ```sh chainctl auth configure-docker --pull-token ``` If you belong to more than one organization, this command prompts you to select one. Choose the organization whose Production Containers you want to pull through ECR. By default, the token expires in 30 days; you can adjust this with the `--ttl` flag (for example, `--ttl=8760h` for one year). This command prints a `docker login` command that includes `--username` and `--password` arguments: ```output . . . docker login "cgr.dev" --username "" --password "" ``` You don't need to run this `docker login` command, but note down the `` and `` values. You'll supply them to AWS Secrets Manager in the next step. ## Storing your pull token in AWS Secrets Manager ECR reads your Chainguard credentials from an AWS Secrets Manager secret rather than from the cache rule itself. The secret's name must begin with the `ecr-pullthroughcache/` prefix, and it must be in the same account and Region where you'll create the cache rule. To create the secret with the AWS CLI, run the following command. Replace `` and `` with the values from the previous step, replace `` with a name for your secret, and set `` to the Region where you'll create the cache rule: ```sh aws secretsmanager create-secret \ --name ecr-pullthroughcache/ \ --secret-string '{"username":"","accessToken":""}' \ --region ``` The secret must use the two keys `username` and `accessToken`, and it must be encrypted with the default `aws/secretsmanager` KMS key. ECR doesn't support customer managed keys for pull through cache secrets. The `create-secret` command uses the default key unless you specify otherwise. Note down the secret's Amazon Resource Name (ARN) from the command's output, as you'll need it when creating the cache rule with the CLI. The ARN has a format like the following: ```ARN arn:aws:secretsmanager:::secret:ecr-pullthroughcache/ ``` Alternatively, you can create the secret in the [AWS Secrets Manager console](https://console.aws.amazon.com/secretsmanager/). Choose **Store a new secret**, select **Other type of secret**, and add two key/value pairs: `username` set to your pull token ID and `accessToken` set to your pull token password. Keep the default `aws/secretsmanager` encryption key, and give the secret a name beginning with `ecr-pullthroughcache/`. You can also create the secret as part of the cache rule workflow described in the next section. ## Creating a pull through cache rule With your credentials stored, you can create a pull through cache rule that points at Chainguard's registry. To create the rule with the AWS CLI, run the following command. Replace `--upstream-repository-prefix` with your organization's name (this guide uses `example.com`), replace the `--credential-arn` value with the ARN of the secret you created, and set `` to the Region where you created the secret: ```sh aws ecr create-pull-through-cache-rule \ --ecr-repository-prefix cg-ecr \ --upstream-registry-url cgr.dev \ --upstream-repository-prefix example.com \ --credential-arn arn:aws:secretsmanager:::secret:ecr-pullthroughcache/ \ --region ``` This command creates a rule scoped to a single Chainguard namespace: * `--upstream-repository-prefix example.com` limits the rule to your organization's private namespace at `cgr.dev/example.com`. Replace `example.com` with your organization's name or ID, which you can find by running `chainctl iam organizations list -o table`. When you pull an image, ECR matches this prefix and drops it from the image path, so you don't repeat the namespace on every pull. * `--ecr-repository-prefix cg-ecr` sets the local namespace for the repositories ECR creates, following the scheme `cg-ecr/`. You can use any prefix you like; this guide uses `cg-ecr` to keep the local prefix distinct from Chainguard's namespaces. * `--upstream-registry-url` **must** be set to `cgr.dev`. Alternatively, you can create the rule in the [Amazon ECR console](https://console.aws.amazon.com/ecr/): 1. From the navigation bar, choose the Region in which to configure your private registry. 2. In the navigation pane, click **Private registry**, then select **Pull through cache** under **Features & Settings**. 3. On the **Pull through cache rules** page, click **Add rule**. 4. On the **Specify upstream** page, select **Chainguard**, then click **Next**. 5. On the **Configure authentication** page, choose **Use an existing AWS secret** and select the secret you created. To create the secret here instead, choose **Create an AWS secret** and enter your pull token ID and password. Click **Next**. 6. On the **Specify namespaces** page, set the **Cache namespace** to `cg-ecr`. ECR populates a default of `chainguard`, but this guide uses `cg-ecr` to keep the local prefix distinct from Chainguard's namespaces. For the **Upstream namespace**, select **A specific prefix** and enter your organization's name (this guide uses `example.com`) to scope the rule to your private namespace. Click **Next**. 7. On the **Review and create** page, review the configuration and click **Create**. After creating the rule, you can [validate it](https://docs.aws.amazon.com/AmazonECR/latest/userguide/pull-through-cache-working-validating.html) from the console or CLI. Validation confirms that ECR can reach Chainguard's registry and authenticate with your stored credentials.

Note: Scoping the rule to a single namespace keeps pull paths short, but it limits the rule to that one namespace. To cache any repository your credentials can access — including your other private namespaces and Free containers from the public chainguard namespace — omit --upstream-repository-prefix on the CLI, or select No prefix for the Upstream namespace in the console. ECR then defaults to ROOT and caches any repository. With an unscoped rule, include the full upstream namespace in each pull path, such as cg-ecr/example.com/chainguard-base:latest or cg-ecr/chainguard/go:latest.

## Testing pull through from Chainguard's registry to Amazon ECR Before pulling an image, authenticate Docker to your ECR private registry. Replace `` and `` to match your setup: ```sh aws ecr get-login-password --region | docker login --username AWS --password-stdin .dkr.ecr..amazonaws.com ``` After logging in, you can pull a Chainguard Container through ECR. Because the rule is scoped to your organization's namespace, ECR drops that namespace from the pull path. Container images pulled through the rule use the following URI format, where the first path element is the repository prefix you configured (`cg-ecr`) and the remainder is the image name: ``` .dkr.ecr..amazonaws.com/cg-ecr/: ``` The following example pulls the `chainguard-base` Production Container from the `example.com` namespace the rule is scoped to: ```sh docker pull .dkr.ecr..amazonaws.com/cg-ecr/chainguard-base:latest ``` Here, `cg-ecr` is your ECR repository prefix and `chainguard-base` is the image name. ECR resolves this to `cgr.dev/example.com/chainguard-base` in Chainguard's registry. You don't include `example.com` in the pull path because the rule is already scoped to it. On the first pull of an image, ECR creates a repository under the `cg-ecr/` prefix and caches the image. ECR serves subsequent pulls from that cache. ## Debugging pull through from Chainguard's registry to Amazon ECR If you run into issues when pulling Containers from Chainguard's registry through ECR, check the following: * Confirm that your environment meets all [Containers network requirements](/chainguard/containers/registry/network-requirements/). The first pull of an image requires a route to the internet, even if you access ECR through a VPC endpoint. * When creating the cache rule, ensure the upstream registry URL is set to `cgr.dev`. This field **must not** contain additional components. * Confirm that your Secrets Manager secret uses the `username` and `accessToken` keys, and that its name begins with the `ecr-pullthroughcache/` prefix. The secret must be in the same account and Region as the cache rule. * Confirm that the pull token stored in the secret has not expired and that its identity has permission to pull the container images you're requesting. * If you scoped the rule to a namespace, confirm your pull path omits that namespace. For a rule scoped to `example.com`, pull `cg-ecr/chainguard-base:latest`, not `cg-ecr/example.com/chainguard-base:latest`. * You can troubleshoot by running `docker login` from another machine (using the pull token credentials) and pulling directly from `cgr.dev/example.com/`, or from `cgr.dev/chainguard/` for Free Containers. * Refer to the AWS guide on [troubleshooting pull through cache issues](https://docs.aws.amazon.com/AmazonECR/latest/userguide/error-pullthroughcache.html) for common errors and their resolutions. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by checking out our [Containers documentation](/chainguard/containers/overview/). If you'd like to learn more about Amazon ECR pull through cache rules, refer to the [official AWS documentation](https://docs.aws.amazon.com/AmazonECR/latest/userguide/pull-through-cache.html). --- ### Overview of Chainguard Repository URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-repository/overview.md Last Modified: September 28, 2026 The Chainguard Repository is a single, policy-managed experience for pulling artifacts that Chainguard either builds or distributes. It gives your organization one place to pull open source artifacts, configure security policies that govern how they are consumed, and monitor coverage and policy enforcement across your environment. All artifacts served through the Chainguard Repository are either rebuilt by Chainguard from verifiable source in a SLSA L3-compliant build environment, or sourced from upstream public registries with configurable policy controls. As Chainguard builds more artifacts from source, your organization's risk shrinks automatically without any changes to your configuration or developer workflows. ## Artifact types As of this writing, the Chainguard Repository contains the following artifact types: | Artifact type | Description | | ----- | ----- | | [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/overview/) | Open source language dependencies rebuilt from source for JavaScript (npm). | | [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) | Open source language dependencies rebuilt from source for Java (Maven). | | [Chainguard Libraries for Python](/chainguard/libraries/python/overview/) | Open source language dependencies rebuilt from source for Python (PyPI). | ## Endpoints Each artifact type is accessible via its own endpoint: | Artifact type | Endpoint | | ----- | ----- | | Libraries for [JavaScript](/chainguard/libraries/javascript/overview/) | `https://libraries.cgr.dev/javascript/` | | Libraries for [Java](/chainguard/libraries/java/overview/) | `https://libraries.cgr.dev/java/` | | Libraries for [Python](/chainguard/libraries/python/overview/) | `https://libraries.cgr.dev/python/` | See each artifact type's documentation for authentication and configuration details. ## Policies for Chainguard Libraries The Chainguard Repository includes a policy engine that lets you define rules governing which artifacts can be consumed and under what conditions. Policies can be configured in the Chainguard Console or with `chainctl`, and are enforced automatically across your environment. For language dependencies, policies apply to both Chainguard-built packages and upstream packages served via the optional fallback to public registries. Available policies include: * **Upstream fallback**: Control whether packages not yet built by Chainguard can be sourced from the upstream public registry. * **Cooldown**: When upstream fallback is enabled, block newly published package versions for a defined period before they can be pulled, giving the security community time to detect threats. The cooldown is configurable (0 to 3650 days) with a default of 7 days. It is applied globally across all packages to prevent dependency resolution errors. All upstream packages are checked against public malware identifier feeds, and any package with a known malware idenitifier is blocked before being served. > **Note**: With fallback enabled, Chainguard's malware and greyware scanning covers upstream packages, which addresses the primary risk a cooldown period is meant to mitigate. A cooldown period can still add a small buffer against issues the broader community surfaces after publication and is applied globally across your organization, but it also adds operational complexity and can introduce breakage. Choose a cooldown policy based on your own risk tolerance and priorities. Refer to [Libraries policies](/chainguard/chainguard-repository/library-policies/) for more information. ## Policies for Chainguard Containers As with Libraries, you can also set policies for Chainguard Containers to define rules governing which container images can be consumed and under what conditions. Policies can be configured in the Chainguard Console or with `chainctl`, and are enforced automatically across your environment. Available policies include: * **cooldown**: Block newly published container image versions for a defined period before they can be pulled, giving the security community time to detect threats. The cooldown is configurable (0 to 3650 days) with a default of 7 days. It is applied globally across all packages to prevent dependency resolution errors. * **no-eol**: Prevent end-of-life container images from being used. * **support-window**: Requires an image's primary package to have a minimum remaining support period, measured as the span between the package's release date and its EOL date. An image is allowed when that span is greater than or equal to the configured number of months, or when the package has no recorded EOL date. > **Note**: Chainguard recommends a 7-day cooldown when enabling upstream fallback, to block a large share of malicious packages identified shortly after publication. Shorter cooldown periods increase the risk of pulling malicious or compromised upstream packages before the broader ecosystem can detect and report them. The packages that make up Chainguard Containers are checked against public malware identifier feeds, and any package with a known malware identifier is remediated before being used in any image. Refer to [Container pull policies](/chainguard/chainguard-repository/container-policies/) for more information. ## Management The Chainguard Console and `chainctl` can be used for configuring and managing policies across your organization. Learn more in [Using the Chainguard Console](/platform/console/images-directory/) and [Get started with chainctl](/get-started/getting-started-with-chainctl/). Access the Console at [console.chainguard.dev](https://console.chainguard.dev). ## Learn more * [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/overview/) * [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) * [Chainguard Libraries for Python](/chainguard/libraries/python/overview/) --- ### Chainguard Catalog Starter URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/reference/catalog-starter.md Last Modified: October 13, 2026 Tags: Chainguard Containers Chainguard Catalog Starter is a way to try production-grade Chainguard Containers for free, without committing to a full subscription. It lets you choose a set of five container images from the broader Chainguard catalog so you can validate security, performance, and operational fit in your own environment before you buy.

Note: Chainguard Catalog Starter is in beta.

To sign up for Catalog Starter, follow the steps on this page. Once set up, you can start piloting up to five secure container images from the Chainguard catalog. ## What is Catalog Starter? With Chainguard Catalog Starter, users can choose any five non-FIPS images from our catalog of secure-by-default containers. Any Helm charts that depend on those images are included and count toward the five-image limit. Catalog Starter is designed as a standalone free plan, separate from paid Catalog or per-image subscriptions. It's meant for teams who want to try Chainguard Containers in real workloads before committing to a larger rollout, giving them a better understanding of how Chainguard’s hardened images and CVE posture behave in practice. > **Note**: Catalog Starter is best suited for small teams to get them started right away. When you are ready to standardize on Chainguard and deploy organization-wide, we have several paid plans that provide unlimited user access to the Chainguard Containers and additional features like Custom Assembly, FIPS images, EOL/EmeritOSS image versions, a contractual CVE-remediation SLA, and dedicated support. ## Sign up for Catalog Starter ### 1. Sign up and select your images 1. Go to [console.chainguard.dev/signup](https://console.chainguard.dev/signup). 2. Sign in with a business email address and create a new account. Catalog Starter requires a corporate email domain — personal email addresses and social media sign-ins are not supported. 3. Enter your company name. 4. Click **Set up an organization**. 5. Complete the form and click **Submit**. 6. Click **Select images**, choose between one and five images, and confirm your selection. You can also go to the **Images** page at any time to make or change your selection. Once you've added all five images, a prompt directs you to contact Chainguard if you need access to additional images. ### 2. Integrate with your registry and pipelines After you complete signup, you're free to use the five images you selected however you like. For example, you can pull them into your CI/CD pipelines and runtime environments, or pull them through a third-party registry like [JFrog Artifactory](/chainguard/containers/registry/pull-through-guides/artifactory-containers-pull-through/). These container images are the same as those provided to Chainguard's paying customers, and are covered by Chainguard’s standard hardening, rebuild, and CVE-remediation processes, although they are not covered by [Chainguard's CVE SLA](https://www.chainguard.dev/legal/cve-policy). #### Authenticate CI pipelines with a pull token Because Catalog Starter organizations can't create assumable identities, CI pipelines authenticate with a pull token. Create one with `chainctl`: ```shell chainctl auth pull-token create --output=env ``` This sets `CHAINGUARD_IDENTITY_ID` to the username and `CHAINGUARD_TOKEN` to the password. Store both as secrets in your CI platform, then log in to `cgr.dev` with them. For example, in a GitHub Actions workflow: ```yaml name: Pull a Chainguard container on: push: branches: ['main'] permissions: contents: read jobs: pull: runs-on: ubuntu-latest steps: - name: Log in to cgr.dev env: CHAINGUARD_IDENTITY_ID: ${{ secrets.CHAINGUARD_IDENTITY_ID }} CHAINGUARD_TOKEN: ${{ secrets.CHAINGUARD_TOKEN }} run: | echo "$CHAINGUARD_TOKEN" | docker login cgr.dev \ --username "$CHAINGUARD_IDENTITY_ID" --password-stdin - run: docker pull cgr.dev/$ORGANIZATION/python:latest ``` This workflow doesn't need the `id-token: write` permission, because it federates no OIDC token. Pull tokens expire. The default lifetime is 30 days and the maximum is one year (`--ttl=8760h`), so rotate the secret before it expires. Refer to [Authenticate to Chainguard's Registry](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) for the rest of the pull token options. ### 3. Upgrade when you’re ready After trying out Chainguard Containers with Catalog Starter, you can reach out to our sales team to upgrade to one of Chainguard's paid plans: * [Catalog pricing](/chainguard/containers/reference/pricing/) — a subscription that grants broad access to the full Chainguard Containers catalog with self-service provisioning through the Console. * Per-image pricing — a scoped set of images licensed individually, often used for tightly defined image deployments. Moving to a paid plan means moving off Catalog Starter; the free five-image entitlement is not stacked on top of a paid subscription. ## Manage Catalog Starter with `chainctl` As an alternative to the Console, you can sign up for and manage Catalog Starter from the command line with `chainctl`. ### Sign up with `chainctl` To sign up for a new account, run the interactive [`chainctl starter init`](/platform/chainctl/chainctl-docs/chainctl_starter_init/) command: ```shell chainctl starter init ``` This requires selection of a valid authentication option: ```shell Choose an identity provider to login to Chainguard > Email and password Google ``` Use the arrow keys and press **Enter** to select an option, which launches a browser window to the Chainguard Console to complete sign-up. ### Request access with `chainctl` If your organization already has a Catalog Starter plan, request access to it with [`chainctl starter request-access`](/platform/chainctl/chainctl-docs/chainctl_starter_request-access/): ```shell chainctl starter request-access ``` ### Add images with `chainctl` You can see which images are available [using the Chainguard Directory](/chainguard/containers/registry/chainguard-directory/). To add one or more images, run [`chainctl starter add-images`](/platform/chainctl/chainctl-docs/chainctl_starter_add-images/), substituting the desired image names for the variables: ```shell chainctl starter add-images $IMAGE1 [$IMAGE2] ... [$IMAGE4] ``` After the Chainguard system has processed your request, the images become accessible. This can take up to a few hours. When available, you can pull the images like this, replacing `$ORGANIZATION` with your organization's name and `$IMAGE` with the desired image's name: ```shell docker pull cgr.dev/$ORGANIZATION/$IMAGE:latest ``` ### Check status with `chainctl` To show the status of your Catalog Starter organization, including the registry path, account provisioning status, image quota usage, and per-image readiness, run [`chainctl starter status`](/platform/chainctl/chainctl-docs/chainctl_starter_status/): ```shell chainctl starter status ``` ## Plan limitations and terms Catalog Starter allows users to try out Chainguard Containers, but it comes with certain limitations, including the following: * You can select up to five non-FIPS images. Once chosen, these images cannot be swapped or replaced during the lifetime of the free plan. * The following types of Chainguard Containers are not included in the Catalog Starter plan: * [FIPS-validated images](/platform/fips/fips-images/) * Images that fall under the [EOL grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/#understanding-chainguards-eol-grace-period) * Images whose software is part of [Chainguard EmeritOSS](https://github.com/chainguard-forks/) * Teams using Chainguard Catalog Starter will not have access to the support services available to paying customers: they will not be added to Chainguard's support platform, be able to create support tickets, or have access to root cause analysis (RCA). * Catalog Starter users will still have access to Chainguard resources like our [documentation](https://edu.chainguard.dev/), [courses](https://courses.chainguard.dev/), the [Community Slack channel](https://www.chainguard.dev/unchained/the-chainguard-slack-community-is-here), and the [public support knowledge base](https://support.chainguard.dev/hc/en-us). * [Chainguard's CVE SLA](https://www.chainguard.dev/legal/cve-policy) does not apply to container images obtained through Catalog Starter. * The free plan does not support user management or role-based access control (RBAC). If a colleague from the same company signs up for Catalog Starter, your organization's administrator receives an email with instructions for adding them. You cannot add users directly. Any additional users in your organization have access to the five images selected by the first user and cannot change them. * Users in a Catalog Starter organization are assigned the `limited_owner` role, which allows them to browse the Console, pull images, create pull tokens, and invite colleagues whose email address is in your organization's domain. It doesn't include access to features like [Custom Assembly](/chainguard/containers/custom-assembly/). * You can create pull tokens, but not [assumable identities](/platform/administration/assumable-ids/assumable-ids/). A pull token is the only credential a Catalog Starter organization can issue for automation. If you try to create any other identity, the request fails with `starter organizations are not allowed to create identities`. * The [`setup-chainctl`](https://github.com/chainguard-dev/setup-chainctl) GitHub Action doesn't work with Catalog Starter. It authenticates by assuming a Chainguard identity with the OIDC token GitHub issues to the workflow, and it has no pull token mode. The same applies to the OIDC federation examples for GitLab, CircleCI, Buildkite, Jenkins, and Kubernetes. Authenticate your pipelines with a pull token instead, as described in [Authenticate CI pipelines with a pull token](#authenticate-ci-pipelines-with-a-pull-token). * Revoking a pull token takes a different path. On the Console's **Pull tokens** page, the **Delete** and **Edit** actions are unavailable, and `chainctl iam identity delete` fails, because the `limited_owner` role can't delete identities. To revoke a token, delete its role binding instead: run `chainctl iam role-bindings list` to find the binding, then `chainctl iam role-bindings delete `. This removes the token's access, although the identity itself remains in the list. * Neither Custom Assembly nor Commercial Builds, Chainguard's bespoke paid build services, are included in the Catalog Starter plan. * Catalog Starter cannot be combined with an existing Chainguard Containers license. Existing customers remain on their current paid plans; this free offering can’t be used to subsidize or partially offset paid image counts. ## Frequently asked questions ### Can I change my five images after I select them? No. Once you’ve selected your five images under Catalog Starter, that selection is fixed for the life of the free plan and cannot be swapped or replaced. If you anticipate needing different images over time, we recommend talking to our team about Catalog Pricing or per-image licensing. ### Does Catalog Starter include FIPS images or Chainguard commercial builds? No. FIPS images and Chainguard Commercial Builds are not included in the Catalog Starter plan. If you require FIPS-validated images or dedicated commercial build work, you’ll need a paid plan that includes those capabilities. ### Can I use Chainguard's GitHub Action with Catalog Starter? No. The [`setup-chainctl`](https://github.com/chainguard-dev/setup-chainctl) action federates a GitHub OIDC token into a Chainguard assumable identity, and Catalog Starter organizations can't create assumable identities. Log in to `cgr.dev` with a pull token instead, as described in [Authenticate CI pipelines with a pull token](#authenticate-ci-pipelines-with-a-pull-token). ### Can my colleagues and I use the same Catalog Starter images? Yes. If a Catalog Starter organization already exists for your email domain, the administrator of that organization receives an email with instructions for adding you to it. Once added, you'll have access to the five images the first user selected, and neither of you will be able to change them. Catalog Starter does not include user management or RBAC, so users can't add others to their Chainguard organization themselves. ### I’m already a Chainguard Containers customer. Can I add Catalog Starter to my account? No. Catalog Starter is a distinct, standalone free plan meant for new organizations. It can’t be combined with existing licenses or used to "carve out" a subset of images from a current subscription. If you try to sign up using an email domain already associated with a paid Chainguard account, you’ll receive an error message. Contact your Chainguard account administrator for access to your organization’s existing account. ### Can I use Catalog Starter to cover some of the images I need? No. Catalog Starter is not a discount instrument for larger deployments. You can’t, for example, license 10 images but expect to pay for only five by applying this plan to the rest. If you know you’ll need more than five images, it’s usually better to start with Catalog Pricing or per-image licensing so you can scale cleanly. ### Can I sign up using a social media account? No. Catalog Starter requires a business email address. If you sign in using a social login (such as GitHub or Google), you'll be prompted to create a new account with a business email instead. ### What happens if I decide to upgrade to a paid plan? When you upgrade: * Your organization moves off the free Catalog Starter plan. * You gain access to the broader capabilities of your chosen paid tier (for example, full Catalog Pricing with self-service provisioning and, optionally, FIPS access), as well as Chainguard's full support services and CVE SLA. * Pricing is based on the paid plan’s terms (such as developer bands for Catalog Pricing or image counts for per-image plans). Our team will work with you to map your existing Catalog Starter images into the right long-term structure. --- ### Migrate a .NET application to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-guides/dotnet.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Migration Chainguard's [.NET container images](https://images.chainguard.dev/directory/image/dotnet-sdk/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-dotnet) provide a security-hardened foundation for building and running applications with significantly fewer vulnerabilities than .NET images provided by Microsoft. Chainguard's .NET container images maintain full .NET compatibility while dramatically reducing the attack surface. This guide demonstrates migrating a .NET application from Microsoft's official images to Chainguard's .NET container images by building one application with two different Dockerfiles and comparing the results side by side. This guide also highlights concrete examples of the security improvements resulting from migrating to Chainguard Containers. ## Prerequisites This tutorial uses the publicly available .NET container images from Chainguard's [Free tier](/chainguard/containers/concepts/container-categories/#free-containers) of images. You don't need special access or permissions to use these images. To follow along, you must have Docker installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/). Optionally, you can install [Grype](https://github.com/anchore/grype) to scan container images for vulnerabilities and compare the security posture of different base images. ## Retrieving the demo application files This step involves downloading the demo application code to your local machine. To prevent the application files from remaining on your system, navigate to a temporary directory like `/tmp/`: ```sh cd /tmp/ ``` Most systems automatically delete the `/tmp/` directory's contents the next time they shut down or reboot. The code that comprises this demo application is hosted in a [public GitHub repository managed by Chainguard](https://github.com/chainguard-dev/edu-images-demos). Pull down the example application files from GitHub with the following command: ```sh git clone --sparse https://github.com/chainguard-dev/edu-images-demos.git ``` Because this guide's demo application code is stored in a repository with other examples, we don't need to pull down every file from this repository. For this reason, this command includes the `--sparse` option. This initializes a [sparse-checkout](https://git-scm.com/docs/git-sparse-checkout) file, causing the working directory to contain only the files in the root of the repository until you modify the sparse-checkout configuration. Navigate into this new directory: ```sh cd edu-images-demos/ ``` To retrieve the files you need for this tutorial's sample application, run the following `git` command: ```sh git sparse-checkout set dotnet ``` This modifies the sparse-checkout configuration initialized in the previous `git clone` command so that the checkout only consists of the repo's `dotnet` directory. Navigate into the `dotnet` directory: ```sh cd dotnet/ ``` This directory holds a single .NET application along with two Dockerfiles that build it: - `notlinky.Dockerfile`: Builds the application on Microsoft's official .NET images - `linky.Dockerfile`: Builds the same application on Chainguard Containers Both Dockerfiles compile the same `Program.cs` and `dotnetapp.csproj`, so the only difference between the two images comes from the base images each Dockerfile uses. ## Understanding the demo application The demo application is a .NET console program that displays runtime and system information, including: - Operating system architecture and description - .NET framework version - Current user and hostname - Hardware information (processor count and memory) - Container cgroup memory limits and usage When it runs, the application prints an ASCII art banner reading `dotnet` followed by these runtime details. It also writes the same output to a file named `output.txt` in its working directory, so the runtime image must allow the application's user to write to that directory. This detail matters when you build with Chainguard Containers, as explained later in this guide. This sample application is based on Microsoft's [dotnet-runtimeinfo sample](https://github.com/dotnet/dotnet-docker/blob/main/samples/) and demonstrates a typical .NET console application that could be containerized for various use cases. In the following sections, we'll build and compare both versions of the application. ## Building with Microsoft's .NET images Start by building and running the application with the .NET container images provided by Microsoft to establish a baseline for comparison. Inspect `notlinky.Dockerfile` to see how it's structured: ```sh cat notlinky.Dockerfile ``` ```output FROM mcr.microsoft.com/dotnet/sdk AS build WORKDIR /source COPY dotnetapp.csproj . RUN dotnet restore COPY . . RUN dotnet publish --no-restore -o /app FROM mcr.microsoft.com/dotnet/runtime COPY --from=build --chown=app:app /app /app WORKDIR /app USER app ENTRYPOINT ["./dotnetapp"] ``` This Dockerfile uses a multi-stage build pattern: 1. **Build stage**: Uses `mcr.microsoft.com/dotnet/sdk` to restore dependencies and publish the application 2. **Runtime stage**: Uses `mcr.microsoft.com/dotnet/runtime` for the final image 3. **User configuration**: Copies the published files with `--chown=app:app` and then switches to the non-root `app` user with `USER app` Build the container image using this Dockerfile: ```sh docker build -t dotnet-example:notlinky -f notlinky.Dockerfile . ``` Using `notlinky.Dockerfile`, this `docker build` command builds an image tagged `dotnet-example:notlinky`. Use this image to run the application: ```sh docker run --rm dotnet-example:notlinky ``` ```output 42 42 ,d ,d 42 42 42 ,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42 8b 42 8b d8 42 42 42 8PP!!!!!!! 42 "8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42, `"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428 OSArchitecture: X64 OSDescription: Ubuntu 24.04.4 LTS FrameworkDescription: .NET 10.0.9 UserName: app HostName : ca426367d7a5 ProcessorCount: 16 TotalAvailableMemoryBytes: 16472748032 (15.34 GiB) ``` Running the container returns system information, including a stylized ASCII art banner followed by runtime details. ## Building with Chainguard's .NET container images Next, build the application with Chainguard's .NET container images. Inspect `linky.Dockerfile` to understand the differences: ```sh cat linky.Dockerfile ``` ```output FROM cgr.dev/chainguard/dotnet-sdk AS build WORKDIR /source COPY dotnetapp.csproj . USER root RUN dotnet restore COPY . . RUN dotnet publish --no-restore -o /app FROM cgr.dev/chainguard/dotnet-runtime COPY --from=build --chown=65532:65532 /app /app WORKDIR /app ENTRYPOINT ["./dotnetapp"] ``` Like `notlinky.Dockerfile`, this Dockerfile uses a multi-stage build. To find the differences between the two, run the following `diff` command: ```sh git diff --no-index -U1000 notlinky.Dockerfile linky.Dockerfile ``` ```diff diff --git a/notlinky.Dockerfile b/linky.Dockerfile index 3b7a6e2..8cd6f2e 100644 --- a/notlinky.Dockerfile +++ b/linky.Dockerfile @@ -1,19 +1,18 @@ -FROM mcr.microsoft.com/dotnet/sdk AS build +FROM cgr.dev/chainguard/dotnet-sdk AS build WORKDIR /source COPY dotnetapp.csproj . +USER root RUN dotnet restore COPY . . RUN dotnet publish --no-restore -o /app -FROM mcr.microsoft.com/dotnet/runtime +FROM cgr.dev/chainguard/dotnet-runtime -COPY --from=build --chown=app:app /app /app +COPY --from=build --chown=65532:65532 /app /app WORKDIR /app -USER app - ENTRYPOINT ["./dotnetapp"] ``` This `diff` output highlights the following differences: 1. **Registry and images**: The Chainguard Dockerfile uses `cgr.dev/chainguard/dotnet-sdk` and `cgr.dev/chainguard/dotnet-runtime` instead of the `mcr.microsoft.com` equivalents. 2. **User switching during restore**: The Chainguard Dockerfile switches to `USER root` before `dotnet restore`. Chainguard's .NET images run as a non-root user by default, and restoring packages requires root. 3. **File ownership**: Both Dockerfiles use `--chown` so the runtime user owns the copied files and can write the application's `output.txt`. The Microsoft image names its user `app`, while Chainguard's default non-root user is UID `65532`. The default username varies across Chainguard's container images (for example, `nonroot` or `app`), so referencing the UID is less ambiguous. 4. **No explicit runtime user**: The Microsoft Dockerfile ends with `USER app` to drop root privileges. The Chainguard runtime already runs as non-root, so the Chainguard Dockerfile omits this line. Build a container image with this Dockerfile: ```sh docker build -t dotnet-example:linky -f linky.Dockerfile . ``` Here, the `docker build` command tags the image `dotnet-example:linky`. Run the application: ```sh docker run --rm dotnet-example:linky ``` ```output 42 42 ,d ,d 42 42 42 ,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42 8b 42 8b d8 42 42 42 8PP!!!!!!! 42 "8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42, `"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428 OSArchitecture: X64 OSDescription: Wolfi FrameworkDescription: .NET 10.0.9 UserName: nonroot HostName : 7e5255990efe ProcessorCount: 16 TotalAvailableMemoryBytes: 16472748032 (15.34 GiB) ``` This output is nearly identical to what the `dotnet-example:notlinky` image returned. The differences reflect the underlying base image: the operating system is `Wolfi` rather than Ubuntu, and the application runs as `nonroot` rather than `app`. Otherwise, the two applications function identically. ## Comparing the results If you have Grype installed, you can scan both of the container images you've built for vulnerabilities. Start by scanning the `dotnet-example:notlinky` image: ```sh grype dotnet-example:notlinky ``` ```output . . . [0014] INFO found 19 vulnerability matches across 270 packages [0014] DEBUG ├── fixed: 0 [0014] DEBUG ├── ignored: 0 (due to user-provided rule) [0014] DEBUG ├── dropped: 0 (due to hard-coded correction) [0014] DEBUG └── matched: 19 [0014] DEBUG ├── unknown severity: 0 [0014] DEBUG ├── negligible: 0 [0014] DEBUG ├── low: 12 [0014] DEBUG ├── medium: 7 [0014] DEBUG ├── high: 0 [0014] DEBUG └── critical: 0 NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY coreutils 9.4-3ubuntu6.2 deb CVE-2025-5278 Low dpkg 1.22.6ubuntu6.6 deb CVE-2026-2219 Medium gpgv 2.4.4-2ubuntu17.4 deb CVE-2025-68972 Medium . . . ``` This portion of the `grype` output shows that the `dotnet-example:notlinky` container image has 19 vulnerabilities, including 12 low-severity and 7 medium-severity vulnerabilities. Next, scan the Chainguard-based image: ```sh grype dotnet-example:linky ``` ```output . . . [0002] INFO found 0 vulnerability matches across 195 packages [0002] DEBUG ├── fixed: 0 [0002] DEBUG ├── ignored: 0 (due to user-provided rule) [0002] DEBUG ├── dropped: 0 (due to hard-coded correction) [0002] DEBUG └── matched: 0 [0002] DEBUG ├── unknown severity: 0 [0002] DEBUG ├── negligible: 0 [0002] DEBUG ├── low: 0 [0002] DEBUG ├── medium: 0 [0002] DEBUG ├── high: 0 [0002] DEBUG └── critical: 0 No vulnerabilities found ``` As this output shows, `grype` didn't find any vulnerabilities in the `dotnet-example:linky` image. You can compare the sizes of the two container images with `docker`: ```sh docker images dotnet-example ``` ```output REPOSITORY TAG IMAGE ID CREATED SIZE dotnet-example linky defe30f42942 5 minutes ago 151MB dotnet-example notlinky 70e03529cea6 7 minutes ago 210MB ``` This output shows that the `dotnet-example:linky` container image is significantly smaller than the `dotnet-example:notlinky` image. > **Note**: The command outputs shown in these examples were validated at the time of this writing. Over time, the number of vulnerabilities in either image is likely to change, though you can always expect the Chainguard-based image to contain fewer vulnerabilities. ## .NET migration considerations and best practices When migrating a .NET application to use Chainguard Containers, keep the following considerations in mind: - **Registry change**: Update image references from `mcr.microsoft.com` to `cgr.dev/chainguard` (or to your organization's private repository within the Chainguard registry, as in `cgr.dev/example.com`) - **Multi-stage builds**: Both approaches use [multi-stage builds](/chainguard/containers/concepts/getting-started-distroless/#multi-stage-builds) to separate build-time and runtime dependencies - **`restore` operations**: Chainguard's security model requires switching to root for `dotnet restore` or `apk` operations: ```dockerfile USER root RUN dotnet restore ``` - **File ownership**: Chainguard runtime images run as a non-root user by default. If your application writes to its working directory, copy the published files with `--chown` so the runtime user owns them. Referencing the default non-root UID, `65532`, avoids ambiguity, since the username varies across Chainguard Containers: ```dockerfile COPY --from=build --chown=65532:65532 /app /app ``` Because the Chainguard runtime image already runs as non-root, you don't need to add a `USER` directive to drop privileges in the runtime stage. Choose the runtime image based on the type of application: - `cgr.dev/chainguard/dotnet-runtime:latest` for .NET Core applications (these are often console applications, like the one in this guide) - `cgr.dev/chainguard/aspnet-runtime:latest` for ASP.NET applications (these are typically web applications) If a build stage needs a shell or package manager, use a [development variant](/chainguard/containers/concepts/container-variants/) of the relevant Chainguard container image. ## Learn more Migrating .NET applications from Microsoft's official images to Chainguard's container images provides significant security benefits with minimal code changes. The multi-stage build pattern remains the same, with the primary differences being: 1. Updated image registry and tags 2. Switching to root during package operations in the build stage 3. Automatic non-root execution in the runtime stage These small changes result in containerized applications with few-to-zero vulnerabilities and smaller image sizes. For detailed information about Chainguard's .NET container images and additional configuration options, refer to the following resources: - The [.NET SDK](https://images.chainguard.dev/directory/image/dotnet-sdk/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-dotnet) and [.NET Runtime](https://images.chainguard.dev/directory/image/dotnet-runtime/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-dotnet) documentation pages contain full details on Chainguard's .NET images, including usage documentation, provenance, and security advisories. - Our [General migration guidance](/chainguard/containers/migration/migrating-to-chainguard-images/) is helpful for understanding migration best practices. - [Guardener](/chainguard/guardener/dockerfile-migration/) is an AI-powered agent that iteratively converts, builds, and validates your Dockerfiles for use with Chainguard Containers. --- ### Chainguard VMs overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/vms/overview.md Last Modified: October 8, 2026 Tags: Chainguard VMs, Overview Chainguard VMs offers a minimal and verifiable foundation for running ephemeral workloads in cloud and on-prem hypervisor deployments, designed to complement and extend the same secure-by-default philosophy found in [Chainguard Containers](https://edu.chainguard.dev/chainguard/containers/overview/). With a strong focus on rapid CVE remediation and a small attack surface, Chainguard's virtual-machine images are purpose-built to service the target workload and include only the packages that are essential for its operation. Built in the Chainguard Factory, Chainguard's virtual-machine images benefit from a highly automated, secure-by-design build pipeline that ensures consistent, reproducible artifacts. This streamlined process enables the delivery of VM images that are continuously updated to eliminate known vulnerabilities. ## Why Chainguard VMs Unlike traditional virtual machines, which are often burdened with legacy components, unnecessary packages, and opaque dependency chains, Chainguard's virtual-machine images are designed for minimalism, security, and operational clarity. Based on Chainguard OS, they include a kernel that closely tracks the upstream Linux stable tree, ensuring timely updates and compatibility, along with a minimal `systemd` for service management. Consistent with the principle of minimalism, only the essential systemd units required to support the VM’s intended workload are included. Every component is fully traceable, with SLSA guarantees and SBOMs generated at every step, providing end-to-end transparency and helping prevent CVEs from ever entering your environment. For platform engineers and DevOps teams, this means: * **Reduced Engineering Toil**: With no unnecessary software to maintain, you reduce noise from non-actionable CVEs and focus only on what matters. * **Improved boot and runtime security**: Minimal, hardened images reduce the chances of privilege escalation, kernel exploits, and lateral movement. * **Operational consistency**: The same secure-by-default toolchain that powers Chainguard Containers now extends to your VMs, making it easier to manage and audit infrastructure uniformly across environments. ## VMs and containers compared To understand the applicability of Chainguard VMs to your organization, it might be helpful to compare the features of Chainguard VMs to Chainguard Containers. In a nutshell, the main differences come from the fact that Chainguard's virtual-machine images boot from and run with their own hardened kernel as part of Chainguard OS, while Chainguard's container images rely on the host system's kernel. | Feature | Chainguard Container | Chainguard VM | | :---- | :------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | | Includes Kernel? | **No** – uses host’s kernel | **Yes** – ships and boots with its own hardened kernel | | Environment | Userspace only, isolated via namespaces & cgroups | Full OS, boots in VM with kernel, init, userspace | | Boot Process | Starts from container entrypoint, no bootloader/kernel | Full bootloader → kernel → init system | | Security Boundaries | Dependent on host kernel isolation | Stronger isolation via hypervisor and custom kernel controls, secure boot, SELinux policies | | Use Case Focus | Microservices, CI/CD, ephemeral workloads | Secure cloud workloads, edge VMs, kernel-level policy control, high performance | ## Chainguard VM types We currently offer 3 distinct types of virtual machine images: * **Container Host:** a versatile option to run containerized workloads, protecting how you deploy containers on underlying hosts * **Base:** general purpose VM base images that can be customized to suit your application needs. Current offerings include Chainguard Base, Java Base, and Python Base. * **Application:** pre-packaged with popular backend applications running as systemd services. We currently offer Nginx, Jenkins, and Squid Proxy Application VMs. ## Availability Chainguard VMs is currently available for the following platforms / hypervisors: * Google Cloud Platform (Compute Engine) * AWS (EC2, ECS, and EKS) * Microsoft Azure (Azure Compute) * QEMU/KVM (qcow2/raw) * VMware vSphere (VMDK) * Nutanix (qcow2/raw) Offering broad compatibility, Chainguard VMs allows for deployment in any environment, from public clouds to self-managed infrastructure. This flexibility facilitates one-click deployment across environments and helps prevent vendor lock-in. ## Compliance and SLAs Chainguard's virtual-machine images (running Chainguard OS) are intentionally designed to minimize risk, maximize transparency, and satisfy security standards such as [CIS Benchmarks](https://www.chainguard.dev/supply-chain-security-101/what-are-cis-benchmarks), [FedRAMP](https://edu.chainguard.dev/chainguard/containers/security-and-compliance/fedramp-considerations/), SOC 2, and others. * CVE remediation backed by an [industry-leading SLA](https://www.chainguard.dev/legal/cve-policy): 7 days for critical, 14 days for all others * Consistent, reproducible builds * Enterprise-grade support for multi-cloud and on-prem * Verifiable provenance for all included components ## Resources * [Chainguard VMs](https://www.chainguard.dev/vms) * [Unchained Blog: Announcing Chainguard VMs: Minimal, Zero-CVE Container Host Images](https://www.chainguard.dev/unchained/announcing-chainguard-vms-minimal-zero-cve-container-host-images) * [Unchained Blog: Expanding Chainguard VMs: Zero-CVE Application & Base Virtual Machine Images](https://www.chainguard.dev/unchained/expanding-chainguard-vms-zero-cve-application-base-virtual-machine-images-for-cloud-and-on-prem) --- ### Overview of Chainguard's package repositories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/building-and-modifying/packages/package-model.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Overview, Product Chainguard's container images are built using packages from the Wolfi and Chainguard OS Linux distributions. If you need to extend or customize an image, it can be useful to access these packages directly. Chainguard offers curated package repositories to support containerized workloads and simplify dependency management. These repositories ensure you can access trusted packages — whether building custom container images, working with Chainguard OS, or using Chainguard Containers in production. This article provides an overview of Chainguard's package model, highlighting the different Chainguard package repositories available to customers. ## Repository types All users have access to two distinct package repositories maintained by Chainguard: * Wolfi * Extra Additionally, Chainguard customers have access to a Private APK Repository specific to their organization. > **Note**: Be aware that SLAs apply to container images, not individual packages. Chainguard's container images are designed to run apk packages, a package format developed for [Alpine Linux](https://www.alpinelinux.org/). Accordingly, Chainguard's package repositories contain apk packages, and you can interact with them using the standard [`apk` commands](https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html). Chainguard maintains the packages within all of these package repositories. The following table presents a high-level overview of these package repositories: | Repository Type | Access Level | Package Scope | Authentication Required | Typical Use Case | | ----------------- | :----------------: | :----------------------: | :-----------------------: | :------------------------: | | Wolfi | Public | Wolfi ecosystem | No | Free images | | Extra | Public | Supplemental utilities | No | Additional dependencies, non-open source software | | Private | Private/Organization-specific | Packages in org-entitled container images ¹ | Yes | Customizing Chainguard Containers with [Custom Assembly](/chainguard/containers/custom-assembly/overview/) | ¹ Customers using Chainguard OS Packages have access to far more packages than this to build their own images, but the additional packages are not available to use with Custom Assembly. Read the details in [Private APK repositories](#private-apk-repositories). ## Public repositories Chainguard has two public package repositories: the Wolfi and Extra Packages repositories. These repos typically include the latest stable versions and explicitly exclude FIPS-validated packages or version streams. ### Wolfi The [Wolfi packages repository](https://github.com/wolfi-dev/os) is the public package source for [Wolfi, Chainguard's open source Linux "undistro."](https://www.chainguard.dev/supply-chain-security-101/wolfi-overview) It contains all the open source packages used in Chainguard's Free container images. As a public repository, the Wolfi APK repo doesn't require authentication. By default, Chainguard's Free container images use a generic address for this repository (`https://apk.cgr.dev/chainguard`) in their `/etc/apk/repositories` files: ```shell docker run -it --rm --entrypoint cat cgr.dev/chainguard/python:latest-dev /etc/apk/repositories ``` ```output https://apk.cgr.dev/chainguard ``` Users that aren't Chainguard customers can use this URL to add packages from the Wolfi repository. Chainguard customers can access it with a URL that's unique to their organization: ```url https://virtualapk.cgr.dev/$ORGANIZATION_ID/chainguard ``` If you need packages outside Wolfi's open source scope, or under less permissive licenses, Chainguard offers a supplemental Packages repository ### Extra packages Chainguard's Extra Packages repository is a public-facing APK repository that includes utilities and compatibility packages that aren't fully open source, but can still be redistributed by Chainguard. The repository’s primary role is to provide supplemental packages needed to support containerized applications, especially when those utilities fall outside the scope of the official base images or are under less permissive licenses than those in the Wolfi repository. The Extra Packages repository follows similar rules to the Wolfi repo, but explicitly allows packages under more restrictive licenses as long as redistribution is permitted. The Extra Packages repository isn't specified in the `/etc/apk/repositories` file of Chainguard's Free images by default. However, users that aren't Chainguard customers can access it through the generic address `https://apk.cgr.dev/extra-packages`. Like the Wolfi repository, customers can access it with a URL that's unique to their organization: ```url https://virtualapk.cgr.dev/$ORGANIZATION_ID/extra-packages ``` ### Working with public repositories As mentioned previously, both the Wolfi and Extra Packages repositories are public and don't require authentication. Chainguard customers can access both with a URL that's unique to their organization: * Wolfi: `https://virtualapk.cgr.dev/$ORGANIZATION_ID/chainguard` * Extra: `https://virtualapk.cgr.dev/$ORGANIZATION_ID/extra-packages` You must replace `$ORGANIZATION_ID` with your organization's unique identifier (UID). You can find this with `chainctl` if [you've installed it](/platform/chainctl-usage/how-to-install-chainctl/): ```shell chainctl iam orgs ls -o table ``` You must replace `$ORGANIZATION_ID` with your organization's `ID` value, not its name: ```output ID | NAME | DESCRIPTION ------------------------------------------|-----------------|-------------------------------------- 0a************************************c8 | example.com | This is an example organization ``` You can also find this in the [Chainguard Console](https://console.chainguard.dev/). After logging in, open the **Settings** tab. There, you'll find your organization's identifier under **Organization UID**. Be aware that these repository URLs **will not** resolve properly if you include the name of your organization instead of the UID. For any of your organization's container images that include the APK package manager, these repositories are included by default. You can also add them to the `/etc/apk/repositories` file of any container that uses APK. Our guide on [How to pull packages from Chainguard package repositories through Artifactory](/chainguard/containers/registry/pull-through-guides/artifactory-packages-pull-through/#configuring-pull-through-caches-for-chainguards-public-repositories) includes directions for setting up pull-through caches for these repositories on Artifactory. ### Package retention in public repositories To limit the size of public APK repositories and maintain efficient `apk` operations, Chainguard periodically removes older package versions based on a package retention policy. The public Wolfi and Extra repositories currently retain non-latest package versions for **12 months**. Over time, this retention window may be reduced to **3 months**; any change will be announced in advance. Retention rules for public repositories: * Non-latest package versions older than 12 months are removed unless: * They are required by other Wolfi packages. * They are required by Chainguard Container images. * Additionally, Chainguard removes latest package versions if the corresponding package definition has been removed from the Wolfi source repository (`https://github.com/wolfi-dev/os`), unless they meet one of these exceptions. If you pin specific package versions, ensure those versions remain within the retention window or mirror them internally. Chainguard removes packages on a monthly basis, on the second Wednesday of each month. Candidates for removal are announced one month in advance. Public repositories affected: * Wolfi (`https://apk.cgr.dev/chainguard`) * Extra (`https://apk.cgr.dev/extra-packages`) ## Private APK repositories Chainguard customers have access to a private APK repository that is only accessible to members of their organization. An organization's private APK repository contains packages not included in either of the public repos, such as the main packages found in [Production containers](/chainguard/containers/concepts/container-categories/#production-containers), as well as any packages relating to FIPS support. That said, it's likely an organization's private APK repository also includes many of the same packages found in the Wolf or Extra Packages repositories. The list of packages available in a given organization’s private repository is based on the packages available in the Chainguard Containers that the organization already has access to. For example, say your organization has access to the Chainguard MySQL container image. Along with the container's main package (`mysql`), this image comes with other apk packages, including `bash`, `glibc`, and `pwgen`. This means that you’ll have access to these apk packages through your organization’s private APK repository, along with any that appear in other Chainguard container images that your organization has access to. Chainguard's private APK repositories have URLs that follow this form: ```url https://apk.cgr.dev/$ORGANIZATION_ID ``` Because Chainguard's private APK repos are only accessible to members of a specific organization, you must authenticate in order to access them. Refer to our overview of [Chainguard's private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) for more information. Additionally, note that if you customize a Chainguard Container using the [Custom Assembly tool](/chainguard/containers/custom-assembly/overview/), the list of packages available for you to add to your container image is taken from your organization's private APK repository. There is one exception: Chainguard OS Packages is an offering for larger customers who already build their own images from packages using tools like Bazel, Dockerfiles, and rules\_apko and want to use a wider set of packages from Chainguard. This includes over 400,000 packages that will be made available in a private APK repository. You are responsible for the image builds, the build tooling, validation, and compatibility while Chainguard builds the packages in the Chainguard Factory with complete SBOMs and our standard enterprise-grade, zero-CVE process. This offering is limited to those who want to use Chainguard-sourced packages in their existing, mature image building processes. Chainguard OS Packages are not currently available for use with Chainguard Custom Assembly. However, they are made available to you in the same private APK repository location as is described in this page. The only difference is the number of packages that are available. So, any existing build automation you are using will continue to work. ### Package retention in private repositories Private APK repositories for Chainguard customers follow a **12-month** retention period for non-latest package versions. They follow the same retention rules and removal schedule as the public Wolfi repository. Customers that require older package versions should mirror or store copies internally. ## Package support Chainguard commits to build packages in the Chainguard Factory with complete SBOMs and our standard enterprise-grade, zero-CVE process. Packages that are used in Chainguard Container images are also covered by our [CVE remediation SLA](https://www.chainguard.dev/legal/cve-policy?utm_source=docs). That commitment continues to apply to these packages when they are included in private APK repositories, but not to images you build yourself using those packages. We cannot extend our SLA to the images because we do not have control of your build. Similar to the [Chainguard shared responsibility model](https://edu.chainguard.dev/chainguard/containers/concepts/shared-responsibility-model/) for container images, you are responsible for your authored image builds, the build tooling, validation, and compatibility, but now you can do all this using packages that are scanned daily for CVEs. We recommend you always update your builds to new package versions as we release them so are still benefiting from the SLA even if your custom-built images are not covered by it. ## Learn more Chainguard’s package repositories provide trusted apk packages to support both standard and advanced containerized workloads. With the public Wolfi and Extra repositories alongside organization-specific private repositories, customers can reliably source the dependencies they need for production environments. We encourage you to learn more about Chainguard's private APK repositories by referring to our [documentation on the subject](/chainguard/containers/building-and-modifying/packages/private-apk-repos/). If you're ready to add a package to one of your container images, [Adding a package to a Chainguard Container](/chainguard/containers/building-and-modifying/adding-packages/) compares the available approaches and walks through the task. --- ### Kubernetes policy enforcement with Kyverno URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/enforcement/kyverno.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Overview, Policy [Kyverno](https://kyverno.io/) is an admission controller that enforces policies in Kubernetes clusters. This article describes how it can be leveraged to ensure resources follow best practices related to the use of Chainguard Containers. ## Prerequisites To follow the examples in this guide, you will need the following: - `kubectl` — the command line interface tool for Kubernetes — installed on your local machine. - Administrative access to a Kubernetes cluster where [Kyverno is already installed](https://kyverno.io/docs/installation/). ## Ensure images are pulled from allowed repositories You can use the [`ClusterPolicy` policy type](https://kyverno.io/docs/policy-types/cluster-policy/overview/) to ensure that images are only pulled from a list of allowed repositories. Create file named `restrict-image-registries.yaml` with the following content: ```yaml apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: restrict-image-registries spec: rules: - name: validate-registries match: any: - resources: kinds: - Pod validate: failureAction: Enforce message: "Invalid image registry. Images must be hosted in cgr.dev." pattern: spec: =(ephemeralContainers): - image: "cgr.dev/*" =(initContainers): - image: "cgr.dev/*" containers: - image: "cgr.dev/*" ``` This defines a policy that only allows images hosted in `cgr.dev`. Note that if you've set up a different registry to function as [a pull-through cache for your Chainguard container images](/chainguard/containers/registry/pull-through-guides/), you could replace this with your own registry. If you need to support more than one registry, you can define multiple values with a pattern like `cgr.dev/* | your.internal.repo/*`. Apply the policy with `kubectl`: ```shell kubectl apply -f restrict-image-registries.yaml ``` To test that the policy works correctly, try to create a non-compliant pod: ```shell kubectl create -f - <` and `` with the appropriate values: ```yaml apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: verify-image-signatures spec: webhookConfiguration: timeoutSeconds: 30 rules: - name: verify-image-signatures match: any: - resources: kinds: - Pod verifyImages: - imageReferences: - "cgr.dev/*" failureAction: Enforce attestors: - entries: - keyless: subjectRegExp: "^https://issuer.enforce.dev/(|)$" issuer: "https://issuer.enforce.dev" rekor: url: https://rekor.sigstore.dev ``` Apply the policy to the cluster: ```shell kubectl apply -f verify-image-signatures.yaml ``` To test the policy, try creating a pod using the public Chainguard `nginx` container image: ```shell kubectl create -f - < src/main/java/com/example/demo/DemoApplication.java < pom.xml < 4.0.0 com.example jib-demo 1.0.0 jar 21 21 UTF-8 yyyyMMddHHmmss org.springframework.boot spring-boot-starter-web 3.5.6 org.springframework.boot spring-boot-maven-plugin 3.5.6 repackage EOF ``` To run the build, run the following command: ```sh mvn clean install ``` Observe the output indicating that the build was successful: ``` ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.733 s [INFO] Finished at: 2025-09-25T11:40:03+01:00 [INFO] --- ``` Now you can run the application with the following command: ```sh java -jar target/jib-demo-1.0.0.jar ``` The application exposes a web server on port `8080`. You can test the application with a `curl` request from another terminal window: ```sh curl localhost:8080 ``` You should get output like the following: ``` Hello from Jib and Chainguard! ``` After validating that the application builds successfully and works as expected, you can stop the server by pressing `Ctrl+C` in the terminal where it's running. ## Stage 2 — Configuring Jib with Chainguard Containers The next step is to include and configure Jib as a plugin in the `pom.xml` file. Locate the `` section of your `pom.xml` file. Add the Jib plugin definition like this: ```xml com.google.cloud.tools jib-maven-plugin 3.4.6 package dockerBuild cgr.dev/chainguard/jre:latest linux amd64 linux arm64 linky 8080 USE_CURRENT_TIMESTAMP ``` The configuration specifies several settings: - **Build goal**: The execution goal is defined as `dockerBuild`. This will build a container image for the application and then load the built image into the local Docker instance, allowing you to immediately run and test the container. - **Base (or "from") image**: Uses Chainguard's JRE image (`cgr.dev/chainguard/jre:latest`). This example explicitly specifies both `arm64` and `amd64` architectures. If you don't do this, Jib will build an `amd64` image only, regardless of host architecture. - **Target image**: Name of the image to build (`linky`). The tag will default to `latest` if not specified. - **Port**: Documents that port `8080` is used for the Spring Boot application - **Creation time**: Uses the current timestamp ## Stage 3 — Building container images with Jib With Jib configured, you can now build your containerized Java application. Because we have specified `dockerBuild` as the execution goal, this step will require a local Docker install. If you want to avoid using Docker and push the image straight to a registry, jump to Stage 5. Run: ```sh mvn clean install ``` You’ll get output like the following: ``` ... [WARNING] Detected multi-platform configuration, only building image that matches the local Docker Engine's os and architecture (linux/arm64) or the first platform specified [INFO] [INFO] [INFO] Container entrypoint set to [java, -cp, @/app/jib-classpath-file, com.example.demo.DemoApplication] [INFO] Container entrypoint set to [java, -cp, @/app/jib-classpath-file, com.example.demo.DemoApplication] [INFO] [INFO] Built image to Docker daemon as linky ... ``` This command has compiled the application, built the container, and loaded it into the local Docker instance. To verify that the image was built: ```sh docker images linky ``` Which will provide details on the new image: ``` REPOSITORY TAG IMAGE ID CREATED SIZE linky latest 43171844f68e 3 minutes ago 466MB ``` You should also scan the image for CVEs. This example uses [grype](https://github.com/anchore/grype), but you can use your preferred scanner: ```sh grype linky ``` This should give you output similar to the following: ``` ✔ Vulnerability DB [updated] ✔ Loaded image linky:latest ✔ Parsed image sha256:bf2e8781db85416a96815753aa5dce19f9d8 ✔ Cataloged contents a25dc3225e1f655da4f3f160acf6004973061ae0a25 ├── ✔ Packages [72 packages] ├── ✔ Executables [121 executables] ├── ✔ File metadata [1,335 locations] └── ✔ File digests [1,335 files] ✔ Scanned for vulnerabilities [0 vulnerability matches] ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible No vulnerabilities found ``` In this case, there were no CVEs found. Try comparing those results to images built with different base images. ## Stage 4 — Testing the containerized application After building your container image, test it to ensure it works correctly. Start by running the container locally: ```sh docker run -p 8080:8080 linky ``` The application will start, and you'll see Spring Boot's startup logs. In a separate terminal, test the endpoints: ```sh curl http://localhost:8080/ ``` This should return: ``` Hello from Jib and Chainguard! ``` Test the health endpoint: ```sh curl http://localhost:8080/health ``` Expected output: ``` Application is healthy ``` Stop the container by pressing `Ctrl+C` in the terminal where it's running. ## Stage 5 — Building and pushing to a remote registry One of the strongest points of jib is that you don't need Docker installed to build and distribute container images. In this step we will change `pom.xml` to push the image directly to a container registry. This example uses `ttl.sh`, which is a free-to-use Docker registry for short-lived hosting of images. You can also change the code to use your own registry such as a local one or the Docker Hub. Replace the previous plugin definition with this one (or add this one if you skipped Stage 3): ```xml com.google.cloud.tools jib-maven-plugin 3.4.6 package build cgr.dev/chainguard/jre:latest linux amd64 linux arm64 ttl.sh/jib-demo-${maven.build.timestamp}:20m 8080 USE_CURRENT_TIMESTAMP ``` This contains a couple of changes: - The execution goal is now `build`. This will push to the registry rather than the local Docker instance. - The image name now refers to the `ttl.sh` registry and includes a timestamp for uniqueness. Run the build again: ```sh mvn clean install ``` You’ll get output like this: ``` ... [INFO] Using base image with digest: sha256:60a6d264537fbad2f5d37a01cf6222f2f172415f686bf59afe3b1043f79323f0 [INFO] [INFO] [INFO] Container entrypoint set to [java, -cp, @/app/jib-classpath-file, com.example.demo.DemoApplication] [INFO] Container entrypoint set to [java, -cp, @/app/jib-classpath-file, com.example.demo.DemoApplication] [INFO] [INFO] Built and pushed image as ttl.sh/jib-demo-20250925104449:20m ... ``` The image name is parameterised with a timestamp, so yours will look different. The image won't be in your local Docker instance, but you can pull it as normal (remember to copy your unique image name from the output): ```sh docker pull ttl.sh/jib-demo-20250925104449:20m ``` And run it as before: ```sh docker run -p 8080:8080 ttl.sh/jib-demo-20250925104449:20m ``` The `ttl.sh` registry is only for temporary testing of images and your image will be deleted in 20 minutes. This time the built image is a multi-platform image -- if you pull the image from an `amd64` host you will get the `amd64` version and if you pull from an `arm64` host you will get the `arm64` version. You can also force the platform when pulling: ```sh docker pull --platform amd64 ttl.sh/jib-demo-20250925104449:20m ``` ## Other features ### Building a tar archive If you want a local copy of the image as a file that can be later loaded by a container runtime or saved to a file server, you can instead build the image as a tar archive. This doesn't work for multiplatform images unfortunately, so to test you will need to remove one of the platforms in the from definition. (Or alternatively, pull the architecture setting into a parameter.) The following plugin definition will build a tarball for the `amd64` platform: ```xml com.google.cloud.tools jib-maven-plugin 3.4.6 package buildTar cgr.dev/chainguard/jre:latest linux amd64 linky 8080 USE_CURRENT_TIMESTAMP ``` After replacing the plugin definition, run: ```sh mvn clean install ``` And the output should give you the location of the built tarball: ``` ... [INFO] [INFO] Built image tarball at /Users/amouat/proj/jib-demo/target/jib-image.tar ... ``` You can directly load this tarball into Docker: ```sh docker load < target/jib-image.tar ``` This should respond with the name of the built image: ``` Loaded image: linky:latest ``` If required, the image can then be retagged and pushed to a registry. ### Calling goals directly In these examples, you've been editing the pom.xml to configure the execution goal. You can also directly call these from Maven, for example: ```sh mvn install jib:dockerBuild ``` For full details of Jib plugin configuration and features, refer to the [guide on GitHub](https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin). ## Next steps You've now successfully containerized a Java application using Jib and Chainguard's minimal container images. You have seen how Jib and Chainguard combine to provide a streamlined path to building secure, minimal container images for Java applications. To continue learning about container security and optimization for the Java ecosystem, consider exploring: - [Chainguard Libraries for Java](https://edu.chainguard.dev/chainguard/libraries/java/overview/): provides enhanced security for the Java ecosystem by rebuilding popular Maven dependencies with the latest patches and comprehensive supply chain protection. - [How to Migrate a Java Application to Chainguard Images](https://edu.chainguard.dev/chainguard/containers/migration/migration-guides/java/): in this video, learn how to migrate Java applications to Chainguard Containers for reduced vulnerabilities, smaller images, and comprehensive JDK/JRE support with daily security updates. By combining Jib's build optimization with Chainguard's security-focused images, you've established a foundation for building secure, efficient container images as part of your Java development workflow. --- ### How to use Chainguard Helm charts URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Helm charts, Product [Helm](https://helm.sh) is a package manager for Kubernetes that simplifies the installation and management of applications by automating the creation of Kubernetes resources. Helm charts are reusable, versioned packages that define a collection of Kubernetes resources required to run an application or service. You use Helm to define, install, and perform upgrades to your applications on Kubernetes. For organizations looking to deploy their Chainguard container images with Helm, Chainguard provides upstream-produced Helm charts. These charts are available from the Chainguard Registry and are intended for customers who are either looking to get started with Helm or are looking for better, trusted alternatives to the public charts they may already be using. > Chainguard also offers a limited set of Helm charts to go with a set of Chainguard-created containers labeled as iamguarded, designed specifically to support organizations migrating off of Bitnami. Learn more about these in [How to use Chainguard iamguarded Helm charts](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-iamguarded-helm-charts/). The community charts have been tested by Chainguard to confirm they produce expected deployment results using the following policies: - Version streaming: Chainguard commits to supporting chart and image versions that match the latest upstream project chart. Within that latest chart we will support the associated image versions. - Testing policy: We test the latest charts with the supported version streams and functionally validate by deploying the Helm chart in its representative environment and exercising the various functionality of the chart(s). We’ll also continue publishing end-of-life (EOL) version streams as long as they continue to pass our functional validation. Chainguard makes the provenance of these charts clear. Helm charts are packaged as [OCI artifacts](https://www.chainguard.dev/supply-chain-security-101/what-are-oci-artifacts) using the upstream version adding an appended revision suffix for updates that include material changes to the chart; otherwise, tags will float based as their dependent images update. The OCI artifacts are signed and generate provenance attestations that link to the exact image digests used to ensure that all artifacts are cryptographically verifiable end-to-end for integrity and origin. You can find Helm charts in the [Chainguard Console](/platform/console/images-directory/#find-helm-charts-in-the-chainguard-console) and in the [Chainguard Directory](/chainguard/containers/registry/chainguard-directory/#find-helm-charts-in-the-chainguard-directory). Before you can use a chart, it must be added to your organization. Catalog customers can add charts and their required images from the Chainguard Console. For instructions, see [Self-serve Helm charts](/get-started/self-serve/helm-charts/). The following is an instructional guide for Chainguard users that are looking for Helm charts to use with their Chainguard container images. You can use these Helm charts with Chainguard FIPS container images by choosing a FIPS chart version during install. For example: ``` helm install --version 3-fips ... ``` ## How chart updates deliver image updates Chainguard rebuilds container images regularly to pick up the latest package versions and CVE fixes. Chart builds run in the same pipeline: whenever Chainguard builds a new image, it automatically kicks off a build for the corresponding Helm chart, which follows shortly after. This keeps your images and charts in sync. A chart tag always refers to the chart version, not to a fixed set of images. Chainguard rolls the underlying image digests forward within a tag, so even a specific tag like `10.5.13-r1` keeps pointing at the latest images for that chart version. As with image tags, you can pick a tag fidelity that matches your upgrade appetite — `latest`, a version stream, or a specific `x.y.y-r#` version — but every chart tag rolls its dependent image digests forward. Helm will not apply these updates on its own. A release picks up newer images only when something triggers a redeploy: a `helm upgrade`, or a controller such as Argo CD or Flux reconciling the release. A chart referenced by tag then pulls the current chart digest along with its newer image digests. Running a `helm upgrade` on the same tag will resolve to its newest digest the tag pins to. To pin both the chart and its images to a fixed, reproducible set, reference the chart by digest instead of by tag. Your deployment then stays fixed to the exact images the chart referenced when you recorded that digest. To pick up newer images, update your pinned digest to the current one. The [Pin to digest](#pin-to-digest) section covers how to do this. ## Authentication You will need to authenticate to pull charts. These instructions explain how to use charts and images with the `cgr.dev` repository. If you have mirrored or copied the charts and images to an organization-specific registry, you will need to adapt these instructions to authenticate to your registry, as appropriate. This section presents multiple authentication methods: - Use Helm values with `global.imagePullSecrets` - Deploy a Chainguard Helm chart using a Kubernetes pull secret - Use cluster node-scoped registry permissions ### Use Helm values with `global.imagePullSecrets` When performing authentication via a `global.imagePullSecrets` key-value pair, include the following in your `values.yaml` file. ```yaml global: imagePullSecrets: - name: chainguard-pull-secret ``` ### Deploy a Chainguard Helm chart using a Kubernetes pull secret To begin, generate a pull token. ```sh chainctl auth configure-docker --pull-token --save --ttl=24h ``` You don't need to run `chainctl auth login` first; `configure-docker` authenticates you when no valid token is available. This token expires in 24 hours by default, which can be modified using the `--ttl` flag. It sets the duration for the validity of the token. The maximum valid value is `8760h` (equivalent to 365 days), Valid unit strings range from nanoseconds to hours and are `ns`, `us`, `ms`, `s`, `m`, and `h`, for example `--ttl=24h`. You will be asked which location the pull token will be associated with, select the **charts** org under your main org. It will look similar to this example. ```shell With which location is the pull token associated? [chainguard.edu] chainguard.edu This group holds the private Chainguard Images hosted under cgr.dev/chainguard.edu > ├ [charts] └ [iamguarded-charts] ``` Find the username and password that are contained in the pull token, as in this sample output: ```sh chainctl auth configure-docker --pull-token --save --ttl=24h ✔ Selected folder chainguard.edu. To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" Configuring identity "" for pulls from cgr.dev (expires 2025-06-12T09:27:45-05:00). Overwriting existing credentials. ``` Save the credentials as variables, like this. ```sh HELMUSER="" HELMPASS="" ``` Create your Kubernetes secret using the variables you just created. ```sh kubectl create secret docker-registry chainguard-pull-secret \ --docker-server=cgr.dev \ --docker-username=$(echo $HELMUSER) \ --docker-password=$(echo $HELMPASS) \ -n ``` Log in to the `cgr.dev` Helm registry. The secret you just created covers the image pulls your pods make, but it has no effect on the Helm client, which authenticates on its own to fetch the chart. ```sh helm registry login cgr.dev \ --username=$HELMUSER \ --password=$HELMPASS ``` Reference the secret in your Helm installation: ```sh helm install grafana oci://cgr.dev/$ORGANIZATION/charts/grafana \ --set "global.imagePullSecrets[0].name=chainguard-pull-secret" ``` When the install is successful, it returns a confirmation message, like this: ```sh Pulled: cgr.dev/chainguard.edu/charts/grafana:10.5.13 Digest: sha256:2629f907b15f26c706b5668b5700340b851176a10f55cf709f89a3701f2b4220 NAME: grafana LAST DEPLOYED: Thu Jan 29 08:19:23 2026 NAMESPACE: default STATUS: deployed REVISION: 1 NOTES: 1. Get your 'admin' user password by running: kubectl get secret --namespace default grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo 2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster: grafana.default.svc.cluster.local Get the Grafana URL to visit by running these commands in the same shell: export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}") kubectl --namespace default port-forward $POD_NAME 3000 3. Login with the password from step 1 and the username: admin ################################################################################# ###### WARNING: Persistence is disabled!!! You will lose your data when ##### ###### the Grafana pod is terminated. ##### ################################################################################# ``` ## Best practices with cluster node-scoped registry permissions If you manage access and permissions at cluster-wide and node-specific levels, these are some best practices to consider. ### Pin to digest It is strongly recommended that you deploy Chainguard-provided charts by pinning to digest. Using tags can be problematic because tags are mutable and the images described by the chart will change regularly when the images themselves are updated. Pinning the chart by digest ensures you are running a consistent set of images and removes the possibility of unexpected breaking changes. Pin to digests like this: 1. Get the digest. ```sh crane digest cgr.dev/$ORGANIZATION/charts/grafana:10.5.13 ``` Which returns a digest like this: ```response sha256:38850bacab587e4cf1177d0fe5b8bd62bad27d3f04f5a1c65ddcd86ea9748a73 ``` 1. Use the digest to install your chart, replacing `sha256:DIGEST` with the response you just received. ```sh helm install grafana \ oci://cgr.dev/$ORGANIZATION/charts/grafana@sha256:$DIGEST ``` If you must, use tags like this: ```sh helm install grafana oci://cgr.dev/$ORGANIZATION/charts/grafana --version 10.5.13 ``` ### Review Helm chart default values The chart provides security-minded defaults that are sensible but may not suit all use cases. Review the chart's `values.yaml` for the full range of configuration options and adjust as needed. #### Override the version of an image being deployed with the chart There may be times when you need to override the version of an image that is set to be deployed with a chart. The override values vary between charts. Refer to the documentation for charts in the Chainguard Console in the **Helm charts** page, accessible from the sidebar. Find your chart in the list and select it, then click through the tabs across the top of the page to learn more about the chart. For example, you can find the default values for the Grafana Helm chart in [its Default Values tab](https://console.chainguard.dev/org/$ORGANIZATION$/helm/organization/community-chart/grafana/defaultValues). #### Example of overriding tag and digest When you override values, you need to stay aware of how the original value was being used and think through the implications of your change. For example, because Chainguard specifies digests in our chart values, you need to override that digest along with the tag. Use: ```sh helm install --set image.digest= ``` or ```sh helm install --set image.tag= --set image.digest="" ``` **Do not** use: ```sh helm install --set image.tag= ``` Getting this wrong could cause you to unknowingly run the version specified by the digest instead of the version you intend. ## Helm chart usage examples ### Install with details in a file You can put values in a file, such as our `values.yaml` sample, and then refer to the file in your install command by passing the detail in a flag. ```sh image: registry: cgr.dev repository: $ORGANIZATION/grafana # replace $ORGANIZATION tag: latest # pin to specific version instead of latest ``` Then you refer to the file like this: ```sh helm install grafana oci://cgr.dev/$ORGANIZATION/charts/grafana \ --values ./values.yaml ``` ### Install on AWS Elastic Kubernetes Service (EKS) auto mode When installing on EKS Auto Mode, you may need to create a storage class for the Helm chart's pod(s). This can be done by creating a storage class: ```sh cat < PACKAGE-provenance.sigstore.json ``` **Verify the attestation was signed by Chainguard** ``` cosign verify-blob-attestation \ --bundle PACKAGE-provenance.sigstore.json \ --type slsaprovenance1 \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="^https://issuer.enforce.dev/" \ --check-claims=false \ PACKAGE-VERSION.tgz ``` If this command returns an error, ensure you are using the latest version of `cosign`. A successful verification returns: ``` Verified OK ``` The `--certificate-oidc-issuer` and `--certificate-identity-regexp` flags confirm the attestation was signed by Chainguard. ### Retrieve SBOMs To save the available SPDX SBOM and verified SLSA provenance bundle during verification, use `chainctl libraries verify --output-attestations` with `--output-dir`. Refer to [Download JavaScript SBOMs and attestations](/chainguard/libraries/policies-and-security/verification/#download-javascript-sboms-and-attestations) for examples, output paths, and the trust status of each file. Chainguard Libraries for JavaScript also include Software Bills of Materials (SBOMs) in SPDX format. To check whether an SBOM is available for a package, use npm show with the dist.sboms field: ```bash npm show PACKAGE@VERSION dist.sboms ``` To retrieve the SBOM directly: ```bash curl -H "Authorization: Bearer $(chainctl auth token --audience=libraries.cgr.dev)" https://libraries.cgr.dev/javascript/-/npm/v1/sbom/spdx/PACKAGE@VERSION ``` Replace `PACKAGE` and `VERSION` with the package name and version (for example, `react-router` and `7.11.0`). ## Upstream fallback policy and controls Chainguard Libraries for JavaScript supports an optional built-in fallback to the upstream npm Registry, managed through the [Chainguard Repository](/chainguard/chainguard-repository/overview/). By default, the endpoint serves only Chainguard-built packages. When the upstream fallback is enabled, upstream packages are subject to additional security controls before being served. Learn about managing fallback and cooldown controls in the [Libraries overview](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). > Note: Upstream fallback has been tested against pnpm v11. When using pnpm, we recommend pnpm v11 or newer. --- ### Chainguard Libraries policies and security overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/policies-and-security/overview.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Policy, Overview Learn how to verify Chainguard Library integrity, identify and remediate vulnerabilities, protect against malicious behavior, and apply configurable policies across Chainguard-built packages and protected upstream fallback packages. * [Chainguard Library policies](/chainguard/chainguard-repository/library-policies/): Create and enforce rules that control which package versions your organization can pull, including cooldown settings, blocklists, allowlists, and deliberate exceptions. * [Verification](/chainguard/libraries/policies-and-security/verification/): Use `chainctl libraries verify` to confirm which dependencies were built by Chainguard and review their signed provenance and software bills of materials (SBOMs). * [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/): Learn how Chainguard backports selected high- and critical-severity fixes to supported library versions and how to identify and use remediated releases. * [Vulnerability scanners](/chainguard/libraries/policies-and-security/scanners/): Learn how common vulnerability scanners work with Chainguard Libraries and how to interpret findings for Chainguard-built and remediated dependencies. * [Container builds](/chainguard/libraries/policies-and-security/build-containers/): Authenticate to Chainguard Libraries during a container build using BuildKit secret mounts, without baking credentials into the final image. --- ### Chainguard Libraries for Python overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/python/overview.md Last Modified: October 9, 2026 Tags: Chainguard Libraries, Python, Overview Chainguard Libraries for Python provides enhanced security for the vast Python ecosystem by rebuilding PyPI packages with comprehensive supply chain protection and automated patching. With over 600,000 packages on the [Python Package Index (PyPI)](https://pypi.org/) serving application development, machine learning, and data science needs, Chainguard addresses the critical security challenges of depending on packages from untrusted sources by rebuilding them within the controlled Chainguard Factory environment. In addition, Chainguard eliminates security risk by remediating High and Critical vulnerabilities across older package versions where upstream maintainers are not able to prioritize fixes. Chainguard Libraries for Python enables access to a growing collection of Python packages rebuilt from source. New releases of common libraries or artifacts requested by customers are added to the index by an automated system. Any request for a library or library version missing in Chainguard Libraries automatically triggers a process to provision the artifacts from relevant sources if available. In combination with third-party software repository managers, you can use Chainguard Libraries for Python as a secure source of truth for your development process. ## Technical details Most organizations consume Chainguard Libraries for Python through a repository manager such as Cloudsmith, JFrog Artifactory, or Sonatype Nexus Repository. For full details, refer to our [Global Configuration documentation](/chainguard/libraries/python/global-configuration/). The rest of this article provides details of the underlying implementation of Chainguard Libraries for Python and how to access individual libraries manually. The Chainguard Libraries for Python indexes use the PyPI repository format and only include release artifacts of the libraries built by Chainguard from source. URLs for the repositories: ``` https://libraries.cgr.dev/python/ https://libraries.cgr.dev/python-remediated/ https://libraries.cgr.dev/cu[**version**]/simple/ ``` The first index provides all Python libraries that Chainguard builds from source, without remediated versions. The second index provides remediated libraries with high and critical CVE fixes applied to older versions. The separate indexes provide you the option to make remediated versions available for your development or to opt out of using these versions completely and continue to use non-remediated versions only. The third index refers to CUDA version-specific indexes which include libraries with GPU-accelerated Python wheels. You can find more details in the [CUDA Enabled Libraries](#cuda-enabled-libraries) section. The Chainguard Libraries for Python repository does not include all packages from PyPI. Chainguard Libraries for Python are rebuilt from source and require that source be available. Therefore, packages that do not provide a valid source URL cannot be rebuilt within the Chainguard Factory. Chainguard Libraries for Python can be consumed through [Chainguard Repository](/chainguard/libraries/javascript/overview/), which provides a single endpoint for Python package retrieval and supports protected upstream fallback when configured for your organization. This allows builds to prefer Chainguard-built packages first while still covering packages or wheel files that Chainguard does not currently serve directly. Configure this endpoint [globally through a repository manager](/chainguard/libraries/python/global-configuration/) for centralized access control across your organization, or use it [directly from individual build tools](/chainguard/libraries/python/build-configuration/). > Note: The Chainguard Repository endpoint does not include remediated packages > with `+cgr` version suffixes. To use those versions, you must also configure the separate > `python-remediated` index. Follow the steps detailed in [Manual access](#manual) to browse the Python index and find available packages, package versions, source distribution (sdist), and Python wheel files for standard and remediated package version. ## CVE remediation Frontier AI models are uncovering vulnerabilities in open source software before traditional security tools can detect them. Chainguard Libraries helps reduce that exposure by delivering remediated artifacts through the [Athena](https://www.chainguard.dev/athena) coalition. Chainguard validates, patches, rebuilds, and tests affected packages, then makes the fixed artifacts available to Athena members through Chainguard Libraries. After public disclosure, Chainguard makes the fixes available to Chainguard customers through supported Chainguard Libraries ecosystems. Customers can continue using supported package versions while receiving verifiable provenance and SBOM metadata for the remediation. Chainguard Libraries for Python includes the [CVE Remediation](/chainguard/libraries/policies-and-security/cve-remediation/) feature. Remediated libraries include an appended local version identifier of `+cgr.N`. Python package management tools interpret the `+cgr.N` suffix as a local version that takes precedence over versions without the version suffix during dependency resolution. For example, the `flask` library has a fix for CVE-2023-30861 available in the upstream codebase. Upon customer request for a specific version, the fix is backported to the flask versions `1.1.2` and `2.0.0` and made available in new versions `1.1.2+cgr.1` and `2.0.0+cgr.1`. Python package management tools consider these local versions, such as `1.1.2+cgr.1` and `2.0.0+cgr.1`, as newer, compatible replacements automatically. In some cases, multiple CVEs may be remediated in a specific library version. For example, `aiohttp` has fixes for both CVE-2024-23334 and CVE-2024-30251 in the version `3.9.1+cgr.2`. ## CUDA-enabled libraries Chainguard Libraries for Python includes libraries optimized for specific CUDA versions. These libraries are GPU‑accelerated Python wheels that are built and tested against specific NVIDIA CUDA versions. An example is `torch` built for CUDA 12.8, available via the `https://libraries.cgr.dev/cu128/simple/` index. These libraries are published to CUDA‑specific indexes to ensure your Python build tools resolve the correct wheel versions, mirroring how other CUDA-optimized ecosystems are commonly distributed. Chainguard Libraries for Python includes the following repositories for GPU‑accelerated libraries, built for specific CUDA versions: ``` https://libraries.cgr.dev/cu126/simple/ https://libraries.cgr.dev/cu128/simple/ https://libraries.cgr.dev/cu129/simple/ ``` Many Python package managers and repository managers support configuring multiple package indexes. You can generally treat Chainguard’s CUDA indexes the same way you use other CUDA-specific indexes (for example, the PyTorch CUDA wheels at https://download.pytorch.org/whl/cu128). If you use a repository manager as described in the [Global Configuration documentation](/chainguard/libraries/python/global-configuration/), configure a remote repository that points at the appropriate Chainguard CUDA index, and use it in place of any CUDA-specific index you previously proxied. If you choose to pull directly without an artifact manager, you can similarly replace the CUDA index you previously used with the corresponding Chainguard CUDA index, with your existing package management tools like `pip`, `uv`, or `poetry`. Note that the CUDA-enabled libraries in these repositories are not dependency complete, since they do not include packages from the NVIDIA CUDA toolkit. You must configure your package manager to pull NVIDIA components from an upstream source such as NVIDIA's index at https://pypi.nvidia.com, or from PyPI where NVIDIA publishes them directly. ## Python version support Python 3.9 reached upstream EOL on October 31, 2025. Chainguard Libraries' extended support window for Python 3.9 ends on May 15, 2026. After this date, no new Python 3.9 packages will be built, and existing Python 3.9 packages will no longer receive security updates. To continue receiving up-to-date, secure packages from Chainguard Libraries, we strongly recommend migrating to a supported Python version. Currently supported versions are Python 3.10 and later. Learn about Chainguard's library version support in the [Libraries overview](/chainguard/libraries/introduction/overview/#library-version-support). ## Runtime and build requirements The runtime requirements for Python artifacts available from Chainguard Libraries for Python are identical to the requirements of the original upstream project. For example, if a Python wheel retrieved from PyPI requires Python 3.10 or higher, the same Python 3.10 runtime requirement applies to the binary artifact from Chainguard Libraries for Python. Chainguard distributes all packages as Python wheels, following the [Python binary distribution format](https://packaging.python.org/en/latest/specifications/binary-distribution-format/) and are typically accompanied by a source distribution tarball. For example, for the package `flask` version `3.1.2` the files are the `flask-3.1.2-py3-none-any.whl` wheel and the `flask-3.1.2.tar.gz` source distribution tarball. Note that the wheel filename includes the indication `none-any` that signals the platform independence of the wheel. Some Python libraries include Python extensions that depend on native binaries supplied by the operating system or included in the distribution archive. The following list displays all available files for the `pyyaml` package version `6.0.3`. This list includes numerous wheel files for different environments and the source distribution tarball: ``` pyyaml-6.0.3-cp310-cp310-manylinux_2_28_aarch64.whl pyyaml-6.0.3-cp310-cp310-manylinux_2_28_x86_64.whl pyyaml-6.0.3-cp310-cp310-manylinux_2_39_aarch64.whl pyyaml-6.0.3-cp310-cp310-manylinux_2_39_x86_64.whl pyyaml-6.0.3-cp311-cp311-manylinux_2_28_aarch64.whl pyyaml-6.0.3-cp311-cp311-manylinux_2_28_x86_64.whl pyyaml-6.0.3-cp311-cp311-manylinux_2_39_aarch64.whl pyyaml-6.0.3-cp311-cp311-manylinux_2_39_x86_64.whl pyyaml-6.0.3-cp312-cp312-manylinux_2_28_aarch64.whl pyyaml-6.0.3-cp312-cp312-manylinux_2_28_x86_64.whl pyyaml-6.0.3-cp312-cp312-manylinux_2_39_aarch64.whl pyyaml-6.0.3-cp312-cp312-manylinux_2_39_x86_64.whl pyyaml-6.0.3-cp313-cp313-manylinux_2_28_aarch64.whl pyyaml-6.0.3-cp313-cp313-manylinux_2_28_x86_64.whl pyyaml-6.0.3-cp313-cp313-manylinux_2_39_aarch64.whl pyyaml-6.0.3-cp313-cp313-manylinux_2_39_x86_64.whl pyyaml-6.0.3-cp39-cp39-manylinux_2_28_aarch64.whl pyyaml-6.0.3-cp39-cp39-manylinux_2_28_x86_64.whl pyyaml-6.0.3-cp39-cp39-manylinux_2_39_aarch64.whl pyyaml-6.0.3-cp39-cp39-manylinux_2_39_x86_64.whl pyyaml-6.0.3.tar.gz ``` The different file names use the following qualifiers to allow the build and packaging tool to download the correct wheel file: * The values `cp*` indicate CPython compatibility. * The first value `cp*` value such as `cp39`, `cp310`, and others, indicates the compatibility with Python version `3.9`, `3.10`, and others. * The second value `cp*` value such as `cp39`, `cp310`, and others, indicates the compatibility with the Python Application Binary Interface (ABI) version `3.9`, `3.10`, and others. * The `manylinux` value indicates that the wheel is suitable for any modern Linux distribution, since no distribution specific requirements are in place. * The `2_28` and `2_39` values indicate suitability for environments using `glibc 2.28` and higher or `glibc 2.39` and higher. * The `aarch64` and `x86_64` values indicated the processor architecture requirement for ARM or x86. As a result of the provided files for these libraries, the following requirements apply: * All Linux distributions must use `glibc 2.28` or higher, such as RHEL 8, Ubuntu 20.04, or Amazon Linux 2023. * On Windows and MacOS you must use a suitable Linux distribution with a container solution, such as [WSL2](https://learn.microsoft.com/en-us/windows/wsl/), [apple/container](https://apple.github.io/container/documentation/) or [Docker Desktop](https://docs.docker.com/desktop/). * The runtime environment's processor architecture must be `x86_64` or `aarch64`. ### Behavior on Windows and macOS As a result of the requirements detailed in the preceding section, the developer experience on Windows and MacOS platforms is impacted. Chainguard only provides packages including native binaries for Linux platforms. These are not suitable for use on Windows or MacOS. When using Chainguard Libraries for Python on these platforms, retrieval for these libraries, and therefore application builds, fail. The suggested deployment, detailed in [Technical details](#technical-details) and [Global Configuration](/chainguard/libraries/python/global-configuration/), involves a repository manager that uses PyPI as a fall back for such packages. With this configuration, any build on Windows or MacOS continues to work and pulls the package from PyPI, since it is not available from Chainguard. Any build on Linux, however, uses a suitable package from Chainguard Libraries. When using CVE remediation, this also means that remediated packages with native binaries are only used on Linux. ## Manual access Use the URLs with your [username and password retrieved with chainctl](/chainguard/libraries/introduction/access/) to access the Chainguard Libraries for Python repository manually with a browser. After successful login, you are redirected to the `simple` sub-context at `https://libraries.cgr.dev/python/simple/` or `https://libraries.cgr.dev/python-remediated/simple/` that allow you to inspect the available packages. The top level contains an alphabetical list of packages: ``` 2captcha-python 3d-converter absql ahrs amqpstorm annogesic apiflask apscheduler ... ``` A list of all wheels and tarballs for the versions of a specific package is available in the context of the package. For example, the `apiflask` context at `https://libraries.cgr.dev/python/simple/apiflask/` shows the following list: ``` Links for apiflask apiflask-0.1.0-py3-none-any.whl apiflask-0.1.0.tar.gz apiflask-0.10.0-py3-none-any.whl apiflask-0.10.0.tar.gz apiflask-0.10.1-py3-none-any.whl apiflask-0.10.1.tar.gz apiflask-0.11.0-py3-none-any.whl apiflask-0.11.0.tar.gz apiflask-0.12.0-py3-none-any.whl apiflask-0.12.0.tar.gz ... ``` Each package name is a link to the specific binary. The link includes long unique identifiers and cannot be determined without browsing. The list uses ascending order for the full name including the version. Use the search functionality on [pypi.org](https://pypi.org/) to locate packages of interest and then browse in the simple index to determine available versions in Chainguard Libraries for Python. Use `curl`, specifying the username and password retrieved with [chainctl](/chainguard/libraries/introduction/access/), and use the URL of the file to download and save the file with the original name: With [.netrc authentication](/chainguard/libraries/introduction/access/#netrc): ```shell curl -n -L -O https://libraries.cgr.dev/files/... ``` With [environment variables](/chainguard/libraries/introduction/access/#env): ```shell curl -L --user "$CHAINGUARD_PYTHON_IDENTITY_ID:$CHAINGUARD_PYTHON_TOKEN" \ -O https://libraries.cgr.dev/files/... ``` The option `-L` is required to follow redirects for the actual file locations. ## Hash verification when migrating to Chainguard Libraries For a step-by-step walkthrough of moving an existing project to Chainguard Libraries, check out the [Python migration guide](/chainguard/libraries/python/migration/). This section covers hash verification, one part of that process. Because Chainguard rebuilds Python packages from source rather than mirroring upstream PyPI artifacts, it is expected that checksums for Chainguard-built packages differ from their PyPI counterparts, even for identical package versions. This affects any tool that pins or verifies hashes: * Tools such as `pip` verify that downloaded files match the hashes specified in requirements.txt when using `--require-hashes` or when hashes are pinned * Tools such as `pip`, `Poetry`, and `uv` generate lock files that include SHA-256 hashes * Repository managers such as JFrog Artifactory or Sonatype Nexus may have cached upstream PyPI wheels and continue serving them instead of Chainguard versions, even after you have reconfigured to use Chainguard Libraries Resolving these issues requires two steps: [clearing cached artifacts](#clearing-caches-before-migration) at every layer of your build pipeline, and [updating lock files or requirements files](#updating-lockfile-hashes) so they reflect Chainguard's checksums. ### Clearing caches before migration Most build systems treat language libraries as immutable artifacts. If a dependency already exists in a cache, the build will reuse the cached copy instead of fetching it again, even after you have reconfigured to use Chainguard. Before migrating, it is critical to start with a clean dependency cache at every layer of your build pipeline. Failing to do so can result in builds silently continuing to use PyPI-sourced artifacts after migration. Cached packages can exist at multiple layers: **Local developer machines** Common cache locations for Python tools include `~/.cache/pip/`, `~/.cache/uv/`, and `~/.cache/pypoetry/`. Clear the cache for your tool: ```bash pip cache purge uv cache clean poetry cache clear --all pypi ``` **CI/CD pipeline caches** Build pipelines frequently cache downloaded packages between runs. These caches must be invalidated after switching to Chainguard. Common locations to check: * GitHub Actions: Packages cached under `/home/runner/.cache/pip` or restored via `actions/cache`. Update the cache key in your `actions/cache` step to force a cache miss after switching to Chainguard. * GitLab CI: Check for .`cache/pip` defined in the job's cache section and update the cache key or clear the directory. * Jenkins: Agents may reuse workspaces or home directories such as `/var/lib/jenkins/.cache/pip`. Ensure cache directories are cleared as part of your build setup step. **Containerized builds** Cached Docker image layers may reuse upstream dependencies even after reconfiguration. If a Dockerfile contains a layer such as `RUN pip install -r requirements.txt`, Docker will reuse the cached layer unless the base image, the requirements file, or the command itself has changed. To force a fresh install from Chainguard, rebuild without the layer cache: ```bash docker build --no-cache ``` To authenticate to Chainguard Libraries during a container build without writing credentials into an image layer, see [Container builds](/chainguard/libraries/policies-and-security/build-containers/). ### Updating lockfile hashes Existing Python lockfiles usually contain upstream integrity hashes. Because Chainguard rebuilds packages from verified source, those hashes must be updated before reinstalling. Use `chainctl libraries update-hashes` to update them in place. Learn more in [Build configuration](/chainguard/libraries/python/build-configuration/). If you install through a repository manager, refer to [Global configuration](/chainguard/libraries/python/global-configuration/#updating-lockfile-hashes/). When the upstream fallback is enabled, [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) keeps a version you pulled from upstream stable after Chainguard publishes its own build, so you don't have to update these hashes again unexpectedly. #### Update lockfiles manually Alternatively, you can use the following instructions to manually update your lockfiles:
Manually updating lockfiles

Before regenerating lock files, ensure your tool is configured to use Chainguard as the package index by following the global configuration or direct access documentation.

To update your lock files and requirements with Chainguard’s checksums:

pip with --require-hashes:

If your requirements.txt was generated using PyPI hashes, installation will fail with a hash mismatch after switching to Chainguard. Once pip is configured to use Chainguard as the index, regenerate your requirements file to update the hashes:

  pip-compile --generate-hashes requirements.in -o requirements.txt

Note that pip-compile embeds your Chainguard index URL, including credentials, as an --index-url line in the generated file. Avoid committing this file to source control, or strip the --index-url line before doing so.

uv:

When the configured index changes, uv sync automatically re-resolves dependencies and updates uv.lock with hashes from the new index. No explicit uv lock step is required; running uv sync after configuring Chainguard as your index is sufficient. However, running uv lock explicitly after switching indexes is recommended to ensure your lock file accurately reflects what is being installed before you run uv sync.

Note that uv sync --frozen bypasses index configuration entirely and downloads packages using the URLs embedded directly in uv.lock. If your lock file was generated against PyPI, uv sync --frozen will continue to download from PyPI regardless of your configured index. Run uv lock first to update the lock file before using --frozen.

The credentials from your Chainguard pull token must be embedded in the index URL with the / in the username percent-encoded as %2F. For example, in uv.toml:

[[index]]
url = "https://<PULLTOKEN_USERNAME_PART1>%2F<PULLTOKEN_USERNAME_PART2>:<PULLTOKEN_PASSWORD>@libraries.cgr.dev/python/simple/"
authenticate = "always"

Poetry:

When the configured source changes, Poetry detects that pyproject.toml has changed significantly and refuses to install until the lock file is regenerated. Regenerate your lock file to update the hashes:

Poetry 1.x:

  poetry lock --no-update

Poetry 2.x:

  poetry lock

Repository managers:

Repository managers such as JFrog Artifactory or Sonatype Nexus may continue serving cached PyPI artifacts even after the upstream index is changed. Clear the cache or invalidate the artifact to ensure the Chainguard-built package is fetched.

Before regenerating lock files, ensure your tool is configured to use Chainguard as the package index by following the global configuration or direct access documentation.

>**Note:** While hash mismatches are expected for some tooling and configurations while migrating to Chainguard, you can verify the authenticity and provenance of Chainguard packages using SBOM and SLSA attestation files as described in the next section. ## SBOM and attestation files Chainguard Libraries for Python include files that contain software bill of material (SBOM) information. Additional files attest details about build infrastructure with the [Supply-chain Levels for Software Artifacts (SLSA)](https://slsa.dev/) provenance information. To save the available signature bundle, verified PEP 740 provenance, and embedded SPDX SBOM while verifying a wheel, use `chainctl libraries verify --output-attestations` with `--output-dir`. Refer to [Download Python SBOMs and attestations](/chainguard/libraries/policies-and-security/verification/#download-python-sboms-and-attestations) for examples and output paths. ### Embedded SBOMs The related files for Chainguard Libraries for Python are located within the Python wheel file for each package following the [PEP 770 Improving measurability of Python packages with Software Bill-of-Materials specification](https://peps.python.org/pep-0770/) for software composition analytis (SCA) using the SPDX format. A wheel file contains two directories: * The main code directory that uses the name of the library only, and * The version-specific distribution info directory `.dist.info`. For example, the wheel archive for Flask version 2.0.0 includes a directory `flask-2.0.0.dist.info`. You can also find this directory in the `site-packages` directory of a Python project using a virtual environment. The SBOM information is in the file `*.dist-info/sboms/sbom.spdx.json`. Any package from Chainguard includes a reference to Chainguard in the `creators` section: ```json { "spdxVersion": "SPDX-2.3", "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "flask", "documentNamespace": "https://anchore.com/syft/dir/flask-17f2a052-031c...", "creationInfo": { "licenseListVersion": "3.27", "creators": [ "Tool: Syft", "Tool: ecosystems-wheels-rebuilder", "Organization: Chainguard, Inc" ], ... } } ``` ### SLSA provenance SLSA provenance is available from the Chainguard Python index following the [PEP 740 – Index support for digital attestations specification](https://peps.python.org/pep-0740/) within the integrity context at `https://libraries.cgr.dev/python/integrity/PACKAGE/VERSION/FILE/provenance` with `PACKAGE` as the Python package name, `VERSION` the package version, and `FILE` the filename of the package archive with configured [basic authentication using a pull token](/chainguard/libraries/introduction/access/). For example, for version `2.0.0` of the package `flask` available as a platform independent wheel archive file `flask-2.0.0-py3-none-any.whl` you can retrieve the provenance information from `https://libraries.cgr.dev/python/integrity/flask/2.0.0/flask-2.0.0-py3-none-any.whl/provenance`. Packages from Chainguard are identified by the `publisher`:`environment` set as `chainguard`: ```json { "attestation_bundles": [ { "attestations": [ ... ] "publisher": { "environment": "chainguard", "kind": "URL", "issuer": "https://issuer.enforce.dev", "identity": "https://issuer.enforce.dev/3fab4fe4edb624d...", "repository": "chainguard-dev/ecosystems-wheel-rebuilder", "workflow": "python-build-versions.yaml" } } ], "version": 1 } ``` ### Sigstore bundle A [Sigstore bundle](https://docs.sigstore.dev/about/bundle/) is a self‑contained JSON file that packages everything needed to verify the authenticity and integrity of a signed artifact. A Sigstore bundle file is available as `bundle.json` from the integrity context at `https://libraries.cgr.dev/python/integrity/PACKAGE/VERSION/FILE/bundle.json` specifically for each package, version, and file. ## Upstream fallback policy and controls Chainguard Libraries for Python supports an optional built-in fallback to the upstream PyPI index, managed through the [Chainguard Repository](/chainguard/chainguard-repository/overview/). By default, the endpoint serves only Chainguard-built packages. When the upstream fallback is enabled, upstream packages are subject to additional security controls before being served. Learn about managing fallback and cooldown controls in the [Chainguard Libraries overview](/chainguard/libraries/introduction/overview/). --- ### Chainguard Libraries FAQ URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/troubleshooting/faq.md Last Modified: October 8, 2026 Tags: Chainguard Libraries, Overview ## How does Chainguard Libraries help developers?
Watch: How Chainguard Libraries helps developers

The following video focuses on Chainguard’s built-from-source model: when a rebuilt package is available, Chainguard serves it with verifiable provenance and SBOMs. Not every package or version can be built immediately, so the Chainguard Repository also supports an optional, policy-governed upstream fallback. When enabled, Chainguard-built packages are preferred, while eligible upstream packages are subject to configurable controls such as a cooldown period and malware and greyware scanning. Packages identified as malicious are blocked.

Watch the video on YouTube

Transcript

Interviewer: So how does Chainguard Libraries help developers?

Dustin Kirkland: Yeah, so building off of that Chainguard Factory, we’ve actually repurposed all of that automation to not just build packages and containers, but actually fetch libraries directly from their upstream source and recompile those Java binaries—JARs—and those Python binaries—wheels—in a new format, or in the same format rather, but totally bootstrapped from source. The fact that we can rebuild those libraries means that we can actually patch them if necessary.

Now, in doing so, we’ve created an entire repository of Python wheels and Java JARs that we can publish and hydrate into a customer’s environment, so that their developers can retrieve their libraries from a secure source, from a trusted source, and avoid malicious packages—deliberately modified or intentionally compromised packages—which, you know, we find in PyPI.org or in Maven Central from time to time. But Chainguard building those from source ensures that that entire open source ecosystem is secured in the same way that we’re securing the packages and the container ecosystems.

## How does Chainguard Libraries plug into a developer's workflow?
Watch: How Chainguard Libraries plugs into a developer's workflow

Watch the video on YouTube

Transcript

Interviewer: So Dustin, how does Libraries actually plug into a developer workflow?

Dustin Kirkland: Yeah, so I used the word “hydrate” earlier. We hydrate typically a JFrog Artifactory or a Cloudsmith—we hydrate that registry of artifacts with Chainguard securely built artifacts. And we produce this constant flow of tens of thousands of those library version tuples into that environment. And our customers can come to us and get a license for our entire Java ecosystem or our entire Python ecosystem.

So from that sense, we’re certainly not an artifact registry. You use the artifact registry that’s typically used inside of your organization. We help populate that with secure artifacts. This is fairly similar to our approach with containers, where we’re not a scanner, but what we’re trying to do is complement the scanners that you have with better containers that have fewer CVEs, and you end up with cleaner scan results. So in this artifact sense, we’re not an artifact registry, but we’re putting better binaries into that artifact destination.

Interviewer: So once the artifact registry is set up, is there any changes to actual developers’ workflow, or will it just work?

Dustin Kirkland: Typically not. I mean, if inside of your organization you already have an artifact registry and your developers are pip installing or maybe even importing those classes and libraries from that artifact repository, we’re just replacing the artifacts that may have additional vulnerabilities or potentially malicious code with artifacts that have fewer vulnerabilities and are immune to that malicious code.

## What security issues can Chainguard Libraries prevent? As detailed on the [background](/chainguard/libraries/introduction/overview/#background) and [introduction](/chainguard/libraries/introduction/overview/#introduction) pages, Chainguard Libraries are built directly from source in the Chainguard Factory and the resulting binaries are directly provided to you by Chainguard. Chainguard operates the whole supply chain for the package lifecycle as one reliable, secure partner. You can therefore avoid issues from the following software supply chain attack surface points: * Build pipeline * Build system * Dependency injection * Bypass of CI/CD systems * Library distribution * Library consumption More information about these stages in the software supply chain is available on the [Supply chain Levels for Software Artifacts (SLSA) website](https://slsa.dev/). The following examples are issues, attacks, and compromises that affect stages of the software supply chain for libraries across different language ecosystems:
Malicious GlueStack packages
  • This May 2025 attack uploaded compromised packages to PyPI and npm that enable remote shell access and uploading files to compromised machines
  • Chainguard Libraries would have protected against this attack. First, the packages have invalid upstream source URLs so there was no source repository. In the case of the lone exception (a package with a valid source repository link), no code was present for Chainguard to build a valid package.
  • The Hacker News blog post on the attack
Ultralytics Python project
  • Attackers compromised the GitHub Actions workflows for the Ultralytics repository, injecting malware into PyPI package releases.
  • Attackers pushed out four malicious versions of the Ultralytics YOLO project over the course of a week (8.3.41, 8.3.42, 8.3.45, 8.3.46).
  • Ultralytics YOLO is a widely-used fast object detection neural network library downloaded about five million times per month. Users affected during this period were infected with cryptomining malware.
  • Chainguard Libraries would have prevented this attack by building the project from clean source. No source code was modified by attackers during this incident.
  • Refer also to PyPI attack analysis and bleepingcomputer blog post.
Lottie Player
  • Hackers gained access to the NPM registry by compromising a developer authentication token.
  • Token used to upload a compromised version of Lottie Player.
  • The malicious package drained crypto wallet funds.
  • Chainguard Libraries would have prevented this attack by building the project from clean source. No source code was modified by attackers during this incident.
  • Refer also to npm package Lottie-Player compromised in supply chain attack, Nov 2024.
MavenGate
  • MavenGate is a proof of concept for exploiting abandoned Java library domains.
  • Vulnerabilities in Maven dependency management allow unauthorized package replacements.
  • All Java build tools using Maven repositories, including Maven, Gradle, and Ant, could be affected.
  • MavenGate relied on the use of multiple repositories and any attack with the proposed mechanism would not publish source code. Chainguard Libraries replace other repositories and the use of Chainguard Libraries, based on building from the original source, would have prevented an attack using this approach
  • Refer also to The Hacker News article, Oversecured blog post, and Sonatype’s take as Maven Central operator.
XZ Utils backdoor
  • Example of a supply chain attack leveraging social engineering by a patient actor
  • Sophisticated backdoor that had remote code execution capability and the potential to affect many systems
  • Vulnerability was patched within hours of disclosure by reverting to a previous version known to be safe.
  • Malicious source tarball and binaries were distributed successfully, but source code repository was not compromised.
  • Since no source code was compromised, a similar attack on a protected library ecosystem would be prevented by Chainguard Libraries
  • XZ Utils is written in C and therefore not available as an ecosystem protected by Chainguard Libraries. However, Chainguard’s container images include XZ Utils packages. These are also built from source and are not affected.
  • Refer also to Wikipedia article and official page from the XZ data compression.
Other examples and resources

The following links provide details for other software supply chain attacks. Depending on the exact details some of these attacks and approaches are prevented by use of Chainguard Libraries.

Find pointers to further resources in the Software supply chain reading list.

## Why do the Chainguard library checksums differ from those published by upstream repositories? Chainguard rebuilds libraries from source in a controlled environment to improve supply-chain security. As a result, while functionality remains the same, build metadata and generated content, such as SBOMs, differs from upstream distributions. Whether Chainguard library checksums match upstream depends on the ecosystem and build process. During initial migration to Chainguard Libraries, some common causes of checksum errors include: * Artifacts were previously cached from upstream repositories * Example: Maven's `.m2` or Gradle's cache. * Dependencies are pinned to upstream checksums or hashes * Example: JavaScript's `package-lock.json` or `yarn.lock`. * Repository managers or build tools enforce strict verification * Example: Artifactory validating against Maven Central. After you migrate, [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) keeps a version you pulled from the upstream fallback stable when Chainguard later publishes its own build, so checksums don't change unexpectedly across rebuilds. ## What’s the difference between malware‑hardened libraries and CVE remediation? Malware‑hardened libraries are the baseline Chainguard Libraries experience: Chainguard rebuilds open source Java, JavaScript, and Python dependencies from upstream source in the [Chainguard Factory](/platform/factory/), a controlled, SLSA‑aligned build environment, and publishes them to hardened registries for customers to consume. This closes off most supply chain malware vectors compared to pulling directly from public registries like Maven Central, npm, and PyPI. [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/) is an additional feature where Chainguard backports High and Critical vulnerability fixes from newer upstream releases to older versions that customers are still using, particularly when upstream maintainers no longer ship patches for those older versions. Each ecosystem uses a version suffix so you can distinguish remediated builds from non‑remediated upstream versions, but how you select them differs: * **Python**: A `+cgr.N` local version suffix (for example, `2.0.0+cgr.1`), published in a separate index at `https://libraries.cgr.dev/python-remediated/simple/`. Python package managers treat it as a higher‑precedence version and can select it automatically during resolution. * **Java**: A `-0.cgr.N` suffix (for example, `3.18.0-0.cgr.1`), published in a separate repository at `https://libraries.cgr.dev/java-remediated/`. Maven and Gradle sort it higher than the base version, so dependency management rules can resolve to it when the overlay repository is available. * **JavaScript**: A `-cgr.N` prerelease suffix (for example, `4.17.21-cgr.1`), served through the standard endpoint at `https://libraries.cgr.dev/javascript/`. Because npm treats `-cgr.N` as a prerelease rather than a newer release, you select remediated versions by running `chainctl libraries remediate --apply`. Rather than relying on version precedence, the command sets the exact `-cgr.N` version for direct dependencies and adds override or resolution rules for transitive ones, then updates the lockfile. ## Why might I still see errors with the upstream fallback enabled through the Chainguard Repository? Chainguard offers an upstream fallback through the [Chainguard Repository](/chainguard/chainguard-repository/) as a single, managed endpoint for each ecosystem. This endpoint: * Serves Chainguard‑built packages first, rebuilt from source. * Serves upstream packages for versions that are not yet available from Chainguard. Chainguard scans these packages for malware and greyware before serving them, and subjects them to configurable policies such as a cooldown period. Because of those controls, you may still see failed pulls when the upstream fallback is enabled, for example when: * A package or version is blocked by malware detection and therefore intentionally not served from upstream. * A recently published version is still in the cooldown period, so the repository will not yet serve it. * The requested package/version truly does not exist in either Chainguard’s catalog or upstream. For customers, this can surface as an error from the Chainguard endpoint even though a version appears in the public registry. For more details on the errors you may see when a package is blocked for these reasons, see the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/). ## What are Chibbies? Chibbies is the internal codename for the Chainguard Libraries. It evolved from Chainguard Libraries being shortened to Chainguard Libbies, and then [finally to Chibbies](https://www.youtube.com/watch?v=adfU9LJg3I0&t=2843s). ## JFrog Artifactory troubleshooting The following questions apply to repo manager configurations for Chainguard Libraries, using JFrog Artifactory. Learn more about using a repo manager in the global configuration pages for each ecosystem: [Java](/chainguard/libraries/java/global-configuration/), [JavaScript](/chainguard/libraries/javascript/global-configuration/), [Python](/chainguard/libraries/python/global-configuration/). ### What are the most common setup mistakes when using Artifactory with Chainguard Libraries? Follow these steps for general troubleshooting: * Verify the token and repository settings. * Confirm that the token is scoped correctly, not expired, and copied correctly. Also confirm that the repository URL is the correct language-specific endpoint and that the expected remote repository settings are in place. * Validate pulling packages with direct access. * Attempt to pull a package directly from the Chainguard endpoint from a controlled environment. This helps you confirm that the token works and that the package is available independently of Artifactory. * Attempt to pull the same package through the Artifactory remote or virtual repository and compare the result. * If direct access works but the Artifactory path fails, check the network path between Artifactory and the Chainguard endpoint. This is where firewall restrictions, proxy behavior, TLS inspection, or object-storage allowlisting issues may have an impact. * Test with a real package fetch. * The **Test** button in Artifactory is not a reliable way to confirm that your integration is working as expected. Instead, use a real package fetch, install, or checksum comparison. * If results aren't as expected, [clear one cache layer at a time and rerun the same test](#inconsistent-fallback). ### Why might I see TLS or SSL handshake errors and authentication failures after configuring Artifactory with Chainguard Libraries? In some cases, authentication failures or malformed token behavior are caused by traffic inspection or proxy-layer handling. When troubleshooting, check the following: * Whether a proxy, TLS inspection layer, or MITM device is in path * Whether the full certificate chain is installed correctly * Whether the environment uses an internal CA * Whether the runtime, package manager, and repository manager trust the same CA bundle * Whether the proxy is modifying or normalizing headers, tokens, or request format ### Why might I experience timeouts or "connection refused" errors after configuring Artifactory with Chainguard Libraries? Firewall rules can prevent dependency resolution or break integration behavior. This is often observed when the repository manager can reach one destination but not another required upstream host or storage host. When troubleshooting, check the following: * Allowed outbound destinations * DNS resolution * Port restrictions * Whether the repository manager can reach all required upstream hosts * Whether object-storage or redirect targets also need to be allowlisted ### Why might I experience authentication loops, 4xx or 5xx responses, incorrect endpoint routing, stale artifacts, or behavior that differs between direct and proxied paths after configuring Artifactory with Chainguard Libraries? Proxy layers can change headers, certificate handling, path routing, protocol support, and cache behavior. When troubleshooting, check the following: * Whether a forward proxy, reverse proxy, or both are present * Header rewriting * Path rewriting * Auth forwarding * TLS termination point * HTTP/2 support where required * Cache behavior at each layer ### Why might I experience inconsistent fallback behavior, outdated package metadata, or inconsistent experience between users after configuring Artifactory with Chainguard Libraries? Caching is a common cause of unexpected behavior during onboarding and testing. Builds can continue using previously cached public-registry artifacts or stale metadata even after you switch a repository over to Chainguard. When troubleshooting, check the following: * Local package manager cache * Artifact repository cache * Proxy cache * CI/CD runner cache Clear one cache layer at a time and rerun the same test after each change. Avoid clearing multiple cache layers simultaneously unless the steps are documented. If you are switching an existing Artifactory repository to Chainguard, invalidate or zap the remote cache before concluding that the configuration is broken. --- ### Chainguard Libraries for Java overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/java/overview.md Last Modified: October 9, 2026 Tags: Chainguard Libraries, Java, Overview Chainguard Libraries for Java provides enhanced security for the Java ecosystem by rebuilding dependencies from Maven Central and other common repositories with the latest patches and comprehensive supply chain protection. This service addresses critical vulnerabilities in the vast Java/JVM ecosystem that spans hundreds of projects from organizations like the Apache Software Foundation, Eclipse Foundation, and numerous independent maintainers. Chainguard Libraries for Java provides access to all open source libraries commonly used. New releases of common libraries or artifacts requested by customers are added to the growing index by an automated system. The number of included libraries continues to grow. These artifacts are accessible through the [Chainguard Repository](/chainguard/libraries/javascript/overview/), a single endpoint for package retrieval that supports configurable security policies for both Chainguard-built and upstream packages. The main public repository for binary artifacts is the [Maven Central Repository](https://central.sonatype.com/). In operation for over 20 years, it hosts artifacts for most open source projects in the Java community. It is the default repository in all commonly used build tools from the Java community including [Apache Maven](https://maven.apache.org/), [Gradle](https://gradle.org/), and others, and uses the Maven repository format. Chainguard Libraries for Java covers a broad and growing set of artifacts from Maven Central. While Maven Central is the primary reference repository, Chainguard Libraries for Java also builds binaries for open source projects available in other repositories like the Google or Confluent repositories. This covers libraries not found on Maven Central, sourced from [Google](https://maven.google.com/web/index.html), [Oracle](https://www.oracle.com/webfolder/application/maven/index.html), [JetBrains](https://www.jetbrains.com/intellij-repository/releases), CERN, [Confluent](https://packages.confluent.io/maven/), [Gradle](https://plugins.gradle.org/m2/), and other public artifact repositories. Note that coverage is not exhaustive for any single repository; the index continues to grow, and any request for a missing library or version automatically triggers a process to provision the artifacts from relevant sources if available. ## Runtime requirements The runtime requirements for Java artifacts available from Chainguard Libraries for Java are identical to the requirements of the original upstream project. For example, if a JAR retrieved from Maven Central requires Java 17 or higher, the same Java 17 runtime requirement applies to the binary artifact from Chainguard Libraries for Java. ## Technical details You must use the [username and password retrieved with chainctl](/chainguard/libraries/introduction/access/) to access the Chainguard Libraries for Java repository. The URL for the repository is: ``` https://libraries.cgr.dev/java/ ``` This site provides a directory browsing and file listing capability similar to the Maven Central repository, allowing you to find available libraries, library versions, and available files. Learn more under [Manual access](#manual). This Chainguard Libraries for Java repository uses the Maven repository format and only includes release artifacts of the libraries built by Chainguard from source. It does not include all artifacts from Maven Central or other repositories. Snapshot versions are not available. The following components can be required by your application builds, but are not included: * Binary versions of closed-source libraries. The Maven Central Repository and other repositories often include such libraries. They enable interoperability with open source applications and development of internal applications as a combination of these libraries and other open source libraries. Examples include JDBC drivers for proprietary databases such as Oracle * Other artifacts that are found in the Maven Central Repository with incomplete information about the location of the source code or a pointer to a location with access restrictions or an incomplete source, that prevents creation of a binary by Chainguard. Some types of artifacts are included if the source build produces them, but are often not available: * JAR artifacts containing the source code * JAR artifacts containing JavaDoc HTML files * Distributable versions of artifacts such JARs with dependencies or tar.gz archives * Other package formats sometimes found such as RPMs, SO files, Android AARs, and similar, rarely used artifacts As a result, **you must configure the repository as the first point of contact and request for any retrieval of a library**. This ensures that any library that is available from Chainguard is also used. In addition, any failed requests are flagged at Chainguard and backfill processes are run where possible. At the same time, you must continue to use the Maven Central Repository, and any other repository that fills the needs for libraries that are not available from the Chainguard Libraries repository. Typically the access is [configured globally on a repository manager for your organization](/chainguard/libraries/java/global-configuration/). This approach is strongly recommended. Alternatively, you can use the token for direct access from a build tool as discussed in [Build configuration](/chainguard/libraries/java/build-configuration/). For a step-by-step walkthrough of moving an existing Maven or Gradle project to Chainguard Libraries, check out the [Java migration guide](/chainguard/libraries/java/migration/). ## CVE remediation Frontier AI models are uncovering vulnerabilities in open source software before traditional security tools can detect them. Chainguard Libraries helps reduce that exposure by delivering remediated artifacts through the [Athena](https://www.chainguard.dev/athena) coalition. Chainguard validates, patches, rebuilds, and tests affected packages, then makes the fixed artifacts available to Athena members through Chainguard Libraries. After public disclosure, Chainguard makes the fixes available to Chainguard customers through supported Chainguard Libraries ecosystems. Customers can continue using supported package versions while receiving verifiable provenance and SBOM metadata for the remediation. Chainguard Libraries for Java includes the [CVE Remediation](/chainguard/libraries/policies-and-security/cve-remediation/) feature, available in beta for Spring Boot. Remediated libraries include an appended local version identifier of `-0.cgr.N`. For example, if `org.apache.commons:commons-lang3:3.18.0` has a remediated build, that build is published as `org.apache.commons:commons-lang3:3.18.0-0.cgr.1`. If Chainguard publishes another remediated iteration for the same base version, the trailing number increases, such as `-0.cgr.2` or `-0.cgr.3`. Maven and Gradle treat the `-0` as part of the version ordering. In practice, `3.18.0-0.cgr.1` sorts higher than `3.18.0`. This means version ranges or dependency management rules can resolve to the remediated build when the overlay repository is available. Learn how to opt in to remediated versions in the [Java build configuration docs](/chainguard/libraries/java/build-configuration/#selecting-remediated-library-versions). ### Troubleshooting resolution issues Enabling the remediated repository does not typically require clearing caches. However, clearing the cache may help when troubleshooting resolution issues. Maven: ```bash rm -rf ~/.m2/repository ``` Gradle: ```bash rm -rf ~/.gradle/caches/ ``` Bazel: ```bash bazel clean --expunge ``` ## Manual access To manually access artifacts in the Chainguard Libraries for Java repository, use the URL [`https://libraries.cgr.dev/java/`](https://libraries.cgr.dev/java/) with your [username and password retrieved with chainctl](/chainguard/libraries/introduction/access/). This site provides a directory browsing and file listing capability similar to the Maven Central repository at [`https://repo1.maven.org/maven2/`](https://repo1.maven.org/maven2/). The structure follows the [Maven repository format](https://maven.apache.org/repository/layout.html). The `groupId` and `artifactId` of a library is used to create a nested directory structure, similar to the package structure within Java projects. For example, the Maven coordinates for [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/) are the following: ```xml org.apache.commons commons-lang3 3.13.0 ``` **Find available versions** List the versions that Chainguard has built for a library by requesting its `maven-metadata.xml` file at the `groupId`/`artifactId` path. The `groupId` `org.apache.commons` becomes the nested directories `org/apache/commons`, and the `artifactId` adds the `commons-lang3` directory: ``` https://libraries.cgr.dev/java/org/apache/commons/commons-lang3/maven-metadata.xml ``` The repository only includes release artifacts that Chainguard builds from source, so the versions listed may differ from those available on Maven Central. **List the files for a version** Each version has its own leaf directory, formed by appending the `version` to the `groupId`/`artifactId` path. This version directory is browsable and lists all files for that specific library version: ``` https://libraries.cgr.dev/java/org/apache/commons/commons-lang3/3.13.0/ ``` For the `org.apache.commons:commons-lang3:3.13.0` library, this directory includes the main Maven metadata file `commons-lang3-3.13.0.pom`, the main JAR file `commons-lang3-3.13.0.jar`, related checksum files, and the SBOM and attestation files described below. Specific files vary between libraries. All filenames can be used to download individual files. Use `curl`, specify the username and password retrieved with [chainctl for basic user authentication](/chainguard/libraries/introduction/access/) and use the URL of the file to download and save the file with the original name. With [.netrc authentication](/chainguard/libraries/introduction/access/#netrc): ```shell curl -n -L \ -O https://libraries.cgr.dev/java/commons-io/commons-io/2.13.0/commons-io-2.13.0.pom ``` With [environment variables](/chainguard/libraries/introduction/access/#env): ```shell curl -L --user "$CHAINGUARD_JAVA_IDENTITY_ID:$CHAINGUARD_JAVA_TOKEN" \ -O https://libraries.cgr.dev/java/commons-io/commons-io/2.13.0/commons-io-2.13.0.pom ``` The option `-L` is required to follow redirects for the actual file locations. [Use checksums of any file to verify](/chainguard/libraries/java/management/#java-verification) if it originates from the Chainguard repository. ## SBOM and attestation files Chainguard Libraries for Java include files that contain software bill of material (SBOM) information. Additional files attest details about build infrastructure with the [Supply-chain Levels for Software Artifacts (SLSA)](https://slsa.dev/) provenance information. The related files for Chainguard Libraries for Java are located in the same location as the `.pom`, `.jar`, and other artifacts for a specific library version and uses the same `artifactId-version` naming convention with the following extensions: * `.slsa-attestation.json` for the SLSA provenance attestation * `.spdx.json` for the SBOM information For example, the files for artifactId `commons-compress` and version `1.23.0` are located in the version directory [https://libraries.cgr.dev/java/org/apache/commons/commons-compress/1.23.0/](https://libraries.cgr.dev/java/org/apache/commons/commons-compress/1.23.0/). It includes the following files: * `commons-compress-1.23.0.pom` * `commons-compress-1.23.0.jar` * `commons-compress-1.23.0.slsa-attestation.json` * `commons-compress-1.23.0.spdx.json` Instead of downloading these files individually, you can save them while verifying a local artifact by adding the `--output-attestations` flag to `chainctl libraries verify`. Refer to [Download SBOMs and attestations](/chainguard/libraries/policies-and-security/verification/#download-sboms-and-attestations) for details. ## Upstream fallback policy and controls Chainguard Libraries for Java supports an optional built-in fallback to the upstream Maven Central repository, managed through the [Chainguard Repository](/chainguard/chainguard-repository/overview/). By default, the endpoint serves only Chainguard-built packages. When the upstream fallback is enabled, upstream packages are subject to additional security controls before being served, including source code and maintainer behavior scanning and a configurable cooldown period. For Java, Chainguard's scanning inspects compiled `.class` files, package metadata, and extracted `.jar`, `.war`, and `.ear` archive contents for suspicious patterns and malicious signals. When the upstream fallback is enabled, [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) keeps a version you pulled from Maven Central stable after Chainguard publishes its own build. This prevents unexpected checksum mismatches across rebuilds. Learn more in the [Chainguard Libraries overview](/chainguard/libraries/introduction/overview/). --- ### Chainguard Libraries overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/introduction/overview.md Last Modified: October 9, 2026 Tags: Chainguard Libraries, Overview [Chainguard Libraries](https://www.chainguard.dev/libraries) is a secure catalog of language dependencies that replaces your team’s reliance on [npm](https://www.npmjs.com/), [PyPI](https://pypi.org/), and [Maven Central](https://central.sonatype.com/). Every package pulled by your team or AI agents passes through multiple layers of defense, including malicious behavior scanning, building from source, and cooldowns that help prevent malware from entering your environment. As frontier coding models advance, attackers are creating more sophisticated social engineering campaigns and malware injections to exfiltrate enterprise secrets. Chainguard Libraries prevents malware instead of making developers pause work, triage incidents, and respond after compromise. ## Background Open source libraries distributed through public repositories face several security challenges: maintainers may not promptly address vulnerabilities, binary artifacts can be compromised, and the sheer volume of transitive dependencies makes manual security management impractical. While these repositories enable rapid development, they also introduce supply chain risks that traditional security approaches struggle to address. While convenient, these services remove the direct link from your application to the source code of a specific project, and create a potential risk for quality issues with the artifacts, man-in-the-middle attacks, removal or override of libraries with vulnerable or malicious versions, and other issues. The [Supply-chain Levels for Software Artifacts (SLSA)](https://slsa.dev/) specification describes these risks and how to protect your software against them. Although this is a common way of accessing open source binaries, it requires you to put tremendous trust into the following aspects for the dozen or even hundreds of open source libraries you typically use for each application: * Maintainers and especially release managers of the projects * Local workstation or CI setup used for the release build * Release process mechanisms to create the binaries * Transport of the binaries from the build system to the public repositories * Management of access to the repositories * Monitoring of repositories for attacks as well as harmful or malicious binaries * Traffic to public repositories and attacks on the transport to your infrastructure There are no real guarantees as to the actual provenance of the software code. Repositories also vary greatly in quality and there is no guarantee that the upstream source of a project is available in a repository. In addition, these repositories also hold non-open source binaries of libraries. All these factors create uncertainty. Using these public repositories can feel as opaque as picking up a USB drive off of the sidewalk and plugging it into your laptop. ## Introduction Chainguard Libraries rebuilds supported language dependencies from verified upstream source in the Chainguard Factory and distributes the resulting artifacts through Chainguard’s secure repositories. The Chainguard Factory is Chainguard’s dedicated build infrastructure for producing software from source in a hardened, SLSA-certified environment, with verifiable provenance and other metadata for customers. Chainguard Libraries can also serve eligible upstream packages through the [Chainguard Repository](/chainguard/chainguard-repository/overview/), where security controls such as malicious behavior scanning and cooldowns help prevent unsafe packages from reaching your environment. Chainguard Libraries and the use of the Chainguard Factory reduce software supply chain risk by combining trusted source rebuilds with security controls for upstream packages: * Chainguard-built libraries are rebuilt from verified upstream source in the Chainguard Factory and distributed with verifiable provenance and SBOMs. For CVE-remediated libraries, the fix is applied to the artifact itself and accompanied by this metadata, giving auditors evidence they can verify directly rather than requiring them to correlate a separate patch file with a dashboard. See the [vulnerability scanning documentation](/chainguard/libraries/policies-and-security/scanners/) for scanner support details. * When a requested package is not yet available as a Chainguard build, the Chainguard Repository can serve eligible upstream packages through configured fallback policies, subject to malicious behavior scanning and cooldowns before they are served. * If there is no open source code available, no binaries are made available by Chainguard. This eliminates any license-related risks from commercial libraries. The policy and process to have no binaries without source also removes the danger from malicious artifacts, since these artifacts do not provide source code in public code repositories. Chainguard Libraries is available for the following library ecosystems: * Java and the larger Java Virtual Machine (JVM) ecosystem with [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) * JavaScript and the larger ecosystem around JavaScript, TypeScript, npm, React, and others with [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/overview/) * Python and the larger ecosystem with [Chainguard Libraries for Python](/chainguard/libraries/python/overview/) To move an existing project over, follow the migration guide for [Java](/chainguard/libraries/java/migration/), [JavaScript](/chainguard/libraries/javascript/migration/), or [Python](/chainguard/libraries/python/migration/). ## CVE remediation Frontier AI models are uncovering vulnerabilities in open source software before traditional security tools can detect them. Chainguard Libraries helps reduce that exposure by delivering remediated artifacts through the [Athena](https://www.chainguard.dev/athena) coalition. Chainguard validates, patches, rebuilds, and tests affected packages, then makes the fixed artifacts available to Athena members through Chainguard Libraries. After public disclosure, Chainguard makes the fixes available to Chainguard customers through supported Chainguard Libraries ecosystems. Customers can continue using supported package versions while receiving verifiable provenance and SBOM metadata for the remediation. ## Chainguard criteria for building a library Chainguard Libraries includes thousands of Java, JavaScript, and Python libraries, and coverage is continuously growing as we add more packages and versions over time. Chainguard aims to build libraries that are relevant to our customers and that support broader software supply chain security goals. However, it is not always feasible or safe to rebuild and redistribute every package from public registries such as Maven Central, npm, or PyPI. ### Licensing and source availability Chainguard Libraries provides a trusted source for Java, JavaScript, and Python dependencies. Our goal is to prevent malware exposure and reduce the security work developers must do after packages enter their environments. For a library to be built and distributed by Chainguard: * Source code must be available and verifiable * The project’s source must be available in a source code manager (such as GitHub or GitLab). Packages that do not provide a valid or verifiable source URL cannot be rebuilt in the Chainguard Factory and are out of scope. * Licensing must allow rebuild and redistribution * The project must be licensed in a way that allows Chainguard to rebuild and redistribute it to customers. ### Library version support Chainguard builds libraries using supported language toolchains in our hardened build environment. We do not aim to replicate all historical runtime environments exactly, but we do attempt to preserve runtime compatibility where it is safe to do so. For older or EOL projects, our ability to build and remediate issues is constrained by runtime compatibility and by upstream maintenance practices. Our current minimum supported toolchains are: * **Python**: Python 3.10 and higher. * **Java**: Java 8 and higher. * **JavaScript**: Any supported, non-EOL version of Node.js. We will attempt to rebuild any libraries that meet the [licensing and source availability criteria](#licensing-and-source-availability) using the supported toolchains. ### EOL version support When a library version reaches end of life (EOL) upstream, Chainguard Libraries continues to build packages and provide security fixes for that version for six months beyond the upstream EOL date. After that six-month window closes, Chainguard Libraries will: * No longer build new packages that require the EOL version * No longer provide security fixes for packages built against the EOL version * Continue to serve previously built packages ## Upstream fallback and controls Chainguard Libraries support an optional protected upstream fallback, managed through the [Chainguard Repository](/chainguard/chainguard-repository/overview/). By default, the Chainguard library endpoints serve only Chainguard-built packages. When the upstream fallback is enabled, upstream packages are subject to additional security controls before being served. > Note: When using `chainctl` to configure upstream fallback or cooldown duration, it can take up to 30 minutes for the repository changes to take effect. ### Enable the upstream repository To enable or change upstream fallback configuration, use the [`chainctl libraries entitlements` command](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/). For example, the following command creates or updates an entitlement to Chainguard Libraries for JavaScript, and adds the npm upstream fallback policy. Enabling upstream fallback includes a 7-day cooldown by default, which can also be configured: ```bash chainctl libraries entitlements create --ecosystems=JAVASCRIPT --policy=CHAINGUARD_AND_UPSTREAM ``` For JavaScript, you can also enable upstream fallback in the Chainguard Console. For Java and Python, you cannot currently enable fallback or view upstream vs. Chainguard-built packages via the Chainguard Console; you must use `chainctl` to enable fallback for Java and Python. ### Fallback options The following options are available: * **No upstream fallback (default)**: Only Chainguard-built packages are served. * **Upstream fallback enabled with default 7-day cooldown**: Upstream packages are available after passing a configurable cooldown period and malware scan. The same cooldown period is enforced across Chainguard-built packages and upstream packages, so that dependency trees resolve consistently across both sources. When the upstream fallback is enabled, [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) keeps a package version you pulled from upstream stable even after Chainguard publishes its own build of that version. This prevents unexpected checksum changes across rebuilds. ### Malware and greyware detection Chainguard's [source code and maintainer behavior scanning](https://www.chainguard.dev/unchained/the-expanding-threat-landscape-chainguard-now-scans-source-code-for-traditional-malware-and-greyware/) identifies and blocks malicious and greyware packages in Chainguard Libraries via the Chainguard Repository. This includes packages that are publicly reported as malicious (including packages associated with OSV malware IDs) and packages that Chainguard determines are unsafe through its own malware source code scanning, even when no public malware advisory exists yet. Malware detection is continuous. If a version that was previously cached is later identified as malicious, it is added to the block list and will be blocked on subsequent requests. Chainguard's scanning evaluates multiple signal types, including: * **Credential and data theft**: The package harvests secrets (for example, GitHub tokens or SSH keys) and sends sensitive data out of the environment. * **Backdoors and malicious behavior**: The package obfuscates malicious behavior, including backdoor setup, remote access, staged payloads, and more. The code may contact known malicious infrastructure to fetch or run additional payloads. * **Install-time script execution**: The package automatically executes malicious behavior through pre-install and post-install lifecycle scripts. * **Typosquatting and impersonation**: The package is named or brand-engineered to deceive users into downloading the wrong package. * **Compromised or untrusted releases**: The package exhibits signs that a release cannot be trusted. #### View blocked packages You can view blocked packages [in the Chainguard Console](/chainguard/libraries/introduction/browse/#view-malware-information), [using `chainctl` commands](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware_list/), or [via the API](#malware-api). For more details on the errors a blocked package surfaces across ecosystems, see the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/). #### Malware API Chainguard's malware API endpoint provides access to Chainguard's malware blocklist. You can use it to retrieve a list of packages that have been identified as malicious and blocked by Chainguard. Learn more about it in the [API documentation](/platform/api/spec-api-v1/#tag/malware). Expand the following section to see examples:
Malware API examples

First, get an access token:

TOKEN=$(chainctl auth token --audience=https://console-api.enforce.dev)

Then, use the API to query malware details.

List blocked packages for an ecosystem. For example, to see the 10 most recent JavaScript packages blocked:

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  "https://console-api.enforce.dev/libraries/v1/malware/blocklist?ecosystem=NPM&page_size=10" \
  | jq .

List packages blocked since a specified date. For example, to see PyPI packages blocked since July 1, 2026:

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  "https://console-api.enforce.dev/libraries/v1/malware/blocklist?ecosystem=PYPI&since=2026-07-01T00:00:00Z&page_size=10" \
  | jq .

Check whether a specific package is blocked. For example, to check for blocked axios packages:

curl -s \
  -H "Authorization: Bearer $TOKEN" \
  "https://console-api.enforce.dev/libraries/v1/malware/blocklist?ecosystem=NPM&packageName=axios" \
  | jq .
### Cooldown period When fallback is enabled, upstream packages are subject to a cooldown period from their publication date before the Chainguard Repository will serve them. The cooldown is an additional layer of security that provides a window for the security community to identify and report malicious packages before your builds can pull them. The cooldown applies globally across Chainguard-built packages and upstream packages served through the fallback. This prevents installs from failing when a Chainguard-built package depends on an upstream dependency that is still under the cooldown window. If a requested package version falls within the cooldown period, the package manager will output a 404 error. The package becomes available once it has passed the cooldown period and cleared malware scanning. Learn how to create, enforce, disable, and list policies in the [Libraries policies documentation](/chainguard/chainguard-repository/library-policies/) page. ### How package resolution works When you request a package from the Chainguard Repository, the following logic applies: * **Chainguard-built package available**: The package is served directly from Chainguard's rebuilt artifact store, complete with SBOM, provenance, and signatures, subject to the configured cooldown. * **Package not yet built by Chainguard**: If upstream fallback is enabled, the repository checks whether the package has passed the cooldown period and malware scan. * **Within the cooldown period**: The request returns an error. This prevents newly published packages — which carry higher malware risk — from being served immediately. * **After the cooldown period**: If upstream fallback is enabled and the version is outside the cooldown window and passes malware scanning, the repository pulls the version through from the upstream registry, serves it to the client, and caches it in the upstream mirror for future requests. * **Malware or greyware detected**: Any package version that is detected for malware or greyware is blocked, whether it originates from Chainguard's builds or the upstream fallback. * Malware scanning checks all packages against the Open Source Vulnerabilities (OSV) database, which includes the OpenSSF Malicious Packages feed among other sources. Any package version flagged with a malware identifier is blocked. This covers reported malicious packages across the npm ecosystem. > **Note**: Chainguard Repository is not a full mirror of upstream repositories. Packages are screened for malware before being made available. Some packages may be delayed by the cooldown period or permanently blocked if flagged as malicious. ## Other resources * [Chainguard Libraries product page](https://www.chainguard.dev/libraries) * [ Learning Lab for October 2025 on Chainguard Libraries for JavaScript and CVE remediation for Python libraries](/software-security/learning-labs/ll202510/) * [ Learning Lab for June 2025 on Chainguard Libraries for Python](/software-security/learning-labs/ll202506/) * [ Learning Lab for May 2025 about Chainguard Libraries for Java](/software-security/learning-labs/ll202505/) Blog posts * [Mitigating Malware in the npm Ecosystem with Chainguard Libraries](https://www.chainguard.dev/unchained/mitigating-malware-in-the-npm-ecosystem-with-chainguard-libraries ) * [Announcing Chainguard Libraries for JavaScript: Malware-Resistant Dependencies Built Securely from Source](https://www.chainguard.dev/unchained/announcing-chainguard-libraries-for-javascript-malware-resistant-dependencies-built-securely-from-source) * [Registries and the npm Breach: Securing the Weakest Link in the Software Supply Chain](https://www.chainguard.dev/unchained/registries-and-the-npm-breach-securing-the-weakest-link-in-the-software-supply-chain) * [Malware-Resistant Python without the Guesswork](https://www.chainguard.dev/unchained/malware-resistant-python-without-the-guesswork) * [This Shit is Hard: Java Archeology at a Massive Scale](https://www.chainguard.dev/unchained/this-shit-is-hard-java-archeology-at-a-massive-scale) * [Mitigating Malware in the Python Ecosystem with Chainguard Libraries](https://www.chainguard.dev/unchained/mitigating-malware-in-the-python-ecosystem-with-chainguard-libraries) * [Announcing Chainguard Libraries for Python: Malware-Resistant Dependencies Built Securely from Source](https://www.chainguard.dev/unchained/announcing-chainguard-libraries-for-python-malware-resistant-dependencies-built-securely-from-source) * [Announcing Chainguard Libraries: Guarded Java Language Dependencies Built from Source](https://www.chainguard.dev/unchained/announcing-chainguard-libraries-guarded-java-language-dependencies-built-from-source) --- ### Dockerfile Converter URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-tools/dockerfile-conversion.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Open Source Chainguard's [Dockerfile Converter (dfc)](https://github.com/chainguard-dev/dfc) was designed to facilitate the process of porting existing Dockerfiles to use Chainguard Containers. The following platforms are currently supported: * Alpine (`apk`) * Debian / Ubuntu (`apt`, `apt-get`) * Fedora / RedHat / UBI (`yum`, `dnf`, `microdnf`) > **Note:** If you prefer a fully automated approach, [Guardener](/chainguard/guardener/dockerfile-migration/) is an AI-powered agent that can migrate, optimize, and validate your Dockerfiles with minimal manual intervention. ## Installation If you use Homebrew, you can install dfc with: ```shell brew install chainguard-dev/tap/dfc ``` If you prefer to use the Go toolchain, you can install dfc directly from source. The following command will download the latest version of dfc and install it in your `$GOPATH/bin` directory: ```shell go install github.com/chainguard-dev/dfc@latest ``` If that does not work, make sure the PATH environment variable is properly set: ```shell export PATH="$PATH:$(go env GOPATH)/bin" ``` To verify that the installation was successful, run: ```shell dfc -v ``` You will receive output indicating which version of dfc you have installed, such as: ``` dfc version v0.9.3 ``` ## Getting started DFC supports inline conversion of entire Dockerfiles and single `FROM` and `RUN` instructions. It can also read Dockerfiles from files or standard input (stdin). The converted Dockerfile will use Chainguard Containers as the base image, which are available at `cgr.dev/`. ### Inline usage With inline usage, you can convert single instructions or entire Dockerfiles. For example, to convert a single `FROM` line, you can run: ```shell echo "FROM node" | dfc - ``` This will give you the following result: ``` FROM cgr.dev/ORG/node:latest ``` The `ORG` here is a placeholder that you should replace with your unique organization's namespace at Chainguard, or use `chainguard` for the free tier of images. In the [customizing section](#customizing-the-conversion) you'll learn how to use the `--org` flag to set this up automatically. You can also convert single `RUN` directives such as the following: ```shell echo "RUN apt-get update && apt-get install -y nano" | dfc - ``` Which will give you the following output: ``` RUN apk add --no-cache nano ``` It is also possible to convert a whole Dockerfile using inline mode. Here we use a heredoc input stream to create the Dockerfile content and pipe it to `dfc`: ```shell cat < ./Dockerfile.converted ``` You can also pipe the Dockerfile’s contents from stdin: ```shell cat ./Dockerfile | dfc - ``` ## How it works DFC was designed to work offline by default, which means it doesn't validate image names or check for the existence of images in a live registry. Instead, it relies on a set of rules and mappings to convert Dockerfiles to use Chainguard Containers. This includes not only the change of base image used in a Dockerfile, but also the conversion of package managers and commands used in `RUN` instructions. ### FROM conversion DFC will convert the `FROM` instruction in a Dockerfile using two main mechanisms: * **Registry Path Rewriting:** `dfc` programmatically rewrites the registry path in all `FROM` instructions to align with the Chainguard registry format. By default, it inserts a placeholder for the organization name, which can be explicitly set using the `--org` flag. This mechanism ensures that resulting images are sourced from the appropriate organization namespace within the Chainguard container registry. * **Image Name Translation:** `dfc` also performs automated translation of image names based on an internal mapping file. This mapping correlates common base images and their versions to their Chainguard Container equivalents. Custom mappings can be defined to accommodate project-specific requirements, as detailed in a subsequent section. When a mapping does not exist for a specific image, `dfc` will default to use the same image name as the original Dockerfile. For example, the following command will convert an inline `FROM` instruction using `node` as the base image: ```shell echo "FROM node" | dfc - ``` You will receive the following output: ```shell FROM cgr.dev/ORG/node:latest ``` ### Tag mappings The tag conversion process follows a set of rules: 1) For `chainguard-base`, always use the `latest` tag. * This means that if the base image is `debian`, it will be converted to `cgr.dev/ORG/chainguard-base:latest`. 2) When no tag is specified, or when a tag is not semantic, use `latest`. * For example, `FROM node` will be converted to `FROM cgr.dev/ORG/node:latest`. 3) When a tag is specified, truncate semantic to major.minor (e.g.: 1.2.3 to 1.2). * For example, `FROM node:14.17.3` will be converted to `FROM cgr.dev/ORG/node:14.17`. * If the tag is not semantic (e.g., `latest`, `stable`, etc.), it will be converted to `latest`. 4) For any version except `chainguard-base`, add `-dev` suffix when there are `RUN` commands. * For example, `FROM node:14` will be converted to `FROM cgr.dev/ORG/node:14-dev` if the Dockerfile has `RUN` instructions. * If there are no `RUN` commands, it will not have the `-dev` suffix, e.g., `FROM node:14` will be converted to `FROM cgr.dev/ORG/node:14`. More Examples: * `FROM node:14` → `FROM cgr.dev/ORG/node:14-dev` (if stage has RUN commands) * `FROM node:14.17.3` → `FROM cgr.dev/ORG/node:14.17-dev` (if stage has RUN commands) * `FROM debian:bullseye` → `FROM cgr.dev/ORG/chainguard-base:latest` (always) * `FROM golang:1.19-alpine` → `FROM cgr.dev/ORG/go:1.19-dev` (if stage has RUN commands) * `FROM node:${VERSION}` → `FROM cgr.dev/ORG/node:${VERSION}-dev` (if stage has RUN commands) ### Package installations When a Dockerfile uses package managers such as `apt`, `dnf`, or `yum` to install software, `dfc` automatically translates these commands to their `apk` equivalents. This conversion leverages an internal mapping file to resolve package name differences between distributions, ensuring accurate and compatible package installation within the Chainguard Containers environment. In addition to that, DFC automatically includes a `USER root` directive when it detects package installation commands in the Dockerfile. This is necessary because Chainguard's container images are built with a non-root user by default, and package installation commands typically require root privileges. For example, consider the following Dockerfile based on Fedora: ```Dockerfile FROM fedora RUN dnf -y update && dnf clean all && dnf -y install python-pip && dnf clean all ADD . /src RUN cd /src; pip install -r requirements.txt EXPOSE 8080 CMD ["python", "/src/index.py"] ``` The following command will convert this Dockerfile and print the results to stdout: ```shell dfc Dockerfile ``` You will receive the following output: ```Dockerfile FROM cgr.dev/ORG/chainguard-base:latest USER root RUN apk add --no-cache python-pip ADD . /src RUN cd /src; pip install -r requirements.txt EXPOSE 8080 CMD ["python", "/src/index.py"] ``` ### BusyBox changes for user and group management DFC will automatically change any `useradd` and `groupadd` instructions in your Dockerfile to `adduser` and `addgroup` to match Busybox’s syntax. This ensures that user and group creation commands execute correctly within the Chainguard Containers environment, maintaining consistent behavior and preventing build failures. For example, consider the following Dockerfile that adds a `nonroot` user to the image: ```Dockerfile FROM php:8.3-cli RUN apt-get update && apt-get install -y \ git \ curl \ libxml2-dev \ zip \ unzip # Install Composer and set up application COPY --from=composer:latest /usr/bin/composer /usr/bin/composer WORKDIR /app COPY . /app # set up nonroot system user RUN useradd -r -s /bin/bash nonroot && \ chown -R nonroot /app && \ cd /app && composer install USER nonroot ENTRYPOINT [ "php", "minicli", "mycommand" ] ``` When converting this Dockerfile with `dfc`, the `useradd` command will be replaced with `adduser`, and the resulting Dockerfile will look like this: ```Dockerfile FROM cgr.dev/ORG/php:8.3-dev USER root RUN apk add --no-cache curl git libxml2-dev unzip zip # Install Composer and set up application COPY --from=composer:latest /usr/bin/composer /usr/bin/composer WORKDIR /app COPY . /app # set up nonroot system user RUN adduser --system --shell /bin/bash nonroot && \ chown -R nonroot /app && \ cd /app && \ composer install USER nonroot ENTRYPOINT [ "php", "minicli", "mycommand" ] ``` ### Multi-stage Dockerfiles Multi-stage builds are crucial for creating smaller, more secure, and efficient container images. By separating the build environment from the final runtime environment, they significantly reduce the final image size by discarding unnecessary build-time dependencies. This also enhances security by minimizing the attack surface. DFC is designed to recognize and process multi-stage Dockerfiles the same way it handles regular single-stage Dockerfiles, converting them to leverage the advantages of Chainguard Containers. Consider this multi-stage Python Dockerfile, which includes a few build-time dependencies and a leaner runtime: ```Dockerfile FROM python:3.9 as builder WORKDIR /app RUN apt update && apt install -y curl git ENV PATH="/venv/bin:$PATH" RUN python -m venv /app/venv COPY requirements.txt /app RUN pip install --no-cache-dir -r requirements.txt FROM python:3.9-slim WORKDIR /app ENV PATH="/venv/bin:$PATH" COPY main.py /app COPY --from=builder /app/venv /venv CMD ["python", "/app/main.py"] ``` Running DFC on this Dockerfile with default options will give you the following result: ```shell FROM cgr.dev/ORG/python:3.9-dev AS builder USER root WORKDIR /app RUN apk add --no-cache curl git ENV PATH="/venv/bin:$PATH" RUN python -m venv /app/venv COPY requirements.txt /app RUN pip install --no-cache-dir -r requirements.txt FROM cgr.dev/ORG/python:3.9 WORKDIR /app ENV PATH="/venv/bin:$PATH" COPY main.py /app COPY --from=builder /app/venv /venv CMD ["python", "/app/main.py"] ``` As you will notice, DFC used a _distroless_ Python Chainguard Container for the final runtime stage: `cgr.dev/ORG/python:3.9`. That is possible because DFC didn't detect any `RUN` instruction in the final stage. If we had system-level dependencies installed via `apk` or other `RUN` instructions in the runtime stage, DFC would instead default to the regular, fully-featured Python image from Chainguard Containers, which in this case would be `cgr.dev/ORG/python:3.9-dev`. For more details on distroless and how to work with multi-stage builds, check our guide on [Getting started with distroless](/chainguard/containers/concepts/getting-started-distroless/). ## Customizing the conversion There are several ways to customize the conversion process of Dockerfiles using `dfc`. ### Setting the ORG By default, `dfc` uses `ORG` as a placeholder for the image registry address. You can provide the `--org` parameter to specify the organization that you’re a member of. To use free tier images, use `chainguard` as the organization. For example, the following command will convert a Dockerfile and set the organization to `chainguard`: ```shell echo "FROM node" | dfc --org chainguard - ``` You will receive output similar to this: ``` FROM cgr.dev/chainguard/node:latest ``` ### Using a custom registry You can also specify a custom registry to use for the conversion. This is useful if you have your own registry. To do this, use the `--registry` flag followed by the desired registry URL. For example, if you want to use `myregistry.example.com` as the registry, you can run: ```shell echo "FROM node" | dfc --registry myregistry.example.com - ``` You'll get output like this: ``` FROM myregistry.example.com/node:latest ``` One thing to note is that the `--registry` flag will override the `--org` flag, so if you specify both, the `--registry` will take precedence. ### Using custom mappings You can also provide a custom mapping file to `dfc` using the `--mapping` flag. This allows you to define your own rules for converting Dockerfiles, such as specific image names or package names that should be translated differently. For example, consider the following Dockerfile based on the `php:fpm` image: ```Dockerfile FROM php:fpm RUN apt-get update && apt-get install -y \ git \ curl \ libxml2-dev \ zip \ unzip # Install Composer and set up application COPY --from=composer:latest /usr/bin/composer /usr/bin/composer RUN mkdir /application COPY . /application/ RUN cd /application && composer install ``` By default, DFC will convert the `php:fpm` base image to Chainguard's main PHP image, `php:latest-dev`. However, we'd like this to be converted to `php:latest-fpm-dev`, since that is the FPM variant of the PHP image in the Chainguard registry. To handle that case, we can create a custom mapping file called `mappings.yaml` with the following content: ```yaml images: php:fpm: php:latest-fpm-dev ``` Then, when running `dfc`, we can specify this mapping file using the `--mapping` flag: ```shell dfc Dockerfile --mappings mappings.yaml ``` And this will produce the following output: ```Dockerfile FROM cgr.dev/ORG/php:latest-fpm-dev USER root RUN apk add --no-cache curl git libxml2-dev unzip zip # Install Composer and set up application COPY --from=composer:latest /usr/bin/composer /usr/bin/composer RUN mkdir /application COPY . /application/ RUN cd /application && composer install ``` ## Advanced topics ### Making in-place changes By default, dfc will print the converted Dockerfile to stdout, and won’t make any changes to your original Dockerfile. You can use the `--in-place` flag to make dfc overwrite the original file. This will also create a `.bak` file to back up the original file contents. ```shell dfc Dockerfile --in-place ``` ``` 2025/03/14 14:54:21 saving dockerfile backup to Dockerfile.bak 2025/03/14 14:54:21 overwriting Dockerfile ``` This method does not work with inline input. ### Working with JSON output If you plan on using `dfc` programmatically, the JSON output can come in handy. For example, the following will convert an inline Dockerfile and output the results in JSON format, parsed by `jq` for readability: ```shell cat < If you run into any issues with your customized container images or with using the Custom Assembly tool, please reach out to your account team for assistance. ### Installing packages from a Chainguard private APK repository Chainguard offers [Private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) which you can use to access the apk packages available to your organization. You can use your organization's private APK repository to further customize your Custom Assembly containers. > Note: [Chainguard OS Packages](https://edu.chainguard.dev/chainguard/containers/building-and-modifying/packages/private-apk-repos/#chainguard-os-packages)—available to larger customers who already build their own images from packages using tools like Bazel, Dockerfiles, and `rules_apko`—is not currently available for use with Chainguard Custom Assembly. As an example, run a container with a Custom Assembly container image that has a shell and package manager, such as a `-dev` variant of a customized container image: ```shell docker run -it --entrypoint /bin/sh --user root \ -e "HTTP_AUTH=basic:apk.cgr.dev:user:$(chainctl auth token --audience apk.cgr.dev)" \ cgr.dev/$ORGANIZATION/$CUSTOMIZED-CONTAINER:latest-dev ``` Note that this command injects an `HTTP_AUTH` environment variable directly into the container by calling `chainctl` from the host machine to obtain an ephemeral token. This is necessary to authenticate to the private repository. By default, your organization's private APK repository will be listed in the container's list of APK repositories: ```container cat /etc/apk/repositories ``` ```Output https://apk.cgr.dev/45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595 ``` The repository address in this file (which includes a long unpronounceable string) will differ from the one shown in the Console (which reflects the organization name). The string shown in the `repositories` file is the ID number of the organization. You can confirm this by running the `chainctl iam organizations ls -o table` command. To search for and install packages from the private APK repository, first update the package index: ```container apk update ``` ```Output fetch https://apk.cgr.dev/45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595/x86_64/APKINDEX.tar.gz [https://apk.cgr.dev/45a0c3X4MPL3977f03X4MPL3ac06a63X4MPL3595] OK: 1019 distinct packages available ``` Then you can search for packages available in your private repo. The following example searches for packages named "mongo": ```container apk search mongo ``` ```Output mongo-5.0-5.0.31-r0 mongo-6.0-6.0.20-r0 mongo-7.0-7.0.16-r0 mongo-8.0-8.0.4-r1 mongod-5.0-5.0.31-r0 mongod-6.0-6.0.20-r0 mongod-7.0-7.0.16-r0 mongod-8.0-8.0.4-r1 ``` Finally, you can install a package with `apk`: ```container apk add mongo ``` ```Output (1/1) Installing mongo-8.0 (8.0.4-r1) Executing busybox-1.37.0-r0.trigger OK: 719 MiB in 78 packages ``` To learn more, refer to our [Private APK repositories documentation](/chainguard/containers/building-and-modifying/packages/private-apk-repos/). ## Custom runtime repositories

Note: Custom runtime repositories is in beta. Contact your Chainguard account team to enable it for your organization.

By default, Custom Assembly images have `/etc/apk/repositories` pointing to Chainguard's `virtualapk.cgr.dev` tracking proxy. Some organizations mirror Chainguard's APK feed to internal registries — for example, to satisfy internal security policies that require all package sources to resolve to internal infrastructure. Custom Assembly lets you specify custom APK repository URLs that replace the default `virtualapk.cgr.dev` entries in `/etc/apk/repositories`. This means runtime `apk add` commands inside the container resolve packages from your internal mirror instead of Chainguard's endpoints, without requiring Dockerfile modifications. This setting doesn't affect build-time package resolution. Packages are always fetched from `apk.cgr.dev` during the Custom Assembly build, preserving Chainguard's supply-chain guarantees. The custom repository URLs are only written to the image's `/etc/apk/repositories` file for use at runtime. To learn how to configure custom runtime repositories using `chainctl`, refer to the [Custom runtime repositories section](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#custom-runtime-repositories) of the `chainctl` Custom Assembly guide. ### Custom runtime keys By default, Custom Assembly images trust only the Chainguard signing key, which is embedded in the image at build time. If your mirror re-signs packages with its own key, `apk` inside the container rejects those packages because it can't verify the new signature. Custom runtime keys let you embed your mirror's public signing key in the image's `/etc/apk/keys` directory alongside the Chainguard key. With the key in place, runtime `apk add` commands can verify packages from mirrors that re-sign them. To learn how to configure custom runtime keys using `chainctl`, refer to the [Custom runtime keys section](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#custom-runtime-keys) of the `chainctl` Custom Assembly guide. ### Limitations and compatibility Whether a mirror requires a custom runtime key depends on how it signs packages: * **Mirrors that preserve Chainguard signatures work without additional configuration.** For example, remote repositories in [JFrog Artifactory](/chainguard/containers/registry/pull-through-guides/artifactory-packages-pull-through/) act as pull-through caches and serve packages with their original signatures intact, so the embedded Chainguard signing key verifies them. * **Mirrors that re-sign packages with their own key require a custom runtime key.** Artifactory virtual repositories add their own signing key to packages, and Sonatype Nexus requires its own signing key for Alpine repositories. To use these mirrors, embed the mirror's public signing key in the image as a custom runtime key. > **Note**: Custom runtime repository URLs must use HTTPS. Chainguard does not validate the reachability of custom repository URLs at configuration time. A misconfigured URL will not cause build failures, but will cause runtime `apk add` failures inside the container. ## Troubleshooting Build failures can occur for a number of reason, including the following: * It's possible for users to select packages that conflict with each other. For example, if two packages install the same files, Custom Assembly may not be able to resolve the conflict and result in a failed build. * Large images taking longer than 1 hour to build will fail with a timeout error. In any case, you won't know whether a container image build fails until after it's complete. If you need assistance troubleshooting, please [reach out to our Customer Support team](https://www.chainguard.dev/contact?utm=docs). ## Learn more This article provided a high-level overview of Custom Assembly. As a next step, we encourage you to checkout our guide on [managing Custom Assembly resources through the Chainguard Console](/chainguard/containers/custom-assembly/custom-assembly-console/). You can also interact with Custom Assembly using [`chainctl`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/) as well as [the Chainguard API](/chainguard/containers/custom-assembly/custom-assembly-api-demo/). You can also add custom certificates to Custom Assembly images. Refer to our guide on [Adding custom certificates with Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/) for more information. To run startup logic in a Custom Assembly image without a derived image build, refer to [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/). It resolves secrets, runs preflight checks, and overrides the command at container start. We encourage you to check out our resources on our other [Chainguard Containers features](/chainguard/containers/), including the following: * [Unique tags](/chainguard/containers/reference/unique-tags/) * [CVE visualizations](/chainguard/containers/security-and-compliance/vulnerability-management/cve-visualizations/) Additionally, for more information on working with Chainguard Containers, refer to our docs on [How to use Chainguard Containers](/chainguard/containers/using-and-deploying/). --- ### Chainguard criteria for determining whether to build a container image URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/how-we-build-and-test/what-chainguard-will-build.md Last Modified: September 28, 2026 Tags: Chainguard Containers There are currently over [2,000 Chainguard Containers](https://images.chainguard.dev/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) and that number is always growing as we add more to our expanding catalog. If you would like a Chainguard Container that is not yet available, or inquire about whether we would build a given container image, Chainguard will endeavor to perform an analysis on the request. Chainguard aims to build new container images that are relevant to our customers and to support broader software security goals. However, it is not always feasible to package and build software. Please note that we have the following general criteria when considering requests. * The source code is freely available in a Source Code Manager (SCM) system such as GitHub or GitLab. * The project is licensed under a FOSS license. Non-FOSS licenses are reviewed on a case-by-case basis. * The project is actively maintained with supported versions. That is: * There are recent commits and releases. * The project has not reached its end of life (EOL). * The project has maintained active versions. * The project does not rely on outdated or unmaintained dependencies; for example, unsupported versions of Python, OpenSSL etc. * There is usually a lead or owner. * There are no technical blockers preventing us from building the container image as we’ve determined as part of an initial analysis process. If the software project or container image you have in mind meets the above criteria, please [contact us](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) for more information. --- ### Chainguard shared responsibility model URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/shared-responsibility-model.md Last Modified: October 8, 2026 Tags: Conceptual, Chainguard Containers Chainguard’s mission is to be the safe source for open source. As part of this mission, Chainguard builds all of our packages and images from upstream open source code and delivers the resulting artifacts to our customers. There are three distinct parties involved here: **Upstream** projects, **Chainguard**, and **Customers**; each of these parties share some measure of responsibility across a few dimensions.
Diagram representing the Chainguard-based open source software supply chain

This guide is an overview of Chainguard's Shared Responsibility Model: a framework that outlines the security responsibilities of upstream open source software projects, Chainguard, and its customers. The dimensions of shared responsibility this guide covers are: * **Releases**: defining and tracking what is and is not supported * **Patching**: defining which parties are responsible for patching each element of what goes into a container image * **Testing**: defining which parties are responsible for testing what scope of functionality ## Releases Upstream projects are responsible for cutting releases and documenting its supported release policy, including any rules it adheres to around quality or breaking changes, and (in the case of more mature projects) a release cadence or schedule. Chainguard is responsible for building all of the upstream-supported versions of a piece of software. Customers are responsible for staying on one of the upstream-supported versions of a piece of software. A common pitfall we see, which drives an enormous incidence of CVEs across our prospects, is the use of "end-of-life" (or "EOL") software. The following diagram, taken from the [Risk section of Sonatype's 2024 State of the Software Supply Chain report](https://www.sonatype.com/state-of-the-software-supply-chain/2024/risk), highlights how more EOL components per application tends to lead to more security vulnerabilities:
Chart from Sonatype's 2024 State of the Software Supply Chain report. This chart is a scatter plot with two axes, with the y-axis labeled 'Vulnerabilities' and the x-axis labeled 'Number of EOL Packages per Application.' The far left of the x-axis represents a lower number of EOL packages per application and the far right represents a higher number. There is a dotted line showing an upward trend of more vulnerabilities as the number of EOL packages per application increases.

It is a customer’s responsibility to stay on a supported version of the software, and the responsibility of Chainguard to ensure that the customer receives builds of that version of the software to consume in their private registry on `cgr.dev`. ## Patching Upstream projects are responsible for staying on supported releases of their own dependencies, such as with an automatic dependency update tool like [dependabot](https://github.com/dependabot) or [Renovate](https://docs.renovatebot.com/). Upstream projects are also, at a minimum, responsible for staying on an API-compatible version of libraries they depend on transitively. Chainguard is responsible for assembling container images from fully patched upstream software. You can find more details on this in our [SLA for CVEs](https://www.chainguard.dev/cve-sla). Chainguard will also attempt to rebuild upstream software with [the latest toolchain](https://www.chainguard.dev/unchained/chainguard-patches-3-silent-golang-cves-in-under-24-hours) and their dependencies updated where that can be done without breaking changes (refer to the following [Testing section](#testing)). Customers are responsible for building on or with fully patched Chainguard container images, and for patching any components they add to the Chainguard container image. There are generally two form-factors of Chainguard container images: **Application** and **Base** images, so let’s go over the patching responsibilities through these respective lenses. ### Application container images These are Chainguard container images that users generally just take and run (for example, by plugging into Helm). We created the following diagram to help customers understand where the division of responsibility is generally drawn for this class of images:
Diagram representing Chainguard's shared responsibility model for Application Images. This diagram shows a pyramid structure with 4 tiers: Apps, LangLibs (go mod, pom, …), Toolchains (go, java, php, …), and System (glibc, openssl). To the left of the pyramid there are two brackets showing that CVE management is performed by the Upstream Open Source project at the Apps level and by Chainguard at the Toolchains and System level, with responsibility being split at the Language Libraries level. To the right of the pyramid are two smaller brackets showing that if updating a dependency is breaking, the CVE management falls to the Upstream project; if it's successful, then CVE management falls to Chainguard.

Upstream projects are responsible for staying on API-compatible versions of libraries. Chainguard is responsible for rebuilding the upstream project [with the latest toolchain](https://www.chainguard.dev/unchained/chainguard-patches-3-silent-golang-cves-in-under-24-hours), and patching static and dynamic dependencies where such a change is non-breaking. Customers are responsible for tracking a supported version of the Chainguard image. Please refer to our [Product release lifecycle documentation](/chainguard/containers/concepts/lifecycle-and-eol/versions/) for more information on what versions are supported. ### Base container images These are Chainguard container images that users extend with their own packages and applications (such as with a Dockerfile). We created the following diagram to clarify where the division of responsibility is drawn for these images:
Diagram representing Chainguard's shared responsibility model for Base Images. The diagram takes the shape of a pyramid with 3 tiers: User App, Toolchains (go, java, php, …), and System (glibc, openssl, …). To the left of this pyramid are two brackets, showing that user apps (the custom code level) are serviced by customers and the Toolchains and System levels are serviced by Chainguard.

Upstream projects are responsible for patching supported releases in a timely manner. Chainguard is responsible for releasing fully patched toolchain and base images. Customers are responsible for patching any applications and dependencies they add to a Chainguard image. We recommend using a fully patched Chainguard toolchain image to build the application, and using a fully patched Chainguard base image to layer the final application on. ## Testing Upstream projects are responsible for defining conformance criteria and establishing conformance benchmarks (such as [Java TCK](https://www.chainguard.dev/unchained/chainguards-openjdk-java-images-are-now-jck-conformant), [Kubernetes Conformance](https://github.com/cncf/k8s-conformance/blob/master/instructions.md), [Knative Conformance](https://github.com/knative/specs)) that clearly outline the criteria for a downstream distribution to represent itself as a "conformant" distribution of the software. Chainguard is responsible for producing conformant distributions of upstream applications; where the upstream project does not define conformance criteria Chainguard is responsible for using its discretion in validating the functionality of the application and (within reason) accepting scenarios from Customers to run as part of the Chainguard qualification process. Refer to our conceptual article on [How Chainguard tests its container images](/chainguard/containers/concepts/how-we-build-and-test/images-testing/) for more information. Customers are responsible for ensuring that Chainguard-provided images cover all of their desired functionality, and partnering with Chainguard to ensure the requisite coverage is part of our qualification process if gaps are identified. Customers are responsible for testing all of their modifications to Chainguard container images and for responsibly rolling out Chainguard container images to ensure there are no regressions specific to their environment or usage. Given the highly subjective nature of performance testing to environment and configuration, Customers are responsible for ensuring that Chainguard container images satisfy performance requirements as part of the responsible rollout process. Chainguard is committed to making our customers successful, and will partner with Customers (within reason) to investigate performance anomalies, but the Customer is responsible for ensuring Chainguard can reproduce the issue. Please refer to our [support policy](https://www.chainguard.dev/legal/support-policy) for more information on how Chainguard will partner with customers. By their minimal nature, some Chainguard container images may not include certain functionalities by default, so it is important that Customers ensure that Chainguard container images drop-in to their environments safely. Again, Chainguard is committed to making our customers successful, and will partner with Customers to ensure that images support the core scenarios in which an image is used. Following [the principle of immutability](https://www.chainguard.dev/unchained/the-principle-of-immutability), Chainguard recommends that customers pin Chainguard container images and make use of tooling such as [Dependabot](/chainguard/containers/security-and-compliance/updating-containers/dependabot/), [Renovate](/chainguard/containers/security-and-compliance/updating-containers/renovate/), or [our own Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/) to qualify image updates through the customers’ CI/CD processes covering in-scope usage scenarios. This is key to responsibly rolling out changes because the reality is that upstream, Chainguard, and customers are all fallible and regressions can happen; but this pattern enables folks to have a clear rollback story. ## Learn more We encourage you to check out our other resources on recommended practices to ensure that your Chainguard container images are effectively maximizing your organization's security posture. As example, you can read through our conceptual articles on [Strategies for minimizing your CVE risk](/chainguard/containers/security-and-compliance/vulnerability-management/cve-risk/) or [Considerations for keeping container images up to date](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/). --- ### Getting started with the C/C++ Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/c.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard provides security-hardened container images for C and C++ development, offering minimal runtime environments with significantly reduced vulnerabilities compared to traditional base images. Built on Chainguard's own OS, these containers enable more secure deployment of compiled programs through purpose-built images for different linking scenarios. This guide demonstrates three approaches to compiling and running C/C++ applications using Chainguard's specialized containers. The container image with which you choose to run your compiled program depends on the nature of your binaries. Static binaries can be executed in the minimal `static` Chainguard Container, while dynamically linked binaries can be run in the `glibc-dynamic` Container. For this demonstration, you will first compile a C binary using the `gcc-glibc` Chainguard Container, and then learn how to use a multi-stage build to run the resulting binary in the `glibc-dynamic` image. You'll also cover an example showing the multi-stage build process for the C++ programming language. To learn more about the differences between these container images, read our article on [Choosing a container for your compiled programs](/chainguard/containers/concepts/choosing-a-base-container/).
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Video The content in this article is also available as a video. [Watch the video on YouTube](https://www.youtube.com/watch?v=g7fCIRJ8_pE) ## Prerequisites To follow along with this guide, you will need to have [Docker Engine](https://docs.docker.com/engine/install/) and `gcc`, the [GNU Compiler Collection](https://gcc.gnu.org/), installed on your machine. You can find the code and Dockerfiles used in our [Containers demos GitHub repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/c). ## Example 1 --- Minimal C Chainguard Container ### Step 1: Setting up a demo application To start, let's create a demo C application to run in your container. First you will create a folder to contain your demo files. The following command will create a new directory `cguide` and navigate to it. ```sh mkdir -p ~/cguide && cd ~/cguide ``` Within this directory, you will create a file to hold the code for your first program. Use the text editor of your choice to begin editing a new file named `hello.c`. We will use `nano` as an example: ```sh nano hello.c ``` Inside of your `hello.c` file, add in the following C code which will execute a "Hello, world!" application. ```C /* Chainguard Academy (edu.chainguard.dev) * Getting Started with the C/C++ Chainguard Containers * Examples 1 & 2 - C */ #include // Main Function int main(){ printf("Hello, world!\n"); printf("I am a demo from the Chainguard Academy.\n"); printf("My code was written in C.\n"); return 0; } ``` When you are done editing the file, save and close it. If you used `nano`, you can do so by pressing `CTRL + X`, `Y`, and then `ENTER`. Now, let's compile this file with `gcc`. This command uses the `-Wall` flag to display compiler errors and warnings, if any occur, and includes the `-o` flag to rename your executable to `hello`. ```sh gcc -Wall -o hello hello.c ``` Once your program has compiled, you can run it with the following command: ```sh ./hello ``` This will return the "Hello, world!" program output in your terminal if the program executed successfully. ```Output Hello, world! I am a demo from the Chainguard Academy. My code was written in C. ``` Now that you have successfully tested your example program locally, next, you will compile and run it from inside of a container image. ### Step 2: Creating the Dockerfile An advantage of choosing to run your code inside of containerized environments is portability. In the previous step, `gcc` compiled the binary to run on your machine. However, if you were to run this binary on a different operating system, it likely will fail to execute properly. Using a container ensures that your program will run on any machine as the containerized environment will be consistent across platforms. Let us begin by creating a Dockerfile called `Dockerfile1` for your container image. ```sh nano Dockerfile1 ``` This Dockerfile will do the following: 1. Use the `gcc-glibc:latest` Chainguard Container as the base image; 2. Create and set the current working directory to `/home/build`; 3. Copy the `hello.c` program code to the current directory; 4. Compile the program and name it `hello`; 5. Copy the compiled binary to `/usr/bin`; 6. Set the image to run as a non-root user; and, 7. Execute the compiled binary when the container is started. ```Dockerfile # Example 1 - Single Stage Build for C FROM cgr.dev/chainguard/gcc-glibc:latest RUN ["mkdir", "/home/build"] WORKDIR /home/build COPY hello.c ./ RUN ["gcc", "-Wall", "-o", "hello", "hello.c"] RUN ["cp", "hello", "/usr/bin/hello"] USER 65532 ENTRYPOINT ["/usr/bin/hello"] ``` Add this text to your Dockerfile, save, and close it. Next, use the Dockerfile you just created to build a container image named `example1` by running the following command. The `-f` flag specifies the Dockerfile which you are using to build from, and the `-t` flag will tag your image with a meaningful name. ```sh docker build -f Dockerfile1 -t example1:latest . ``` With your container image built, you can now run it with the following command. ```sh docker run --name example1 example1:latest ``` You will see output in your terminal identical to that of the binary you compiled locally. ```Output Hello, world! I am a demo from the Chainguard Academy. My code was written in C. ``` In the next example, we will look at an alternative way to run your binary using a multi-stage build. ## Example 2 --- Multi-stage build for C applications In our first example, you successfully compiled and executed your C binary in the `gcc-glibc` image. To go a step further, you can use a multi-stage build, allowing you to compile your program in one image and execute it in another image. A multi-stage build gives you more control over your final image, as you can transfer your program to an image with a smaller footprint after build time to reduce your program's attack surface. The `glibc-dynamic` image, which you will use as your second stage in the build, does not contain `gcc`. Because of this, a malicious binary could not be compiled by an attacker tampering with the image. ### Creating the Dockerfile Create a new Dockerfile called `Dockerfile2`. ```sh nano Dockerfile2 ``` This time, the Dockerfile will do the following: 1. Use the `gcc-glibc` Chainguard Container as the builder stage; 2. Create and set the current working directory to `/home/build`; 3. Copy your example `hello.c` program code to the current directory; 4. Compile the program using `gcc` and name it `hello`; 5. Begin a new stage using the `glibc-dynamic` Chainguard Container; 6. Copy the compiled binary to `/usr/bin` from the builder stage; 7. Set the image to run as a non-root user; and, 8. Execute your binary from the `glibc-dynamic` image when the container is started. ```Dockerfile # Example 2 - Multi-Stage Build for C FROM cgr.dev/chainguard/gcc-glibc:latest AS builder RUN ["mkdir", "/home/build"] WORKDIR /home/build COPY hello.c ./ RUN ["gcc", "-Wall", "-o", "hello", "hello.c"] FROM cgr.dev/chainguard/glibc-dynamic:latest COPY --from=builder /home/build/hello /usr/bin/ USER 65532 ENTRYPOINT ["/usr/bin/hello"] ``` When you are finished editing your Dockerfile, save and close it. With the new Dockerfile created, you can build the image. Execute the following command in your terminal to build your multi-stage image. ```sh docker build -f Dockerfile2 -t example2:latest . ``` With your image built, you can now run it with the following command. ```sh docker run --name example2 example2:latest ``` You will see output in your terminal identical to that of the previous example. ```Output Hello, world! I am a demo from the Chainguard Academy. My code was written in C. ``` Having your program execute from a smaller container image with less packages reduces your potential attack surface, making it a more secure approach for production-facing builds. ## Example 3 --- Multi-stage build for C++ applications So far, our demonstrations have featured a program coded in C. A similar image building process applies to binaries compiled for the C++ programming language. ### Step 1: Setting up a demo application In your terminal, create a new file called `hello.cpp`. ```sh nano hello.cpp ``` Add the following C++ code to the file you just created. This code will display a greeting specifying that it was written in C++. ```C++ /* Chainguard Academy (edu.chainguard.dev) * Getting Started with the C/C++ Chainguard Containers * Example 3 - C++ */ #include using namespace std; // Main Function int main(){ cout << "Hello, world!\n"; cout << "I am a demo from the Chainguard Academy.\n"; cout << "My code was written in C++.\n"; return 0; } ``` When you are done editing your file, save and close it. You can now compile your C++ program using `g++`. Execute the following command in your terminal to compile the program. The command will display any compiler warnings or errors and will name the resultant binary `hello`. ```sh g++ -Wall -o hello hello.cpp ``` Now you can test your compiled binary. ```sh ./hello ``` You will see the following output in your terminal. ```Output Hello, world! I am a demo from the Chainguard Academy. My code was written in C++. ``` Now that you have confirmed that your C++ program executes, you are ready to build it inside of a container image. ### Step 2: Creating the Dockerfile With a working C++ example, you can compile and run our program using a multi-stage build. With the text editor of your choice, create a new file named `Dockerfile3`. ```sh nano Dockerfile3 ``` This Dockerfile will do the following: 1. Use the `gcc-glibc` Chainguard Container as the builder stage; 2. Create and set the current working directory to `/home/build`; 3. Copy your example `hello.cpp` program code to the current directory; 4. Compile the program using `g++` and name it `hello`; 5. Begin a new stage using the `glibc-dynamic` Chainguard Container; 6. Copy the compiled binary to `/usr/bin` from the builder stage; 7. Set the image to run as a non-root user; and, 8. Execute your binary from the `glibc-dynamic` image when the container is started. ```Dockerfile # Example 3 - Multi-Stage Build for C++ FROM cgr.dev/chainguard/gcc-glibc:latest AS builder RUN ["mkdir", "/home/build"] WORKDIR /home/build COPY hello.cpp ./ RUN ["g++", "-Wall", "-o", "hello", "hello.cpp"] FROM cgr.dev/chainguard/glibc-dynamic:latest COPY --from=builder /home/build/hello /usr/bin/ USER 65532 ENTRYPOINT ["/usr/bin/hello"] ``` When you are finished editing your Dockerfile, save and close it. With your new Dockerfile created, you can build the container image. Execute the following command in your terminal to build your multi-stage C++ image. ```sh docker build -f Dockerfile3 -t example3:latest . ``` With your image built, you can now run it with the following command. ``` docker run --name example3 example3:latest ``` You will see output in your terminal identical to that of the C++ binary you compiled locally. ```Output Hello, world! I am a demo from the Chainguard Academy. My code was written in C++. ``` With that, you have successfully performed a multi-stage image build for both C and C++ programs. ## Clean up After completing the previous examples, you will have containers, images, and files remaining on your local machine. This section will show you how to remove these artifacts. You can remove the containers you built by executing the following command. ```sh docker container rm example1 example2 example3 ``` Then, you can remove their associated container image builds as well: ```sh docker image rm example1:latest example2:latest example3:latest ``` To remove the directory containing your Dockerfiles, binaries, and program code, run the following command: ``` rm -r ~/cguide ``` Following these commands, all artifacts introduced in this guide will now be removed from your machine. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose C/C++ Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### How Chainguard issues Security Advisories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues.md Last Modified: October 1, 2026 Tags: Chainguard Containers, CVE When you scan a newly-built Chainguard Container with a vulnerability scanner, typically, no CVEs will be reported. However, as software packages age, more vulnerabilities are reported and CVEs will begin to accumulate in container images. When this happens, Chainguard releases security advisories to communicate these vulnerabilities to downstream images users. > **Note:** Advisory timestamps represent when updates are made to the advisory page, not when they were first detected and triaged by Chainguard. In alignment with the [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/#examples-of-what-chainguard-supports-and-maintains-for-chainguard-containers), our vulnerability management strategy focuses on the latest versions of any given release track, as these are the versions we actively maintain and secure. Accordingly, we only publish new CVE advisories for packages that fall within our defined support scope. We do not actively monitor non-supported versions of a package or image. Our efforts are centered on keeping the latest versions up-to-date and as close to zero CVEs as we can, while encouraging customers to upgrade and stay on supported versions. On this page, learn about [how to access Security Advisories](#accessing-security-advisories) and [the stages of a Security Advisory](#stages-of-a-security-advisory), from CVE disclosure to remediation. ## Accessing Security Advisories ### In the Chainguard images directory Chainguard publishes its security advisories to a dedicated [Security Advisories page](https://images.chainguard.dev/security/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-security-advisories-how-chainguard-issues) on the container images Directory. There, you can find a complete listing of CVEs found in various Chainguard Containers, including their CVE ID, affected packages, and vulnerability status. Each advisory is built from the metadata associated with a security vulnerability. ### Programmatic access For automated vulnerability monitoring and integration with CI/CD pipelines, Chainguard publishes its security advisories as a feed in the [OSV format](https://ossf.github.io/osv-schema/). The OSV feed covers packages from both the Wolfi and Chainguard package repositories, and it includes every advisory status, from detections to fixes. Vulnerability scanners, security tools, and your own automation can read it. The feed is available in two current versions, which contain the same data: - **v2:** [advisories.cgr.dev/chainguard/v2/osv/all.json](https://advisories.cgr.dev/chainguard/v2/osv/all.json). Each record collects all of Chainguard's advisories for one upstream vulnerability, such as a CVE. - **v3:** [advisories.cgr.dev/chainguard/v3/osv/all.json](https://advisories.cgr.dev/chainguard/v3/osv/all.json). Each record holds a single advisory. Each `all.json` file is an index. The advisory details, including each affected package's fixed version in `affected[].ranges[].events[].fixed`, are in the individual record files, such as `https://advisories.cgr.dev/chainguard/v2/osv/.json`. Chainguard publishes updates several times a day. An integration should poll the index at least hourly and re-fetch any record whose `modified` timestamp has changed. For an example lookup, see [Look up the fixed version in the advisory feed](/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve/#look-up-the-fixed-version-in-the-advisory-feed). Older feeds remain available, but they're deprecated for new integrations: - The original OSV feed at `packages.cgr.dev/chainguard/osv/all.json`, which includes only resolved advisories. - The [Alpine-style `secdb`](https://deepwiki.com/aquasecurity/trivy-db/4.2.6-alpine-security-database) feeds at `packages.wolfi.dev/os/security.json` and `packages.cgr.dev/chainguard/security.json`. For the feed specifications, see [Foundational concepts](https://github.com/chainguard-dev/vulnerability-scanner-support/blob/main/docs/foundational_concepts.md) in Chainguard's [vulnerability scanner support](https://github.com/chainguard-dev/vulnerability-scanner-support/tree/main) GitHub repository. ## Stages of a security advisory If you’re wondering how these security advisories are made, you’re in the right place! In this section, we will walk through the life of a security advisory -- starting from a CVE’s disclosure, all the way to its remediation. We’ll also explore what happens after an advisory is released and how its record may be updated over time. ### Stage 1: A CVE is disclosed All security advisories begin with the disclosure of a security vulnerability. The [CVE Project](https://www.cve.org/) coordinates the processing of reported vulnerabilities through a network of CVE Numbering Authorities (CNAs). CNAs assign CVE IDs to new entries, and they are then added to a CVE Catalog. Each catalog entry contains information such as what packages or components are affected by the vulnerability, their versions, and remediation procedures, if applicable. ### Stage 2: Scanners detect the CVE The [National Vulnerability Database (NVD)](https://nvd.nist.gov/), the U.S. government vulnerability repository, will pick up these CVE records and review them further. During this secondary review process, the CVE entry is enriched with details that scanners later use to identify affected software. This process can take some time, so there will be issued CVEs that have not yet been analyzed by the NVD. These CVEs pending review will be marked as such by the NVD, as shown in the following image. ![NVD notice stating a vulnerability has not yet been analyzed](nvd-analyze.png) In addition to the NVD, vulnerability scanners also reference other databases such as the [GitHub Advisory Database](https://github.com/advisories) (GHSA) and the [Go Vulnerability Database](https://vuln.go.dev/). ### Stage 3: Advisory is issued Once a CVE has been reviewed by the NVD, it will be picked up by vulnerability scanners and reported in any affected container images. Chainguard uses [Grype](https://github.com/anchore/grype), an open-source vulnerability scanner from Anchore, as its primary tool for vulnerability detection. The newly detected CVE is then moved into the next phase where it waits for a team member to assess it. A security advisory will be issued with the status of "Under Investigation" to alert downstream users that Chainguard is aware of its presence. Security advisories are issued per package, as one CVE may impact different packages in different ways. From there, this security advisory will be updated over time. ### Stage 4: Advisory is updated With an advisory issued for the package, further investigation is often needed to determine the impact of the CVE. In some cases, it will be determined that the CVE is not truly present in the package, therefore making it a [false positive](/chainguard/containers/security-and-compliance/working-with-scanners/false-results/). The associated security advisory would have its status updated to "Not Affected", and further updates to the advisory would not occur. If the vulnerability is a true positive finding, then it is present in the package and further action must be taken. When an upstream fix is available, such as a newer package version which remediates the CVE, then this update will be made and the advisory modified to state the vulnerability is now "Fixed". Chainguard may even proactively bump a vulnerable dependency to its newer, patched version before upstream projects have done so themselves. Or, patches issued outside of the upstream repository may be applied to remediate the vulnerability when a new package version is not yet available. Note that an end user would need to pull the new version of the container image with the updated fix for the CVE as older versions of the image would still be vulnerable. In some cases, a fix for the CVE may not yet be available. A package will be marked as having the "Pending upstream fix" status in this situation. Once an upstream fix is released, then it will be applied to the package and the advisory status updated to "Fixed". Sometimes, a vulnerability may be present in a piece of software, but remediation is not planned. This could be because the package is no longer supported, such as in the case of an outdated package version or because the software is reaching the end of its life. If this is the case, then the security advisory status will be marked as "Fix not planned". Rarely, a vulnerability is found in a package but there is no current status update on whether it can be remediated, or if plans exist to remediate it. In these few situations, the package is simply marked as being "Affected" by the vulnerability. This status is likely to be updated soon as the next steps towards remediation are established. ### Summary of advisory statuses
Status Description Metadata
Under investigation A vulnerability has been detected for the package and is awaiting further investigation to determine its impact. detection
Affected A vulnerability finding has been determined to be present and affect the package. true-positive-determination
Not affected The vulnerability was determined to not impact the package, making it a false positive finding. false-positive-determination
Pending upstream fix Remediating the vulnerability is not possible until an upstream fix is made available. pending-upstream-fix
Fixed A patch has been applied to the affected package and the vulnerability is no longer present. fixed
Fix not planned There are no plans to address the vulnerability in the package at this time. fix-not-planned
## Further reading Chainguard’s Security Advisory feed is a helpful tool to have at hand when scanning your containers for the presence of vulnerabilities. Though you won’t need it often thanks to the low CVE counts of our container images, it is a useful reference when working with your scans, giving you insight into how you can approach and fix any vulnerabilities which pop up. For more information on how to use Chainguard’s Security Advisories page to inform your vulnerability remediation, consider reading our article on [How to use Chainguard Security Advisories]( /chainguard/containers/security-and-compliance/security-advisories/how-to-use/). If you are using Chainguard Containers at your organization or want to learn more about advisories for enterprise container images, please [contact us](https://www.chainguard.dev/contact). --- ### Overview of migrating to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migrations-overview.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard's container images are minimal by design — most are [distroless](/chainguard/containers/concepts/getting-started-distroless/), with no shell or package manager. That keeps the attack surface small, but it also means moving an existing workload over usually requires adjusting how your image installs dependencies, which user it runs as, and what its entrypoint expects. This guide covers the differences that matter during a migration, a recommended rollout strategy, and what to do when something breaks. For background on what Chainguard Containers is and how its images are built, refer to the [Chainguard Containers overview](/chainguard/containers/overview/). ## Migration key points * Most of Chainguard's container images have no shell or package manager by default. This is great for security, but sometimes you need these things, especially in builder images. For those cases we have development container images (also known as `-dev` images, as in `cgr.dev/chainguard/python:latest-dev`) which do include a shell and package manager. * The development variants and `wolfi-base` / `chainguard-base` use BusyBox by default, so any `groupadd` or `useradd` commands will need to be ported to `addgroup` and `adduser`. * The free tier of Containers provides only the `:latest` and `:latest-dev` versions. Our paid Production Containers offer tags for major and minor versions. * Chainguard's container images are [based on `glibc`](/chainguard/containers/concepts/glibc-vs-musl/) and our packages cannot be mixed with Alpine packages (which are instead based on musl). * In some cases, the entrypoint in Chainguard Containers can be different from equivalent images based on other distros, which can lead to unexpected behavior. You should always check the image's specific documentation to understand how the entrypoint works. * When needed, Chainguard recommends using a base image like `chainguard-base` or a development variant to install an application's OS-level dependencies. Perhaps the best place for most users to get started with migrating to Chainguard Containers is by following our guide on [How to port a sample application to Chainguard Containers](/chainguard/containers/migration/porting-apps-to-chainguard/). This guide involves updating a sample application made up of three services to use Chainguard Containers. Although the application involved is fairly simple, the concepts outlined in the guide can also be useful for migrating more complex applications. ### Development containers As mentioned previously, Chainguard's standard container images typically do not include a shell or package manager. This helps to minimize the size of containers and also reduces their potential attack surface, but many users find that they need images that include a shell or package manager to support their specific use case. For this reason, Chainguard offers development containers (also known as `-dev` variants, since their image tags are appended with `-dev`). These variants come with more tooling than our standard container images, including a shell and package manager. Although development variants are still more secure than most popular container images based on other distros, for increased security on production environments we recommend combining them with a distroless variant in a multi-stage build. Refer to our guide on [Chainguard's Container variants](/chainguard/containers/concepts/container-variants/) for more information on development containers. ### Full variants For a number of our most popular Containers, Chainguard also offers *full* variants, whose tags are appended with `-full`. Unlike standard or development variants, full variants are designed to map to their upstream equivalent — typically the Debian-based image on Docker Hub — including the packages, environment variables, and entrypoint scripts that our minimal images omit. They exist primarily to ease migration: if your build or test pipelines depend on components from your previous image, starting with a full variant lets you adopt Chainguard Containers without untangling those dependencies first. Once you've migrated, we recommend moving to a slimmer variant that includes only what your workload needs. Refer to our guide on [Chainguard's Container variants](/chainguard/containers/concepts/container-variants/#full-container-variants) for more information on full variants. ## Before migrating Before you begin actively migrating to Chainguard Containers, review the images that your organization has access to and determine which teams and applications will be using each image. To see what a given repository uses today, which Chainguard container image replaces each upstream image, and which of those your organization can already pull, run [`chainctl images discover`](/chainguard/containers/migration/migration-tools/images-discover/) against a checkout. Notify the teams involved in the migration process so they can begin preparing. For teams that are new to Chainguard Containers, we recommend taking the online self-paced course, [Linky’s Guide to Chainguard Containers](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images). Next, determine which users and/or systems are going to need access to your Chainguard registry in order to begin preparing access. Most customers will need to copy images from their Chainguard registry into their organization's registry. An easy way to do this is by configuring the organization's registry as a pull-through mirror of the Chainguard registry. We have a guide on [how to configure Artifactory](/chainguard/containers/registry/pull-through-guides/artifactory-containers-pull-through/) for this use case. ## Recommended rollout approach When the goal is to migrate multiple deployments to Chainguard Containers, a major consideration is the strategy in which these container images are rolled out and deployed throughout your environment. The recommended rollout strategy for most customers is as follows: * Start with less complex and non-critical applications to build confidence before migrating mission-critical workloads. * Employ a gradual approach where you choose a small subset of container images and deploy those first to a non-production environment for testing and validation, and then to a small percentage of production instances and then gradually scale up. * Use strategies like blue-green deployments or canary releases to introduce the updated container images gradually into production. We understand that customers may have urgent timelines and need to accelerate Chainguard Container deployment by rolling out many images to a larger percentage of production simultaneously. For customers to be successful in this accelerated approach, we recommend the following: * Have internal Chainguard Container champions identified and ready to lead and assist cross-functionally across teams. * Your teams participating in the Chainguard Container migration should have the following technical skillset: * Familiarity with tools used to build images and run containers such as Docker CLI, Dockerfiles (including multi-stage builds), and orchestration platforms (Kubernetes, ECS, etc). * Experience using appropriate deployment security standards (such as the [Kubernetes Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/)). * Experience debugging containers that have no shell. * Review and understand concepts in the [Shared responsibility model](/chainguard/containers/concepts/shared-responsibility-model/). * Your organization should already have a mature container strategy in place, including: * Mature and Established UAT and regression testing processes. * Automated CI/CD with permissions restrictions and auditing. * Monitoring, logging and runtime security. Finally, as you plan for the migration to Chainguard Containers you should ensure there’s a clear rollback plan in case of unforeseen issues during migration. Keep the previous container image tagged and accessible for quick redeployment if necessary. ### Adding certificates A common requirement for many customers is to add a company specific certificate or other security related content. The three most common ways to accomplish this are: 1. [Incert](/chainguard/containers/custom-assembly/incert-custom-certs/) 2. Dockerfile and `update-ca-certificates` utility 3. Dockerfile and java keytool The process of adding or updating certificates, configuring APK repositories, and implementing other organization-specific customizations into an image is commonly known as creating a “Golden Image”. This approach enables these standard modifications to be applied once and then distributed across all teams, thereby reducing the risk of errors and minimizing friction during the migration process. ### Image type considerations Many Chainguard customers use both application and base container images, but it often takes more time to migrate your applications to a base image in comparison to an application image due to the complexity of coordinating multiple teams, testing, and release schedules. We recommend starting with and migrating to application images first while your teams get trained and onboarded with base images. #### Application images When migrating to a [Chainguard Application Container](/chainguard/containers/concepts/container-categories/#application-containers) you should first check the image’s overview page on the [Containers Directory](https://images.chainguard.dev) for usage details and any compatibility notes. There may be user, permissions, or volume path differences with the Chainguard Image that you should be aware of. It is a best practice to use the same version of the Chainguard Application Image as what is currently running in your environment, if that version is available from Chainguard. Post-migration you should thoroughly test and monitor your application. #### Base images When migrating to a [Chainguard Base Container](/chainguard/containers/concepts/container-categories/#base-containers) you should first check the images’s overview page on the [Containers Directory](https://images.chainguard.dev) for usage details and any compatibility remarks. You should understand the libraries, runtime requirements, and operating system dependencies of the applications you plan to have running on the base image. It is a best practice to use the same versions of any languages or applications that will be running on the Chainguard Base Container as what is currently running in your environment. Do not upgrade language or application versions at the same time that you migrate. Post-migration you should test and monitor your application as outlined below in Section 6. If you need a package to use with your Chainguard Base Container, ChainguardOS packages are available using `apk`. Ensure you only use ChainguardOS packages, as Alpine APKs are not compatible with ChainguardOS. Additionally, it is important to note that vendor provided packages need to be [glibc](/chainguard/containers/concepts/glibc-vs-musl/) based and their functionality should be fully tested along with the application. ### Extending Chainguard Containers You can take advantage of Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) and [Private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) features to extend your container images Custom Assembly allows users to create customers container images with extra packages added. This reduces their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs. Private APK Repositories, meanwhile, allow customers to pull secure apk packages from Chainguard. The list of packages available in an organization’s private repository is based on the apk repositories that the organization already has access to. For example, say your organization has access to the [Chainguard MySQL container image](https://images.chainguard.dev/directory/image/mysql/versions). Along with `mysql`, this image comes with other apk packages, including `bash`, `openssl`, and `pwgen`. This means that you'll have access to these apk packages through your organization's private APK repository, along with any others that appear in Chainguard container images that your organization has access to. ## Tips for migrating to Chainguard Containers Although not fully comprehensive, it can be helpful to keep the following list of tips and strategies in mind when migrating to Chainguard Containers. * Use development variants when you need a shell * If necessary, install a different shell * Use `apk search` to find the utilities needed by your application * Beware of entrypoint differences between Chainguard Containers and their upstream counterparts * Be aware that Chainguard's container images typically do not run as root by default * If packages you need are missing, install them into a base image, preferably as part of a multi-stage build Each of these tips and strategies are explained in greater detail in our guide on [Container migration tips](/chainguard/containers/migration/migration-tips/). ## Troubleshooting The move to a distroless workflow can be confusing for both individual developers and larger teams. We recommend taking the following steps when you encounter issues as you begin using Chainguard Containers: * **Debugging Distroless Container Images**: Debugging distroless images can be challenging due to the absence of a shell and package manager. * **Temporary Rebuild with `-dev` Tag**: Temporarily rebuild your image using the development variant to get a shell and other debugging tools. This is useful for local troubleshooting or in developer clusters. * Remember to remove the `-dev` tag before merging. * **Ephemeral Debug Containers**: In Kubernetes, use `kubectl debug` to launch an ephemeral container attached to the existing Pod for troubleshooting. * **Docker Debug**: While `docker debug` is available, it requires a Docker Desktop Pro license. * **`cdebug` and `kubectl debug`**: These tools allow you to enter a running container for debugging and can access the target container's file system. * **`chainctl images diff`**: This command allows you to compare two container images and identify differences between them. * **General Troubleshooting Tips**: * Check the image's overview page in the [Containers Directory](https://images.chainguard.dev/) for specific usage details. * If you encounter issues, use a development image as a starting point. * Ensure you've replaced `apt install` or equivalent commands with `apk add`. * If elevated privileges are needed, use `USER root` before commands that require administrative access, then switch back to a non-root user before finalizing the image build. * Always check the image documentation for entrypoint details, as they may vary from other distributions. You can find entrypoint details in the [Specifications tab](https://images.chainguard.dev/directory/image/python/specifications) on any image's entry in the [Containers Directory](https://images.chainguard.dev/). ### Troubleshooting resources To help with troubleshooting issues that can occur, Chainguard Academy has a guide on [Debugging distroless containers](/chainguard/containers/troubleshooting/debugging-distroless-images/). Lastly, you might also find help in the [Chainguard Containers FAQs](/chainguard/containers/faq/). ## Migration resources Once you have worked through the [sample application port](/chainguard/containers/migration/porting-apps-to-chainguard/#porting-key-points), the [Migration best practices and checklist](/chainguard/containers/migration/migration-checklist/) collects the steps worth running through before and during a rollout. To automate Dockerfile migration, [Guardener](/chainguard/guardener/dockerfile-migration/) is an AI-powered agent that iteratively converts, builds, and validates your Dockerfiles for use with Chainguard Containers. Chainguard Academy groups the rest of its migration material into three sets: * **[Migration guides](/chainguard/containers/migration/migration-guides/)** — language- and platform-specific guidance for Python, Node, PHP, .NET, Go, and Java. * **[Compatibility guides](/chainguard/containers/migration/compatibility/)** — what changes when moving from Alpine, Debian, Red Hat, or Ubuntu. * **[Getting started guides](/chainguard/containers/getting-started/)** — how to work with a specific Container, several with a sample application. ### Courses Chainguard also offers a number of courses aimed to help teams understand and use Chainguard Containers. #### Quickstart * [Chainguard Containers Crash Course](https://courses.chainguard.dev/linkys-crash-course-on-chainguard-images): A quick overview of everything you need to know to get started ASAP. #### Getting started (developer-focused) * [Getting Started With Chainguard Containers](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/getting-started-with-chainguard-images): Intro to everything you need to know - from basic setup to security scanning. * [Foundations of Supply Chain Security](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/foundations-of-software-supply-chain-security): The what, why, and how of keeping your software supply chain secure. * [Migration guidance](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/migration-guidance): Your friendly guide to moving to Chainguard Containers without the headaches. #### Level up (next level courses) * [Foundations of Supply Chain Security](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/foundations-of-software-supply-chain-security): The what, why, and how of keeping your software supply chain secure. * [Images! Images! Images!](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/images-images-images): Become an image expert - from FIPS to SBOMs and everything in between. * [Crush Your CVEs](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/crush-your-cves): Master vulnerability management and keep your systems secure. #### Running the show (admin courses) * [Registry Rockstar](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/registry-rockstar): Everything you need to know about managing access, identity, and registry setup. * [Chainguard's Superstar Support](https://courses.chainguard.dev/path/linkys-guide-to-chainguard-images/chainguards-superstar-support): Get the most out of Chainguard's support resources and tools. ## Further reading * [Overview of Chainguard Containers](/chainguard/containers/overview/) * [How to use Chainguard Containers](/chainguard/containers/using-and-deploying/using-containers/) * [How to transition to secure container images with new migration guides (Blog)](https://www.chainguard.dev/unchained/how-to-transition-to-secure-container-images-with-new-migration-guides) * [Getting started with distroless containers](/chainguard/containers/concepts/getting-started-distroless/) --- ### Getting started with the NeMo Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/ai-and-data/nemo.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [NeMo container image](https://images.chainguard.dev/directory/image/nemo/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-nemo) provides a security-hardened environment for NVIDIA's [NeMo](https://github.com/NVIDIA/NeMo) deep learning framework with minimal vulnerabilities compared to traditional AI/ML containers. NeMo enables building conversational AI models through module collections for Automatic Speech Recognition (ASR), Natural Language Processing (NLP), and Text-to-Speech (TTS) tasks. Built for [CUDA 12](https://developer.nvidia.com/about-cuda) GPU acceleration, this lightweight container maintains full NeMo functionality while significantly reducing security risks for both training and production inference workloads.
What is Deep Learning?

Deep learning is a subset of machine learning that leverages a flexible computational architecture, the neural network, to address a wide variety of tasks. Neural networks emulate the structure of the brain and consist of interconnected nodes (neurons) that each contain an associated weight and threshold. In concert with an activation function, these values determine whether data is propagated within the network, producing an output layer corresponding to a classification, regression, or other result.

By technical convention, a deep neural network (DNN) has at least three layers: an input layer, an output layer, and one or more hidden layers. In practice, DNNs often have many layers.

Deep neural networks underpin many common computational tasks in modern applications, such as speech to text and generative AI.

In this getting started guide, we will use the NeMo Chainguard Container to generate speech from plain text using models provided by NeMo's text-to-speech (TTS) and natural language processing (NLP) collections. In doing so, we'll compare the security and footprint of the NeMo Chainguard Container to the official runtime container image and consider further approaches and resources for applying the NeMo Chainguard Container to additional tasks in conversational AI. This guide is primarily designed for use in an environment with access to one or more NVIDIA GPUs. However, NeMo is built on [PyTorch Lightning](https://lightning.ai/docs/pytorch/stable/), which supports a wide variety of [accelerators](https://pytorch-lightning.readthedocs.io/en/1.1.8/accelerators.html), or interfaces to categories of processing units (CPU, GPU, TPU) or high-level clustering mechanisms such as [Distributed Data Parallel](https://pytorch.org/tutorials/intermediate/ddp_tutorial.html). Some consideration will be given to alternative computing environments such as CPU in this tutorial.

Note: In November 2024, after this article was first written, Chainguard made changes to its free tier of container images. In order to access the non-free container images used in this guide, you will need to be part of an organization that has access to them. For a full list of container images that will remain in Chainguard's free tier, please refer to this support page.

## Prerequisites If Docker Engine (or Docker Desktop) is not already installed, follow the [instructions for installing Docker Engine on your host machine](https://docs.docker.com/engine/install/). To take advantage of connected GPUs, you'll need to install CUDA Toolkit on your host machine.
Installing CUDA Toolkit

Compute Unified Device Architecture (CUDA) is a parallel computing platform developed by NVIDIA. To take advantage of connected GPUs, you’ll need to follow the setup instructions for your local machine or create a CUDA-enabled instance on a cloud provider.

To set up CUDA on your local machine, follow the installation instructions for Linux or Windows. CUDA is not currently supported on Mac OS.

Google Cloud Platform provides CUDA-ready deep learning instances, including PyTorch-specific instructions. Amazon Web Services also provides CUDA-ready deep learning instances.

This tutorial can be followed without connected GPUs or CUDA Toolkit. To run commands in this tutorial on CPU, omit the `--gpus all` flag when executing container commands. Keep in mind that some functionality within NeMo (such as training models) will take significantly longer on CPU. ## Testing access to GPUs We'll start by running the NeMo Chainguard Container interactively and determine whether the environment has access to connected GPUs. Use the following command to pull the container image, run it with GPU access, and start a Python interpreter inside the running container. ```shell docker run -it --rm \ --gpus all \ --shm-size=8g \ --ulimit memlock=-1 \ --ulimit stack=67108864 \ cgr.dev/$ORGANIZATION/nemo:latest ``` > **Note**: Be aware that you will need to change `$ORGANIZATION` to reflect the name of your organization's repository within Chainguard's registry. These options allow access to all available GPUs, allocate a custom amount of shared memory (8 GB) to the container, and set an upper bound on container memory use. Running this command for the first time may take a few minutes, since it will download the NeMo Chainguard Container to your host machine. Once the image is pulled and the command runs successfully, you will be interacting with a bash shell in the running container. Enter the following commands at the prompt to check the availability of your GPU. ```shell python ``` ```python Python 3.11.9 (main, May 1 2024, 21:48:03) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from nemo.core import pytorch_lightning >>> len(pytorch_lightning.accelerators.find_usable_cuda_devices()) 1 ``` The above output shows that one GPU is connected and available. Since PyTorch is also accessible within our NeMo Chainguard Container, you can also use it to access more granular information on CUDA and attached GPUs. ```python >>> import torch >>> torch.cuda.is_available() True >>> torch.cuda.get_device_name(0) 'Tesla V100-SXM2-16GB' ``` Once you've determined that your environment has access to CUDA and connected GPUs, exit the container by typing `Control-d` or by typing `exit()` and pressing `Enter`. You should be returned to the prompt of your host machine. ## NeMo overview NeMo is a generative AI toolkit and framework with a focus on conversational AI tasks such as NLP, ASR, and TTS, as well as large language models (LLM) and multimodal (MM) models. NeMo uses a system of [neural modules](https://docs.nvidia.com/nemo-framework/user-guide/latest/nemotoolkit/core/neural_modules.html), an abstraction over a variety of common elements in model training and inference such as encoders, decoders, loss functions, layers, or models. NeMo also provides [collections of modules](https://docs.nvidia.com/nemo-framework/user-guide/latest/nemotoolkit/collections.html) targeting specific areas of concern in conversational and generative AI, such as LLMs, speech AI / NLP, and TTS. NeMo is built on [PyTorch Lightning](https://lightning.ai/docs/pytorch/stable/), a high-level interface to PyTorch with a focus on scalability, and uses the [Hydra](https://hydra.cc/) library for configuration management. Since NeMo is a framework with many collections of modules suitable for a wide variety of projects, we've chosen an example task, generative text to speech, requiring the use of two TTS modules. This is an appropriate example of a task that might be run as part of a larger production application. ## Text to speech (TTS) example In this section, we'll run a script that uses the NeMo Chainguard Container to: - Start with a message in plain text - Transform it into a set of phonemes - Generate a spectrogram (waveform representation) using a NeMo-provided spectrogram model - Transform the spectrogram values into audio using a NeMo-provided vocoder (human voice) model - Write the resulting audio to a `.wav` file at a set rate First, let's create a folder to work in on your host machine: ```shell mkdir -p ~/nemo-tts && cd ~/nemo-tts ``` Next, let's download our [tts.py](https://github.com/chainguard-dev/nemo-examples/blob/main/tts.py) script: ```shell curl https://raw.githubusercontent.com/chainguard-dev/nemo-examples/main/tts.py > tts.py ``` You should now be in a working directory containing only one file, `tts.py`. We'll be mounting this folder in our container as a volume, which will allow us to both pass in our script and extract our output. We'll now start a container based on our NeMo Chainguard Container, mount the current working directory containing our `tts.py` script inside the container as a volume, and run the script in the container: ```shell docker run -it --rm \ --gpus all \ --user root \ --shm-size=8g \ --ulimit memlock=-1 \ --ulimit stack=67108864 \ -v $PWD:/home/nonroot/nemo-test \ cgr.dev/$ORGANIZATION/nemo:latest \ "/home/nonroot/nemo-test/tts.py" ``` Note that we ran the above script as root. This allows us to share the script and output `.wav` file between the host and container. Remember not to run your container image as root in a production environment. If your host machine does not have attached GPUs and you'd like to run the above on your CPU, omit the `--gpus all \` line. The script tests for availability of the CUDA platform and sets the accelerator to CPU if CUDA is not detected, so the script will also function on CPU. Since we're using pretrained models to perform text to speech, this example will only take a few minutes using a CPU only. However, other tasks such as model training and finetuning may take significantly longer without connected GPUs. Note that NeMo collections are large, and initial imports can take up to a minute depending on your environment. The script may appear to hang during that time. After imports are complete, you should see a large amount of output as NeMo pulls models and works through the steps in the script (tokenizing, generating a spectrogram, generating audio, and writing audio to disk). On completion, the script outputs a `test.wav` file. Because we mounted a volume, this file should now be present in the working directory of your host machine. ```shell ls ``` ```output test.wav tts.py ``` The `test.wav` file should contain audio similar to this output:
Output from the TTS script
## Final considerations and next steps This section will consider next steps for applying the NeMo Chainguard Container to other tasks in conversational AI. In the [tts.py](https://github.com/chainguard-dev/nemo-examples/blob/main/tts.py) script run above, we used two models provided by NeMo, both contained within the TTS collection. - [Tacotron2 speech synthesis model](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/nemo/models/tts_en_tacotron2) - [HiFi-GAN speech synthesis model](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/tao/models/speechsynthesis_hifigan) The former model allows us to convert plain text into a spectrogram, or a representation of a waveform. The second model generates audio from the spectrogram. Note that NVIDIA's model overview pages provide useful background information, tags, and sample code. You can search the [full NGC model catalog](https://catalog.ngc.nvidia.com/models?filters=&orderBy=weightPopularDESC&query=&page=&pageSize=) to find pretrained models for use with NeMo. In this script, we used pretrained models to create the phonemes and audio output. These models can be finetuned with your own speech data to customize the results. The following resources may give a starting point for further explorations with the NeMo Chainguard Container: - NVIDIA provides a wide variety of [NeMo Tutorials](https://docs.nvidia.com/nemo-framework/user-guide/latest/nemotoolkit/starthere/tutorials.html) that are a strong entry point for working with the framework to accomplish specific tasks. - NVIDIA's [NeMo Playbooks](https://docs.nvidia.com/nemo-framework/user-guide/latest/playbooks/index.html) provide a basis for more advanced tasks and configurations and address running workloads on different platforms and orchestration tooling. - The [NeMo Collections](https://docs.nvidia.com/nemo-framework/user-guide/latest/nemotoolkit/collections.html) organizes reference documentation for NeMo collections and modules. - The [NVIDIA NGC model catalog](https://catalog.ngc.nvidia.com/models?filters=&orderBy=weightPopularDESC&query=&page=&pageSize=) can be searched to find models suitable for specific tasks, and each model's overview page provides a useful reference with sample code. - This [NVIDIA Conversational AI publications page](https://research.nvidia.com/labs/conv-ai/publications) collects papers that use the NeMo framework, showcasing cutting-edge generative deep learning using NeMo --- ### Alpine compatibility URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/compatibility/alpine-compatibility.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Reference Chainguard Containers and Alpine base images have different binaries and scripts included in their respective `busybox` and `coreutils` packages. The following table lists common tools and their corresponding package(s) in both Wolfi and Alpine distributions. Note that `$PATH` locations like `/usr/bin` or `/sbin` are not included here. If you have compatibility issues with tools that are included in both `busybox` and `coreutils`, be sure to check `$PATH` order and confirm which version of a tool is being run. Generally, if a tool exists in `busybox` but does not have a `coreutils` counterpart, there will be a specific package that includes it. For example the `zcat` utility is included in the `gzip` package in both Wolfi and Alpine. Additionally, be aware that binaries are not compatible between Alpine and Wolfi. You **should not** attempt to copy Alpine binaries into a Wolfi-based container image. You can use the `apk search` command in Wolfi and Alpine to find out which package includes a tool. | Utility | Wolfi busybox | Alpine busybox | Wolfi coreutils | Alpine coreutils | | --------------- | :-----------: | :------------: | :-------------: | :--------------: | | `[` | ✅ | ✅ | ✅ | ✅ | | `[[` | ✅ | ✅ | | | | `acpid` | | ✅ | | | | `add-shell` | ✅ | ✅ | | | | `addgroup` | ✅ | ✅ | | | | `adduser` | ✅ | ✅ | | | | `adjtimex` | ✅ | ✅ | | | | `arch` | ✅ | ✅ | | | | `arp` | | ✅ | | | | `arping` | ✅ | ✅ | | | | `ash` | ✅ | ✅ | | | | `awk` | ✅ | ✅ | | | | `b2sum` | | | ✅ | ✅ | | `base32` | | | ✅ | ✅ | | `base64` | ✅ | ✅ | ✅ | ✅ | | `basename` | ✅ | ✅ | ✅ | ✅ | | `basenc` | | | ✅ | ✅ | | `bbconfig` | ✅ | ✅ | | | | `bc` | ✅ | ✅ | | | | `beep` | ✅ | ✅ | | | | `blkdiscard` | | ✅ | | | | `blkid` | | ✅ | | | | `blockdev` | | ✅ | | | | `brctl` | | ✅ | | | | `bunzip2` | ✅ | ✅ | | | | `bzcat` | ✅ | ✅ | | | | `bzip2` | ✅ | ✅ | | | | `cal` | ✅ | ✅ | | | | `cat` | ✅ | ✅ | ✅ | ✅ | | `chattr` | ✅ | ✅ | | | | `chcon` | | | ✅ | ✅ | | `chgrp` | ✅ | ✅ | ✅ | ✅ | | `chmod` | ✅ | ✅ | ✅ | ✅ | | `chown` | ✅ | ✅ | ✅ | ✅ | | `chpasswd` | ✅ | ✅ | | | | `chroot` | ✅ | ✅ | ✅ | ✅ | | `chrt` | ✅ | | | | | `chvt` | | ✅ | | | | `cksum` | ✅ | ✅ | ✅ | ✅ | | `clear` | ✅ | ✅ | | | | `cmp` | ✅ | ✅ | | | | `comm` | ✅ | ✅ | ✅ | ✅ | | `coreutils` | | | ✅ | ✅ | | `cp` | ✅ | ✅ | ✅ | ✅ | | `cpio` | ✅ | ✅ | | | | `crond` | | ✅ | | | | `crontab` | | ✅ | | | | `cryptpw` | ✅ | ✅ | | | | `csplit` | | | ✅ | ✅ | | `cut` | ✅ | ✅ | ✅ | ✅ | | `date` | ✅ | ✅ | ✅ | ✅ | | `dc` | ✅ | ✅ | | | | `dd` | ✅ | ✅ | ✅ | ✅ | | `deallocvt` | | ✅ | | | | `delgroup` | ✅ | ✅ | | | | `deluser` | ✅ | ✅ | | | | `depmod` | | ✅ | | | | `df` | ✅ | ✅ | ✅ | ✅ | | `diff` | ✅ | ✅ | | | | `dir` | | | ✅ | ✅ | | `dircolors` | | | ✅ | ✅ | | `dirname` | ✅ | ✅ | ✅ | ✅ | | `dmesg` | ✅ | ✅ | | | | `dnsdomainname` | ✅ | ✅ | | | | `dos2unix` | ✅ | ✅ | | | | `du` | ✅ | ✅ | ✅ | ✅ | | `dumpkmap` | | ✅ | | | | `echo` | ✅ | ✅ | ✅ | ✅ | | `ed` | ✅ | | | | | `egrep` | ✅ | ✅ | | | | `eject` | | ✅ | | | | `env` | ✅ | ✅ | ✅ | | | `ether-wake` | | ✅ | | | | `expand` | ✅ | ✅ | ✅ | ✅ | | `expr` | ✅ | ✅ | ✅ | ✅ | | `factor` | ✅ | ✅ | ✅ | ✅ | | `fallocate` | ✅ | ✅ | | | | `false` | ✅ | ✅ | ✅ | ✅ | | `fatattr` | | ✅ | | | | `fbset` | | ✅ | | | | `fbsplash` | | ✅ | | | | `fdflush` | | ✅ | | | | `fdisk` | | ✅ | | | | `fgrep` | ✅ | ✅ | | | | `find` | ✅ | ✅ | | | | `findfs` | ✅ | ✅ | | | | `flock` | ✅ | ✅ | | | | `fmt` | | | ✅ | | | `fold` | ✅ | ✅ | ✅ | ✅ | | `free` | ✅ | ✅ | | | | `fsck` | | ✅ | | | | `fstrim` | | ✅ | | | | `fsync` | ✅ | ✅ | | | | `fuser` | ✅ | ✅ | | | | `getopt` | ✅ | ✅ | | | | `getty` | ✅ | ✅ | | | | `grep` | ✅ | ✅ | | | | `groups` | ✅ | ✅ | | | | `gunzip` | ✅ | ✅ | | | | `gzip` | ✅ | ✅ | | | | `halt` | | ✅ | | | | `hd` | ✅ | ✅ | | | | `head` | ✅ | ✅ | ✅ | ✅ | | `hexdump` | ✅ | ✅ | | | | `hostid` | ✅ | ✅ | ✅ | ✅ | | `hostname` | ✅ | ✅ | | | | `hwclock` | | ✅ | | | | `id` | ✅ | ✅ | ✅ | ✅ | | `ifconfig` | | ✅ | | | | `ifdown` | | ✅ | | | | `ifenslave` | | ✅ | | | | `ifup` | | ✅ | | | | `init` | | ✅ | | | | `inotifyd` | ✅ | ✅ | | | | `insmod` | | ✅ | | | | `install` | ✅ | ✅ | ✅ | ✅ | | `ionice` | ✅ | ✅ | | | | `iostat` | ✅ | ✅ | | | | `ip` | | ✅ | | | | `ipaddr` | | ✅ | | | | `ipcalc` | | ✅ | | | | `ipcrm` | ✅ | ✅ | | | | `ipcs` | ✅ | ✅ | | | | `iplink` | | ✅ | | | | `ipneigh` | | ✅ | | | | `iproute` | | ✅ | | | | `iprule` | | ✅ | | | | `iptunnel` | | ✅ | | | | `join` | | | ✅ | ✅ | | `kbd_mode` | | ✅ | | | | `kill` | ✅ | ✅ | | | | `killall` | ✅ | ✅ | | | | `killall5` | ✅ | ✅ | | | | `klogd` | | ✅ | | | | `last` | | ✅ | | | | `less` | ✅ | ✅ | | | | `link` | ✅ | ✅ | ✅ | ✅ | | `linux32` | ✅ | ✅ | | | | `linux64` | ✅ | ✅ | | | | `ln` | ✅ | ✅ | ✅ | ✅ | | `loadfont` | | ✅ | | | | `loadkmap` | | ✅ | | | | `logger` | ✅ | ✅ | | | | `login` | ✅ | ✅ | | | | `logname` | | | ✅ | ✅ | | `logread` | | ✅ | | | | `losetup` | | ✅ | | | | `ls` | ✅ | ✅ | ✅ | ✅ | | `lsattr` | ✅ | ✅ | | | | `lsmod` | | ✅ | | | | `lsof` | ✅ | ✅ | | | | `lsusb` | | ✅ | | | | `lzcat` | ✅ | ✅ | | | | `lzma` | ✅ | ✅ | | | | `lzop` | ✅ | ✅ | | | | `lzopcat` | ✅ | ✅ | | | | `makemime` | | ✅ | | | | `md5sum` | ✅ | ✅ | ✅ | ✅ | | `mdev` | | ✅ | | | | `mesg` | | ✅ | | | | `microcom` | ✅ | ✅ | | | | `mkdir` | ✅ | ✅ | ✅ | ✅ | | `mkdosfs` | | ✅ | | | | `mkfifo` | ✅ | ✅ | ✅ | ✅ | | `mkfs.vfat` | | ✅ | | | | `mknod` | ✅ | ✅ | ✅ | ✅ | | `mkpasswd` | ✅ | ✅ | | | | `mkswap` | | ✅ | | | | `mktemp` | ✅ | ✅ | ✅ | ✅ | | `modinfo` | | ✅ | | | | `modprobe` | | ✅ | | | | `more` | ✅ | ✅ | | | | `mount` | | ✅ | | | | `mountpoint` | ✅ | ✅ | | | | `mpstat` | ✅ | ✅ | | | | `mv` | ✅ | ✅ | ✅ | ✅ | | `nameif` | | ✅ | | | | `nanddump` | | ✅ | | | | `nandwrite` | | ✅ | | | | `nbd-client` | | ✅ | | | | `nc` | | ✅ | | | | `netstat` | ✅ | ✅ | | | | `nice` | ✅ | ✅ | ✅ | ✅ | | `nl` | ✅ | ✅ | ✅ | ✅ | | `nmeter` | ✅ | ✅ | | | | `nohup` | ✅ | ✅ | ✅ | ✅ | | `nologin` | ✅ | ✅ | | | | `nproc` | ✅ | ✅ | ✅ | ✅ | | `nsenter` | ✅ | ✅ | | | | `nslookup` | | ✅ | | | | `ntpd` | | ✅ | | | | `numfmt` | | | ✅ | ✅ | | `od` | ✅ | ✅ | ✅ | ✅ | | `openvt` | | ✅ | | | | `partprobe` | | ✅ | | | | `passwd` | ✅ | ✅ | | | | `paste` | ✅ | ✅ | ✅ | ✅ | | `pathchk` | | | ✅ | ✅ | | `pgrep` | ✅ | ✅ | | | | `pidof` | ✅ | ✅ | | | | `ping` | ✅ | ✅ | | | | `ping6` | ✅ | ✅ | | | | `pinky` | | | ✅ | ✅ | | `pipe_progress` | ✅ | ✅ | | | | `pivot_root` | ✅ | ✅ | | | | `pkill` | ✅ | ✅ | | | | `pmap` | ✅ | ✅ | | | | `poweroff` | | ✅ | | | | `pr` | | | ✅ | ✅ | | `printenv` | ✅ | ✅ | ✅ | ✅ | | `printf` | ✅ | ✅ | ✅ | ✅ | | `ps` | ✅ | ✅ | | | | `pscan` | | ✅ | | | | `pstree` | ✅ | ✅ | | | | `ptx` | | | ✅ | ✅ | | `pwd` | ✅ | ✅ | ✅ | ✅ | | `pwdx` | ✅ | ✅ | | | | `raidautorun` | | ✅ | | | | `rdate` | | ✅ | | | | `rdev` | ✅ | ✅ | | | | `readahead` | ✅ | ✅ | | | | `readlink` | ✅ | ✅ | ✅ | ✅ | | `realpath` | ✅ | ✅ | ✅ | ✅ | | `reboot` | | ✅ | | | | `reformime` | | ✅ | | | | `remove-shell` | ✅ | ✅ | | | | `renice` | ✅ | ✅ | | | | `reset` | ✅ | ✅ | | | | `resize` | ✅ | ✅ | | | | `rev` | ✅ | ✅ | | | | `rfkill` | | ✅ | | | | `rm` | ✅ | ✅ | ✅ | ✅ | | `rmdir` | ✅ | ✅ | ✅ | ✅ | | `rmmod` | | ✅ | | | | `route` | | ✅ | | | | `run-parts` | ✅ | ✅ | | | | `runcon` | | | ✅ | ✅ | | `sed` | ✅ | ✅ | | | | `sendmail` | | ✅ | | | | `seq` | ✅ | ✅ | ✅ | ✅ | | `setconsole` | | ✅ | | | | `setfont` | | ✅ | | | | `setkeycodes` | | ✅ | | | | `setlogcons` | | ✅ | | | | `setpriv` | ✅ | ✅ | | | | `setserial` | ✅ | ✅ | | | | `setsid` | ✅ | ✅ | | | | `sh` | ✅ | ✅ | | | | `sha1sum` | ✅ | ✅ | ✅ | ✅ | | `sha224sum` | | | ✅ | ✅ | | `sha256sum` | ✅ | ✅ | ✅ | ✅ | | `sha384sum` | | | ✅ | ✅ | | `sha3sum` | ✅ | ✅ | | | | `sha512sum` | ✅ | ✅ | ✅ | | | `showkey` | | ✅ | | | | `shred` | ✅ | ✅ | ✅ | ✅ | | `shuf` | ✅ | ✅ | ✅ | ✅ | | `slattach` | | ✅ | | | | `sleep` | ✅ | ✅ | ✅ | ✅ | | `sort` | ✅ | ✅ | ✅ | ✅ | | `split` | ✅ | ✅ | ✅ | ✅ | | `stat` | ✅ | ✅ | ✅ | ✅ | | `stdbuf` | | | ✅ | ✅ | | `strings` | ✅ | ✅ | | | | `stty` | ✅ | ✅ | ✅ | ✅ | | `su` | ✅ | ✅ | | | | `sum` | ✅ | ✅ | ✅ | ✅ | | `swapoff` | | ✅ | | | | `swapon` | | ✅ | | | | `switch_root` | | ✅ | | | | `sync` | ✅ | ✅ | ✅ | ✅ | | `sysctl` | ✅ | ✅ | | | | `syslogd` | | ✅ | | | | `tac` | ✅ | ✅ | ✅ | ✅ | | `tail` | ✅ | ✅ | ✅ | ✅ | | `tar` | ✅ | ✅ | | | | `tee` | ✅ | ✅ | ✅ | ✅ | | `test` | ✅ | ✅ | ✅ | ✅ | | `time` | ✅ | ✅ | | | | `timeout` | ✅ | ✅ | ✅ | ✅ | | `top` | ✅ | ✅ | | | | `touch` | ✅ | ✅ | ✅ | ✅ | | `tr` | ✅ | ✅ | ✅ | ✅ | | `traceroute` | ✅ | ✅ | | | | `traceroute6` | ✅ | ✅ | | | | `tree` | ✅ | ✅ | | | | `true` | ✅ | ✅ | ✅ | ✅ | | `truncate` | ✅ | ✅ | ✅ | ✅ | | `tsort` | ✅ | | ✅ | ✅ | | `tty` | ✅ | ✅ | ✅ | ✅ | | `ttysize` | ✅ | ✅ | | | | `tunctl` | ✅ | ✅ | | | | `udhcpc` | | ✅ | | | | `udhcpc6` | | ✅ | | | | `umount` | | ✅ | | | | `uname` | ✅ | ✅ | ✅ | ✅ | | `unexpand` | ✅ | ✅ | ✅ | ✅ | | `uniq` | ✅ | ✅ | ✅ | ✅ | | `unix2dos` | ✅ | ✅ | | | | `unlink` | ✅ | ✅ | ✅ | ✅ | | `unlzma` | ✅ | ✅ | | | | `unlzop` | ✅ | ✅ | | | | `unshare` | | ✅ | | | | `unxz` | ✅ | ✅ | | | | `unzip` | ✅ | ✅ | | | | `uptime` | ✅ | ✅ | | | | `users` | | | ✅ | ✅ | | `usleep` | ✅ | ✅ | | | | `uudecode` | ✅ | ✅ | | | | `uuencode` | ✅ | ✅ | | | | `vconfig` | ✅ | ✅ | | | | `vdir` | | | ✅ | ✅ | | `vi` | ✅ | ✅ | | | | `vlock` | ✅ | ✅ | | | | `volname` | | ✅ | | | | `watch` | ✅ | ✅ | | | | `watchdog` | | ✅ | | | | `wc` | ✅ | ✅ | ✅ | ✅ | | `wget` | | ✅ | | | | `which` | ✅ | ✅ | | | | `who` | ✅ | ✅ | ✅ | ✅ | | `whoami` | ✅ | ✅ | ✅ | ✅ | | `whois` | | ✅ | | | | `xargs` | ✅ | ✅ | | | | `xxd` | ✅ | ✅ | | | | `xzcat` | ✅ | ✅ | | | | `yes` | ✅ | ✅ | ✅ | ✅ | | `zcat` | ✅ | ✅ | | | | `zcip` | | ✅ | | | --- ### Chainguard Containers product release lifecycle URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/lifecycle-and-eol/versions.md Last Modified: October 8, 2026 Tags: Chainguard Containers [Chainguard Containers](https://images.chainguard.dev/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) are able to offer few-to-zero known vulnerabilities because they are updated frequently. Because of this continuous release cycle, the best way to mitigate vulnerabilities is to use the newest build of each Chainguard Container available. Chainguard keeps Containers up to date by doing one or more of the following: * Applying new releases from upstream projects * Rapidly applying upstream patches to current releases — you can read more about this in our blog post, “[How Chainguard fixes vulnerabilities before they're detected](https://www.chainguard.dev/unchained/how-chainguard-fixes-vulnerabilities?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement)” * Applying Chainguard patches to OSS software Upstream projects are updated frequently for many reasons, including to combat CVEs, and Chainguard ensures that the most up-to-date software is available in all Chainguard Containers. Additionally, Chainguard often identifies CVEs and other issues before scanners can detect them, so Chainguard may offer a patch to a vulnerable dependency to support Chainguard Containers with few-to-zero vulnerabilities. The best way to mitigate vulnerabilities is to continually update to the latest patched releases of software, but testing and updating can take time and effort. To support flexibility and user choice, Chainguard aims to offer multiple versions of a Chainguard Container that provide the lowest number of vulnerabilities realistically possible. This document provides an overview of Chainguard’s approach to updates, releases, and versions within Chainguard Containers. For more specific guidance, please [contact us](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). This page explains the policy. If you're trying to resolve a specific container image or version you can't pull, refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/). ## How versions are maintained Chainguard's container images are built on open source software, so understanding how Chainguard manages releases starts with understanding how open source projects version and release software. Generally, projects follow one of two approaches: * **Multiple release track**: Popular open source projects often provide maintenance for a number of release tracks concurrently. For example, Java, Go, Postgres, and Kubernetes patch multiple release versions, each on their own defined maintenance schedule. * For these types of projects, Chainguard will maintain every version track of the upstream software that receives updates from the project. * **Single release track**: Many open source projects support only a single stream of releases that are continuously incremented; often, this is simply the latest release. In the case of a single release track, any security fix that is published will only be applied to the most recent release of the project, and the project release tags will be updated to indicate a new version is available. * For this type of project, Chainguard only warrants that the latest release of the software and its corresponding version tags have the most up-to-date patches available. In rare cases, a project may not follow a defined release pattern at all. ### Examples of what Chainguard supports and maintains for Chainguard Containers There are several scenarios that define what Chainguard agrees to maintain regarding software versions in the [Chainguard Containers Directory](/platform/console/images-directory/). All container images that Chainguard currently supports are those with upstream software that is still supported and maintained, and Chainguard patches and rebuilds these Containers daily. If you have purchased a container image during its lifecycle that is no longer being supported upstream, you will still be able to access this Container, _but_ Chainguard will not be patching or rebuilding this Container and it will start to accrue CVEs. It is recommended to upgrade to an actively maintained version. The table provides some example scenarios to help illustrate our approach. | **Category** | **Example** | **Maintained Upstream Releases** | **Chainguard Patches** | **Chainguard No Longer Patches** | | --------------- | ------------- | ---------------------------------- | ------------------------ | ---------------------------------- | | **Multiple Release Tracks** | [Go](https://images.chainguard.dev/directory/image/go/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) | 1.23, 1.22 | `:latest`, 1, 1.23, 1.22 | 1.23.old, 1.22.old, 1.21 and below | | | [Python](https://images.chainguard.dev/directory/image/python/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) | 3.13, 3.12, 3.11, 3.10, 3.9 | `:latest`, 3, 3.9 and above | 3.8 and below, 3.8.old, 3.9.old, 3.10.old, 3.11.old, 3.12.old | | | [Postgres](https://images.chainguard.dev/directory/image/postgres/version?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) | 17, 16, 15, 14, 13 | `:latest`, 17, 16, 15, 14, 13 | 12 (EOL November 21, 2024) and below | | **Single Release Track** | [Cosign](https://images.chainguard.dev/directory/image/cosign/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) | 2 | `:latest`, 2, 2.4 | 2.3, 2.2, 2.1, 2.0, 1.x, 0.x | | | [Bank-Vaults](https://images.chainguard.dev/directory/image/bank-vaults/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) | 1 | `:latest`, 1 | Any previous version tag | **No Release Track** | [envoyproxy/ratelimit](https://images.chainguard.dev/directory/image/envoy-ratelimit/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) | No versioned releases | `:latest` | Any previous version tag | > **Note**: The "Maintained Upstream Releases" column is current as of December 2024. ### Daily rebuilds and proactive patching [Actively maintained](#list-active-tags) container images are rebuilt on a daily cadence, so you can be sure the container image you are using is up to date. In some cases, Chainguard will fix vulnerabilities in tools without waiting for the external project to release patches. Learn more about this under [Epoch tags](#epoch-tags). ### Maintaining Wolfi packages in Chainguard Containers Chainguard's container images only contain packages that are either built and maintained internally by Chainguard or packages from the [Wolfi Project](https://github.com/wolfi-dev). These packages follow the same conventions of minimalism and rapid updates as Chainguard Containers. Starting in March of 2024, Chainguard will maintain one version of each Wolfi package at a time. These will track the latest version of the upstream software in the package. Chainguard will end patch support for previous versions of packages in Wolfi. Existing packages will not be removed from Wolfi and you may continue to use them, but be aware that older packages will no longer be updated and will accrue vulnerabilities over time. The tools we use to build packages and container images remain freely available and open source in Wolfi. This change ensures that Chainguard can provide the most up-to-date patches to all packages for our Containers customers. Note that specific package versions can be made available in Production Containers. If you have a request for a specific package version, please [contact support](https://support.chainguard.dev?utm=docs). Refer to [Get support](/get-started/get-support/) for the portal's prerequisites. ## Versions available by tier If you use [Chainguard Free Containers](/chainguard/containers/concepts/container-categories/#free-containers), you will have access to the `:latest` version of any Container available to the public. In some cases, you will also have access to the `:latest-dev` version, which includes a shell and package manager. For example, the Python container image has both `cgr.dev/chainguard/python:latest` and `cgr.dev/chainguard/python:latest-dev`. Many of the programming languages have these options available, including the Java JDK and JRE containers, PHP, Go, Node, Ruby, and Rust. If you use enterprise Chainguard Production Containers, you will have access to more versions. The Chainguard approach is as follows: * For **multiple-release track projects**, you will have access to major and minor versions that are actively maintained. * For **single-release track projects**, you will receive the `:latest` tag as well as every versioned tag that is released over time. ## Floating tags and epoch tags Chainguard's container images use _floating tags_. This means that a container image's tag always points to the most recent build or version within a version stream, rather than a fixed, immutable image. For example, `python:3.13` will always point to the latest version of that version stream (`3.13.9`, as of this writing), even if older tags like `3.13.8` and `3.13.7` are available. ### Epoch tags Epoch tags provide a clear, human-readable way to track incremental updates to a specific version of a container image, especially when changes are made to the main package or for security fixes. When upstream projects are slow to release fixes, Chainguard will sometimes patch vulnerabilities directly rather than wait. For example, say there's a CVE in Go 1.21.3 and the Go team is uncharacteristically slow releasing a fix. Chainguard could patch 1.21.3 directly and release it as 1.21.3-r2, while continuing to make the original available as 1.21.3-r1. Any further patches to that version would increment the suffix to -r3, -r4, and so on. This suffix is called the epoch number. Chainguard may do this to patch vulnerabilities, remove unnecessary bloat, rebuild the same source with newer tools, or address bugs in our build configs and tooling. Epoch tags make it easy to track these incremental updates in a clear, human-readable way — particularly useful when changes affect the main package or involve security fixes. #### How epoch tags work Once a newer epoch tag is available, the previous one stops being updated. For example, the tag `1.14.5-r3` will no longer be updated once `1.14.5-r4` is available. Because Chainguard's container images are rebuilt frequently, this may not always be apparent; a container image with these tags may show both as being updated on the same day, when in fact `-r3` was updated only to be replaced later in the day by `-r4`. As mentioned previously, Chainguard's container images use floating tags. In the context of epoch tags, this means that the minor version and patch will both always point to the latest available epoch tag. For example, if the latest epoch tag for Chainguard's `python` container image is `python:3.14.0-r6`, then `python:3`, `python:3.14`, `python:3.14.0`, and `python3.14.0-r6` will all point to the same container image. However, you could still specify `python:3.14.0-r5` should you need. #### How epoch tags affect other packages Bear in mind that Chainguard's Containers, although minimal, will almost always contain more than one package. At the time of writing this, the Go image has more than 60 distinct packages in it, such as bash, busybox, git, glibc, make, and zlib. When we fix a vulnerability in bash for example, we likewise ensure that fix gets rolled out to every container image that includes bash, including the `go:1.21.3` image. The image tagged `1.21.3-r2` will pull in that bash fix, and fixes for any of the other packages in the image. Put simply, when you opt in to pulling `go:1.21.3-r2`, you're opting in to a consistent version of Go, and potentially floating versions of all the other packages. This means you get CVE fixes as well as patch, and minor, and even major version releases of bash, and every other package the image contains. It's important to note that using epoch tags to "lock" to specific images is discouraged, as even specific epoch tags can change over time and may introduce breaking or functional changes. For true immutability, use [image digests](/chainguard/containers/troubleshooting/inspecting-containers/) instead. You can learn more about our approach by reviewing our [blog on Chainguard's container image tagging philosophy](https://www.chainguard.dev/unchained/chainguards-image-tagging-philosophy-enabling-high-velocity-updates-pt-1-of-3?utm=docs). ## SLAs A vulnerability and patch service-level agreement (SLA) is available for Chainguard Production Containers. There are no SLAs available for Chainguard's free tier of container images, but you will have access to frequently updated and patched container images with low-to-zero CVEs. If you are a Chainguard Production Containers user, Chainguard vulnerability and patch SLAs apply only to supported and maintained versions of upstream projects as clearly published by the upstream projects or published container images that can be rebuilt using updated compilers and/or libraries. In the case of single-release track projects, this means that the Chainguard vulnerability and patch SLAs apply only to the latest version and corresponding version tags of the upstream projects. Containers that use open source applications that have reached their end of life are no longer patched. ## End of life and end of support software When an open source application version is no longer maintained by the upstream project or has otherwise met its end of life (EOL), Chainguard will generally no longer provide patches to that software. While the Chainguard Production Containers organization directory will continue to have previously purchased container images available, new builds will no longer be published and vulnerabilities are expected to accumulate in those Containers over time. It is recommended to move to an up-to-date, actively maintained version. If you need a version that has reached EOL, or one Chainguard never built, refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) for the options available to you. For software applications that maintain multiple concurrent release tracks, Chainguard will endeavor to provide [reasonable notice](/chainguard/containers/concepts/lifecycle-and-eol/versions/) when a particular software release version is expected to reach EOL status, thus no longer updated. No EOL notice will be provided for single-release applications where the only supported release is the `:latest` or corresponding version tag. ### How Chainguard determines EOL dates Chainguard sets the EOL status of each version stream from that project's lifecycle data: * For many projects, the data comes from [endoflife.date](https://endoflife.date). * For projects that endoflife.date doesn't cover, Chainguard uses the project's published support policy or upstream release data instead. * If a source doesn't reflect the project's open source releases, Chainguard sets the date manually. A manual date takes precedence over all other sources. For example, endoflife.date sometimes tracks a vendor's commercial support window, which continues after the project's last open source release. In that case, Chainguard sets the EOL date to the date of the last open source release, because that's the last release Chainguard can build from source. The Chainguard Console and `chainctl` show the EOL status or date that Chainguard uses for each version stream. That date can differ from the one on the upstream project's website when the two use different sources or define support differently. If a version stream is eligible for the End-of-Life (EOL) Grace Period, the grace period starts on that date. ### EOL grace period There are cases where an organization may want to continue using a container image after it has reached end-of-life. This could be because an image reaches EOL before the organization's release schedule, or perhaps later image versions have one or more issues that prevent the organization from upgrading. To help in situations like this, Chainguard offers an end-of-life grace period for eligible Containers, allowing customers access to new builds of container images whose primary package has entered its end-of-life phase for up to six months after they have reached EOL. Refer to our [overview of the EOL Grace Period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for more information. ## Inspecting the product release lifecycle There are a number of ways that you can inspect and understand the version lifecycle of your Chainguard Containers. ### List active tags #### Active tags in the Chainguard Console You can view tag statuses in the Chainguard Console. In the Console under **Images > Organization**, under the **Status** column, you can see: * `Active` status for images you are entitled to * `Expired` status for images you are no longer entitled to If you use Chainguard Production Containers, it is also possible to view the statuses of different tag versions for an image. Click into an image to see the statuses alongside tag versions. If a tag is no longer being actively maintained, it displays in gray text and an icon appears next to its name to indicate that it's inactive. >**Note**: This feature is not applicable to customers who use [Unique >tags](/chainguard/containers/reference/unique-tags/). #### List active tags with chainctl You can use `chainctl` to retrieve the list of tags that are being actively maintained for a Chainguard container image by running: ```shell chainctl image repo list ``` For instance, the following command lists the tags that are currently active for `python`: ```sh chainctl image repo list --repo=python -o json | jq -r '.items[].activeTags' ``` ```output [ "3", "3-dev", "3.10", "3.10-dev", "3.10.19", "3.10.19-dev", "3.10.19-r2", "3.10.19-r2-dev", "3.11", "3.11-dev", "3.11.14", "3.11.14-dev", "3.11.14-r2", "3.11.14-r2-dev", "3.12", "3.12-dev", "3.12.12", "3.12.12-dev", "3.12.12-r2", "3.12.12-r2-dev", "3.13", "3.13-dev", "3.13.9", "3.13.9-dev", "3.13.9-r2", "3.13.9-r2-dev", "3.14", "3.14-dev", "3.14.0", "3.14.0-dev", "3.14.0-r8", "3.14.0-r8-dev", "3.9", "3.9-dev", "3.9.25", "3.9.25-dev", "3.9.25-r0", "3.9.25-r0-dev", "latest", "latest-dev" ] ``` ### List package version information You can list the version information for packages that have multiple release tracks with `chainctl package versions list`, which uses the [List Repos API](/platform/api/spec/#/operations/Registry_ListRepos/). The following `chainctl` command lists the active release tracks for the `python` package: ```sh chainctl package versions list python --show-active ``` ```output VERSION | EOL DATE | EOL GRACE PERIOD END DATE ---------|------------|--------------------------- 3.10 | 2026-10-31 | 2027-05-01 3.11 | 2027-10-31 | 2028-05-01 3.12 | 2028-10-31 | 2029-05-01 3.13 | 2029-10-31 | 2030-05-01 3.14 | 2030-10-31 | 2031-05-01 ``` This output also includes the date when the version will become EOL, as well as the date after which it will no longer be covered by the EOL grace period. Refer to the [`chainctl` documentation](/platform/chainctl/chainctl-docs/chainctl_packages_versions_list/) for the full list of options that can be passed to the command. ### EOL grace period API The [EOL Grace Period API](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/#using-the-eol-grace-period-api) provides lifecycle information about a Chainguard container image's tags. For instance, the following snippet retrieves EOL data for the `python` image: ```sh REPO_ID=$(chainctl images repos list --repo=python -o json | jq -r '.items[0].id') curl -H "Authorization: Bearer $(chainctl auth token)" \ "https://console-api.enforce.dev/registry/v1/eoltags?uidp.childrenOf=${REPO_ID}" \ | jq '.' ``` This command will return output like the following: ```output { "items": [ ... { "id": "326bcde5903252f3ae2fe01c691b5a50f7046b5d/256c7780003faa0a/f37adb295422587a", "name": "3.10.16-r4-dev", "mainPackageName": "python", "tagStatus": "TAG_INACTIVE", "mainPackageVersion": { "eolDate": "2026-10-31", "exists": true, "fips": false, "legacyLts": "", "lts": false, "releaseDate": "2021-10-04", "version": "3.10", "eolBroken": false, "latestVersion": "", "versionSource": null }, "graceStatus": "GRACE_ELIGIBLE", "gracePeriodExpiryDate": null }, ... ] } ``` This output tells you whether a tag is active, inactive (no longer being rebuilt by Chainguard) whether it is eligible for an EOL grace period, and whether it is currently within its grace period. For full instructions on how to use the API and interpret its output, refer to [our overview of the EOL grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/#using-the-eol-grace-period-api). ### Refer to endoflife.date The [endoflife.date](https://endoflife.date) website lists the release tracks and product lifecycles of many open source projects. Chainguard uses it as the source of EOL dates for many projects, so it's a useful reference when you plan upgrades. The date that applies to a Chainguard container image is the one the Console or `chainctl` shows, which can differ from endoflife.date for the reasons outlined in [How Chainguard determines EOL dates](#how-chainguard-determines-eol-dates). --- ### Getting started with the Cilium Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/platform-and-infrastructure/cilium.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's Cilium container images provide a security-hardened foundation for Kubernetes networking with significantly reduced vulnerabilities compared to standard Cilium deployments. Cilium leverages [eBPF](https://ebpf.io/) technology to transparently secure network connectivity between services, enabling powerful security policies without application changes. Built on Wolfi OS, Chainguard's minimal Cilium images enhance your cluster's security posture while maintaining full compatibility with Cilium's advanced networking features. We will demonstrate how to get started with the Chainguard Cilium container images on an example K3s cluster. To get started, you'll need Docker, `k3d` (a CLI tool to install `k3s`), `kubectl`, and the `cilium` CLI installed. * [Docker](https://docs.docker.com/get-docker/) * [k3d](https://k3d.io/stable/#installation) * [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) * [cilium CLI](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-default/#install-the-cilium-cli)

Note: In November 2024, after this article was first written, Chainguard made changes to its free tier of container images. In order to access the non-free container images used in this guide, you will need to be part of an organization that has access to them. For a full list of container images that will remain in Chainguard's free tier, please refer to this support page.

What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Start up a K3s cluster Cilium does not work with the default Container Network Interface (CNI) plugin in K3s, so we'll start up a CNI for our K3s cluster and disable the network policy. To do so, create a YAML manifest named `k3d.yaml` with the following command: ```sh cat > k3d.yaml < export AGENT_IMAGE=cgr.dev/$ORGANIZATION/cilium-agent:latest export HUBBLE_RELAY_IMAGE=cgr.dev/$ORGANIZATION/cilium-hubble-relay:latest export HUBBLE_UI_IMAGE=cgr.dev/$ORGANIZATION/cilium-hubble-ui:latest export HUBBLE_UI_BACKEND_IMAGE=cgr.dev/$ORGANIZATION/cilium-hubble-ui-backend:latest export OPERATOR_IMAGE=cgr.dev/$ORGANIZATION/cilium-operator-generic:latest ``` > **Note**: If you don't remember the name of your Chainguard organization, you can find it by running `chainctl iam organizations list -o table`. After that, install Cilium using the following command: ```sh cilium install \ --helm-set hubble.relay.enabled=true \ --helm-set hubble.ui.enabled=true \ --helm-set image.override=$AGENT_IMAGE \ --helm-set hubble.relay.image.override=$HUBBLE_RELAY_IMAGE \ --helm-set hubble.ui.frontend.image.override=$HUBBLE_UI_IMAGE \ --helm-set hubble.ui.backend.image.override=$HUBBLE_UI_BACKEND_IMAGE \ --helm-set operator.image.override=$OPERATOR_IMAGE ``` This will return output similar to the following: ```output 🔮 Auto-detected Kubernetes kind: K3s ℹ️ Using Cilium version 1.14.2 🔮 Auto-detected cluster name: k3d-k3s-default ``` Now that your cluster has a CNI plugin installed, the Pods will start to transition to the `Running` state. This may take a few minutes. Run the following command to check the status of the Pods: ```sh watch kubectl get pods --all-namespaces ``` When all the Pods have a status of `Running` or `Completed`, press `Ctrl+C` to exit the watch. ## Verify that the Cilium installation is successful Cilium comes with the `connectivity test` command, which is useful for verifying whether the Cilium installation was successful. Run the following command to run the connectivity test: ```sh # We skip one of the tests because it needs `jq` util on the agent image, which we don't bundle. cilium connectivity test \ --external-cidr 8.0.0.0/8 \ --external-ip 8.8.8.8 \ --external-other-ip 8.8.4.4 \ --test \!no-unexpected-packet-drops ``` This should takes about 5 minutes to complete. It will return output similar to the following: ``` ℹ️ Single-node environment detected, enabling single-node connectivity test ℹ️ Monitor aggregation detected, will skip some flow validation steps ✨ [k3d-k3s-default] Creating namespace cilium-test for connectivity check... ✨ [k3d-k3s-default] Deploying echo-same-node service... ✨ [k3d-k3s-default] Deploying DNS test server configmap... ✨ [k3d-k3s-default] Deploying same-node deployment... ✨ [k3d-k3s-default] Deploying client deployment... ✨ [k3d-k3s-default] Deploying client2 deployment... ⌛ [k3d-k3s-default] Waiting for deployment cilium-test/client to become ready... ⌛ [k3d-k3s-default] Waiting for deployment cilium-test/client2 to become ready... ... ✅ All 32 tests (263 actions) successful, 2 tests skipped, 1 scenarios skipped. ``` Note that the exact output and results of individual tests may differ based on your local machine's configuration. ## Exploring the Cilium Hubble UI Before you can explore the Hubble user interface, you will need to enable it with the `cilium` command: ```sh cilium hubble enable --ui ``` Then run the following command to bring up the Hubble UI: ```sh cilium hubble ui ``` A new browser window will open showing the Hubble UI. You can explore the Hubble UI to see the network traffic in your cluster. If you are running this during the connectivity test, it will show a visualization of the test traffic. ## Clean up your K3s cluster Once you are done exploring Cilium, you can clean up your K3s cluster by running the following command: ```sh k3d cluster delete ``` ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose Cilium Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Strategies for minimizing your CVE risk URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/vulnerability-management/cve-risk.md Last Modified: October 8, 2026 Tags: Conceptual, Chainguard Containers, CVE [Common vulnerabilities and exposures](https://www.chainguard.dev/supply-chain-security-101/what-is-a-cve#what-is-a-cve) (CVEs) are an increasing concern for developers and organizations, which is why Chainguard developed its minimal container images that reduce the attack surface. A new CVE in a widely-used application or a vulnerability scan with numerous positive results can significantly impact security posture, compliance requirements, and development timelines. Chances are, your software has already been impacted by a CVE. It's likely there are active CVEs in software you are using. After all, there are software vulnerabilities currently in existence that haven't even been discovered (known as zero-day vulnerabilities). With that said, this conceptual article aims to highlight a few practices and strategies you and your team can use to reduce the risk of CVEs on your software. It also includes a section on [tools recommended by Chainguard](#recommended-tools) that can help to reduce your attack surface area and minimize your risk of CVEs. ## Understanding potential risks An important step to minimizing your CVE risk is to understand the potential impacts and develop a sense of how CVEs can make their way into your applications. One way you can do this is to familiarize yourself with the various databases that list known CVEs and their exploitations, including the [CVE Program](https://www.cve.org/About/Overview) and the [Known Exploited Vulnerabilities (KEV) Catalog](https://www.cisa.gov/known-exploited-vulnerabilities-catalog). It also might be helpful to stay up to date with industry news. Software vulnerabilities and attacks have gained more attention from technology journalists. For example, the [Heartbleed](https://www.cve.org/CVERecord?id=CVE-2014-0160) vulnerability received widespread coverage when it was discovered in 2014, as did the [Log4Shell](https://www.cve.org/CVERecord?id=CVE-2021-44228) vulnerability in 2021. Bear in mind, just keeping up to date with industry news and recent CVEs won't prevent vulnerabilities from encroaching on your projects. After all, malicious actors could be exploiting a vulnerability in your code long before the CVE is discovered and reported. Having said that, understanding how vulnerabilities are discovered, categorized, and exploited can be useful when thinking about how to harden your system's defenses. Another way to prepare yourself for the potential risks associated with CVEs is to have a plan in place for what you should do if new vulnerabilities affecting your software arise. It's becoming more common for organizations to develop a playbook built out ahead of time. The Cybersecurity & Infrastructure Security Agency (CISA) recently published a [Vulnerability Response Playbook](https://www.cisa.gov/sites/default/files/publications/Federal_Government_Cybersecurity_Incident_and_Vulnerability_Response_Playbooks_508C.pdf). The goal of this playbook is to provide a framework for Federal Civilian Executive Branch agencies to identify and mitigate vulnerabilities. Although this playbook is meant specifically for organizations working with the federal government, the idea of having a plan in place ahead of a vulnerability is a great way to minimize the risk. ## Maintaining dependency hygiene Dependency hygiene is the practice of ensuring that the dependencies you introduce into your project do not contain vulnerabilities. We've found that many popular container images, when not updated, [will accumulate about one known vulnerability per day](https://www.chainguard.dev/unchained/enforce-against-vulnerability-sprawl-with-up-to-date-images). Many of the most widely used container images aren't updated regularly and [past academic software engineering research](https://arxiv.org/pdf/1811.12874.pdf) has found that most images on Docker Hub, the most widely used container registry, have not been updated for over 120 days. Some teams go so far as to actively avoid using dependencies, working under the belief that taking a "roll-your-own" approach to all their tooling is an effective way to reduce CVEs. Although first-party tools may report fewer CVEs when scanned, they won't be battle-tested like widely used open source software, meaning there are drawbacks to this approach. ## Recommended tools This section outlines a few categories of tools that can be useful for minimizing CVEs, and provides a few examples for each. ### Scanners There are a number of tools available that allow you to scan your third party code for CVEs. At Chainguard, we use [**Grype**](https://www.chainguard.dev/unchained/why-chainguard-uses-grype-as-its-first-line-of-defense-for-cves) to scan our own Chainguard Containers, as it's open source and it can scan Software Bills of Materials (or SBOMs). Additionally, Grype biases towards [false positives](/chainguard/containers/security-and-compliance/working-with-scanners/false-results/) over false negatives. Looking into a vulnerability in an image that turns out to be a false positive can be preferable to overlooking a real vulnerability that impacts end users. Another open-source scanning option is [**Falco**](https://falco.org/). Falco works with environments running in individual containers, hosts, Kubernetes, and the cloud. Falco works by collecting data from various sources — including Linux kernel syscalls, Kubernetes audit logs, and events from systems like GitHub or Okta — and compares them with a set of rules. Falco comes with a list of rules by default, but you can also create your own rules to suit the needs of your project. If any of the collected data breaks one of the rules, Falco will identify it as a security issue. Be aware that the results of a vulnerability scan can only tell you part of the story. Say, for example, that Project A has only a few dependencies while Project B has many. Logically, a vulnerability scan of Project A will likely return fewer results than one for Project B, but that doesn't mean that Project A is at any less of a risk. Although its results might show only a few CVEs, one of these vulnerabilities could still have a serious impact on Project A. Conversely, Project B's scan results might show a large number of CVEs, but most or all of these could be false positives. [**Scorecard**](https://securityscorecards.dev/) is another scanning tool, though it isn't specifically a vulnerability scanner. Scorecard is an automated tool from the Open Source Security Foundation that performs a variety of security checks on software, returning a score between 1 and 10 for each one. These scores can help you understand what you need to work on to improve your project's security, and can also help you assess the security of your dependencies. For a more in-depth discussion of how scanners may fail to collect certain information, we encourage you to check out our [blog post on "Software Dark Matter"](https://www.chainguard.dev/unchained/software-dark-matter-is-the-enemy-of-software-transparency?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). ### Automating updates As stated in the preceding section on dependency hygiene, out-of-date dependencies pose a serious risk to software projects. The older and more out of date a given dependency is, the more likely it is to contain vulnerabilities. Automating updates for your projects can help avoid the problems associated with outdated dependencies One tool that's useful for automating updates for your dependencies is [**Dependabot**](https://docs.github.com/en/code-security/dependabot). Dependabot is a free tool offered by GitHub that sends alerts to repositories affected by new vulnerabilities and — if possible — raises a pull request to update the affected dependencies. If you'd like to automate updates outside of GitHub repositories, [**Snyk**](https://snyk.io/) is another tool that enables automatic updates. Synk can integrate into IDEs as well as repositories, allowing you to continuously scan for vulnerabilities. Like Dependabot, Snyk will automatically submit a pull request when it encounters a vulnerability and it can recommend a solution. There are a number of important considerations one should make when keeping container images up to date. Please check out our [conceptual article on the subject](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/). ### Minimal container images As mentioned previously, the fewer dependencies a given piece of software uses, the lower likelihood that it will be impacted by CVEs. To this end, Chainguard provides a library of minimal container images that can minimize your CVE risk. [**Chainguard's container images**](https://www.chainguard.dev/chainguard-images) are distroless, meaning they contain only an application and its runtime dependencies. These images do not even contain a shell or package manager. Because these images minimize the number of dependencies in this manner and thus reduce the potential attack surface, they inherently contain few to zero CVEs. Also, they are rebuilt nightly to ensure images are completely up-to-date and contain all available security patches, and the engineering team [often fixes vulnerabilities before they're detected](https://www.chainguard.dev/unchained/how-chainguard-fixes-vulnerabilities). If you're looking for a certain image that isn't included in Chainguard's library, you can build your own minimal container images with [**Wolfi**](https://github.com/wolfi-dev), a community Linux distribution designed by Chainguard for the container and cloud-native era. Chainguard started the Wolfi project to enable building Chainguard Containers, which required a Linux distribution with components at the appropriate granularity and with support for [glibc](https://www.gnu.org/software/libc/). Wolfi includes a fully declarative build system and provides a high-quality, build-time SBOM as standard for all packages. Its packages are designed to be granular and independent — in order to support minimal images — and uses the proven and reliable `apk` package format. Please note that as of March of 2024, Chainguard will maintain one version of each Wolfi package at a time. These track the latest version of the upstream software in the package. Chainguard does not provide patch support for previous versions of packages in Wolfi. Existing packages will not be removed from Wolfi and you may continue to use them, but be aware that older packages will no longer be updated and will accrue vulnerabilities over time. This change ensures that Chainguard can provide the most up-to-date patches to all packages for our container images users. Note that specific package versions can be made available in Production Containers, if you have a request for a specific package version, please [contact support](https://support.chainguard.dev?utm=docs). Refer to [Get support](/get-started/get-support/) for the portal's prerequisites. ## Learn more As mentioned in the introduction, there's no way to guarantee that no CVEs will affect your software. However, we hope that by reading this article you'll have learned some practical tips you can use to help minimize your exposure to vulnerabilities and keep your software secure. If you'd like to learn more about CVEs, and strategies for remediating them, we encourage you to check out the following resources: * [What are software vulnerabilities and CVEs?](https://www.chainguard.dev/supply-chain-security-101/what-is-a-cve#what-is-a-cve) * [False positives and false negatives with container scanners](/chainguard/containers/security-and-compliance/working-with-scanners/false-results/) * [Considerations for keeping containers up to date](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/) --- ### Considerations for keeping containers up to date URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard rebuilds container images daily to ensure the latest security patches are always included, addressing a critical challenge in container security. While keeping images up-to-date is essential for receiving security updates and new features, updates must be balanced with stability concerns since any code change can potentially introduce breaking changes or impact dependent systems. Due to the complexity involved in modern containerized applications, there is no one-size-fits-all approach to keeping your container images up to date. With these conflicting approaches in mind, this article will explore how best to keep container images up-to-date. ## Understanding image versioning and naming conventions Before discussing image updates, it's helpful to have a baseline understanding of how images are typically versioned and named. [*Semantic versioning*](https://semver.org/) — also known as "semver" — is a system for determining how version numbers are assigned to a given piece of software. Software using semver has versions numbered in the format of `X.Y.Z`. `X` is reserved for major versions that are backwards incompatible, `Y` is used for minor versions that are backward compatible, and `Z` is used for patches and bug fixes. As an example, for a piece of software with the version number `3.5.2`, `3` is the major version, `5` is the minor version, and `2` is the patch number. Semantic versioning is intended to improve problems associated with having a large number of dependencies. Although semver has become a common practice throughout the software industry, it isn't used universally. When referencing an image, it's important to know exactly what image you are working with. Docker uses the following format for image names. ```image [host]/[repository][image_name][:tag] ``` For example, the full name for the [Go Chainguard Container](https://images.chainguard.dev/directory/image/go/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-recommended-practices-considerations-for-image-updates), is `cgr.dev/chainguard/go:latest`. Certain elements of this name format are optional in many cases. For instance, if you omit the hostname portion of an image name in either Docker or Kubernetes, both will default to using the public Docker registry. ## Automating updates One solution to avoiding out-of-date container images would be to automate the process of updating images. This may be a system that scans a container registry for new versions of a given image and updates the application any time a new version of an image is released. However, you will need to consider a few specific details around your particular software and organization's situation prior to fully automating container image updates, as you may run into issues with breaking changes upstream. You’ll want to think through the following: * What is your risk appetite: is your software part of critical infrastructure or is its function of less urgent importance? * What tests do you have in place? If you have confidence in your test suite, you may have a higher risk tolerance as you’ll trust that tests will fail and prevent broken images from going live. You’ll also want to consider what exact version of an image you are updating, whether you are pinning to a digest to guarantee reproducibility, defaulting to a tag, or automating minor or patch version updates. Even with safeguards in place, there is still a risk that at some point your application will use a version of an image it’s no longer fully compatible with, and you’ll need to have a plan in place to remedy this. There are some tools that can automate some or all of the process of updating your images. One such example is [watchtower](https://github.com/containrrr/watchtower), which will update the running version of your app when an appropriately tagged image is pushed to a relevant registry. Additionally, there are [FluxCD](https://fluxcd.io/flux/guides/image-update/) and [Argo CD's Image Update tool](https://argocd-image-updater.readthedocs.io/en/stable/). If you're going to use tools like these to automate image updates, it is a recommended that you have testing and monitoring in place. Monitoring, alerting, and logging support you and your organization in making the information you need for debugging, security-related analysis, and compliance requirements available while also keeping a history of relevant data. ## Be mindful about tagging practices Developers will often create multiple variations of the same image. Sometimes these different images may represent distinct numbered versions of the image, or they may contain unique sets of packages. Typically, these different images in the same series will share a name and be stored in the same repository, but the developer will differentiate them by giving them different *tags*. In the context of containers, a tag is a human-readable identifier associated with an image. These make it easier to distinguish between different versions of images within the same repository. Oftentimes, developers will pin their project to a specific tag. As an example, imagine a project that uses an image named `example_image` and it is pinned to version `1.9`, as in `cgr.dev/chainguard/example_image:1.9`. One day, version `2.0` is released, but the project developers choose not to upgrade, as doing so would introduce breaking changes. There may be situations where different tags point to the same image. For example, the first version of an image named `sample_image` is released with the tag `1`. Later on, a minor version is released with the tag `1.2`, and then even later patch is released with the tag `1.2.3`. In this case, the images tagged `1` and `1.2` would point to the same image as the one tagged `1.2.3`, since `1.2.3` is the latest patch of both these major and minor versions. Conversely, if the developer later decides to patch version `1.1` of the `sample_image` and tags it as `1.1.4`, the image tagged `1` will still point to `1.2.3`, as that's the latest iteration of the most recent minor version. Many systems will default to using the `latest` tag in certain cases if you don't specify one. For example, if you use Docker to build an image but don't specify a tag, it will always default to tagging it with `latest`. There's a misconception that the `latest` tag always represents the most recent stable version of a given image. The normal convention is for the latest tag to point to the most up-to-date stable version of the image, but it is only a convention and not an enforced rule. One of the most important features of container builds is their *reproducibility* as you would like to ensure that you are using the same image each time. However, container tags are mutable, meaning that they can change over time. If you pin your application to a specific image tag and then the image associated with that tag gets updated and you redeploy or pull the image again, your application will be using a different image than it was before. Eventually, the image could change to the point that it no longer works with your application. When it comes to container versions, pinning an application to a major version is usually an acceptable practice since minor version increases typically won't break things. That being said, the potential for "jumping” across minor or major versions without warning means that pinning an application to a major or minor tag isn't suitable for many production workflows. To avoid this problem, it's recommended to pin projects to an *image digest*. A digest is a content-based hash of the image contents and is guaranteed to be immutable. Because a digest will always point to the same image, its reproducibility is guaranteed. To find the digest for an image, users can run a command like the following. ```sh docker images --digests cgr.dev/chainguard/wolfi-base ``` ```output REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE cgr.dev/chainguard/wolfi-base latest sha256:490977f0fd3d8596d173839dbb314153797312553b43f6a24b0e341cf2e8d473 2606ed78c658 9 days ago 10.9MB ``` To clarify, using tags to keep your images may work for you and your organization. However, if you're concerned about ensuring reproducibility — or you just want more control over what images you're running — using digests can be a better approach for your situation. ## Recommendations As mentioned in the previous section, image digests guarantee full reproducibility. For that reason, it's generally recommended that projects update their container images by digest whenever possible. Of course, digests do come with their own drawbacks. Image digests are not human readable; unlike tags, you can't always tell the difference between two image digests with a quick visual scan. Digests also don't make it clear whether a digest represents an older version, or whether one digest is older than another. Combined, these factors mean that digests can make it more difficult to know whether an image is up to date. [Chainguard's container images](/chainguard/containers/) are rebuilt daily in order to ensure that they're kept up to date and include all the latest available security patches. We recommend adopting a development pattern where digests are used to identify Chainguard Containers and are regularly updated by a bot, ensuring that the project and its downstream users benefit from reduced vulnerability counts, bug fixes, and new features. We do this ourselves with [Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/), our own GitHub Action for keeping digest-pinned references current. As with the recommendation stated previously, Chainguard requires a human to approve the digestabot's update before it's deployed. Technically, we could set up an automatic approval for the bot's updates, but requiring a human approval combines the stability and reproducibility of using a digest with the good security hygiene of keeping images regularly updated. There are many factors to consider when developing a process for keeping your images up to date, so you're unlikely to find useful advice on the subject beyond the general strategies outlined in this guide. Of course, different update strategies will work for different circumstances. Ultimately, whatever process you or your organization land on for keeping images up to date should suit the needs of your users without becoming a burden to you or your organization. ## Learn more To reiterate, there's no one-size-fits-all approach to keeping one's images up to date. Our goal for this article is to introduce some of the important factors one should consider when developing a container image update plan for their application. If you'd like to learn more about the subjects touched on in this guide, we encourage you to check out the following resources. * [How to use Chainguard Containers](/chainguard/containers/using-and-deploying/using-containers/) * [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/) * [How to compare Chainguard Containers with chainctl](/platform/chainctl-usage/comparing-images/) --- ### False positives and false negatives with container image scanners URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/working-with-scanners/false-results.md Last Modified: October 15, 2026 Tags: CVE, Overview, Conceptual A *vulnerability scanner* is a tool that analyzes your software components and reports any [CVEs](https://www.chainguard.dev/supply-chain-security-101/what-is-a-cve) it finds. Using a vulnerability scanner to find CVEs that impact your system is a critical step in [software vulnerability remediation](https://www.chainguard.dev/supply-chain-security-101/cve-remediation-explained), but as you begin to triage scanner-reported vulnerabilities, you may find that your scanner's results are not perfectly accurate. The goal of a vulnerability scanner is to identify the vulnerabilities that impact your container images, which can be considered *true positive vulnerabilities*. Sometimes, a scanner surfaces CVEs which are not actually impacting your images, which are called *false positive vulnerabilities*. Your scanner may even miss some vulnerabilities that are impacting you, termed *false negative vulnerabilities*. The presence of false positive and negative vulnerabilities can add a tricky layer to the vulnerability remediation process. False positive vulnerabilities can be "noisy" and distract you from remediating the vulnerabilities that are actively impacting your containers. Additionally, false negative vulnerabilities can silently affect you, making them a hidden threat to your container image security. This article aims to explain the formation of false positive and false negative vulnerabilities, allowing you to better understand what they mean, how they impact you, and how you can use tools to fine-tune your scanner to improve the accuracy of your scan results. > If a Chainguard advisory or the Chainguard Console says a CVE is fixed but your scanner still reports it, see [Resolve a scanner finding for a CVE Chainguard has fixed](/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve/). For more about why scan results can differ between your scanner and the Chainguard Console, refer to [the support article "Understanding Vulnerability Scanner Discrepancies with Chainguard Images."](https://support.chainguard.dev/hc/en-us/articles/49564106705819-Understanding-Vulnerability-Scanner-Discrepancies-with-Chainguard-Images) ## How false positives and false negatives occur Understanding how false positives and negatives occur first requires insight into how scanners operate. A vulnerability scanner starts by ingesting various *vulnerability databases*. A vulnerability database (such as the [National Vulnerability Database](https://nvd.nist.gov/)) is home to information regarding causes, impacts, and security advisories for software vulnerabilities. A container image scanner will often use multiple vulnerability databases as references for what software components may be vulnerable. Different databases may focus on documenting vulnerabilities for certain software vendors or products. As a result, collecting vulnerability data from numerous sources allows a scanner to identify a more exhaustive selection of vulnerabilities across software components. When you conduct a vulnerability scan on your container images, the scanner attempts to detect what packages comprise the image. It does so by sifting through the images' source code and files introduced by package managers in search of open source software components. If successful, the scanner collects key metadata (such as package names and versions) that can be referred to later. It will then compare this metadata with the various databases it references in an attempt to determine if any packages in the software are affected by vulnerabilities. Due to the complexity of cross-referencing image components against databases, scanners are not infallible. Sometimes vulnerabilities are misidentified — or even missed entirely. There are multiple ways in which this can occur throughout the scanning process from start to finish. ### Scanner-level issues Vulnerability scanners may be unable to consistently detect container components, causing the results of scans to be incomplete or inaccurate. Scanners often rely on package managers and package metadata to catalog the parts of a container. If a scanner cannot collect complete information about package metadata the results of the scan may not reflect the true contents of the image. For example, if a package in a container is not tracked by a package manager, it may go undetected, so vulnerabilities contained within are not reported. For an in-depth discussion of how scanners fail to collect certain information, we encourage you to check out our [blog post on "Software Dark Matter"](https://www.chainguard.dev/unchained/software-dark-matter-is-the-enemy-of-software-transparency?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). ### Program scope Some reported vulnerabilities may be falsely positive because they are detected outside of the scope of your container images. For example, a container may have packages in it which have vulnerabilities, though the [specific vulnerable functions](https://www.chainguard.dev/unchained/stemming-the-tide-of-false-positive-vulnerabilities?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) may not be called or reachable by your images. As a result, these vulnerabilities may pop up in your scans despite your program not interacting with their vulnerable sources. It is worth noting that these false positive vulnerabilities could impact you if other vulnerabilities are leveraged to access them, so they are not negligible. However, such a situation arises infrequently as a result of the exploitation of other vulnerabilities, which are first in a line of defense. ### Missing or mismatched information Inconsistencies in package versioning conventions may cause scanners to fail in detecting the correct versions of your software components. Software vendors choose different version naming schemes for their products, so scanners may not easily detect what package versions are in use. Alternatively, missing or inconsistent data on vulnerable package versions in vulnerability databases can have a similar effect. In both cases, your scanner may struggle in correlating the package version in your container to package versions in vulnerability records, producing false positives and negatives where components are mismatched. ### Go components reported as `(devel)` Some Go binaries report their module version as `(devel)` instead of a release version. This commonly occurs when a binary is built without release-version metadata. A component catalog can show the pattern like this: ```output NAME VERSION TYPE cmd/addr2line (devel) go-module cmd/asm (devel) go-module cmd/buildid (devel) go-module cmd/cgo (devel) go-module cmd/compile (devel) go-module ... ``` This is component-catalog output, not a list of confirmed vulnerabilities. The important signal is the combination of a Go component type and the literal `(devel)` version. When a scanner cannot map `(devel)` to a concrete module version, it may be unable to compare the component with fixed-version ranges. The scanner can then report every CVE known for that module, including CVEs that do not apply to the exact source revision or binary being scanned. Treat this pattern as a version-metadata limitation to investigate, not as proof that every reported CVE is present. #### How to investigate a `(devel)` result 1. Confirm the component name, module path, and component type in the scanner or SBOM output. 2. Find the file where the scanner found the binary, and check whether a Chainguard package owns it, as described in [When the finding is a Go module or Java dependency](/chainguard/containers/security-and-compliance/vulnerability-management/cve-status/#when-the-finding-is-a-go-module-or-java-dependency). If a Chainguard package ships the binary, Chainguard records the binary's vulnerabilities against that package. For example, the Go toolchain binaries in the earlier output belong to a Go package such as `go-1.27`. Check that package's advisories with `chainctl images advisories list`, and compare its installed version with the fixed version, as described in [Components that a Chainguard package ships](/chainguard/containers/security-and-compliance/vulnerability-management/cve-status/#components-that-a-chainguard-package-ships). If your own build added the binary, Chainguard's advisories don't cover it. Investigate its version metadata instead: 1. Inspect the binary’s embedded Go build metadata, or the build configuration that produced it, to determine whether a release or commit version is available. 2. Compare the scanner’s affected and fixed-version ranges with the source revision or release used to build the binary. 3. Rebuild with version metadata when possible, then regenerate the SBOM and rescan. If the scanner still reports the CVEs, provide the image digest, binary or module name, reported `(devel)` version, scanner and database versions, and the relevant scan output when requesting support. ### SCA vs SAST tools [Software Composition Analysis (SCA)](https://snyk.io/series/open-source-security/software-composition-analysis-sca/) tools focus on the detection and assessment of open source software components, making them ideal for container image scans. SCA tools cross-reference package metadata with vulnerability databases to determine if vulnerabilities are present. However, in situations where SCA tools cannot match a package to its entries in a vulnerability database, false negatives can occur. [Static Application Security Testing (SAST)](https://owasp.org/www-community/Source_Code_Analysis_Tools) tools scan your proprietary software to search for vulnerabilities. They provide guidance on how to modify code to resolve a given vulnerability. However, SAST tools lack context surrounding the software's behavior at runtime, giving an incomplete analysis of how the software may function. Without a comprehensive view of the software's anatomy and use, many false positive vulnerabilities [occur in SAST scans](https://blog.checkpoint.com/security/avoiding-false-positive-the-silent-sast-killer/). To learn how to choose the right scanner method for your application, check out [this comparison of SCA and SAST scanning tools](https://github.blog/2022-09-09-sca-vs-sast-what-are-they-and-which-one-is-right-for-you/ ). ## Impacts of false positives and false negatives False positive vulnerabilities are [red herrings](https://en.wikipedia.org/wiki/Red_herring): They look important, but take you away from the vulnerabilities which truly matter. Identifying what vulnerabilities are true and false positives can be difficult, as your false positive results are mixed in with other true positive finds. When dealing with hundreds of vulnerabilities, the [costs of extensive triage and remediation [PDF]](https://media.bitpipe.com/io_15x/io_152272/item_2184126/ponemon-state-of-vulnerability-response-.pdf) can add up fast. False negative vulnerabilities can have major impacts on a system if they are not discovered. They are a goldmine for attackers if they find them before you do. The successful exploitation of a false negative vulnerability can result in a *zero-day attack*. A [zero-day attack](https://usa.kaspersky.com/resource-center/definitions/zero-day-exploit) occurs when an adversary exploits a vulnerability in software which the maintainer is unaware of. This can cause the attack to go unnoticed or unresolved until the vulnerability is discovered. Many [infamous CVEs](https://www.chainguard.dev/supply-chain-security-101/5-real-cve-examples-and-how-to-prevent-them), such as Log4Shell, are examples of incredibly impactful zero-day vulnerabilities. The presence of false positive and negative vulnerabilities in your scans add another unnecessary layer of complexity to the remediation process. Having an unreliable scanner can increase the amount of time you spend filtering out false positive results, giving you less time to focus on the true positives affecting you. Additionally, an inconsistent scanner reduces your confidence in the completeness and accuracy of your scans, making it difficult to determine if false negatives are lurking among your containers. ## Reducing false results Unfortunately, there is no single way to stop false positives and false negatives from occurring in your vulnerability scans. Triaging reported vulnerabilities will always be necessary to determine what vulnerabilities need to be addressed based on their severity. However, steps can be taken to reduce the overall number of false positive and negative results that surface. ### SBOMs, purls, and VEX An SBOM, or [Software bill of materials](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom), is a helpful document that catalogs the packages and components of your software in a machine-readable format. Using an SBOM can improve your vulnerability scans as package information is stored in one place, so scanners don't have to hunt down and risk missing component information throughout your software. There are [different ways to generate an SBOM](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom) in order to improve their comprehensiveness and utility. To address the inconsistencies caused by software vendors using proprietary version naming schemes, adopting the [purl specification](https://github.com/package-url/purl-spec) can help. A purl, or package URL, aims to standardize versioning by outlining a convention that incorporates pertinent package information in every identifier. Using purls can [reduce the number of false positives which surface](https://www.chainguard.dev/unchained/a-purl-of-wisdom-on-sboms-and-vulnerabilities?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) by making it easier for scanners to align version information between data sources. [Vulnerability Exploitability eXchange (VEX) [PDF]](https://www.cisa.gov/sites/default/files/2023-01/VEX_Use_Cases_Aprill2022.pdf) documents can provide helpful insight about the status of software vulnerabilities in a product. VEX documents allow developers to report the exploitability status of a vulnerability in a product through the use of identifiers. Using VEX can streamline the triage and remediation process by making it simpler to determine when action is needed for a given vulnerability. One way to leverage VEX documents is through [OpenVEX](https://github.com/openvex), an open source implementation of the VEX specification. OpenVEX offers a set of tools to make ingesting and manipulating VEX documents easier. To learn more, check out our article on [getting started with OpenVEX](/open-source/sbom/getting-started-openvex-vexctl/). ### Hardened base images A primary cause of large vulnerability counts reported in scanners is the dead weight caused by unnecessary dependencies. Many popular container images contain hundreds of packages, each with their own potential to introduce vulnerabilities, both true and false positives. Having so much noise to sift through draws out the vulnerability management process. [Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement), built on the [Wolfi un-distro](/open-source/wolfi/), can help you reduce your CVE count dramatically by keeping things minimal. By bundling only what is necessary to run the image, Chainguard's container images are hardened and lightweight in comparison to their counterparts. To learn more about how Chainguard Containers can help you achieve low (or zero!) CVEs in your containers, check out our [documentation](/chainguard/containers/overview/). ### Updating and rebuilding images Updating your container images to utilize recent stable package releases can help reduce the number of vulnerabilities found in your images. Research from [Chainguard Labs](https://www.chainguard.dev/labs?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) has shown that [popular container images accumulate about one CVE per day](https://www.chainguard.dev/unchained/enforce-against-vulnerability-sprawl-with-up-to-date-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) when not updated. In many cases, updating packages and rebuilding your images to incorporate them can readily resolve numerous vulnerabilities with released fixes. This may resolve false positive vulnerabilities outside of your program scope, allowing you to focus on the true positives which still remain. ## Learn more With false results mixed into your scans, triaging and addressing true positive vulnerabilities can quickly become a time-consuming task. Taking steps to adjust your scanner can help reduce the deluge of false positives and negatives clouding your scans, bringing you closer to securing your software. In this article, you learned how false results from your vulnerability scanners can occur, and how they can impact your development workflow. Additionally, you explored various ways you can improve the accuracy of your scanner through the application of tools like VEX, rebuilding your images, and choosing a base image suitable for your applications. To learn more about reducing false positives and negatives in your images, you can check out our [collection of articles on SBOMs and VEX](/open-source/sbom/), read about [selecting a base image](https://www.chainguard.dev/supply-chain-security-101/selecting-a-base-container-image) for your applications, or discover how Chainguard Containers can help you [reach zero CVEs in your containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). --- ### Getting started with the MariaDB Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/web-and-data-services/mariadb.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's MariaDB container image provides a security-hardened foundation for database workloads. Built on Wolfi with a distroless design, it removes unnecessary components while maintaining full MariaDB functionality. For current CVE data on each image and tag, see the [MariaDB entry in the Chainguard Containers directory](https://images.chainguard.dev/directory/image/mariadb/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-mariadb). Through daily rebuilds with the latest patches and minimal dependencies, Chainguard's MariaDB image dramatically reduces your database's attack surface. This enables you to run production MariaDB databases with enhanced security posture and a smaller container footprint. In order to illustrate how the MariaDB Chainguard Container might be used in practice, this tutorial involves setting up an example PHP application that uses a MariaDB database. This guide assumes you have Docker installed to run the demo; specifically, the procedure outlined in this guide uses [Docker Compose](https://docs.docker.com/compose/install/) to manage the environment on your local machine.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Step 1: Setting up a demo application This step involves downloading the demo application code to your local machine. To ensure that the application files don't remain on your system navigate to a temporary directory like `/tmp/`. ```sh cd /tmp/ ``` Your system will automatically delete the `/tmp/` directory's contents the next time it shuts down or reboots. The code that comprises this demo application is hosted in a public GitHub repository managed by Chainguard. Pull down the example application files from GitHub with the following command. ```sh git clone --sparse https://github.com/chainguard-dev/edu-images-demos.git ``` Because this guide's demo application code is stored in a repository with other examples, we don't need to pull down every file from this repository. For this reason, this command includes the `--sparse` option. This will initialize a sparse-checkout file, causing the working directory to contain only the files in the root of the repository until the sparse-checkout configuration is modified. Navigate into this new directory and list its contents to confirm this. ```sh cd edu-images-demos/ && ls ``` For now, this directory will only contain the repository's `LICENSE` and `README` files. ``` LICENSE README.md ``` To retrieve the files you need for this tutorial's sample application, run the following `git` command. ```sh git sparse-checkout set mariadb ``` This modifies the sparse-checkout configuration initialized in the previous `git clone` command so that the checkout only consists of the repo's `mariadb` directory. Navigate into this new directory. ```sh cd mariadb/ ``` From here, you can run the application and use a web browser to observe it working in real time, which we'll do in the next section. ## Step 2: Inspect, run, and test the sample application We encourage you to check out [the application code on GitHub](https://github.com/chainguard-dev/edu-images-demos/tree/main/mariadb) to better understand how this application works, but we'll provide a brief overview here. This demo creates a LEMP (Linux, (E)NGINX, MariaDB and PHP-FPM) environment based on Wolfi Chainguard Containers. We will use Docker Compose to bring up the environment, which will spin up three containers: an `app` container, a `mariadb` container, and an `nginx` container. These will run as services. Once the environment is up, you can visit the demo in your web browser. The `index.php` file contains code that does the following: * Connects to the MariaDB server running in the `mariadb` container * Creates a new table named `data` if it doesn't already exist * Inserts a new entry into the table with a random number * Queries the table to show all the entries Every time you reload the page, a new entry will be added to the table. Execute the following command to create and start each of the three containers and bring up the application. ```sh docker compose up -d ``` The `-d` option is short for `--detach`; this will cause the containers to run in the background, allowing you to continue using the same terminal window. If you run into permissions issues when running this command, try running it again with `sudo` privileges. > **Note**: If at any point you'd like to stop and remove these containers, run `docker compose down`. Once all the containers have started, you'll be able to visit the application and observe it working. Open up your preferred web browser and navigate to `localhost:8000`. There, you'll be presented with text like the following. ![Screenshot showing a Firefox web browser window with "localhost:8000" in the address bar. On the page is the following text: "Array ( [data_key] => code [data_value] => 7064 )"](mdb-demo-success-1.png) Every time you refresh your browser, a new entry will appear. ![Screenshot showing a Firefox web browser window with "localhost:8000" in the address bar. On the page is the following text: "Array ( [data_key] => code [data_value] => 7064 ), Array ( [data_key] => code [data_value] => 7006 ) Array ( [data_key] => code [data_value] => 7507 )". This indicates that the page was refreshed twice since the previous screenshot.](mdb-demo-success-2.png) This shows that the application is recording each visit in the MariaDB database and that the application is working correctly. After confirming that the application is functioning as expected, you can read through the next section to explore how else you can work with the `mariadb` container. ## Step 3: Working with the database The `docker-compose.yml` file contains some configuration details regarding the MariaDB database used in this example application. Run the following command to inspect the contents of this file. ```sh cat docker-compose.yml ``` We're interested in the `mariadb` service: ``` . . . mariadb: image: cgr.dev/chainguard/mariadb restart: unless-stopped environment: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 MARIADB_USER: php MARIADB_PASSWORD: password MARIADB_DATABASE: php-test ports: - 3306:3306 volumes: - ./:/app networks: - wolfi . . . ``` This section defines a few environment variables relating to the database used in the example application. Importantly, they specify that the application database runs under a user named `php` with the password "password". Using this information, you can connect to the `php` database running in the container with a command like the following. ```sh docker exec -it mariadb-mariadb-1 mariadb --user php -p ``` `docker exec` allows you to execute commands within a running container. The `-i` argument allows you to execute an interactive command while the `-t` option allocates a pseudo-TTY to the process within the container. Because our goal is to access the sample database through the `mariadb` command line client, these options are necessary. Next, enter the name of the container running the MariaDB database; by default, this will be named `mariadb-mariadb-1`. Following that, the remainder of this command represents the command that will be run within the container. Here, we run the `mariadb` command to access the database specifying that we want to connect as the `php` user. The final `-p` option indicates that we want to be prompted to enter the password. ``` Enter password: ``` Enter `password` and you'll then be presented with the MariaDB command line SQL shell. ``` Welcome to the MariaDB monitor. Commands end with ; or \g. Your MariaDB connection id is 3 Server version: 10.11.4-MariaDB MariaDB Server Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. MariaDB [(none)]> ``` From here, you can interact with the database from within the `mariadb-mariadb-1` container as you would with any other MariaDB database. For example, you could update existing tables, create new ones, and insert or delete data. To close the MariaDB prompt, you can enter the following command. ```MariaDB \q ``` Of course, you likely won't be regularly managing your containerized databases over the command line. The purpose of this section is to only show that you can interact with the database running in this container just like you would with any other MariaDB database. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose MariaDB Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Registry overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/overview.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Registry Chainguard Registry hosts more secure container images with two access tiers: public Free images available to everyone, and production images that require authentication for enterprise features like SLAs and version pinning. The registry integrates with standard container tools while providing enhanced security through signed images and comprehensive metadata. While all of Chainguard's public container images are freely available, logging in with a Chainguard account and authenticating when pulling from the registry provides a mechanism for Chainguard to contact you if there are any current or known upcoming issues with images you are pulling. If you would like to learn more about **Chainguard Containers**, you can review our [documentation](/chainguard/containers/overview/), and you can request further information through our [inquiry form](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). ## What your organization can pull Your organization's registry at `cgr.dev/$ORGANIZATION/` holds only the containers your organization has access to, which is a subset of what the public [Chainguard Containers Directory](https://images.chainguard.dev/) lists. Browsing a container in the Directory doesn't mean you can pull it. Refer to [Onboard your teams](/get-started/onboard-your-teams/#what-your-organization-can-pull) for how your subscription determines what's available, and to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) when a container you expected is missing. ## Troubleshooting authentication errors If a login or pull returns a `401`, `403`, or `404`, refer to [Troubleshoot registry authentication errors](/chainguard/containers/troubleshooting/registry-errors/), which maps each error to its cause. ## Status You can check the status of Chainguard's registry at [https://status.cgr.dev](https://status.cgr.dev/). ## Network requirements Refer to our [Network requirements](/chainguard/containers/registry/network-requirements/) reference page for details about how to ensure access to Chainguard's registry in environments using firewalls, access control lists, and proxies. ## Using a caching proxy with Chainguard's registry Chainguard does not offer an SLA for uptime for the Chainguard's registry. In order to minimize production dependency on the Chainguard's registry, we recommend that customers use a pull-through proxy for maximum reliability. We currently provide documentation on how you can set up a pull-through cache for the Chainguard's registry on the following platforms: * [Amazon ECR](/chainguard/containers/registry/pull-through-guides/ecr-pull-through/) * [Google Artifact Registry](/chainguard/containers/registry/pull-through-guides/artifact-registry-pull-through/) * [JFrog Artifactory](/chainguard/containers/registry/pull-through-guides/artifactory-containers-pull-through/) * [Sonatype Nexus](/chainguard/containers/registry/pull-through-guides/nexus-pull-through/) * [Cloudsmith](/chainguard/containers/registry/pull-through-guides/cloudsmith-pull-through/) --- ### Overview of Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/overview.md Last Modified: October 8, 2026 Tags: Chainguard Containers [Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) is a collection of container images designed for enhanced security through minimalism and supply chain integrity. These images follow a distroless philosophy, containing only the application and its essential runtime dependencies, without shells, package managers, or other common utilities that can increase attack surface. Many of Chainguard's container images implement a [distroless approach](/chainguard/containers/concepts/getting-started-distroless/), which means they exclude shells, package managers, and other utilities typically found in container images. This design significantly reduces potential security vulnerabilities. For development and debugging purposes, Chainguard provides `-dev` variants that include necessary tools while maintaining security best practices. All images are built using Chainguard OS, an operating system specifically designed to meet secure software supply chain requirements. Chainguard's container images are primarily available from [the Chainguard registry](/chainguard/containers/registry/overview/), but a selection of developer images is also available on [Docker Hub](https://hub.docker.com/u/chainguard). You can find the complete list of available Chainguard Containers in our public [Containers Directory](https://images.chainguard.dev/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-overview) or within the [Chainguard Console](https://console.chainguard.dev/). ## Built-in security and supply chain guarantees All of Chainguard's container images are built with a consistent set of security and supply-chain guarantees, without requiring additional configuration: - Minimal design, with no unnecessary software bloat - Automated nightly builds to ensure container images are completely up-to-date and contain all available security patches - [High quality build-time SBOMs](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) (software bill of materials) attesting the provenance of all artifacts within the container image - [Verifiable signatures](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) provided by [Sigstore](/open-source/sigstore/cosign/an-introduction-to-cosign/) - Reproducible builds with Cosign and apko ([read more about reproducibility](https://www.chainguard.dev/unchained/reproducing-chainguards-reproducible-image-builds)) ## Chainguard Container customization and lifecycle features Chainguard Containers includes features that allow you to customize images, manage updates, and meet security and compliance requirements across the container lifecycle: - [Custom Assembly](/chainguard/containers/custom-assembly/overview/): Customize Chainguard images by adding packages, configuration files, and certificates from the Chainguard Console, `chainctl`, or the Chainguard API, without maintaining your own Dockerfiles. - Custom certificates: Add trusted certificates [to existing containers via Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/) (for organization-specific or environment-specific certificates) or by [using incert to build images with certificates embedded at build time](/chainguard/containers/custom-assembly/incert-custom-certs/). - [Packages](/chainguard/containers/building-and-modifying/adding-packages/): Install and manage additional packages in Chainguard images while preserving Chainguard’s minimal, secure-by-default base images. - [EOL grace periods](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/): Control how end-of-life packages are handled in images to balance security requirements with operational stability. - [STIGs](/chainguard/containers/security-and-compliance/stigs/): Use DISA STIG–aligned images to support compliance-driven environments. - [Unique tags](/chainguard/containers/reference/unique-tags/) and [tag history](/chainguard/containers/reference/using-the-tag-history-api/): Track image changes over time with immutable tags and access tag history via API. - [CVE visualization](/chainguard/containers/security-and-compliance/vulnerability-management/cve-visualizations/): Explore vulnerability data for images to better understand risk and remediation timelines. ## Why minimal container images The fewer dependencies a given piece of software uses, the lower likelihood that it will be impacted by CVEs. By minimizing the number of dependencies and thus reducing their potential attack surface, Chainguard's container images inherently contain few to zero CVEs. These images are rebuilt nightly to ensure they are completely up-to-date and contain all available security patches. With this nightly build approach, our engineering team sometimes [fixes vulnerabilities before they’re detected](https://www.chainguard.dev/unchained/how-chainguard-fixes-vulnerabilities?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). Note that there is often a development variant of each Chainguard Container available. These are sometimes called the `-dev` variant, as their tags include the `-dev` suffix (as in `:latest-dev`). For example, the development variant of the `mariadb:latest` container image is `mariadb:latest-dev`. These container images typically contain a shell and tools like a package manager to allow users to more easily debug and modify the image. ## Why multi-layer container images Chainguard originally took a single-layer approach to container images built [with apko](/open-source/build-tools/apko/getting-started-with-apko/) in order to offer simplicity and clarity. However, in an effort to deliver better stability, security, and efficiency for larger and more complex applications, Chainguard introduced multi-layer container images in May 2025. This approach leverages container runtime caching so that a layer used by multiple images does not need to be downloaded more than once, and you don't need to download the whole image each time there is an update on one layer. Chainguard's approach to layering is a "per-origin" strategy, where packages that derive from the same upstream source are grouped in the same layer because they tend to receive updates together. We observed that this approach achieved the following: - A ~70% reduction in the total size of unique layer data across our image catalog compared to the single-layer approach - A 70-85% reduction in the cumulative bytes transferred when simulating sequential pulls of updated images like PyTorch and NeMo To maximize the stability and re-usability of our layers, Chainguard identified, analyzed, and implemented three additional technical changes: - Added in an additional final layer that captures frequently updated OS-level metadata - Developed intelligent layer ordering to optimize compatibility - Ensured sufficient layer counts to optimize parallel downloads by container clients The primary benefit of this layered approach is that when one package changes it impacts only its particular layer, requiring only that layer to be downloaded again. Because the other layers don't need to be downloaded again, Chainguard's multi-layer container images support greater efficiency and developer velocity. ## Production and free containers Chainguard offers a collection of container images that are publicly available and don't require authentication, being free to use by anyone. We refer to these images as **Free images**, and they cover several use cases for different language ecosystems. Free images are limited to the latest build of a given image, tagged as `latest` and `latest-dev`. Production containers are enterprise-ready images that come with patch SLAs and features such as [Federal Information Processing Standard (FIPS) readiness](/platform/fips/fips-images/) and [unique time-stamped tags](/chainguard/containers/reference/unique-tags/). Unlike Free containers, which are typically paired with only the latest version of an upstream package, Production containers offer specific major and minor versions of open source software. Chainguard offers two pricing options for Production containers: Per-Image Pricing and [Catalog pricing](/chainguard/containers/reference/pricing/). You can access our container images directly from [Chainguard's registry](/chainguard/containers/registry/overview/). Chainguard's registry provides public access to all public Chainguard Containers, and provides customer access for Production Containers after logging in and authenticating. For a complete list of Free Containers that are currently available, check our [Containers Directory](https://images.chainguard.dev/?category=developer). Registered users can also access all Free and Production container images in the [Chainguard Console](https://console.chainguard.dev/?utm=docs). After logging in you will be able to find all the current Free containers in the **Chainguard catalog** tab. If you've selected an appropriate Organization in the drop-down menu above the left hand navigation, you can find your organization's Production containers in the **Organization** tab. ## Comparing container images The major advantage of distroless images is the reduced size and complexity, which results in a vastly reduced attack surface. Security scanners such as [Grype](https://github.com/anchore/grype) detect far fewer CVEs (common vulnerabilities and exposures) in Chainguard Containers than in comparable general-purpose images. `chainctl`, Chainguard's command line interface tool, comes with a useful `diff` feature that allows you to [compare two Chainguard Containers](/platform/chainctl-usage/comparing-images/). Every container listed in the [Containers Directory](https://images.chainguard.dev/?category=developer) also has a **Comparison** tab that compares the number of CVEs in the Chainguard Container — often zero — against the number in its upstream counterpart, and charts how each has changed over time. For example, see the [nginx container's **Comparison** tab](https://images.chainguard.dev/directory/image/nginx/compare). ## Architecture By default, all Wolfi-based images are built for x86_64 (also known as AMD64) and AArch64 (also known as ARM64) architecture with the following CPU Instruction Set Architecture (ISA) baseline features: - x86_64: x86-64-v2 (Sapphire Rapids) - AArch64: Armv8-A with CRC and Cryptographic extensions (Neoverse V2) Being able to provide multi-platform Chainguard Containers enables the support of more than one runtime environment, like those available on all three major clouds, AWS, GCP, and Azure. The macOS M-series (M1, M2, etc.) chips are also based on ARM architecture. Chainguard's container images allow you to take advantage of ARM's power consumption and cost benefits. You can confirm the available architecture of a given Chainguard Container with Crane. In this example, we'll use the latest Ruby image, but you can opt to use an alternate image. ```shell crane manifest cgr.dev/chainguard/ruby:latest |jq -r '.manifests []| .platform' ``` Once you run this command, you'll receive output similar to the following. ```output { "architecture": "amd64", "os": "linux" } { "architecture": "arm64", "os": "linux" } ``` This verifies that the Ruby Chainguard Container is built for both AMD64 and ARM64 architectures. You can read more about our support of ARM64 in our blog on [Building Wolfi from the ground up](https://www.chainguard.dev/unchained/building-wolfi-from-the-ground-up-and-announcing-arm64-support?utm=docs). ## Annotations All of Chainguard's container images include metadata in the form of *annotations* (also commonly referred to as "*labels*"). These annotations provide important information about a container image's origin, contents, and characteristics. The annotations are visible in every container image's **Specifications** tab in both the [Chainguard Console](https://console.chainguard.dev) and [Directory](https://images.chainguard.dev/), and can also be inspected programmatically using container tools. These annotations follow the [Open Container Initiative (OCI) Image Specification](https://github.com/opencontainers/image-spec/blob/main/annotations.md) for annotations. Chainguard sets the following standard OCI annotations on its container images: - `org.opencontainers.image.authors`: Contact details for the Chainguard Container's author (typically `Chainguard Team https://www.chainguard.dev/`) - `org.opencontainers.image.base.digest`: The SHA256 digest of the base image used to build this container image - `org.opencontainers.image.created`: Timestamp indicating when the image was built; specifically, this annotation is calculated from the build time of the most recently built package within the container image - `org.opencontainers.image.source`: URL to the source code used to build the image in the Chainguard images repository - `org.opencontainers.image.title`: The original image name as listed in the catalogue (for example `chainguard-base`) - `org.opencontainers.image.url`: URL to the container image's Overview page in the Chainguard Directory (for example, `https://images.chainguard.dev/directory/image/nginx/overview`) - `org.opencontainers.image.vendor`: The distributing organization, always set to `Chainguard` In addition to the standard OCI annotations, Chainguard sets custom annotations (which begin with `dev.chainguard` instead of `org.opencontainers`) that provide additional context about the container image: - `dev.chainguard.package.main`: The name of the primary package in the image. This may change between different versions of an image. In some situations, it may also be empty or unset. - `dev.chainguard.image.title`: Duplicate of the original image name as listed in the catalogue, in case downstream build processes override the same value in the `org.opencontainers` namespace. ### Retrieving annotation information You can inspect image annotations using [`crane`](https://github.com/google/go-containerregistry/tree/main/cmd/crane). This section's examples use [`jq`](https://jqlang.org/), a command-line JSON processor, to filter the output to only show the relevant information: ```shell crane manifest cgr.dev/chainguard/node:latest | jq -r .annotations ``` This will output all the annotations set on the image: ```json { "dev.chainguard.image.title": "node", "dev.chainguard.package.main": "node", "org.opencontainers.image.authors": "Chainguard Team https://www.chainguard.dev/", "org.opencontainers.image.created": "2025-10-20T02:17:10Z", "org.opencontainers.image.source": "https://github.com/chainguard-images/images/tree/main/images/node", "org.opencontainers.image.title": "node", "org.opencontainers.image.url": "https://images.chainguard.dev/directory/image/node/overview", "org.opencontainers.image.vendor": "Chainguard" } ``` Chainguard also sets these annotation values as *labels*. In the context of OCI specifications, labels are similar to annotations but the two are ultimately distinct. [This blog post](https://adrianmouat.com/posts/annotations-and-labels-in-container-images/) outlines the differences between the two. You can also inspect a Chainguard Container's labels using the `crane config` command: ```shell crane config cgr.dev/chainguard/node:latest | jq '.config.Labels' ``` The expected output is the same: ```json { "dev.chainguard.image.title": "node", "dev.chainguard.package.main": "node", "org.opencontainers.image.authors": "Chainguard Team https://www.chainguard.dev/", "org.opencontainers.image.created": "2025-10-20T02:17:10Z", "org.opencontainers.image.source": "https://github.com/chainguard-images/images/tree/main/images/node", "org.opencontainers.image.title": "node", "org.opencontainers.image.url": "https://images.chainguard.dev/directory/image/node/overview", "org.opencontainers.image.vendor": "Chainguard" } ``` This returns the same output as the previous `crane` command. Lastly, you can use the `docker inspect` command to inspect a container image's labels: ```shell docker pull cgr.dev/chainguard/node:latest docker inspect cgr.dev/chainguard/node:latest | jq '.[].Config.Labels' ``` The expected output is the same: ```json { "dev.chainguard.image.title": "node", "dev.chainguard.package.main": "node", "org.opencontainers.image.authors": "Chainguard Team https://www.chainguard.dev/", "org.opencontainers.image.created": "2025-10-20T02:17:10Z", "org.opencontainers.image.source": "https://github.com/chainguard-images/images/tree/main/images/node", "org.opencontainers.image.title": "node", "org.opencontainers.image.url": "https://images.chainguard.dev/directory/image/node/overview", "org.opencontainers.image.vendor": "Chainguard" } ``` Again, this returns the same information as before. However, using `docker inspect` requires you to download the container image beforehand. Additionally, images have "`image.title` by `image.vendor`" encoded in the image config as a comment. You can use `docker history` to retrieve this information: ```shell docker history cgr.dev/chainguard/node ``` Note the `COMMENT` column in the output: ```output IMAGE CREATED CREATED BY SIZE COMMENT a063d5e94934 39 hours ago apko 151kB node by Chainguard 39 hours ago apko 1.97MB node by Chainguard 39 hours ago apko 889kB node by Chainguard 39 hours ago apko 1.12MB node by Chainguard 39 hours ago apko 2.88MB node by Chainguard 39 hours ago apko 3.47MB node by Chainguard 39 hours ago apko 6.79MB node by Chainguard 39 hours ago apko 7.17MB node by Chainguard 39 hours ago apko 8.5MB node by Chainguard 39 hours ago apko 39.6MB node by Chainguard 39 hours ago apko 81.1MB node by Chainguard ``` ### Adding container image annotations OCI labels are specific to a container image, not to an entire layer. This means that for base images, annotation information is often overridden later on with more accurate details after the image has been ingested. For example, the `image.author` annotation might be reset to reflect the customer consuming the container image. Some users relabel their container images after they've been ingested. As an example, you may wish to add an annotation like `com.mycompany.image.source=chainguard` to your Chainguard Containers; this would allow you to filter for all the container images provided by Chainguard at `mycompany`. Some package mirroring tools support this functionality, but we recommend using Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) tool to add custom annotations to your Chainguard Containers. Refer to our guide on [managing Custom Assembly resources with `chainctl`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#adding-custom-annotations-and-environment-variables) for more information. --- ### How to use Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/using-containers.md Last Modified: October 8, 2026 Tags: Chainguard Containers [Chainguard Containers](https://images.chainguard.dev) is a collection of minimal container images designed to reduce vulnerabilities and attack surface compared to traditional base images. These images use the [apk](https://wiki.alpinelinux.org/wiki/Package_management) package format to achieve smaller sizes while maintaining complete provenance information with cryptographic signatures, ensuring both enhanced security and traceability. In this guide, you'll find general instructions on how to get started using Chainguard Containers and how to migrate existing container-based workflows to use our images. For specific image usage instructions, please refer to our [Chainguard Containers Directory](https://images.chainguard.dev), which contains the full list of all images available to the public and their respective documentation. ## Quickstart: Using Chainguard Containers To get up and running with Chainguard Containers, you can use `docker` commands to pull and run images. For each specific image, you'll find this guidance on its overview page (for example, refer to [Node](https://images.chainguard.dev/directory/image/node/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images), [Python](https://images.chainguard.dev/directory/image/python/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images), or [NGINX](https://images.chainguard.dev/directory/image/nginx/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images)). ### Pulling a Chainguard Container You can pull a Chainguard Container with the `docker pull` command. For example, to pull down the Git Chainguard Container, you can run the following. ```sh docker pull cgr.dev/chainguard/git ``` Without passing a tag or a digest, the reference to the Git image will pull down the default tag, which is `:latest`. If you have your own registry, you'll need to change the `cgr.dev/chainguard` path to your own registry path. Chainguard free Starter container images are also available on Docker Hub. Check out [Chainguard's organization page on Docker Hub](https://hub.docker.com/u/chainguard?utm_source=academy&utm_medium=referral&utm_campaign=FY25-DockerHub-Orgprofile) for a list of all images and instructions. Note that paid Production images can only be accessed from cgr.dev. ### Pulling by tag You can also add a relevant tag that you have access to. In the case of the public Git image, you can always pull the `:latest` tag. [Note that not all tags are available for public container images](/chainguard/containers/faq/#do-i-need-to-authenticate-into-chainguard-to-use-chainguard-containers). ```sh docker pull cgr.dev/chainguard/git:latest ``` You may use tags to pull a specific version of a software like Git, or programming language version in a catalog you have access to. The Chainguard Containers Directory has tag history pages for each image, for example, the [Git Image Tags History](https://images.chainguard.dev/directory/image/git/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images), [PHP Image Tags History](https://images.chainguard.dev/directory/image/php/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images), and [JDK Image Tags History](https://images.chainguard.dev/directory/image/jdk/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images). You can learn about the Chainguard Containers tags history in our guide about [Using the Tag History API](/chainguard/containers/reference/using-the-tag-history-api/). ### Pulling by digest Pulling a Chainguard Container by its digest guarantees reproducibility, as it will ensure that you are using the same image each time (versus the tag that may receive updates). To pull an image by its digest, you can do so by appending the digest (which begins with `sha256`) as in the following example. If your organization has access to a container image, you can find the digest for each of its versions in the Chainguard Console by navigating to that image's [**Tags** page](https://console.chainguard.dev/org/$ORGANIZATION$/images/organization/image/git/versions). Per-version digests are a Production container feature. Free containers are published only as `latest` and `latest-dev`, so the public [Chainguard Containers Directory](https://images.chainguard.dev/) has no version history to choose a digest from. To retrieve the digest of a container you already have, or to pin a reference to one, refer to [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/). ```sh docker pull cgr.dev/chainguard/git@sha256:f6658e10edde332c6f1dc804f0f664676dc40db78ba4009071fea6b9d97d592f ``` When you pull this image, you'll receive output of the digest which should match the exact digest you have pulled. ### Specifying architecture As Chainguard's container images are [built for both AMD64 and ARM64 architecture](/chainguard/containers/overview/#architecture), you can specify the architecture you would like to use by employing the `--platform` flag with the `docker pull` command. In this example, we'll specify using the `linux/arm64` architecture with the Go image. ```sh docker pull --platform=linux/arm64 cgr.dev/chainguard/go ``` After pulling the image, you can verify the architecture by calling the version. ```sh docker run --rm -t cgr.dev/chainguard/go:latest version ``` You'll receive output similar to the following: ```sh go version go1.21.0 linux/arm64 ``` Specifying the platform will ensure that you're using the desired container image and relevant architecture. ### Running a Chainguard Container You can run a Chainguard container image with the `docker run` command. Note that because Chainguard's container images are minimalist, most of them ship without a shell or package manager. If you would like a shell, you can often use the development image, which is tagged as `:latest-dev`. For example, [Python](https://images.chainguard.dev/directory/image/python/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images) has its development variant at `cgr.dev/chainguard/python:latest-dev`. Otherwise, you can work with Chainguard Containers in way similar to other images. Let's run the [Cosign Chainguard Container](https://images.chainguard.dev/directory/image/cosign/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images) to check its version. ```sh docker run --rm -t cgr.dev/chainguard/cosign:latest version ``` You'll receive the version information that confirms the image is working as expected. ```output ______ ______ _______. __ _______ .__ __. / | / __ \ / || | / _____|| \ | | | ,----'| | | | | (----`| | | | __ | \| | | | | | | | \ \ | | | | |_ | | . ` | | `----.| `--' | .----) | | | | |__| | | |\ | \______| \______/ |_______/ |__| \______| |__| \__| cosign: A tool for Container Signing, Verification and Storage in an OCI registry. GitVersion: 2.0.0 GitCommit: unknown GitTreeState: unknown BuildDate: unknown GoVersion: go1.20.1 Compiler: gc Platform: linux/arm64 ``` If you would like to review a filesystem, you can use the [wolfi-base image](https://images.chainguard.dev/directory/image/wolfi-base/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-how-to-use-chainguard-images): ```sh docker run -it cgr.dev/chainguard/wolfi-base ``` This will start a Wolfi container where you can explore the file system and investigate which packages are available. Continue reading the next section to learn more about building off of the Wolfi base image. ## Extending Chainguard base containers It often happens that you want a [distroless](/chainguard/containers/concepts/getting-started-distroless/) image with one or two extra packages, for example you may have a binary with a dependency on `curl` or `git`. Ideally you’d like a base image with this dependency already installed. There are a few options here: 1. Compile the dependency from source and use a multi-stage Dockerfile to create a new base image. This works, but may require considerable effort to get the dependency compiling and to keep it up to date. This process quickly becomes untenable if you require several dependencies. 2. Use the `wolfi-base` image that includes apk tools to install the package in the traditional Dockerfile manner. This works but sacrifices a lot of the advantages of the “distroless” philosophy. 3. Use Chainguard’s [melange and apko tooling to create a custom base image](/open-source/build-tools/melange/getting-started-with-melange/). This keeps the image as minimal as possible without sacrificing maintainability. ### Using the wolfi-base container image The `wolfi-base` image is a good starting point to try out Chainguard Containers. Unlike most of the other images, which are strictly distroless, `wolfi-base` includes the `apk` package manager, which facilitates composing additional software into it. Just keep in mind that the resulting image will be a little larger due to the extra software and won't have a comprehensive SBOM that covers all your dependencies, since the new software will be added as a layer on top of `wolfi-base`. The following command will pull the `wolfi-base` image to your local system and run an interactive shell that you can use to explore the image features: ```shell docker run -it --rm cgr.dev/chainguard/wolfi-base /bin/sh -l ``` First you will need to update the list of packages available in Wolfi: ```shell apk update ``` Now you can use `apk search` to search for packages that are already available on Wolfi repositories: ```shell apk search curl ``` More packages will be added with time, as the ecosystem matures and drives community involvement. _Looking for a specific package that is not yet available? Feel free to open an issue on the [wolfi-os](https://github.com/chainguard-dev/wolfi-os) GitHub repository._ ### Using the wolfi-base container image within Dockerfiles Following, you can see an example of a Dockerfile that uses `wolfi-base` as base image, installing the packages `curl` and `jq` in order to make a query to the [advice slip](https://api.adviceslip.com/) API: ```dockerfile FROM cgr.dev/chainguard/wolfi-base RUN apk update && apk add --no-cache --update-cache curl jq SHELL ["/bin/sh", "-c"] CMD curl -s https://api.adviceslip.com/advice --http1.1 | jq .slip.advice ``` The `SHELL` command suppresses a warning about the `CMD` line using shell syntax, which isn't a problem in this example. In other cases, you may want to use the [exec form](https://docs.docker.com/reference/dockerfile/#shell-and-exec-form). You can build this Dockerfile as usual: ```shell docker build . -t advice-slip:test ``` Then, execute the image with: ```shell docker run -it --rm advice-slip:test ``` You should get output like this, with a random piece of advice: ```output "Big things have small beginnings." ``` Check also the [Wolfi images with Dockerfiles](/open-source/wolfi/wolfi-with-dockerfiles/) guide for more examples using Wolfi-based images with Dockerfiles, and the [Getting started with distroless](/chainguard/containers/concepts/getting-started-distroless/) guide for more details about distroless images and how to use them in Docker multi-stage builds. ## A note regarding package availability in Chainguard Containers Chainguard's container images only contain packages that come from the [Wolfi Project](https://github.com/wolfi-dev) or those that are built and maintained internally by Chainguard. Starting in March of 2024, Chainguard will maintain one version of each Wolfi package at a time. These will track the latest version of the upstream software in the package. Chainguard will end patch support for previous versions of packages in Wolfi. Existing packages will not be removed from Wolfi and you may continue to use them, but be aware that older packages will no longer be updated and will accrue vulnerabilities over time. The tools we use to build packages and images remain freely available and open source in [Wolfi](https://github.com/wolfi-dev). This change ensures that Chainguard can provide the most up-to-date patches to all packages for our container images customers. Note that specific package versions can be made available in Production images. If you have a request for a specific package version, please [contact us](https://www.chainguard.dev/contact?utm=docs). --- ### Get started with Chainguard Checks URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/getting-started.md Last Modified: October 15, 2026 Tags: Chainguard Checks, Getting Started, GitHub, Procedural This guide walks you through signing up for Chainguard Checks, adding checks to one of your GitHub repositories, and seeing them run on a pull request. It uses a Go repository as the example and takes about 15 minutes.

Note: Chainguard Checks is in beta. Available to organizations that turn it on with chainctl develop init, which comes with a free monthly compute allowance, or that arrange access with their Chainguard account team.

## Step 1: Sign up {#step-1-sign-up} Sign up as described in [Set up Chainguard Checks and Chainguard Workspaces](/get-started/checks-and-workspaces/set-up/). You install or update `chainctl`, install the Chainguard App on the account that owns your repository, and run `chainctl develop init`, which turns on Chainguard Checks and Chainguard Workspaces together. You also need a local clone of a repository on github.com that you can push to. ## Step 2: Add a configuration file to your repository In your local clone, create a branch for the change. Replace `$REPO_DIR` with the path to your local clone: ```shell cd $REPO_DIR git checkout -b add-chainguard-checks ``` Next, create a `.chainguard/ci.yaml` configuration file that defines two checks: ```shell mkdir -p .chainguard cat > .chainguard/ci.yaml < Your teammates don't need a Chainguard account to get checks on their pull requests or to read the results on GitHub. A teammate needs a role in your Chainguard organization only to open the run page, to use `chainctl checks` commands such as `status` and `rerun`, or to run checks in a Workspaces session. To give them one, refer to [Give teammates access](/get-started/checks-and-workspaces/set-up/#give-teammates-access). ## Next steps - [Chainguard Checks configuration reference](/chainguard/checks/configuration/) covers packages, network access, conditions, and more. - [Use Chainguard Checks on GitHub](/chainguard/checks/github/) explains reruns, checks that run on request, and checks that run after merging. - [Run checks before you push](/chainguard/checks/run-checks/) shows how to run the same checks in a Chainguard Workspaces session. - [Workload identities and trust](/chainguard/checks/identity-and-trust/) explains how to give a check short-lived Google Cloud or Chainguard credentials. - [chainctl checks command reference](/chainguard/checks/chainctl-checks-reference/) lists every `chainctl checks` command and flag. - Ask questions in the #checks-questions channel in the [Chainguard community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw). --- ### Get started with Chainguard Workspaces URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/getting-started.md Last Modified: October 15, 2026 Tags: Chainguard Workspaces, Getting Started, Procedural This guide walks you through signing up for Chainguard Workspaces, opening a session on one of your repositories, detaching from the session, and returning to it. It takes about 15 minutes.

Note: Chainguard Workspaces is in beta. Available to organizations that turn it on with chainctl develop init, which comes with a free monthly allowance, or that arrange access with their Chainguard account team.

## Step 1: Sign up {#step-1-sign-up} Sign up as described in [Set up Chainguard Checks and Chainguard Workspaces](/get-started/checks-and-workspaces/set-up/). You install or update `chainctl`, install the Chainguard App on the account that owns your repository, and run `chainctl develop init`, which turns on Chainguard Workspaces and Chainguard Checks together. You also need a local clone of a repository on github.com that you can push to. Optionally, install and log in to [Claude Code](https://code.claude.com/docs/en/overview) on your laptop, so you can use it in the session. ## Step 2: Open a session Change to your repository's directory, then run `chainctl develop`. Replace `$REPO_DIR` with the path to your local clone, and `$ORGANIZATION` with the name of your Chainguard organization: ```shell cd $REPO_DIR chainctl develop --parent $ORGANIZATION ``` If you belong to only one Chainguard organization, you can omit `--parent`. The first time you run the command in a directory, `chainctl` creates a session, syncs your directory into it, and attaches your terminal to a shell in the session. The session gets a name made of the repository name and two generated words, such as `app/quiet-harbor`, and a short ID, such as `3f9a2c1e`. If your repository has neither a `.chainguard/develop.yaml` file nor a `.chainguard/ci.yaml` file, the session opens on the default environment. While you're attached, `chainctl` keeps a status row at the bottom of your terminal. The row lists the session's terminals and shows messages from `chainctl`. To open more terminals in the session, refer to [Use several terminals in a session](/chainguard/workspaces/manage-sessions/#use-several-terminals-in-a-session). The first time you attach to a session from your laptop, `chainctl` also prints a short guide to the status row and the session menu's keys above the shell's prompt. ## Step 3: Work in the session Your shell runs as `root` in `/work`, which holds your repository. Try a few commands to look around: ```shell git status cat /etc/chainguard/session.yaml ``` The `git status` output matches your laptop's checkout at the moment you opened the session, including uncommitted changes. The `/etc/chainguard/session.yaml` file records the session's ID, repository, and commit, and the environment it was built from. From here, you can work as you would on your laptop. For example: - Run `claude` to start Claude Code. It uses your laptop's Claude Code login. Refer to [Use Claude Code in a session](/chainguard/workspaces/coding-agents/). - Run `run-ci` to run your repository's checks against the working tree, if your `.chainguard/ci.yaml` defines checks. - Commit and run `git push`. Your laptop's git `user.name` and `user.email` are already set in the session, and the Chainguard App, or Chainguard Develop if you connected your GitHub account, adds the credential for the push. Commits you make in a session aren't signed, because the session gets your git `user.name` and `user.email` but not your signing key or settings. If a branch requires signed commits, GitHub rejects your push to it. To get your work onto that branch, refer to [The branch requires signed commits](/chainguard/workspaces/troubleshooting/#the-branch-requires-signed-commits). ## Step 4: Detach and return to the session To detach, press `Ctrl+]` to open the session menu, then press `Enter` or `d` to choose **Detach — let it park**. Your terminal returns to your laptop's shell. The session keeps running for about 10 minutes, then parks to a snapshot and stops using compute. To return, run `chainctl develop` again from the same directory: ```shell chainctl develop --parent $ORGANIZATION ``` Because the directory has exactly one session, `chainctl` reattaches to it, resuming it first if it parked. Your files, running processes, and shell are as you left them. Your directory syncs into the session only the first time you attach. Changes you make on your laptop after that don't reach the session, and changes in the session don't reach your laptop. To move work between them, push from one and pull on the other. ## Step 5: Delete the session A parked session expires 5 days after it parks, and until then it counts toward your session limit. With the free monthly allowance, you can have one session at a time, so delete it when you're done. First, list your sessions to find its name: ```shell chainctl develop list --parent $ORGANIZATION ``` Then delete the session by name. This example deletes `app/quiet-harbor`: ```shell chainctl develop delete app/quiet-harbor --parent $ORGANIZATION ``` The `delete` command asks you to confirm, then destroys the session's VM and its snapshot. Anything you didn't push is gone. ## Give teammates access {#give-teammates-access} Each teammate opens and manages their own sessions, which count toward your organization's [limits](/chainguard/workspaces/overview/#availability-and-limits). To open sessions, a teammate needs the `owner` or `limited_owner` role, or the `sandbox.user` role together with `viewer`. To give them one, refer to [Give teammates access](/get-started/checks-and-workspaces/set-up/#give-teammates-access). ## Next steps - [Manage sessions](/chainguard/workspaces/manage-sessions/) covers using several terminals or sessions, keeping a session running while you're away, and the session menu. - [Configure a session's environment](/chainguard/workspaces/session-environment/) explains how to choose the packages and network access a session gets. - [Manage session credentials](/chainguard/workspaces/credentials/) explains what a session can reach and how to change it. - Ask questions in the #workspaces-questions channel in the [Chainguard community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw). --- ### Guarded Entrypoint examples URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/guarded-entrypoint/examples.md Last Modified: October 7, 2026 Tags: Chainguard Containers, Custom Assembly, Procedural, Configuration > **Note**: Guarded Entrypoint is in beta. To use it, contact Chainguard customer support to enable it for your organization. This page has four example manifests for [Guarded Entrypoint](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/). Each one is a complete manifest for `chainctl images repos build edit` or `chainctl images repos build apply`. None of them contains a literal secret. Each secret is a reference that the wrapper resolves when the container starts. Applying a manifest replaces the repo's stored configuration. If your repo already has other customizations, such as packages, add the Guarded Entrypoint keys to your existing manifest instead of replacing it. The examples use the following variables: ```shell export REPO=my-custom-app export ORGANIZATION=example.com ``` To try an example, save it as `build.yaml`. Preview the change, then apply it: ```shell chainctl images repos build apply -f build.yaml --repo $REPO --parent $ORGANIZATION --dry-run chainctl images repos build apply -f build.yaml --repo $REPO --parent $ORGANIZATION --yes ``` The first command prints the diff and exits with a non-zero status when it finds a change. The second command applies the manifest and starts a rebuild. ## Inject secrets from Vault or Consul into a Java application A Java application reads its database password and an API address from its environment. Today the team adds `envconsul` to a derived image to supply them. With Guarded Entrypoint, the references are part of the Custom Assembly repo. ```yaml guarded_entrypoint: true environment: VAULT_ADDR: https://vault.example.com:8200 VAULT_K8S_ROLE: orders-service DB_PASSWORD: cg+vault://secret/data/orders#db_password CONSUL_HTTP_ADDR: https://consul.example.com:8501 ORDERS_API_URL: cg+consul://apps/orders/api-url ``` When the container starts, the wrapper does the following: 1. Logs in to Vault with the Kubernetes auth method as the `orders-service` role. It reads the `db_password` field of the `orders` secret, in the `secret` KV version 2 mount, into `DB_PASSWORD`. 1. Reads the `apps/orders/api-url` key from Consul into `ORDERS_API_URL`. 1. Starts the Java application with both variables resolved. The image stores the references and the addresses. It doesn't store the secrets. To read from Consul with a token, set `CONSUL_HTTP_TOKEN` or `CONSUL_HTTP_TOKEN_FILE` on the deployment, not in the manifest. A token in the manifest would be visible to anyone who can pull the image. `VAULT_TOKEN`, if you set it, takes precedence over the Kubernetes login, and `CONSUL_HTTP_TOKEN_FILE` wins over `CONSUL_HTTP_TOKEN`. A token can't be a `cg+` reference. The default `fail_mode` is `closed`. If Vault or Consul can't serve a reference, the container stops with exit code 121 and the application doesn't start. ## Wait for a dependency before starting An application fails when its database isn't ready at start. The following manifest holds the container until the database accepts connections, and then checks that a mounted file exists: ```yaml guarded_entrypoint: true preflight: - tcp: db.internal:5432 timeout: 60s interval: 1s on_failure: fail - path: /run/secrets/tls-ready timeout: 10s on_failure: continue ``` The wrapper runs the checks in order, after it resolves secrets and before it starts the application: * The first check tries to connect to `db.internal:5432` for up to 60 seconds, and pauses one second between attempts. Each attempt has its own timeout. If the connection never succeeds, the container stops with exit code 122. * The second check waits up to 10 seconds for `/run/secrets/tls-ready` to exist. With `on_failure: continue`, the wrapper logs a warning and starts the application if the file never appears. A target can read from the environment. The following entry waits for the address in the `CACHE_ADDR` variable, which you set on the deployment: ```yaml guarded_entrypoint: true preflight: - tcp: ${CACHE_ADDR} timeout: 30s ``` If `CACHE_ADDR` isn't set, the check fails. ## Override the command on a shell-less Python image A Python image has no shell and ships with both an ENTRYPOINT and a CMD. The application needs a different command, and both defaults must go. Without Guarded Entrypoint, you build a derived image to do this. ```yaml guarded_entrypoint: true environment: PORT: "8080" command_override: mode: override command: - python - /app/main.py - --port - ${PORT} ``` In `override` mode, the wrapper starts `command` alone. The wrapper drops the image's ENTRYPOINT and CMD, and any arguments you pass at run time. The wrapper expands `${PORT}` from the container's environment, so a deployment can change the port by setting `PORT`. The image needs no shell, because the wrapper starts `python` directly. To keep the image's own command and add arguments in front of it, use `mode: prepend` instead. Refer to [Command override](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/#command-override). Don't pass a secret on the command line. The expanded value is visible in the process's command line. Have the application read a secret from its environment, and use a [secret reference](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/#secret-references) to supply it. ## Fail open for a value that isn't a credential An application reads a feature-flag address from Consul. The application has a built-in default, so it can start when Consul is down. The following manifest sets the repo to fail open: ```yaml guarded_entrypoint: true fail_mode: open environment: CONSUL_HTTP_ADDR: https://consul.example.com:8501 FEATURE_FLAGS_URL: cg+consul://apps/web/feature-flags-url ``` If Consul can't serve the key, the application starts anyway. `FEATURE_FLAGS_URL` keeps the literal value `cg+consul://apps/web/feature-flags-url`, and the wrapper logs a warning for it. The application must handle that value, for example by falling back to its default when the value starts with `cg+`. Use `open` only for variables that aren't credentials. An unresolved variable holds a value that anyone with access to the image configuration can read. For a password or a token, keep the default of `closed`. Refer to [Fail mode](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/#fail-mode). ## Learn more * [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/) * [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/) * [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/) --- ### Manage Custom Assembly Overlays with Terraform URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/overlays/terraform.md Last Modified: October 9, 2026 Tags: Chainguard Containers, Procedural, Custom Assembly, Automation

Note: Custom Assembly Overlays is in beta.

This guide shows how to manage Custom Assembly Overlays with the [Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest). You define overlays with the `chainguard_image_overlay` resource and bind them to repositories with the `chainguard_image_overlay_binding` resource. For an explanation of overlays, bindings, and tag selectors, refer to [Overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/overview/). The provider doesn't support [all-repos bindings](/chainguard/containers/custom-assembly/overlays/overview/#all-repos-bindings) yet; each binding resource names one repository. To bind an overlay to every repository in your organization or a folder, use [`chainctl`](/chainguard/containers/custom-assembly/overlays/chainctl/#apply-an-overlay-to-every-repository). ## Prerequisites Before you start, you need the following: * Terraform and the Chainguard Terraform provider, version 0.5.0 or later. To configure the provider, refer to [Introduction to the Chainguard Terraform provider](/platform/administration/terraform-provider/). * An identity with the `registry.overlays.edit` capability, such as one bound to the built-in `editor` or `owner` role. * A repository in your organization with no standard Custom Assembly customization. A repository can't use both. ## Look up your organization and repository Overlays belong to your organization, and bindings belong to a repository, so you need the IDs of both. The following configuration requires the Chainguard provider and uses data sources to look up the `example.com` organization and its `python` repository: ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" version = ">= 0.5.0" } } } data "chainguard_group" "org" { name = "example.com" } data "chainguard_image_repo" "python" { parent_id = data.chainguard_group.org.id name = "python" } locals { python_repo_id = data.chainguard_image_repo.python.items[0].id } ``` The `chainguard_image_repo` data source returns a list of matching repositories. The `python_repo_id` local value holds the ID of the first match, which the binding examples later in this guide use. ## Create overlays Each `chainguard_image_overlay` resource defines a named set of customizations. For an overlay that only adds packages, set the `packages` attribute. The following example defines two overlays that add packages: ```hcl resource "chainguard_image_overlay" "typer" { parent_id = data.chainguard_group.org.id name = "typer" packages = ["py3.13-typer"] } resource "chainguard_image_overlay" "debug_tools" { parent_id = data.chainguard_group.org.id name = "debug-tools" packages = ["strace", "gdb"] } ``` Package names can use the `{{major}}` and `{{minor}}` placeholders, as in `py{{major}}.{{minor}}-cryptography`. For details, refer to [Version templates in package names](/chainguard/containers/custom-assembly/overlays/overview/#version-templates-in-package-names). To add other customizations, such as certificates, environment variables, or annotations, set the `config` attribute instead of `packages`. An overlay can set one of the two, but not both. The `config` attribute takes a JSON-encoded configuration. Its field names follow the Chainguard API, not the YAML file that `chainctl` accepts, and some names differ. For example, the user an image runs as is `accounts.run_as` in `config` but `accounts.run-as` in a `chainctl` file. For the field names, refer to the [`chainguard_image_overlay` schema](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/resources/image_overlay). The following example uses `jsonencode` to build an overlay that adds an internal certificate authority, an environment variable, and a package: ```hcl resource "chainguard_image_overlay" "internal_ca" { parent_id = data.chainguard_group.org.id name = "internal-ca" config = jsonencode({ contents = { packages = ["curl"] } environment = { REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt" } certificates = { additional = [{ name = "internal-ca" content = file("${path.module}/internal-ca.pem") }] } }) } ``` The `file` function reads the certificate from `internal-ca.pem` in the same directory as your configuration, so the certificate text doesn't need to appear in the configuration itself. For the full list of supported fields, refer to [Supported customizations](/chainguard/containers/custom-assembly/overlays/overview/#supported-customizations). ## Bind overlays to tags Each `chainguard_image_overlay_binding` resource attaches one overlay to one repository. The `tag_selector` block chooses which tags the overlay applies to. To apply an overlay to specific tags, set `kind` to `EXACT` and list the tags: ```hcl resource "chainguard_image_overlay_binding" "typer" { repo_id = local.python_repo_id overlay_id = chainguard_image_overlay.typer.id tag_selector { kind = "EXACT" tags = ["3.13", "3.13-dev"] } } ``` To apply an overlay to every `-dev` tag, set `kind` to `VARIANT` and `variant_type` to `DEV`: ```hcl resource "chainguard_image_overlay_binding" "debug_tools" { repo_id = local.python_repo_id overlay_id = chainguard_image_overlay.debug_tools.id tag_selector { kind = "VARIANT" variant_type = "DEV" } } ``` To apply an overlay to every tag, set `kind` to `ALL`: ```hcl tag_selector { kind = "ALL" } ``` You can bind a given overlay to a repository only once. To apply an overlay to more tags, change its binding's selector instead of adding a second binding. To create the overlays and bindings, apply the configuration: ```shell terraform apply ``` After Terraform creates the bindings, Chainguard rebuilds the matching tags. To check on the builds, run `chainctl images repos build list --repo python --parent example.com`. ## Change or remove customizations The overlay and binding resources don't support in-place updates. When you change an overlay's name, packages, or configuration, or a binding's selector, Terraform deletes the resource and creates a new one. Replacing an overlay gives it a new ID, so Terraform also replaces the bindings that refer to it. Each replacement removes the customization before adding it back, so Chainguard might rebuild the affected tags twice: once without the customization and once with it. Review the plan before you apply changes to repositories that serve production traffic. To remove a customization, delete the binding resource from your configuration and apply. Chainguard rebuilds the tags that the binding matched without the removed overlay. Any other matching overlays still apply. You can't delete an overlay while a binding still refers to it. Terraform removes the binding first when you delete both in one change. ## Learn more * [Overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/overview/) * [Manage Custom Assembly Overlays with chainctl](/chainguard/containers/custom-assembly/overlays/chainctl/) * [`chainguard_image_overlay` in the Terraform Registry](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/resources/image_overlay) * [`chainguard_image_overlay_binding` in the Terraform Registry](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/resources/image_overlay_binding) --- ### Check whether a reported CVE affects your container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/vulnerability-management/cve-status.md Last Modified: October 1, 2026 Tags: Procedural, Chainguard Containers, CVE Use `chainctl images advisories list` to compare the advisories for the APK packages in an image with the CVEs reported by your scanner. > **Note:** This command looks up advisories by APK package. Chainguard records a vulnerability in a Go module, Java archive, or other language component against the APK package that ships it, so the results cover those components too. They don't cover dependencies that your own build adds to the image. For details, see [When the finding is a Go module or Java dependency](#when-the-finding-is-a-go-module-or-java-dependency). ## FIPS variants FIPS variants use the same package-level advisory process as other Chainguard images. `chainctl images advisories list` does not determine coverage from the image name or the `-fips` suffix. It reads the APK package names and versions in the image SBOM, then looks up advisories for those APK packages. This means: - If a FIPS image contains the same APK package and version as a non-FIPS image, the same package advisory can apply. - If a FIPS image contains a FIPS-specific package name or version, that package needs a corresponding advisory record. - A FIPS image may have a different advisory result from its non-FIPS counterpart because its package set or versions differ. - An empty result does not by itself mean that the FIPS image is not affected or that the CVE is not covered. For a FIPS finding, use the exact image digest and platform, then compare the scanner’s package name and version with the APK package data in the image SBOM. Search the Security Advisories page for the exact package and CVE. If the APK package is present but no matching advisory exists, treat the result as an advisory-coverage question and include the image digest, package name and version, CVE, scanner and database versions, and scan output when contacting Support. ## Prerequisites You need: - `chainctl` installed and authenticated. - An image reference with an SBOM attestation attached. - The image digest or the exact tag scanned by your scanner. A digest is preferred because tags can move. - The platform that matches the scanner result. The default platform is `linux/amd64`. ## List advisories for the image Run the command against the same image reference and platform that your scanner analyzed: ```bash chainctl images advisories list \ cgr.dev/chainguard/@sha256: \ --platform=linux/amd64 \ -o wide ``` The output includes the APK package, package version, advisory ID, CVE aliases, status, and advisory type. Compare the package name and version—not only the CVE alias—with the scanner's finding. The `TYPE` column shows `apk` on every row, including advisories for a Go module or other component inside the package. If the image has no SBOM attestation, or the requested platform does not have one, the command cannot use that image to perform the lookup. If no matching advisory is shown, do not treat that alone as proof that the scanner finding is a false positive; first verify the image digest, platform, package ecosystem, package version, and scanner database. ## Triage a long scanner report with `--status` Use `--status` to narrow the output to the advisory states you need to review. You can provide multiple values as a comma-separated list, or by repeating the flag: ```bash # Findings that still need attention or confirmation chainctl images advisories list "$IMAGE" \ --status=detected,true-positive,pending-upstream # Findings with a recorded fix or backported patch chainctl images advisories list "$IMAGE" \ --status=fixed,patched # The same filter using repeated flags chainctl images advisories list "$IMAGE" \ --status=detected \ --status=pending-upstream ``` To inspect one CVE from the table output, filter the displayed aliases after retrieving the results: ```bash chainctl images advisories list "$IMAGE" -o wide | grep 'CVE-2026-42151' ``` The command filters advisories by their current status. It does not accept a CVE as the primary lookup key, and it does not replace the scanner's analysis of components that no Chainguard package ships. ## Status values The command reports the status derived from the advisory's most recent event: | Status | Meaning | How to use it when triaging | |---|---|---| | `detected` | The advisory has a recorded detection event. | Treat it as a finding that still needs validation or remediation. | | `true-positive` | The advisory has been confirmed as applicable. | Prioritize it as an applicable vulnerability. | | `fixed:` | A fix is recorded in the specified package version. | Compare the fixed version with the package version in your image and rebuild or upgrade if needed. Filtering with `--status=fixed` matches version-qualified values. | | `false-positive` | The advisory has been marked as not applicable. | Use the advisory record as context, but keep the scanner finding separate until you understand why the scanner reported it. | | `analysis-not-planned` | No applicability analysis is planned for the advisory. | Do not interpret this as "not vulnerable." Use independent evidence for your risk decision. | | `fix-not-planned` | No fix is planned for the advisory. | Review the advisory context and choose a mitigation, exception, or replacement according to your policy. | | `pending-upstream` | Chainguard is waiting for an upstream fix. | This explains why a fixed package may not yet be available; it is not confirmation that the finding is absent. | | `patched:` | The vulnerability is patched in the specified package version, typically by backporting the fix. | Compare the patched version with the package version in your image. Filtering with `--status=patched` matches version-qualified values. | | `unknown` | The advisory has no recognized status event. | Investigate the advisory and scanner evidence rather than assuming either presence or absence. | ### What a missing result means A missing result can have several explanations: - No Chainguard package ships the reported component, as with a dependency that your build added. - The image reference, digest, or platform differs from the image that was scanned. - The image does not have an SBOM attestation available to the command. - The advisory database does not contain a matching advisory for that package and version. - The scanner and advisory database use different package or version metadata. A missing result is therefore a prompt to reconcile the two data sources, not a standalone false-positive determination. ## When the finding is a Go module or Java dependency Scanners report Go modules, Java archives, and other language components separately from APK packages. How you validate one of these findings depends on where the component came from: a Chainguard package that ships it, or your own build. If your build didn't add the file where the scanner found the component, a Chainguard package ships it. To confirm, look up the APK package that owns the file: 1. In the scanner output, find the file path for the component, such as `/usr/bin/crane` for a Go binary or `/usr/share/java/maven/boot/plexus-classworlds-2.11.0.jar` for a Java archive. 2. Run the following command against the image and platform that your scanner analyzed, replacing `` with that path. The command exports the image, reads its APK database, and prints the name and version of the package that owns the file: ```bash crane export --platform linux/amd64 \ cgr.dev//@sha256: - \ | tar -xO usr/lib/apk/db/installed \ | awk -v path="" ' BEGIN { sub(/^\//, "", path) } /^P:/ { package = substr($0, 3) } /^V:/ { version = substr($0, 3) } /^F:/ { directory = substr($0, 3) } /^R:/ && directory "/" substr($0, 3) == path { print package, version } ' ``` For `/usr/bin/crane` in the `crane` image, the command prints `crane 0.22.1-r0`. If it prints nothing, no APK package owns the file. The `crane` command uses the same registry credentials as `docker`. If it can't authenticate, run `chainctl auth configure-docker` first. If an APK package owns the file, follow [Components that a Chainguard package ships](#components-that-a-chainguard-package-ships). Otherwise, follow [Dependencies that your build adds](#dependencies-that-your-build-adds). ### Components that a Chainguard package ships Chainguard records a vulnerability in a bundled component against the APK package that ships it. For example, Chainguard records CVE-2026-39836, a vulnerability in the Go standard library that is compiled into `/usr/bin/crane`, as an advisory for the `crane` package. Validate the finding the same way as an APK finding: 1. List the advisories for the image that your scanner analyzed, and filter for the CVE: ```bash chainctl images advisories list "$IMAGE" -o wide | grep 'CVE-2026-39836' ``` 2. In the row for the package that owns the file, read the status. A fixed version is a version of that package, such as `0.21.5-r1`, not a version of the component. Scanners report the component's own version, such as `go1.25.9` for the Go standard library, so don't compare the fixed version with that value. 3. Compare the owning package's installed version with the fixed version, as described in [Compare the installed package with Chainguard's fixed version](/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve/#compare-the-installed-package-with-chainguards-fixed-version). If the installed version is the fixed version or later, Chainguard has fixed the CVE in that package. If your scanner still reports it, work through the rest of [Resolve a scanner finding for a CVE Chainguard has fixed](/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve/). If no advisory for the owning package lists the CVE, Chainguard hasn't recorded the CVE for that package, and the finding stands. To ask about it, [contact support](#further-troubleshooting). ### Dependencies that your build adds Chainguard's advisories don't cover software that you add to an image, such as a Go binary that you compile in a later build stage or a JAR that your application copies in. The absence of a Chainguard advisory for one of these dependencies doesn't make the finding a false positive. Use the scanner's language-package evidence and the dependency's upstream vulnerability data instead. #### Go modules For a Go-module finding: 1. Confirm the module path and version in your build's SBOM and, where available, in `go.mod` or `go.sum`. 2. Check the module's upstream advisory and the scanner's reachability or affected-symbol evidence. 3. Rebuild with a non-vulnerable module version when one is available, then rescan the resulting image. #### Java dependencies For a Java finding: 1. Confirm the Maven or Gradle coordinates and version in the SBOM. 2. Check whether the finding is in a standalone dependency, a shaded JAR, or an application layer. 3. Consult the dependency's upstream advisory and the scanner's evidence for the affected class or code path. 4. Upgrade, replace, or otherwise mitigate the dependency, then rebuild and rescan. ## Further troubleshooting If an advisory reports a CVE as fixed or not affected but your scanner still reports it, see [Resolve a scanner finding for a CVE Chainguard has fixed](/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve/). If a result is still unclear, you can [contact support](/get-started/get-support/). When contacting support, include: - The image digest and platform. - The scanner name and database version. - The CVE and advisory identifiers. - The detected ecosystem, package/module coordinates, and version. - The relevant `chainctl images advisories list` output, preferably in `-o wide` or JSON form. --- ### Troubleshoot registry authentication errors URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/troubleshooting/registry-errors.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Registry A login or pull against `cgr.dev` failed and you have an error string. This page maps the errors Chainguard's registry returns to their causes and tells you what to check for each one. If your pull fails for a container you can see in the [Chainguard Containers Directory](https://images.chainguard.dev/), the container most likely hasn't been added to your organization yet. That case has its own guide: refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/). ## Where registry errors come from Pulling a container from `cgr.dev` takes two separate requests, and each one fails differently: 1. **Credential exchange.** Your tool sends your credentials to the token endpoint at `https://cgr.dev/token` and gets back a bearer token scoped to the repository you asked for. `docker login`, `helm registry login`, `podman login`, and `chainctl auth configure-docker` all exercise this step. 1. **The pull.** Your tool presents that bearer token to the registry API at `https://cgr.dev/v2/` and requests the tags, manifest, and layers. The two steps reuse the same HTTP status codes for unrelated problems. A `403` from the token endpoint means something different from a `403` from the registry API, so read the error code and message in the response body rather than the status number on its own: | Response body | Returned by | Meaning | | -- | -- | -- | | `UNAUTHORIZED`, "Authentication required" | Token endpoint | The repository requires credentials and you presented none that worked. | | `FORBIDDEN`, "Forbidden" | Token endpoint | The endpoint won't issue a token for the repository you named, or it couldn't use the credentials you sent. | | `BAD_REQUEST`, "InvalidArgument" | Token endpoint | The organization in the image reference didn't resolve, or the pull token you sent has expired. | | `FORBIDDEN`, "caller does not have the required capabilities" | Registry API | You're authenticated, but you aren't authorized for this repository. | | `NAME_UNKNOWN`, "repository does not exist" | Registry API | The repository name doesn't exist in that organization. | A bare `Forbidden` message doesn't tell you which of those two it is. Chainguard's registry returns the same `403` to a caller that sent no credentials, a caller whose credentials it can't use, and a caller naming a repository it won't grant a token for. Treat it as a prompt to work through the possible causes in order rather than as a single diagnosis, and in particular don't read it as confirmation that your credentials were accepted. ## Authentication required from the token endpoint You'll see a `401` like the following when a build or pull runs with no credentials configured: ```output failed to fetch anonymous token: unexpected status from GET request to https://cgr.dev/token?scope=repository%3A$ORGANIZATION%2Fpython%3Apull&service=cgr.dev: 401 Unauthorized ``` The word "anonymous" is the signal. Your tool found no credentials for `cgr.dev`, so it asked for a public token, and the repository you named isn't public. Containers in the `cgr.dev/chainguard/` namespace are public and need no authentication. Everything in your organization's own namespace at `cgr.dev/$ORGANIZATION/` requires credentials. Configure the credential helper, then retry: ```shell chainctl auth configure-docker ``` You don't need to run `chainctl auth login` first. For headless machines, CI systems, and other login flows, refer to [Authenticate to Chainguard's Registry](/chainguard/containers/registry/authenticating/) and [Authentication options for `chainctl`](/platform/chainctl-usage/authentication-options/). If the failure happens inside `docker build` rather than `docker pull`, check that the builder can see your Docker configuration. A `FROM` line pointing at your organization's namespace needs the same credentials a direct pull does. If the message appears in a Kubernetes pod's events, the credential helper won't help, because the kubelet can't run it. Refer to [Pods stuck in ImagePullBackOff](#pods-stuck-in-imagepullbackoff) instead. ## Forbidden from the token endpoint A `403` from the token endpoint looks like this, often from `helm registry login` or another tool that logs in without naming a repository: ```output Error: authenticating to "cgr.dev": GET "https://cgr.dev/token?service=cgr.dev": response status code 403: forbidden ``` This response means the token endpoint rejected the request outright. It doesn't tell you which part of the request it objected to, so check these in order: 1. **The credential format.** The username for a pull token is the Chainguard identity ID that owns the token, not your email address. Refer to [Check your credential format](#check-your-credential-format). 1. **The credential's age.** Pull tokens expire 30 days after creation by default. Create a replacement with `chainctl auth configure-docker --pull-token`. 1. **The repository name.** A scope the endpoint won't grant returns this `403` rather than a `404`, whether the repository doesn't exist or isn't in your organization's catalog. Confirm the name against `chainctl images repos list`. ## Bad request from the token endpoint A `400` from the token endpoint has two causes, and the message text tells them apart. ### The pull token expired When a pull presents an expired pull token, the token endpoint returns a `400` rather than a `401` or `403`: ```output {"errors":[{"code":"BAD_REQUEST","message":"rpc error: code = InvalidArgument desc = this identity's keys have expired"}]} ``` Create a replacement with `chainctl auth configure-docker --pull-token` and update every place that holds the old one. A login with an expired token, such as `docker login` or `helm registry login`, returns the bare `403` described in [Forbidden from the token endpoint](#forbidden-from-the-token-endpoint) instead. ### The organization didn't resolve When the message says `unable to resolve`, the organization portion of the image reference didn't match any Chainguard organization: ```output {"errors":[{"code":"BAD_REQUEST","message":"rpc error: code = InvalidArgument desc = unable to resolve ..."}]} ``` This cause has nothing to do with your credentials. The organization name is wrong. List the organizations you belong to and compare: ```shell chainctl iam organizations list ``` Organization names are usually domain names, such as `example.com`, and the full image reference is `cgr.dev/$ORGANIZATION/$IMAGE:$TAG`. Omitting the organization, or using your organization's display name instead of its registry name, can produce this error. ## Missing capabilities from the registry API This `403` is the one that means your login worked and your authorization didn't: ```output {"errors":[{"code":"FORBIDDEN","message":"caller does not have the required capabilities at \"...\""}]} ``` It also surfaces through the tool you're running. A Helm install that logged in successfully and then failed reports it against the API path: ```output Error: INSTALLATION FAILED: ... /$ORGANIZATION/postgresql/tags/list ... response status code 403 ``` Two different problems produce this error, and the fix differs: - **The repository isn't in your organization's catalog.** This is the more common cause. Your credentials are valid, but the container hasn't been added to your organization. Refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) for how to add it or request it, along with the roles that each path requires. - **Your identity lacks a role that grants pull access.** Check which capabilities your current credentials carry: ```shell chainctl auth status ``` Read the `Capabilities` field in the output. Pulling containers needs a role with the `manifest (list)` capability; `registry.pull` is the least privileged built-in role that has it. To grant it, refer to [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). Pulling APK packages needs `apk (list)` instead, which [Private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/#troubleshooting) covers. An identity federated from a CI system carries only the role you assigned when you created it. An identity created with `--role=registry.pull` can pull containers and do nothing else, which is intentional, but it means the same credentials fail if your workflow later tries to list repositories or read entitlements. ## Repository does not exist Once you hold a valid token, a name that isn't in the organization returns a `404` rather than a `403`: ```output {"errors":[{"code":"NAME_UNKNOWN","message":"repository does not exist \"...\""}]} ``` Check the spelling against `chainctl images repos list`, then against the [Chainguard Containers Directory](https://images.chainguard.dev/). If the Directory doesn't list the container either, Chainguard doesn't build it yet and you can ask for it through [Requesting new Chainguard resources](/chainguard/containers/reference/request-resources/). To work out which of those situations you're in, refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/). ## Check your credential format When you authenticate with a pull token, the username is the Chainguard identity ID associated with that token, and the password is the token itself: ```shell docker login "cgr.dev" \ --username "" \ --password "" ``` The email address you use to sign in to the Chainguard Console is not a valid registry username. Neither is your organization name. `chainctl auth configure-docker --pull-token` prints the correct pair, and `chainctl auth pull-token create --output=env` writes them to `CHAINGUARD_IDENTITY_ID` and `CHAINGUARD_TOKEN` for use in scripts. The same pair works with any tool that logs in to an OCI registry: ```shell helm registry login cgr.dev \ --username "$CHAINGUARD_IDENTITY_ID" \ --password "$CHAINGUARD_TOKEN" ``` Helm and Podman need `chainctl` installed only to create the token. Once you have the identity ID and token, the login itself doesn't call `chainctl`, so you can run it on a machine that doesn't have `chainctl` at all. Refer to [pull token output formats and credential names](/platform/chainctl-usage/pull-token-output/) for the other output formats. ## Errors from a pull-through cache or mirror A registry mirror reports Chainguard's errors in its own wording, which can obscure which of the preceding cases you're in. Artifactory, for example, reports a rejected credential as a configuration problem: ```output Invalid username/password configured for Remote Docker repository: $REPOSITORY_NAME ... Can't fetch token for repo ... realm: https://cgr.dev/token ``` The `realm: https://cgr.dev/token` fragment tells you the failure happened during credential exchange, so work through [Forbidden from the token endpoint](#forbidden-from-the-token-endpoint) and [Check your credential format](#check-your-credential-format). Mirrors are a common place for the username to be wrong, because their configuration forms label the field "username" and invite an email address. Pull tokens expire, so a mirror that worked for a month and then stopped is likely holding an expired token. For the tool-specific settings each platform needs, refer to the [pull-through guides](/chainguard/containers/registry/pull-through-guides/). ## Pods stuck in ImagePullBackOff A Kubernetes pod that can't pull its container reports `ErrImagePull` and then `ImagePullBackOff`. That often happens with credentials that pull the same container without trouble through `docker` or `crane`. When the credentials work everywhere except Kubernetes, the kubelet usually never sent them. Read the pull failure in the pod's events: ```shell kubectl describe pod --namespace ``` The `Failed` event repeats the token endpoint's response: ```output Failed to pull image "cgr.dev/$ORGANIZATION/python:latest": ... failed to authorize: failed to fetch anonymous token: unexpected status from GET request to https://cgr.dev/token?scope=repository%3A$ORGANIZATION%2Fpython%3Apull&service=cgr.dev: 401 Unauthorized ``` The word before `token` tells you where to look: - **`anonymous token`**: The kubelet found no credentials it could use for `cgr.dev`, so it pulled without any. The credentials themselves may be correct. Work through the checks in this section, in order. - **`oauth token`**: The kubelet sent credentials and the token endpoint rejected them. A `400` means the pull token expired, as described in [The pull token expired](#the-pull-token-expired). A `401` or `403` means the username or token is wrong, so refer to [Check your credential format](#check-your-credential-format). ### Check the secret type The kubelet reads pull credentials only from a secret of type `kubernetes.io/dockerconfigjson`. Print the type of the secret your pod references: ```shell kubectl get secret --namespace -o jsonpath='{.type}' ``` If the output is `Opaque`, the kubelet ignores the secret and emits no warning about it. `kubectl create secret generic` creates an `Opaque` secret unless you pass `--type=kubernetes.io/dockerconfigjson`, and so does a manifest that omits `type:`. [Authenticating with Kubernetes](/chainguard/containers/registry/authenticating/#authenticating-with-kubernetes) covers this in more detail. Replace the secret under the same name: ```shell kubectl delete secret --namespace kubectl create secret docker-registry \ --namespace \ --docker-server=cgr.dev \ --docker-username="" \ --docker-password="" ``` The kubelet reads the secret again on its next pull attempt, so the pod recovers without changes. A pod that has been failing for a while can wait many minutes between attempts, though. To retry now, delete the pod and let its controller recreate it, or restart its Deployment as shown in [Check that the pod references the secret](#check-that-the-pod-references-the-secret). ### Check the secret's namespace A pod can use only pull secrets in its own namespace. When the secret it names isn't there, the pod's events include this warning alongside the `anonymous token` failure: ```output Unable to retrieve some image pull secrets (); attempting to pull the image may not succeed. ``` Create the secret in the namespace where the pod runs. Each namespace that pulls from `cgr.dev` needs its own copy. ### Check that the pod references the secret A secret in the right namespace has no effect until the pod names it. Print the pull secrets the pod received: ```shell kubectl get pod --namespace -o jsonpath='{.spec.imagePullSecrets}' ``` Empty output means the pod has none. Add the secret to the pod template's `imagePullSecrets`, or to the service account the pod runs as. For Helm charts, the [Chainguard Helm charts guide](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts/#use-helm-values-with-globalimagepullsecrets) shows the `global.imagePullSecrets` value. Kubernetes copies a service account's `imagePullSecrets` into a pod only when the pod is created, and it won't add them to a pod that already exists. After you update a service account, recreate the pods that use it. For a Deployment: ```shell kubectl rollout restart deployment --namespace ``` ### Check the registry name in the secret The kubelet uses a credential only when its key in the secret matches the image's registry. List the keys without printing the credentials: ```shell kubectl get secret --namespace \ -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq '.auths // {} | keys' ``` The output needs to include `"cgr.dev"`. The kubelet also accepts `"https://cgr.dev"`, and a key that includes your organization's path, such as `"cgr.dev/$ORGANIZATION"`. Two outputs point to a specific mistake: - **`["https://index.docker.io/v1/"]`**: The secret was created with `kubectl create secret docker-registry` and no `--docker-server` flag, so the credentials are registered for Docker Hub. Re-create it with `--docker-server=cgr.dev`. - **`[]`, or a list without `cgr.dev`**: The secret was copied from a Docker configuration that reaches `cgr.dev` through the `chainctl` credential helper. That configuration stores no credentials for `cgr.dev`, only the name of a helper program, and the kubelet can't run it. Create a pull token and build the secret from it, as described in [Authenticating with Kubernetes](/chainguard/containers/registry/authenticating/#authenticating-with-kubernetes). A key that names a different organization, such as `"cgr.dev/another-org"`, or that adds a port, such as `"cgr.dev:443"`, also fails to match. ## Isolate the failing step To find out which of the two requests is failing, ask the token endpoint directly. This returns the HTTP status for a credential exchange with no credentials attached: ```shell curl -s -o /dev/null -w '%{http_code}\n' \ "https://cgr.dev/token?scope=repository%3A$ORGANIZATION%2F$IMAGE%3Apull&service=cgr.dev" ``` A `401` means the reference resolved and the endpoint wants credentials, so your tool's configuration is the problem rather than the name you used. Drop the `-o /dev/null` to read the error code and message in the response body. To check the second request, compare `chainctl auth status` against the capabilities listed in [Missing capabilities from the registry API](#missing-capabilities-from-the-registry-api). If the pull fails only in Kubernetes, start from the pod's events instead, as described in [Pods stuck in ImagePullBackOff](#pods-stuck-in-imagepullbackoff). ## Can't create a pull token If the Console won't let you create a pull token, or `chainctl auth pull-token create` fails, the problem is your role rather than the registry. Creating a pull token creates a Chainguard identity and a role-binding for it, so it needs a role carrying the `identity (create)` and `role_bindings (create)` capabilities. `registry.pull_token_creator` is the least privileged built-in role with both. Ask an administrator in your organization to bind it to your account, or refer to [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/) to do it yourself. The [capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/) lists every capability each built-in role carries. ## Learn more - [Authenticate to Chainguard's Registry](/chainguard/containers/registry/authenticating/) - [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) - [Onboard your teams](/get-started/onboard-your-teams/#what-your-organization-can-pull) - [Network requirements](/chainguard/containers/registry/network-requirements/) - [Get support](/get-started/get-support/) --- ### Error messages URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/troubleshooting/errors.md Last Modified: September 28, 2026 Tags: Libraries, Product Chainguard Libraries applies security controls to every package it serves through the Chainguard Repository: malware and greyware scanning, and configurable policies such as a cooldown period. When one of these controls blocks a package or version, Chainguard withholds it and the install fails. Chainguard also surfaces errors for other reasons, such as a missing entitlement or invalid authentication. **Note**: If your build tool or repository manager pulls from a public registry as a fallback, it may fetch a blocked package and bypass Chainguard's controls. Chainguard recommends pulling all open source packages through the [Chainguard Repository](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) only. ## Why a package or version is blocked Chainguard blocks a package or version for one of the following reasons: * **Malware or greyware detected**: The package or version is on Chainguard's malware and greyware block list, either from a public advisory (MAL ID) or from Chainguard's own source code scanning. * **Malware scan pending**: A newly published version has not completed malware scanning yet. * **Policy block**: The version is blocked by a policy configured by your organization, such as a cooldown policy. To resolve a blocked package, choose a version that is not blocked, wait for a pending scan or cooldown period to pass, or configure an [override](/chainguard/chainguard-repository/library-policies/) to allow an exception for a specific package or version. ## Package manager behavior How a blocked package appears during development will depend on the package manager and language ecosystem: * **`npm`** surfaces the block reason directly, reporting a blocked version as a `403` with the reason (for example, `MALWARE_DETECTED`). * **Other package managers** (such as `pnpm`, `yarn`, `pip`, `uv`, `poetry`, `Maven`, and `Gradle`) typically report a blocked version as a `not found` or `no matching version found` error. When an entire package (all of its versions) is blocked for malware, a `409` error surfaces across most package managers. ## Other errors The following errors indicate problems with authentication, entitlements, or a nonexistent package. | Error | Meaning | Next steps | | -- | -- | -- | | Not authenticated (`401`) | Your pull token is missing or expired. | Reconfigure access. See [Access Chainguard Libraries](/chainguard/libraries/introduction/access/). | | Missing entitlement (`403`) | Your organization is not entitled to the specific ecosystem. | See [Manage library entitlements](/chainguard/libraries/introduction/access/#manage-library-entitlements). | | Package does not exist (`404`) | The requested package or version does not exist. | Confirm that the package name and version exist on the public upstream registry. | ## Learn more * [View blocked malware in the Console](/chainguard/libraries/introduction/browse/#view-malware-information) * [Library policies, overrides, and cooldown](/chainguard/chainguard-repository/library-policies/) * [Access Chainguard Libraries](/chainguard/libraries/introduction/access/) * [Manage build pinning](/chainguard/libraries/policies-and-security/build-pinning/) --- ### Quickstart for Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/quickstart.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Getting Started This quickstart covers one full cycle of working with a [Chainguard Container](/chainguard/containers/overview/): pull an image from Chainguard's registry, run your own code on it, and verify where it came from. The example uses the Node container, but the same workflow applies to every image in the [Chainguard Containers Directory](https://images.chainguard.dev/). These steps link to reference documentation instead of explaining each concept in place. Follow the links whenever you want additional details and context. ## Prerequisites To follow this quickstart, you need: * [Docker](https://docs.docker.com/engine/install/) or another OCI-compatible container runtime installed on your local machine. * [Cosign](/open-source/sigstore/cosign/how-to-install-cosign/), which you use in Step 4 to verify a container image, installed. * [jq](https://jqlang.github.io/jq/download/) installed. jq is a lightweight, command-line JSON processor; this guide uses it in Step 4 to make Cosign output more easily readable. You don't need a Chainguard account. Every image in this guide is a [Free container](/chainguard/containers/concepts/container-categories/#free-containers): publicly available, with no authentication required. Production Containers, which add version-specific tags and patch SLAs, require [authenticating to the registry](/chainguard/containers/registry/authenticating/). ## Step 1: Pull and run a container Pull the Node container from `cgr.dev`, Chainguard's registry: ```shell docker pull cgr.dev/chainguard/node:latest ``` Chainguard publishes two tags for Free Containers — `latest` and `latest-dev` — and both point at the most recent build of the image. The image's entrypoint is `node`, so any arguments you pass go to the Node binary. Run the container, passing an argument to check the Node version: ```shell docker run --rm cgr.dev/chainguard/node:latest --version ``` ```output v26.6.0 ``` Your output may show a different version. Chainguard rebuilds its containers as upstream releases and package updates land, and the `latest` tag follows the newest build. ## Step 2: Build your application on the container Create a directory for a demo application: ```shell mkdir ~/hello-chainguard && cd ~/hello-chainguard ``` Create a file named `server.js` to hold an example JavaScript application. It uses only the Node standard library, so the application has no dependencies to install: ```shell cat > server.js < { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify({ message: "Hello from a Chainguard Container", node: process.version, uid: process.getuid(), })); }); server.listen(8080, () => console.log("Listening on port 8080")); EOF ``` Then create a `Dockerfile` in the same directory: ```shell cat > Dockerfile <

Note: Guardener is in beta. Available to organizations that have installed and linked the Chainguard App.

## How connections work A connection has two halves, one on each side: 1. **A GitHub App installation** — the [Chainguard App](https://github.com/apps/chainguard) installed on a GitHub organization (or personal account). The installation is what grants Guardener access to repositories and delivers repository events to it. GitHub assigns each installation a numeric **installation ID**. 2. **A link** — an association, stored on the Chainguard platform, between that installation and a Chainguard organization. The link is what tells Guardener which Chainguard organization the GitHub activity belongs to. The two halves do different jobs. Installing the app is enough for Guardener to start responding on **public** repositories — each feature still has to be enabled with a configuration file, but no link is needed. The link adds the Chainguard side: it attributes the activity to your Chainguard organization and unlocks the features that need one, such as covering **private** repositories (subject to the [repository visibility scope](#repository-visibility-scope)) and group-scoped operations like triggering an Actions migration with `chainctl`. Linking fails if the app is not installed. A few rules govern connections: - **A GitHub organization can be linked to exactly one Chainguard organization at a time.** Linking an organization that is already linked elsewhere fails with an "already linked to a different Chainguard group" error; unlink it first (refer to [Moving a GitHub organization](#moving-a-github-organization-to-a-different-chainguard-organization)). - **A Chainguard organization can link any number of GitHub organizations.** Each linked GitHub organization appears as its own connection. - **Personal accounts work like organizations.** To connect your own GitHub user account instead of an organization, install the app on your account and pass your GitHub username as the organization. - **`chainctl develop init` creates the same link.** Turning on [Chainguard Checks](/chainguard/checks/overview/) and [Chainguard Workspaces](/chainguard/workspaces/overview/) with `chainctl develop init` links a GitHub organization the same way `chainctl guardener github link` does, so the one-organization rule applies to it too. - **Re-linking is safe.** Linking a GitHub organization that is already linked to the same Chainguard organization succeeds without changing anything. ### Required permissions The `chainctl guardener github` commands check permissions on both sides of the connection: | Action | Requires | | ------ | -------- | | Link (`link`) | **Both**: the `guardener.association.manage` capability on the Chainguard organization (held by owners), **and** ownership of the GitHub organization. | | List connections (`status`) | The `guardener.association.list` capability on the Chainguard organization. | | Unlink (`unlink`) | **Either**: the `guardener.association.manage` capability on the Chainguard organization, **or** ownership of the GitHub organization (refer to [Removing a connection](#removing-a-connection)). | Refer to the [Built-in roles and capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/) for how capabilities map to roles. Commands that prove GitHub organization ownership (`link`, and the fallback path of `unlink`) open a browser window to authorize with GitHub. Listing connections with `status` is read-only and never involves a browser. ## Setting up a connection ### Step 1: Install the Chainguard App Install the app on the GitHub organization whose repositories Guardener should manage: 1. Go to the [Chainguard App page](https://github.com/apps/chainguard). 2. Select **Install** (or **Configure** if it is already installed on another account). 3. Choose the GitHub organization (or your personal account) to install it on. 4. Choose which repositories Guardener can access — **All repositories** or a selected subset. You can change this later (refer to [Changing repository access](#changing-which-repositories-are-connected)). 5. Review the requested permissions and confirm. Installing the app does not change any repository on its own. Every Guardener feature stays disabled until you opt in with a configuration file, as described in [Configuration](/chainguard/guardener/github/configuration/). ### Step 2: Link the GitHub organization to your Chainguard organization Run `chainctl guardener github link`, passing the GitHub organization login and your Chainguard organization name: ```shell chainctl guardener github link \ --github-org \ --group ``` If you omit `--group`, `chainctl` prompts you to select from the Chainguard organizations you have access to. A browser window opens to authorize with GitHub; completing it proves that you own the GitHub organization. On success, `chainctl` prints the linked organization and its installation ID: ``` Linked GitHub organization "example-org" to group example.com (installation 12345678). ``` > **Note:** The first time an organization links a GitHub organization, `chainctl` prompts you to accept the Guardener [Terms of Service](https://www.chainguard.dev/legal/guardener) and [Data Privacy Agreement](https://www.chainguard.dev/legal/supplemental-dpa) on behalf of your organization. Acceptance is recorded once per organization and covers subsequent links. If the browser flow cannot use its default local port (8989), pass a different one with `--port`. ### Step 3: Verify the connection List the connections for your Chainguard organization: ```shell chainctl guardener github status --group ``` The linked GitHub organization should appear in the output, as described in the next section. ## Inspecting connections `chainctl guardener github status` lists every GitHub organization linked to a Chainguard organization: ``` Repository visibility scope: PUBLIC (default; public repositories only) ORGANIZATION INSTALLATION ID SETTINGS example-org 12345678 https://github.com/settings/installations/12345678 ``` - **ORGANIZATION** — the GitHub organization (or user) login for the connection. - **INSTALLATION ID** — GitHub's identifier for the app installation backing the connection. - **SETTINGS** — the GitHub settings page for the installation, where a GitHub owner can review its permissions and repository access. ### Repository visibility scope Above the connection list, `status` prints the organization's **repository visibility scope**, which controls which repositories Guardener responds to across all of the organization's connections: - **PUBLIC** (the default) — Guardener acts on public repositories only, even when the installation grants it access to private repositories. - **ALL** — Guardener acts on both public and private repositories. The visibility scope is managed by Chainguard. If you need Guardener to cover private repositories, contact Chainguard support to have your organization's scope updated. > **Note:** Reading the visibility scope requires the `guardener.entitlement.list` capability. Without it, `status` still lists your connections and prints a warning that the scope was skipped. ## Changing which repositories are connected Repository access is controlled on the GitHub side, by the app installation. To change it: 1. Open the installation's settings page — the **SETTINGS** URL from `chainctl guardener github status`, or your GitHub organization's **Settings → GitHub Apps** page. 2. Under **Repository access**, switch between **All repositories** and **Only select repositories**, or adjust the selected list. No change on the Chainguard side is needed; the existing link continues to apply to whatever the installation can access. Remember that access alone does nothing — each repository still needs a `.chainguard/` configuration file (or an org-level default) to enable a feature, and the [repository visibility scope](#repository-visibility-scope) still applies. ## Moving a GitHub organization to a different Chainguard organization Because a GitHub organization can be linked to only one Chainguard organization at a time, moving it is an unlink followed by a link: ```shell chainctl guardener github unlink \ --github-org \ --group chainctl guardener github link \ --github-org \ --group ``` The GitHub App installation is untouched by the move; only the Chainguard-side association changes. ## Removing a connection To remove a connection, unlink the GitHub organization: ```shell chainctl guardener github unlink \ --github-org \ --group ``` Unlinking accepts either side's authority: - **Chainguard credentials.** When you pass `--group` and hold the `guardener.association.manage` capability on that organization, the unlink completes with no browser involved. - **GitHub ownership.** Otherwise — including when you have lost access to the Chainguard organization — `chainctl` falls back to the GitHub authorization flow, and proving that you own the GitHub organization is sufficient. You must still be logged in to Chainguard (`chainctl auth login`), but no access to the linked organization is required. Unlinking removes only the Chainguard-side half of the connection: Guardener stops covering the organization's private repositories and group-scoped operations, but features enabled on public repositories keep working for as long as the app remains installed. To stop Guardener entirely, also uninstall the Chainguard App from your GitHub organization's **Settings → GitHub Apps** page. Uninstalling the app without unlinking also stops Guardener, but leaves a dangling association behind; prefer unlinking first. ## Troubleshooting **"the guardener GitHub App must be installed on the GitHub account (organization or user) before it can be linked"** Linking checks for an existing installation. Install the app on the GitHub organization ([Step 1](#step-1-install-the-chainguard-app)), then run `link` again. **"that GitHub organization is already linked to a different Chainguard group"** Each GitHub organization can be linked to only one Chainguard organization. Unlink it from its current organization first — anyone who owns the GitHub organization can do this, even without access to the current Chainguard organization (refer to [Removing a connection](#removing-a-connection)). **The browser authorization fails or never completes** The GitHub flow requires that you are an owner of the GitHub organization; membership alone is not enough. If the local callback port is in use, re-run the command with `--port `. The flow times out after a few minutes — re-run the command to try again. **`status` shows the connection but Guardener isn't doing anything** A connection alone changes nothing. Check that the repository is covered by the installation's repository access, that its visibility matches your [repository visibility scope](#repository-visibility-scope), and that the feature you expect is enabled by a `.chainguard/` configuration file (refer to [Configuration](/chainguard/guardener/github/configuration/)). ## Command reference For the complete set of flags and options, refer to the `chainctl` reference: - [`chainctl guardener`](/platform/chainctl/chainctl-docs/chainctl_guardener/) - [`chainctl guardener github`](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - [`chainctl guardener github link`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_link/) - [`chainctl guardener github unlink`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_unlink/) ## Next steps - **[Configuration](/chainguard/guardener/github/configuration/)** — Enable Guardener features with `.chainguard/` configuration files. - **[Hardened Actions](/chainguard/guardener/github/actions-security/)** — Recommend and migrate GitHub Actions to hardened, SHA-pinned equivalents. - **[Commit Verification](/chainguard/guardener/github/commit-verification/)** — Require cryptographically signed commits in pull requests. --- ### Chainguard Actions telemetry and privacy URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/actions/telemetry.md Last Modified: September 28, 2026 Tags: Chainguard Actions, Telemetry, Privacy Most Chainguard hardened actions run a best-effort "phone-home" hook that records a usage event to `https://actions.enforce.dev/actions/v1/record`. JavaScript actions run it as a `pre` script, and composite actions run it as their first step. Docker actions don't include the hook, and some JavaScript and composite actions don't either. The hook is fire-and-forget, with a 2 second timeout that fails open, so it cannot break your build. If your runners use an egress allowlist, add `actions.enforce.dev` so the hook doesn't wait out its timeout on every step that uses a hardened action. ## Why we collect this data We collect this data for two reasons: - **Billing**: usage events show which repositories use Chainguard hardened actions, so we can meter usage against your entitlements and bill accurately. - **Security**: verified usage records help us confirm that requests come from legitimate workflows, identify where our actions run, and detect anomalous or unauthorized use. ## What we collect What we collect depends on whether your workflow grants `id-token: write`: - **Without `id-token: write`**: we record your repository name, a timestamp, and an "unverified" flag. - **With `id-token: write`**: the hook mints a GitHub OIDC token scoped to the `actions.chainguard.dev` audience and sends it so we can verify the record. From that token we store metadata: repository, ref, sha, workflow path, repository visibility, and run identifiers. We do not store who triggered the run. The OIDC token identifies the account that started the workflow, and our service discards that claim where it assembles the usage event, so the actor never reaches any of our storage. The hook never grants itself `id-token: write`. It only uses the permission if your workflow already grants it. If you would rather we receive only your repository name, do not grant `id-token: write` to that job. ## About the token The token is a short-lived, audience-locked GitHub OIDC token. Because it is locked to the `actions.chainguard.dev` audience, it cannot be used against GitHub, a cloud provider, or any other service. The underlying `ACTIONS_ID_TOKEN_REQUEST_TOKEN` never leaves the runner; it is used locally only to mint the audience-scoped token. ## Learn more - [Chainguard Actions overview](/chainguard/actions/overview/) - For other questions, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). --- ### Getting started with the GitLab Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/platform-and-infrastructure/gitlab.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's GitLab container images provide a security-hardened foundation for deploying GitLab's DevOps platform. Built on Wolfi, these images are rebuilt daily with the latest security patches and include verifiable Sigstore signatures and SBOMs for every component. The Chainguard GitLab offering spans fifteen components that align with the official GitLab Helm chart architecture: `gitlab-base`, `gitlab-certificates`, `gitaly`, `gitlab-agent`, `gitlab-kas`, `gitlab-shell`, `gitlab-exporter`, `gitlab-pages`, `gitlab-container-registry`, `gitlab-runner`, `gitlab-runner-helper`, and the Community Edition services `gitlab-sidekiq-ce`, `gitlab-toolbox-ce`, `gitlab-webservice-ce`, and `gitlab-workhorse-ce`. A FIPS-validated variant is also available for FedRAMP compliance. This guide shows how to deploy a GitLab instance on a local Kubernetes cluster using the official [GitLab Helm chart](https://docs.gitlab.com/charts/) with Chainguard images. The configuration here is suitable for development and evaluation; for production deployments, follow [GitLab's production recommendations](https://docs.gitlab.com/charts/installation/deployment.html).
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Prerequisites This guide requires the following tools installed on your local machine: - [Docker](https://docs.docker.com/get-docker/) - [k3d](https://k3d.io/stable/#installation) — a CLI tool for running K3s clusters in Docker - [kubectl](https://kubernetes.io/docs/tasks/tools/) - [Helm](https://helm.sh/docs/intro/install/) - [chainctl](https://edu.chainguard.dev/platform/chainctl/) — the Chainguard CLI Your machine should have at least 8 GB of RAM available for Docker to allocate to the cluster. GitLab deploys several services simultaneously, and insufficient memory is the most common cause of failed or stuck deployments. You'll also need access to a Chainguard organization with the GitLab image group enabled. If you don't have access yet, [contact Chainguard](https://www.chainguard.dev/contact) to request a trial. ## Step 1: Authenticate with the Chainguard registry Log in to the Chainguard registry: ```shell chainctl auth login ```

Note: chainctl auth login supports several login flows, including device-code login for machines without a browser and assumable identities for CI. If the default browser flow doesn't work in your environment, see Authentication options.

Configure Docker to use your Chainguard credentials: ```shell chainctl auth configure-docker ``` Set your Chainguard organization name as an environment variable. If you don't know your organization name, run `chainctl iam organizations list -o table` to find it. ```shell export ORGANIZATION= ``` ## Step 2: Create a local Kubernetes cluster Create a K3s cluster with k3d, disabling the default Traefik ingress and mapping host ports 80 and 443 to the cluster's load balancer: ```shell k3d cluster create gitlab-demo \ --k3s-arg "--disable=traefik@server:*" \ -p "80:80@loadbalancer" \ -p "443:443@loadbalancer" ``` The GitLab Helm chart installs its own NGINX ingress controller, so Traefik must be disabled to avoid a conflict. The port mappings let you reach GitLab at the standard HTTP and HTTPS ports on your local machine. You'll see output similar to the following: ```output INFO[0000] Created network 'k3d-gitlab-demo' INFO[0001] Creating node 'k3d-gitlab-demo-server-0' INFO[0001] Creating LoadBalancer 'k3d-gitlab-demo-serverlb' INFO[0005] Starting servers... INFO[0013] Cluster 'gitlab-demo' created successfully! ``` Verify that the cluster is running: ```shell kubectl get nodes ``` ```output NAME STATUS ROLES AGE VERSION k3d-gitlab-demo-server-0 Ready control-plane,master 19s v1.31.5+k3s1 ``` The node should show a `Ready` status before you proceed. ## Step 3: Create an image pull secret GitLab's Helm chart deploys pods in the `gitlab-system` namespace that pull images from `cgr.dev`. Kubernetes nodes don't inherit credentials from your local Docker configuration, so you need to create a pull secret explicitly before installing. Create the namespace first so you can pre-populate the secret: ```shell kubectl create namespace gitlab-system ``` Create a long-lived pull token for your Chainguard organization: ```shell chainctl auth pull-token create \ --name gitlab-pull-token \ --ttl 8760h ``` The command outputs a username and password. Use those values to create a Kubernetes image pull secret: ```shell kubectl create secret docker-registry cgr-pull-secret \ --namespace gitlab-system \ --docker-server=cgr.dev \ --docker-username= \ --docker-password= ``` ```output secret/cgr-pull-secret created ``` ## Step 4: Create the GitLab configuration file Create a file named `gitlab-values.yaml` with the following content. This file configures the GitLab Helm chart to pull all images from the Chainguard registry, references the pull secret you just created, and sets reduced resource requests appropriate for a local development environment. Replace every occurrence of `ORGANIZATION` with your Chainguard organization name. If you set `$ORGANIZATION` in [Step 1](#step-1-authenticate-with-the-chainguard-registry), run the following command after saving the file to substitute it automatically: ```shell sed -i "s/ORGANIZATION/$ORGANIZATION/g" gitlab-values.yaml ``` ```yaml installCertmanager: false certmanager-issuer: email: admin@example.com global: gitlabBase: image: repository: cgr.dev/ORGANIZATION/gitlab-base tag: "18.11" certificates: image: repository: cgr.dev/ORGANIZATION/gitlab-certificates tag: "18.11" hosts: domain: 127.0.0.1.nip.io externalIP: 127.0.0.1 ingress: configureCertmanager: false image: pullSecrets: - name: cgr-pull-secret gitlab: gitlab-exporter: image: repository: cgr.dev/ORGANIZATION/gitlab-exporter tag: "18.11" gitlab-shell: image: repository: cgr.dev/ORGANIZATION/gitlab-shell tag: "18.11" gitaly: image: repository: cgr.dev/ORGANIZATION/gitaly tag: "18.11" gitlab-pages: image: repository: cgr.dev/ORGANIZATION/gitlab-pages tag: "18.11" kas: image: repository: cgr.dev/ORGANIZATION/gitlab-kas tag: "18.11" sidekiq: image: repository: cgr.dev/ORGANIZATION/gitlab-sidekiq-ce tag: "18.11" resources: requests: cpu: 100m memory: 500Mi toolbox: image: repository: cgr.dev/ORGANIZATION/gitlab-toolbox-ce tag: "18.11" webservice: image: repository: cgr.dev/ORGANIZATION/gitlab-webservice-ce tag: "18.11" resources: requests: cpu: 300m memory: 1500Mi workhorse: image: cgr.dev/ORGANIZATION/gitlab-workhorse-ce tag: "18.11" registry: image: repository: cgr.dev/ORGANIZATION/gitlab-container-registry tag: "18.11" gitlab-runner: install: false prometheus: install: false redis: resources: requests: cpu: 50m memory: 64Mi minio: resources: requests: cpu: 50m memory: 128Mi ``` A few notes on this configuration: - **Image tags**: The `18.11` tag tracks the latest security-patched build within the GitLab 18.11 minor release. This provides a longer guide lifespan than `latest` while still receiving daily security updates. - **Chart version**: The Helm install command in the next step pins to chart version `9.11.7`, which corresponds to GitLab 18.x. The chart 10.x series (GitLab 19+) requires externally managed PostgreSQL, Redis, and object storage. When Chainguard's `18.11` images are succeeded by a `19.x` series, this guide will require a corresponding update. - **TLS**: With cert-manager disabled, the chart generates self-signed TLS certificates. Your browser will display a certificate warning when you first access GitLab — this is expected for a local development setup. - **Workhorse**: The workhorse container runs inside the webservice pod and must use a Chainguard image built from the same commit as `gitlab-webservice-ce`. It is configured separately under `gitlab.webservice.workhorse` rather than as a standalone subchart. ## Step 5: Deploy GitLab with Helm Add the official GitLab Helm chart repository: ```shell helm repo add gitlab https://charts.gitlab.io/ helm repo update ``` Deploy GitLab, pinning to the chart version that matches the Chainguard image series: ```shell helm upgrade --install gitlab gitlab/gitlab \ --version 9.11.7 \ --namespace gitlab-system \ --values gitlab-values.yaml \ --timeout 600s ``` The `--timeout 600s` flag gives the deployment up to ten minutes to complete. GitLab pulls several container images and initializes multiple services on first install, so the process takes several minutes. Watch the deployment progress: ```shell watch kubectl get pods -n gitlab-system ``` When the output stabilizes and all pods show a `Running` or `Completed` status, press `Ctrl+C` to exit. A healthy deployment looks like this: ```output NAME READY STATUS RESTARTS AGE gitlab-gitaly-0 1/1 Running 0 8m gitlab-gitlab-exporter-... 1/1 Running 0 8m gitlab-gitlab-shell-... 1/1 Running 0 8m gitlab-kas-... 1/1 Running 0 8m gitlab-migrations-...-... 0/1 Completed 0 8m gitlab-minio-... 1/1 Running 0 8m gitlab-minio-create-buckets-... 0/1 Completed 0 8m gitlab-nginx-ingress-controller-... 1/1 Running 0 8m gitlab-postgresql-0 2/2 Running 0 8m gitlab-redis-master-0 2/2 Running 0 8m gitlab-registry-... 1/1 Running 0 8m gitlab-sidekiq-all-in-1-v2-... 1/1 Running 0 8m gitlab-toolbox-... 1/1 Running 0 8m gitlab-webservice-default-... 2/2 Running 0 8m ``` > **Note**: The KAS pods may restart once on first boot while waiting for Redis to become ready. This is a normal startup ordering effect and resolves on its own within a minute or two. ## Step 6: Access GitLab Retrieve the initial root password from the Kubernetes secret created during deployment: ```shell kubectl get secret gitlab-gitlab-initial-root-password \ -n gitlab-system \ -o jsonpath='{.data.password}' | base64 --decode; echo ``` Open a browser and navigate to `https://gitlab.127.0.0.1.nip.io`. Proceed past the browser's certificate warning (expected because the deployment uses a self-signed certificate), then log in with the username `root` and the password you retrieved. After logging in, navigate to **User menu > Edit profile > Password** and set a new password for the root account. ## Step 7: Clean up When you're done, delete the cluster to remove all resources: ```shell k3d cluster delete gitlab-demo ``` ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose GitLab Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

### Enable GitLab Runner To add GitLab Runner with Chainguard images, update `gitlab-values.yaml` with the following: ```yaml gitlab-runner: install: true image: registry: cgr.dev image: ORGANIZATION/gitlab-runner tag: "18.11" runners: config: | [[runners]] [runners.kubernetes] helper_image = "cgr.dev/ORGANIZATION/gitlab-runner-helper:18.11" ``` Run `helm upgrade` with the updated values file to apply the change. ### Use the FIPS variant Chainguard provides FIPS-validated variants of all GitLab images for FedRAMP and compliance-sensitive environments. To use them, append `-fips` to every image repository path in your values file. For example: ```yaml global: gitlabBase: image: repository: cgr.dev/ORGANIZATION/gitlab-base-fips tag: "18.11" certificates: image: repository: cgr.dev/ORGANIZATION/gitlab-certificates-fips tag: "18.11" ``` Apply the same `-fips` suffix to all component image repositories in the `gitlab:` section, the `workhorse` block, and the `registry:` image. --- ### Getting started with the Chainguard Skills Registry URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/agent-skills/skills-registry.md Last Modified: October 14, 2026 Tags: Agent Skills, Overview The Chainguard Skills Registry lets you publish, manage, and distribute skills scoped to your organization. Use `chainctl` to upload original skills to `uploads.cgr.dev`, submit them for hardening, and install the hardened results from `skills.cgr.dev`. This guide walks through enabling the registry for your organization, then uploading, hardening, installing, and running a skill. For job tracking, digest-based submissions, and browsing user folders, see [Getting started with skill hardening](/chainguard/agent-skills/skill-hardening/).

Note: Chainguard Skills Registry is in beta.

## Prerequisites To follow this guide, you need: * `chainctl` **v0.2.376** or later, installed and authenticated. Check your version with `chainctl version`. Refer to [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't have it yet. * An active Chainguard organization. * Owner access on the organization. In the commands below, replace `your-organization` with your organization's name or UIDP (its unique identifier). ## Enabling the skills entitlement Before your org can push or install skills, create a skills entitlement. > **Note**: You must have the `owner` role in your organization to create a skills entitlement and accept the Skills Registry terms of service. ```shell chainctl skills entitlements create --parent your-organization ``` ```output Created skills entitlement for org example.dev (717b474ac6972745c5706a898aa6e67ffba97dad) ``` The `entitlements` subcommands take the organization with `--parent`, while the other `skills` subcommands use `--group`. Omit `--parent` to pick the organization from an interactive list. Next, accept the Skills Registry terms of service for your org: ```shell chainctl skills accept-terms --group your-organization ``` This opens an interactive prompt: ```output Chainguard Legal Agreements To continue, please review and accept the following: ▶ [ ] I agree to the Agent Skills Terms of Service https://www.chainguard.dev/legal/agent-skills [ ] I agree to the Data Privacy Agreement https://www.chainguard.dev/legal/supplemental-dpa ↑/↓ navigate • space toggle • enter confirm • q cancel ``` Press SPACE to check each agreement, using the arrow keys to move between them, then press ENTER to confirm. The prompt doesn't continue until you accept both. In CI, where no interactive terminal is available, pass `--yes` instead. By using `--yes`, you confirm that you have read and agreed to the [Agent Skills Terms of Service](https://www.chainguard.dev/legal/agent-skills) and the [Data Privacy Agreement](https://www.chainguard.dev/legal/supplemental-dpa). ## Creating an example skill A skill is a directory containing a `SKILL.md` file. The `SKILL.md` frontmatter declares the skill's `name` and a `description` that tells an agent when to use it. The rest of the file contains the instructions the agent follows. The next section has a few examples that refer to a skill named `hello-world`. You can create a sample `hello-world` skill with the following command: ```shell mkdir hello-world cat > hello-world/SKILL.md << 'EOF' --- name: hello-world description: A simple hello world skill. Use this to verify your skills registry setup is working end to end. --- When this skill is invoked, greet the user with: "Hello from Chainguard Agent Skills! Your skill installed and loaded successfully." If the user provides their name, greet them by name instead: "Hello, ! Welcome to Chainguard Agent Skills." EOF ``` After running this command, your directory has the following structure: ``` hello-world/ └── SKILL.md ``` The directory name (`hello-world/`) must match the `name` field in the frontmatter (`name: hello-world`). If they don't match, both `validate` and `push` fail. ## Manage skills with `chainctl` This section outlines some of the `chainctl` commands you can use to manage skills in your organization's private Skills Registry. The following commands use the `hello-world` skill as an example, but you can use any other skills you've created in its place. Refer to the [`chainctl skills` reference documentation](/platform/chainctl/chainctl-docs/chainctl_skills/) for more information. ### Validate the skill Before you publish, check that the skill directory meets the spec with the `validate` subcommand. It runs locally and makes no network calls: ```shell chainctl skills validate hello-world ``` ```output ✓ SKILL.md found ✓ Frontmatter valid ✓ name: "hello-world" (matches directory basename) ✓ description: 96 chars ✓ Total size: 387 B / 10 MB ✓ 1 file(s) will be published: SKILL.md Validation passed. This checks format only, not authenticity. To check that Chainguard signed a published skill, run `chainctl skills verify `. ``` `validate` confirms that the directory contains a `SKILL.md`, that its frontmatter is valid, that the `name` field matches the directory name, and that the skill is within the size limit. It also lists the files that `push` publishes. `validate` checks format only, not who published a skill. To check that, verify the hardened skill's signature, as described in [Verify the skill](#verify-the-skill). To also flag optional fields that Chainguard recommends, add the `--strict` flag: ```shell chainctl skills validate hello-world --strict ``` ```output ✓ SKILL.md found ✓ Frontmatter valid ✓ name: "hello-world" (matches directory basename) ✓ description: 96 chars ✓ Total size: 387 B / 10 MB ✓ 1 file(s) will be published: SKILL.md ⚠ license field is recommended Validation passed. This checks format only, not authenticity. To check that Chainguard signed a published skill, run `chainctl skills verify `. ``` Here, `--strict` warns that the skill omits the recommended `license` field. Warnings don't cause validation to fail, but addressing them produces a more complete skill. ### Push the skill to your organization's uploads registry From the parent directory of `hello-world/`, push the skill to your organization's uploads registry with a version tag: ```shell chainctl skills push hello-world --group your-organization --tag v1.0.0 ``` ```output REFERENCE | DIGEST ------------------------------------------------|---------------- uploads.cgr.dev/example.dev/hello-world:v1.0.0 | sha256:3196... ``` You can repeat `--tag` to publish one artifact under several tags, such as `--tag v1.0.0 --tag latest`. If you omit `--tag`, `chainctl` publishes the skill as `latest` and warns that you didn't pin a version. To build and validate the artifact without publishing it, add `--dry-run`. Keep the versioned reference for the hardening submission below. ### List your uploads Confirm the upload with the `list` subcommand and `--source uploads`: ```shell chainctl skills list --group your-organization --source uploads ``` ```output SOURCE | TYPE | NAME | TAGS | UPDATED -----------------|-------|-------------|--------|---------- uploads.cgr.dev | skill | hello-world | v1.0.0 | just now ``` Without `--source uploads`, `list` shows the hardened registry. A successful push does not mean a hardened result is available there. Submit the upload for hardening in the next step. The `TAGS` column shows all tags for each skill. A `latest` tag is not required. If your output has a `LATEST TAG` column or omits the upload, see [Find a skill that is missing from the listing](/chainguard/agent-skills/skill-hardening/#find-a-skill-that-is-missing-from-the-listing). ### Harden the skill Submit the uploaded artifact and wait for the result: ```shell chainctl skills harden uploads.cgr.dev/your-organization/hello-world:v1.0.0 \ --group your-organization --wait --timeout 30m ``` The command prints a job ID, waits for hardening, and downloads the result to `./hardened/hello-world/`. Review the instructions and `HARDENING.md` report, including any findings that remain open. Save the exact hardened reference returned by the command. It includes a user namespace and digest, in the form `skills.cgr.dev//users//hello-world@sha256:`: ```shell export HARDENED_REF='' chainctl skills describe "$HARDENED_REF" ``` For submissions directly from a local directory, checking a job later, and resuming after a timeout, see [Getting started with skill hardening](/chainguard/agent-skills/skill-hardening/). ### List hardened skills Hardened results are nested under `users//`. Add `--recursive` to browse skills inside those folders: ```shell chainctl skills list --group your-organization --source skills --recursive ``` Without `--recursive`, the organization-level listing may show only a `users` row with `TYPE` set to `folder`. Expand the folder with `--recursive`, or browse it with `chainctl skills list --group your-organization/users`. See [Browse results in user folders](/chainguard/agent-skills/skill-hardening/#browse-results-in-user-folders) for the folder layout and how to show uploads alongside hardened results. The listing includes skills with generated version tags and skills without tags. Use the exact `$HARDENED_REF` returned by the job to inspect and install the result you reviewed. ### Verify the skill Chainguard signs every hardened skill it publishes to your organization's registry. Check that signature with the `verify` subcommand: ```shell chainctl skills verify "$HARDENED_REF" ``` A `Status` of `verified` confirms that Chainguard signed the skill for your organization and that it hasn't changed since. Verifying a skill in your organization requires the `groups.list` capability on the organization, such as with the built-in `owner`, `editor`, `viewer`, `limited_owner`, or `console_viewer` role. For what `verify` checks, its other results, and how to verify with Cosign, refer to [Verifying Chainguard Agent Skills signatures](/chainguard/agent-skills/verifying-skills/). ### Install the skill Download and install the skill to make it available to agents on your machine: ```shell chainctl skills install "$HARDENED_REF" ``` This command automatically detects agents on your machine and reports where it placed the skill. The install name includes the registry namespace to distinguish skills with the same name. Copy the **Install Name** from `chainctl skills describe "$HARDENED_REF"` for use in the following steps: ```shell export INSTALLED_SKILL='' ``` By default, `install` writes one shared copy to `.agents/skills/` and symlinks each agent's skills directory to it, so every agent reads the same files. Add `--copy` to give each agent its own copy, `--global` to install under your home directory instead of the current project, or `--agent` to target specific agents instead of every detected one. To use the skill in [Chainguard Workspaces](/chainguard/workspaces/overview/) sessions, run `install` from your repository's top-level directory and commit what it writes: the `.agents/skills/` and `.claude/skills/` entries and the `skills-lock.json` file. Refer to [Configure Claude Code in a session](/chainguard/workspaces/coding-agents/#configure-claude-code-in-a-session). To check the signature as part of the install, add `--verify`. `install` then verifies the exact digest it fetches and installs nothing unless the skill is `verified`: ```shell chainctl skills install --verify "$HARDENED_REF" ``` ### Run the skill from an agent Load the skill from the location reported by `install`. In Claude Code, invoke it with `/`, replacing `` with the value you saved in `$INSTALLED_SKILL`. Ask the agent to greet you, and check that its response follows the instructions you reviewed in the hardened `SKILL.md`. ### Uninstall the skill To remove a skill from your machine, pass its install name to the `uninstall` subcommand: ```shell chainctl skills uninstall "$INSTALLED_SKILL" ``` The command prompts for confirmation before removing any files. By default, `uninstall` removes the project-local copy from every agent directory where it's installed. If a global copy also exists, `uninstall` leaves it in place and prints a warning. Re-run the command with `--global` to remove that copy. Use the `--agent` flag to remove the skill from specific agents only, and the `-y` flag to skip the confirmation prompt. `uninstall` operates only on the local files on your machine. It doesn't modify your organization's registry. To remove a published skill from the registry, use [`chainctl skills delete`](/platform/chainctl/chainctl-docs/chainctl_skills_delete/) instead. ### Delete a skill from the registry To remove a published version of a skill from your organization's hardened registry, first list its tags. Use the repository portion of the hardened reference, preserving its user namespace: ```shell HARDENED_REPO="${HARDENED_REF%@*}" chainctl skills versions "$HARDENED_REPO" ``` Select a tag from that output and pass the full tagged reference to `delete`. The upload's `v1.0.0` tag is not a substitute for a tag from the hardened repository. Digest references are not accepted by `delete`: ```shell export HARDENED_TAG='' chainctl skills delete "$HARDENED_REPO:$HARDENED_TAG" ``` The command prompts for confirmation before removing the version. Press y and ENTER to confirm. Add the `-y` flag to skip the prompt and delete the version non-interactively. The command requires a tag so you don't delete the `latest` tag by accident. Deleting `latest` is still possible, but it prompts for an additional confirmation. When you delete a skill's last remaining version, `chainctl` also removes the empty skill entry, so it doesn't linger in `list` output with nothing to pull. `delete` accepts a reference without a tag, such as `"$HARDENED_REPO"`, only for a skill with no versions left. A skill that still has versions requires an explicit tag. Unlike `uninstall`, `delete` removes the skill from the registry for your whole organization. It doesn't remove copies already installed on anyone's machine. ## Command reference | Action | Command | | ----- | ----- | | Enable the entitlement | `chainctl skills entitlements create --parent your-organization` | | Accept the registry terms | `chainctl skills accept-terms --group your-organization` | | Validate a skill | `chainctl skills validate ` | | Upload a skill | `chainctl skills push --group your-organization --tag ` | | List uploads | `chainctl skills list --group your-organization --source uploads` | | Harden a local skill | `chainctl skills harden ./ --group your-organization --wait` | | Check a hardening job | `chainctl skills status --group your-organization --id "$JOB_ID"` | | List hardened skills in all folders | `chainctl skills list --group your-organization --recursive` | | Describe a hardened skill | `chainctl skills describe "$HARDENED_REF"` | | Verify a hardened skill's signature | `chainctl skills verify "$HARDENED_REF"` | | Install a hardened skill | `chainctl skills install "$HARDENED_REF"` | | Install only if the signature verifies | `chainctl skills install --verify "$HARDENED_REF"` | | Uninstall a skill | `chainctl skills uninstall "$INSTALLED_SKILL"` | | Delete a published version | `chainctl skills delete "$HARDENED_REPO:$HARDENED_TAG"` | --- ### Image Matcher overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-tools/image-matcher.md Last Modified: September 28, 2026 Tags: Chainguard Images, Migration, SBOM, API The [Chainguard Image Matcher](/platform/api/spec-api-v1/#tag/imagematcher) is an API-based tool that analyzes the software bill of materials (SBOM) of an existing container image and returns a ranked list of Chainguard images that most closely match it. It is designed to support migration workflows where you know what you are running today and want to find the best Chainguard equivalent. ## How it works You supply an SBOM for your current image, along with the source Linux distribution. The Image Matcher maps the packages in your SBOM to Chainguard APK packages, scores each candidate Chainguard image based on how well its contents cover your requirements, and returns a ranked list of recommendations with confidence scores. The matcher is deterministic: the same SBOM submitted to the same API will always produce the same results. There is no machine learning model involved. Results may change slightly over time as Chainguard improves its mapping data and publishes new images, but there is no per-request variation. ### What the matcher returns Each recommendation includes: - The recommended Chainguard image reference - A probability score (0–100) indicating how closely the image matches your SBOM - The packages from your SBOM that are satisfied by the recommended image - The packages that would be missing if you migrated to that image - Any extra packages present in the image that your SBOM did not request The number of candidates returned by the API is configurable, and the results are ranked by score. For well-known stacks such as `nginx`, `postgres`, or `redis`, the top result is typically the right choice. For less common images, the results may warrant human review. ## Understanding the probability score The probability score is the matcher's confidence estimate, normalized to a 0–100 scale. It is calculated from three weighted components: - **Satisfied score**: the sum of weights of SBOM packages present in the candidate image. This is the dominant factor. - **Missing score**: the sum of weights of SBOM packages absent from the candidate image. This is weighted lightly. Chainguard images are intentionally minimal, so some missing packages are expected and do not significantly penalize the score. - **Extra score**: the sum of weights of packages in the candidate image that your SBOM did not request. This carries a moderate penalty. The combined raw score is passed through a sigmoid-like transform and clamped to 0–100. A score of 90 or above is a strong match. Scores in the 50–70 range warrant human review. The score is capped at 100, so multiple candidates may report the same top score while differing in their underlying ranking — use the `overallScore` field to differentiate them when needed. Package weights are not uniform. The package that defines an image's primary purpose (for example, `postgres` in the `postgres` image) carries a weight orders of magnitude larger than common shared dependencies like `openssl` or `ca-certificates`. This is why the matcher reliably identifies the right base image even when many low-level library names differ between distributions. ## How packages are mapped across distributions Linux distributions use different names for packages that provide the same upstream software. The matcher translates source package names from your SBOM into Chainguard APK names before scoring. This translation is where most of the recommendation logic is concentrated. The following table shows representative examples of how the same upstream software appears across distributions and in the Chainguard catalog: | Upstream project | Debian / Ubuntu | Alpine | Red Hat / Amazon Linux | Chainguard APK(s) | |--------------------|------------------------------|----------------------|------------------------|----------------------------------| | GNU C Library | `libc6`, `libc-bin` | (musl libc instead) | `glibc` | `glibc` | | OpenSSL library | `libssl3` | `libssl3` | `openssl-libs` | `libssl3`, `libcrypto3` | | Apache HTTP Server | `apache2` | `apache2` | `httpd` | `httpd` | | nginx web server | `nginx` | `nginx` | `nginx` | `nginx-mainline`, `nginx-stable` | | PostgreSQL server | `postgresql-16`, `postgresql-17` | `postgresql16` | `postgresql-server` | `postgresql-16`, `postgresql-17` | | curl HTTP client | `curl` + `libcurl4` | `curl` + `libcurl` | `curl` + `libcurl` | `curl` | Two patterns are common: - **Many source packages map to one Chainguard APK.** Debian splits the GNU C Library into `libc6`, `libc-bin`, and other sub-packages, while Chainguard ships the same software under the single name `glibc`. An SBOM listing `libc6` is translated to `glibc` before any image is scored. - **One source package maps to multiple Chainguard APKs.** Some upstream packages correspond to more than one Chainguard APK because Chainguard publishes multiple variants. A reference to `nginx` fans out to both `nginx-mainline` and `nginx-stable`; a reference to `postgresql` fans out to `postgresql-16`, `postgresql-17`, and so on. ### Mapping sources The matcher builds its package map from three sources, applied in priority order: 1. **Curated equivalences.** Hand-maintained rules for cases where automatic matching would be ambiguous or incorrect, such as the `apache2` → `httpd` rename and version-specific PostgreSQL fan-out. 2. **CPE and PURL matching.** Each Chainguard APK carries CPE and PURL identifiers from its `melange` build definition. When a source package's identifiers overlap with a Chainguard APK's, the matcher treats them as equivalent. This handles the long tail of common dependencies without per-package curation. 3. **Runtime module overlap.** For packages whose value is a bundled language ecosystem (Go modules, Python packages, Node modules), the matcher can detect shared underlying modules and treat packages as equivalent even when their distro-level names differ. Source packages that cannot be mapped appear in the `unmatchedExternalPkgs` field of the response. A long list of unmatched packages is normal for general-purpose distributions like Debian: most entries are build-time tools, base-image scaffolding, or documentation packages that Chainguard's minimal images do not include. ## Limitations - **Distribution must be specified.** The matcher works against a per-distribution package universe. You must supply `dist_name` with each request. Providing `dist_version` and `arch` is optional but improves results. - **Results depend on SBOM quality.** The matcher maps packages primarily via `purl` and `cpe` fields. SBOMs that lack these fields on components will produce weaker results. - **Chainguard images are intentionally minimal.** Expect missing packages in recommendations. This is by design: Chainguard images ship fewer packages than upstream equivalents in order to reduce attack surface. - **The image catalog is refreshed daily.** Newly published Chainguard images typically appear in recommendations within one to two days of release. - **The score is an estimate, not a guarantee.** A high probability score means the matcher is confident, not that the migration will be drop-in compatible. Always validate the recommended image in your environment before replacing production images. --- ### Chainguard container image pull policies URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-repository/container-policies.md Last Modified: October 16, 2026 Tags: Overview Policies enable you to filter and restrict Chainguard artifact updates. You do this by defining policies that control and restrict versions that will be pulled from Chainguard.

Note: Policies is in beta.

## Definitions This page uses the following terms: * **Policy**: A reusable rule that determines whether an image is allowed. Each policy has a name, a description, and the resource type it applies to. The system policies on this page apply to registry repositories. * **Binding**: A link between a policy and an organization. While a binding exists, the policy is active for image pulls under that organization. Without a binding, the policy has no effect. * **Mode**: A binding's mode controls what happens when the policy denies an image: * `ENFORCE`: Block the pull. * `DRY_RUN`: Allow the pull but record the violation. * **Parameter**: A configurable value declared by a policy's schema (for example, `days` on the `cooldown` policy). Supply values when you enable a policy with `--param=KEY=VALUE` (repeatable). Omitted parameters fall back to the schema's declared default. Use `chainctl policies describe --policy=$POLICY` to see which parameters a policy accepts and what defaults apply. * **Policy type**: Whether a policy is authored by Chainguard or by you. Refer to [Policy types](#policy-types). The `--mode` flag is required when you enable a policy. Chainguard recommends starting with `DRY_RUN` so you can review a policy's impact before promoting it to `ENFORCE`. Multiple policies can be active at the same time, and they compose: an image is allowed only when *every* active policy allows it. A single policy in `ENFORCE` mode returning `DENIED` is enough to block the pull. ## Policy types Every policy belongs to one of two types. **System policies** are authored and maintained by Chainguard and are available to every organization. You cannot create, edit, or delete one. You choose whether to activate it and, where the policy declares parameters, what values to give it. The `no-eol`, `cooldown`, and `support-window` policies documented on this page are all system policies. **Custom policies** are authored by you. A custom policy is a [Rego](https://www.openpolicyagent.org/docs/policy-language) expression paired with a single supported resource type and an optional parameter schema, submitted as a YAML manifest. Custom policies are visible only to the organization that owns them. Both types are the same kind of object. They are evaluated by the same engine, activated through the same bindings, produce the same decisions, and can be waived by the same overrides. The difference is who owns the definition, and therefore what you are allowed to do to it. Reach for a system policy when it already expresses the guardrail you want. Write a custom policy when no system policy expresses the rule you require. Because policies compose this way, a custom policy cannot loosen a system policy. To let one specific image through, use an [override](#granting-an-exception-with-an-override) instead. Both types appear together in `chainctl policies list`, distinguished by a type column. To author and manage your own policies, see: * [Writing custom policies](#writing-custom-policies) — the `allow` rule convention, the input fields a policy can read, and the Rego subset available to it. * [Managing custom policies with chainctl](#managing-custom-policies-with-chainctl) — the manifest format and the `validate`, `create`, `update`, and `delete` subcommands. * [Custom policies FAQ](#custom-policies-faq) — debugging denials, write-time rejections, and testing a policy before you enforce it. ## Available policies Chainguard ships a set of system policies that are available to every organization. The list below describes each one and its configurable parameters. To confirm which policies are available to you and see their full definitions, use `chainctl policies list` and `chainctl policies describe` as shown under [Usage](#usage). When a policy denies a pull, it emits a reason describing what fired. Each policy has its own reason format. All reasons are visible in the REASON column of `chainctl policies decision list` and expand to every reason line under `--show-decision-details` (see [Policy decisions](#policy-decisions)). ### no-eol The `no-eol` policy denies any image whose primary package has reached its end-of-life (EOL) date. An image is allowed when it has no recorded EOL date, or when that date is still in the future. This policy takes no parameters. ```shell chainctl policies enable --policy=no-eol --mode=DRY_RUN ``` ### cooldown The `cooldown` policy denies images that are newer than a minimum age, measured from the image's build timestamp. Use it to hold off on adopting a version until it has been published long enough to shake out early regressions. An image is allowed once its age is greater than or equal to the configured number of days. It accepts one parameter, `days`, an integer number of days an image must exist before it is allowed. The default is `7`, and accepted values range from `1` to `365`. ```shell chainctl policies enable --policy=cooldown --mode=DRY_RUN --param=days=14 ``` ### support-window The `support-window` policy requires an image's primary package to have a minimum remaining support period, measured as the span between the package's release date and its EOL date. An image is allowed when that span is greater than or equal to the configured number of months, or when the package has no recorded EOL date. It accepts one parameter, `months`, an integer minimum number of months of support. The default is `6`, and accepted values range from `1` to `24`. ```shell chainctl policies enable --policy=support-window --mode=DRY_RUN --param=months=12 ``` ## Usage Policies are managed using `chainctl`. System policies are shipped with the platform; to author your own, refer to [Managing custom policies with chainctl](#managing-custom-policies-with-chainctl). See which policies are available to your organization: ```shell chainctl policies list ``` Inspect a policy to see its full definition and configurable parameters before enabling it: ```shell chainctl policies describe --policy=$POLICY ``` See which policies are currently active: ```shell chainctl policies binding list ``` Activate a policy in `DRY_RUN` mode. This example activates the "no end-of-life" artifacts policy. Chainguard recommends that you roll out policies using `DRY_RUN` mode first and track for a time to be certain it has the impact you intend before moving to `ENFORCE`. ```shell chainctl policies enable --policy=no-eol --mode=DRY_RUN ``` Some policies accept parameters. Use `--param=KEY=VALUE` to supply them: ```shell chainctl policies enable --policy=cooldown --mode=DRY_RUN --param=days=7 ``` Promote a policy to `ENFORCE`: ```shell chainctl policies enable --policy=no-eol --mode=ENFORCE ``` Disable a policy: ```shell chainctl policies disable --policy=no-eol ``` ## Checking whether an image is allowed Before you depend on a specific image (pinning it in a deployment, promoting it through an environment, or adding it to a base image), you often want to know whether your active policies will let it through. `chainctl policies check` answers that for one image on demand, without waiting for a pull to happen. Pass a full image reference, by tag or by digest: ```shell chainctl policies check cgr.dev/$ORGANIZATION/python:latest ``` The reference identifies the organization and repository; a tag is resolved to its current digest, and a digest is used as-is. The image is then evaluated against every policy active for that organization, and each result is printed: ```output POLICY | MODE | PARAMETERS | RESULT ----------|---------|------------|--------- cooldown | DRY_RUN | days=7 | DENIED no-eol | ENFORCE | | ALLOWED ``` Read the table per policy. The image is blocked at pull time only when a policy in `ENFORCE` mode returns `DENIED`. A `DENIED` from a `DRY_RUN` policy is reported here but would not block a real pull; it is a preview of what enforcing that policy would do. If no policies apply to the image, the command says so rather than printing a table. Because the check evaluates against the policies you have enabled right now, it reflects your current configuration, not a historical record. To review outcomes from pulls that already happened, use [policy decisions](#policy-decisions) instead. `check` is also built for automation. Its exit status is non-zero whenever any policy returns `DENIED` or `ERROR`, regardless of that policy's mode, so you can gate a CI pipeline on it: ```shell chainctl policies check cgr.dev/$ORGANIZATION/python@sha256:abc... || echo "image violates a policy" ``` Note that a `DRY_RUN` denial also produces a non-zero exit, so a passing `check` is stricter than what enforcement alone would block, which is useful as an early warning while you stage policies. ## Policy decisions Every time an image is pulled and evaluated against an active policy, the platform records the outcome as a *decision*. A decision is the result of evaluating a single image digest against a single policy at pull time. It captures the policy, the artifact, the mode the policy ran under (`ENFORCE` or `DRY_RUN`), the outcome (`ALLOWED`, `DENIED`, or `ERROR`), the reason the policy produced for that outcome, and the day the pull happened. Decisions are recorded for all evaluations, regardless of mode or outcome. Together they form an audit log of what your policies did against real pull traffic. Use them to answer questions like "why was this image blocked?", "what has this policy decided over the past month?", and "what would this policy block if I enforced it?". This is distinct from `chainctl policies check`, which evaluates one image against your active policies on demand. Decisions are the historical record of evaluations that already happened during real pulls. List the decisions recorded for your organization: ```shell chainctl policies decision list ``` ```output REPOSITORY | ARTIFACT ID | POLICY | MODE | RESULT | REASON | PULLED ON ------------|----------------|----------|----------|---------|---------------------------------------|------------ nginx | sha256:1a2b3c… | cooldown | DRY_RUN | DENIED | denied: image built within the 7-…, + | 2026-06-28 nginx | sha256:4d5e6f… | no-eol | ENFORCED | DENIED | denied: main package reached end o… | 2026-06-28 bash | sha256:7a8b9c… | no-eol | ENFORCED | ALLOWED | - | 2026-06-27 ``` The ARTIFACT ID column shows a short form of the image digest, use `-o json` to see the full value or `chainctl policies decision list --show-artifact-ids` to list all artifact ids (see [List the unique artifacts in the current result set](#list-the-unique-artifacts-in-the-current-result-set)). The REASON column shows the first line of the policy's own denial reason, with `+ N more` appended when a decision has additional lines. ### Filter decisions Narrow the results with filters. To see only the pulls a policy denied, filter by outcome: ```shell chainctl policies decision list --result=DENIED ``` You can also restrict to a single policy, a single mode (`ENFORCE` or `DRY_RUN`), a time window, a single image, or a single artifact. The `--since` flag takes a number of days with a `d` suffix (for example `7d`); `--repo` accepts an image as `REPO` or `REPO:TAG`; and `--artifact-id` accepts a full image digest (`sha256:…`) or a PURL and returns every decision recorded for that specific artifact: ```shell chainctl policies decision list --policy=no-eol --mode=ENFORCE --since=30d -o json chainctl policies decision list --repo=nginx:latest chainctl policies decision list --artifact-id=sha256:1a2b3c4d5e6f... ``` ### Inspect full denial reasons with --show-decision-details The compact table only shows the first reason line. To see every reason a policy produced for a decision, use `--show-decision-details`. This replaces the table with a per-artifact expanded block: every policy that evaluated the artifact is listed, and every reason it produced is printed under a `Reasons:` sub-section. `ALLOWED` verdicts print `- None.` as a positive summary; `DENIED` verdicts without a reasons rule and `ERROR` verdicts drop the `Reasons:` sub-section entirely. ```shell chainctl policies decision list --show-decision-details ``` ```output Repository: nginx Artifact ID: sha256:1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809 Pulled on: 2026-06-28 Policy: cooldown (DRY_RUN) → DENIED Reasons: - denied: image built within the 7-day cooldown window (built 2026-06-24, pulled 2026-06-28) ``` Combine `--show-decision-details` with `--artifact-id` to focus on a single image and see the full picture across every policy that evaluated it: ```shell chainctl policies decision list --artifact-id=sha256:1a2b3c4d5e6f... --show-decision-details ``` ### List the unique artifacts in the current result set `--show-artifact-ids` prints the unique full artifact IDs in the current result set, one per line, deduplicated across policies (an image evaluated by four policies still shows once). Useful for scripting a follow-up query per artifact: ```shell chainctl policies decision list --result=DENIED --show-artifact-ids ``` ```output sha256:1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809 sha256:4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c ``` Decisions are deduplicated per day, so repeated pulls of the same digest under the same policy and outcome appear once for that day rather than once per pull. Decisions are listed newest first. The command returns at most 20 decisions by default; use `--limit` to change the page size, which accepts a value from `1` to `100`: ```shell chainctl policies decision list --limit=50 ``` JSON output is not affected by `--show-decision-details` or `--show-artifact-ids`, those flags are display-only. `-o json` always returns the full artifact ID and every reason line unchanged. ## Example: staging a policy with dry run `DRY_RUN` decisions let you measure a policy's impact before it can block anything. Because a `DRY_RUN` binding records outcomes without blocking pulls, you can enable a policy, watch what it would have denied against your real traffic, and only promote it to `ENFORCE` once the results match your expectations. First, enable the policy in `DRY_RUN` mode: ```shell chainctl policies enable --policy=no-eol --mode=DRY_RUN ``` Let your normal pull traffic flow for a representative period. Then review what the policy denied while in dry run. Use `--since` to limit the review to a recent window, here the last seven days: ```shell chainctl policies decision list --policy=no-eol --mode=DRY_RUN --result=DENIED --since=7d ``` Each `DENIED` row is a pull that would have been blocked under `ENFORCE`. Inspect the digests to confirm the policy is catching what you intend and nothing critical to your workloads. If the results look wrong, adjust the policy's parameters or disable it; if they look right, promote the binding to enforcement: ```shell chainctl policies enable --policy=no-eol --mode=ENFORCE ``` After promoting, keep reviewing decisions to confirm enforcement behaves as expected. The same command now returns `ENFORCE`-mode rows for the pulls the policy is actively blocking. ## Granting an exception with an override Once a policy is enforcing, it blocks every image that violates it. Sometimes you need to let one specific image through anyway: a known-good build that trips a policy, an artifact approved through a separate review, or an urgent fix that cannot wait for the policy itself to change. An override is an admin-granted waiver that flips a `DENIED` result to `ALLOWED` for one policy and one image, without disabling the policy or changing its binding. Every other image stays subject to the policy. An override is deliberate and attributable. It applies to exactly one policy and one image digest, requires a reason, and records who created it and when. The engine applies it after evaluation, so the recorded decision still reflects the policy's verdict while the override is what ultimately allows the pull. Creating an override requires the `policies.override.create` capability, which is typically held by organization owners. Waive a policy for a specific image, identified by digest, with a reason: ```shell chainctl policies override create \ --policy=no-eol \ --artifact_id=sha256:abc123... \ --reason="approved exception, ticket OPS-42" ``` The `--artifact_id` value must be a manifest digest rather than a tag, so the waiver targets one exact artifact. A given policy and image can carry at most one override; to change an existing one, delete it and create it again. For `chainctl` versions 0.2.337 and earlier, use the `--digest` tag instead of `--artifact_id`. Review the active overrides for an organization to see what has been waived, by whom, and why: ```shell chainctl policies override list ``` ```output OVERRIDE ID | POLICY | TARGET | REASON | CREATED BY | CREATED AT --------------|--------|---------------------|----------------------------|-------------------|------------------------- | no-eol | image sha256:abc12… | approved exception, OPS-42 | alice@example.com | 2026-06-28 14:30:00 UTC ``` When the exception is no longer needed, delete the override to restore the policy's result for that image. Use `chainctl policies override list` to find the ID: ```shell chainctl policies override delete ``` Once deleted, the image is again subject to whatever the policy decides. ## Writing custom policies A [custom policy](#policy-types) is driven by a [Rego](https://www.openpolicyagent.org/docs/policy-language) expression that Chainguard evaluates against one artifact at a time. This section covers what that expression must look like, what data it can read, and which parts of the Rego language are available to it. ### Anatomy of a policy expression Every custom policy expression follows the same shape: ```rego package chainguard.policies import rego.v1 default allow := false allow if { input.main_package_version.lts } ``` Three rules are non-negotiable, and Chainguard rejects the policy at write time if any is missing: * **The package must be `chainguard.policies`.** Any other package name is an error. * **The expression must define a rule named `allow`.** This is the only rule Chainguard reads. Its value decides the outcome: `true` allows the artifact, anything else denies it. * **The expression must compile**, using only the builtins listed under [The available Rego subset](#the-available-rego-subset). Beyond that, the module is ordinary Rego. You can define helper rules, comprehensions, and functions freely, as long as `allow` ends up defined. #### Always declare a default `default allow := false` is a convention worth following in every policy. Without it, an expression whose body is undefined leaves `allow` undefined rather than `false`. An undefined `allow` denies the artifact, so the outcome is the same either way, but the explicit default makes that fail-closed behavior obvious to whoever reads the policy next. #### Multiple allow rules are ORed Rego evaluates every `allow` rule and the artifact is allowed if any one of them succeeds. This is the idiomatic way to express alternatives. The `no-eol` system policy uses it to allow both "no end-of-life date recorded" and "end-of-life date is still in the future": ```rego package chainguard.policies import rego.v1 default allow := false allow if { not input.main_package_version.eolDate } allow if { eol_ns := time.parse_ns("2006-01-02", input.main_package_version.eolDate) eol_ns > time.now_ns() } ``` To express "all of these conditions must hold" instead, put the conditions in a single rule body — the statements inside one `allow if { … }` block are ANDed. ### The input document Your expression reads the artifact under evaluation from the `input` document. At launch this surface is deliberately narrow: it carries lifecycle data about the artifact's main package and nothing else. The fields below are what is available for policies whose resource type is `registry.chainguard.dev/Repo@v1` (the `Repo` shorthand). | Field | Type | Description | |---|---|---| | `input.main_package` | string | Name of the image's main package. | | `input.main_package_version` | object | Version metadata for the main package. Refer to the details below. | | `input.create_time` | string | When the image was created, as an RFC 3339 timestamp. Derived from the image's `org.opencontainers.image.created` annotation. | | `input.parameters` | object | Values for the parameters your policy declares. Refer to [Parameters](#parameters-in-custom-policies). | | `input.main_package_license` | string | Effective-license expression for the image's main package (SPDX identifiers, `AND`-composed over the effective-license closure). **Not yet populated at runtime — see the warning below.** | | `input.packages` | array | Every APK in the image, each carrying a `purl` and `license`. **Not yet populated at runtime — see the warning below.** | > **Warning:** `input.main_package_license` and `input.packages` are not yet populated at runtime, a custom policy referencing them today evaluates against empty values on every pull. `input.main_package_version` carries these fields: | Field | Type | Description | |---|---|---| | `version` | string | The version stream identifier. | | `latestVersion` | string | The latest version within this version stream. | | `eolDate` | string | The date this version reaches end-of-life, as `YYYY-MM-DD`. | | `releaseDate` | string | The date this version was released, as `YYYY-MM-DD`. | | `exists` | boolean | Whether the package exists in an apk repository. | | `fips` | boolean | Whether a FIPS-enabled build of the package exists. | | `lts` | boolean | Whether upstream considers this a long-term support version. | | `eolBroken` | boolean | Whether an end-of-life version can no longer be supported. | | `versionSource` | object | Source reference for the version, with `ref` and `sha` fields. | ### Parameters in custom policies A parameter is a value your policy declares but does not hardcode. Whoever enables the policy supplies the value on the binding with `--param=KEY=VALUE`, so one policy definition can serve several thresholds without being edited. Declare parameters in the `parameters` block of the policy manifest, then read them at `input.parameters.`. The `cooldown` system policy is a good template: ```yaml name: cooldown description: Denies images with a build timestamp less than the specified number of days old. supported_resource_type: registry.chainguard.dev/Repo@v1 parameters: - name: days type: PARAMETER_TYPE_INTEGER description: Minimum age in days before access is allowed. default: 7 minimum: 1 maximum: 365 expression: | package chainguard.policies import rego.v1 default cooldown_days := 7 cooldown_days := input.parameters.days if { is_number(input.parameters.days) } default allow := false allow if { create_ns := time.parse_rfc3339_ns(input.create_time) age_days := (time.now_ns() - create_ns) / (24 * 60 * 60 * 1000000000) age_days >= cooldown_days } ``` Available parameter types are `PARAMETER_TYPE_STRING`, `PARAMETER_TYPE_INTEGER`, `PARAMETER_TYPE_STRING_LIST`, and `PARAMETER_TYPE_BOOLEAN`. The `minimum` and `maximum` constraints apply to integer parameters. #### Undeclared parameters are rejected at write time Chainguard type-checks your expression against the parameters your manifest declares. Referencing `input.parameters.days` when the manifest does not declare a `days` parameter is an error at create, update, and validate time, rather than a policy that silently denies everything once it is live. ### The available Rego subset Custom policies may use most of the OPA standard library. Referencing a blocked builtin is a compile error, reported at validate, create, and update time rather than at evaluation. The blocked builtins are: * `http.send` * Everything under `net.`, including `net.lookup_ip_addr` * Everything under `opa.`, including `opa.runtime` * Everything under `rego.`, for example `rego.metadata.chain` * `trace` and `print` * The certificate and key parsers: `crypto.x509.parse_certificates`, `crypto.x509.parse_and_verify_certificates`, `crypto.x509.parse_certificate_request`, `crypto.x509.parse_keypair`, `crypto.x509.parse_rsa_private_key`, and `crypto.parse_private_keys` The `import rego.v1` statement is not affected by the `rego.` entry above — that entry covers builtin functions, not language imports. Use `import rego.v1` as shown in the examples on this page. Everything else in the OPA standard library is available, including the namespaces most policies need: * String handling: `strings.*`, `sprintf`, `format_int`, `concat`, `contains`, `startswith`, `endswith`, `upper`, `lower`, `split`, `trim` * Numbers and aggregates: `numbers.*`, `count`, `sum`, `min`, `max`, `product`, `sort`, `all`, `any`, `round`, `abs` * Collections: `arrays.*`, `objects.*`, `sets.*` * Time: `time.*`, including `time.now_ns`, `time.parse_ns`, `time.parse_rfc3339_ns`, and `time.add_date` * Pattern matching: `regex.*`, `glob.*` * Encoding: `json.*`, `yaml.*`, `base64.*`, `hex.*` * Versions: `semver.*`, useful for comparing `input.main_package_version.version` against a floor * Hashing: the non-parsing parts of `crypto.*`, and `units.*` ## Managing custom policies with chainctl Custom policies are managed with the `chainctl policies custom` command group, which provides four subcommands: `validate`, `create`, `update`, and `delete`. ### The policy manifest A custom policy is defined by a YAML manifest. Because the manifest is a single file, it can be checked into git, reviewed on a pull request, and validated in CI alongside the rest of your infrastructure. ```yaml name: lts-only description: Allow only images whose main package is a long-term support version. supported_resource_type: registry.chainguard.dev/Repo@v1 expression_type: EXPRESSION_TYPE_REGO parameters: - name: allow_non_lts type: PARAMETER_TYPE_BOOLEAN description: When true, also allow images whose main package is not an LTS version. default: false expression: | package chainguard.policies import rego.v1 default allow := false allow if { input.main_package_version.lts } allow if { input.parameters.allow_non_lts } ``` #### Manifest fields | Field | Required | Description | |---|---|---| | `name` | Yes | The policy name. Must be unique within the organization for the given resource type. | | `expression` | Yes | The Rego expression. Refer to [Writing custom policies](#writing-custom-policies). | | `supported_resource_type` | Yes | The single resource type this policy applies to. | | `description` | No | A human-readable description, shown in `chainctl policies list` and `describe`. | | `expression_type` | No | Defaults to `EXPRESSION_TYPE_REGO`, which is the only accepted value. | | `parameters` | No | The parameters the policy declares. Omit entirely for a parameterless policy. | Each entry under `parameters` accepts `name`, `type`, `description`, `default`, `minimum`, `maximum`, `allowed_values`, `required`, and `deprecated`. #### Resource types Every custom policy declares exactly one supported resource type. Where a command takes a `--resource-type` flag, it accepts either a shorthand or the fully-qualified type: | Shorthand | Fully-qualified type | |---|---| | `Repo` | `registry.chainguard.dev/Repo@v1` | | `Python` | `libraries.chainguard.dev/PythonPackage@v1` | | `Java` | `libraries.chainguard.dev/JavaPackage@v1` | | `Javascript` | `libraries.chainguard.dev/NPMPackage@v1` | The resource type is fixed at creation and cannot be changed afterwards. To move a policy to a different resource type, create a new policy and delete the old one. Within an organization, a policy is identified by the combination of its name and its resource type. The same name may be reused across different resource types — you can have both a `Repo` and a `Python` policy called `minimum-version`. Where that ambiguity exists, `update` and `delete` accept `--resource-type` to disambiguate. ### Validating a policy `chainctl policies custom validate` checks a policy without persisting anything. It is the fastest way to iterate while authoring, and it is what you should run in CI on any repository that holds policy manifests. Validate a full manifest, checking both the expression and the parameter schemas: ```shell chainctl policies custom validate --file policy.yaml ``` ```output Policy is valid. ``` Validate just a Rego expression, for a quick parse-and-compile check while writing: ```shell chainctl policies custom validate --expression policy.rego ``` ```output Expression is valid. ``` The two flags are mutually exclusive, and exactly one is required. On failure, the command prints one diagnostic per problem and exits non-zero. Expression errors carry the line and column in the Rego source; parameter schema errors identify the offending field: ```output line 7:3: rego_type_error: undefined ref: input.mian_package line 12:5: rego_type_error: undefined ref: input.parameters.day ``` ### Creating a policy `chainctl policies custom create` creates a policy in your organization. It supports two modes. **From a manifest.** This is the primary path, and the only one that supports parameters: ```shell chainctl policies custom create --file policy.yaml ``` **From a raw Rego file.** A shortcut for parameterless policies, where writing a manifest for two fields is more ceremony than it is worth. `--name` and `--resource-type` are both required in this mode; `--description` is optional: ```shell chainctl policies custom create \ --expression lts-only.rego \ --name lts-only \ --resource-type Repo \ --description "Allow only LTS versions of the main package" ``` If a policy declares parameters, it must be created from a manifest — there is no flag equivalent for a parameter schema. In `--file` mode the manifest is authoritative. Creation validates the policy before storing it. A rejected policy reports one diagnostic per problem, covering both the name and the expression at once. #### Policy names Names are validated on create and update. A name must: * Be at most 255 characters * Start and end with an alphanumeric character, which rejects leading and trailing whitespace * Contain no control characters * Be unique within the organization for its resource type ### Updating a policy `chainctl policies custom update` modifies an existing policy, identified by `--policy` with either its name or its UIDP. Only custom policies can be updated; system policies are managed by Chainguard and are rejected with an error. It supports two modes. **Full replacement from a manifest.** The manifest supplants the entire definition — the resulting policy is exactly what the manifest declares, and any field the manifest omits is cleared: ```shell chainctl policies custom update --policy lts-only --file policy.yaml ``` **Partial update from flags.** Only the fields you pass are changed; everything else is preserved: ```shell # Rename chainctl policies custom update --policy lts-only --name lts-strict # Change just the description chainctl policies custom update --policy lts-only --description "Allow only LTS main packages" # Replace the expression from a new .rego file chainctl policies custom update --policy lts-only --expression new-lts-only.rego ``` Flag mode is restricted to policies that do **not** declare parameter schemas. For a parameterized policy, use `--file`. #### Constraints on update The supported resource type is immutable. A `--file` update whose manifest declares a resource type different from the current policy's is rejected. Where a policy name is shared across resource types, pass `--resource-type` to disambiguate. It is ignored when `--policy` is given as a UIDP, which is already unambiguous: ```shell chainctl policies custom update --policy minimum-version --resource-type Python --description "python-only variant" ``` #### Parameter changes and existing bindings Because a `--file` update is a full replacement, a policy can gain, lose, or change parameters between versions. Existing bindings are **not** re-validated against the new schema — a binding created against the old parameter shape stays as it was. When an update changes the parameter schema, `chainctl` warns you: ```output Note: parameter_schemas changed. Existing bindings were not re-validated against the new schema; run `chainctl policies binding list` to check for stale bindings. ``` ### Deleting a policy `chainctl policies custom delete` permanently removes a custom policy: ```shell chainctl policies custom delete --policy lts-only ``` Only custom policies can be deleted; system policies are managed by Chainguard and are rejected with an error. Deletion is permanent — a deleted policy cannot be recovered. Deletion also cascades. Every binding that referenced the policy is removed, and every override waiving it is removed as well. Before deleting, `chainctl` reports how many of each will go and asks you to confirm: ```output Will delete custom policy "lts-only" (720a...c81). This also removes 2 binding(s) and 1 override(s) that reference this policy. ``` Pass `--force` to skip the confirmation prompt: ```shell chainctl policies custom delete --policy 720a...c81 --force ``` As with `update`, pass `--resource-type` when a name is shared across resource types: ```shell chainctl policies custom delete --policy minimum-version --resource-type Python ``` ### A full custom policy lifecycle The following walks a policy from authoring to enforcement and back out again. Write the manifest and validate it locally: ```shell chainctl policies custom validate --file lts-only.yaml ``` Create the policy. This defines it but does not activate it — a policy with no binding has no effect: ```shell chainctl policies custom create --file lts-only.yaml ``` Confirm it is there. Custom policies appear alongside system policies, distinguished by the type column: ```shell chainctl policies list ``` Activate it in `DRY_RUN` mode, which records outcomes without blocking any pulls: ```shell chainctl policies enable --policy=lts-only --mode=DRY_RUN ``` Check a specific image against your active policies without waiting for a pull: ```shell chainctl policies check cgr.dev/$ORGANIZATION/python:latest ``` Let your normal pull traffic run for a representative period, then review what the policy would have denied: ```shell chainctl policies decision list --policy=lts-only --result=DENIED --since=7d ``` Each `DENIED` row is a pull that would have been blocked under `ENFORCE`. If the results are as expected, promote the binding: ```shell chainctl policies enable --policy=lts-only --mode=ENFORCE ``` To stop enforcing without deleting the policy, disable the binding. The definition stays in place and can be re-enabled later: ```shell chainctl policies disable --policy=lts-only ``` To remove the policy entirely, along with its bindings and overrides: ```shell chainctl policies custom delete --policy lts-only ``` ## Custom policies FAQ Common questions that come up when authoring and operating custom policies. ### Why was my image denied, and how do I debug it? Start by confirming *which* policy denied it. An image is allowed only when every active policy allows it, so a denial you attribute to your new custom policy may be coming from an entirely different one. ```shell chainctl policies check cgr.dev/$ORGANIZATION/python:latest ``` ```output POLICY | MODE | PARAMETERS | RESULT ----------|---------|------------|--------- cooldown | DRY_RUN | days=7 | DENIED lts-only | ENFORCE | | DENIED no-eol | ENFORCE | | ALLOWED ``` Read the table per row. The pull is blocked only when a policy in `ENFORCE` mode returns `DENIED`. A `DENIED` from a `DRY_RUN` policy is a preview of what enforcing it would do, not a block. Once you know which policy is responsible, work through the usual causes in this order: **1. A field you referenced is absent.** This is by far the most common cause. Fields with no value are omitted from the input document entirely, so a reference to an absent field makes the surrounding rule body undefined, `allow` falls through to its default of `false`, and everything is denied. Refer to [The input document](#the-input-document) for the fields a policy can read. **2. A nested field name is misspelled.** Write-time validation catches typos in the top-level fields (`input.crete_time` fails), but it does **not** check inside `input.main_package_version`. A misspelled `input.main_package_version.eolDte` compiles cleanly and never matches. Watch the casing in particular: top-level fields are snake_case (`main_package`, `create_time`) while the fields under `main_package_version` are camelCase (`eolDate`, `releaseDate`, `latestVersion`). **3. The rule logic is inverted.** Remember that `allow` decides what is *permitted*. A policy meant to block end-of-life images allows everything that is *not* end-of-life, rather than matching the images you want to stop. To see what a policy has actually decided against real pull traffic, use the decision log rather than `check`: ```shell chainctl policies decision list --policy=lts-only --result=DENIED --since=7d ``` Note that `check` evaluates against your current configuration on demand, while decisions are the historical record of evaluations that already happened during real pulls. Finally, if you need to let one specific image through while you sort the policy out, an [override](#granting-an-exception-with-an-override) waives a single policy for a single digest without disabling the policy for everything else. ### What input fields are available to a policy? At launch, the input document carries lifecycle data about the artifact's main package and nothing else: * `input.main_package` — the main package name * `input.main_package_version` — version metadata, including `version`, `latestVersion`, `eolDate`, `releaseDate`, `exists`, `fips`, `lts`, `eolBroken`, and `versionSource` * `input.create_time` — the image creation timestamp, as RFC 3339 * `input.parameters.` — values for the parameters your policy declares * `input.main_package_license` — main-package effective-license expression (not yet populated) * `input.packages` — every APK's `purl` and `license` (not yet populated) The [input document reference](#the-input-document) has the full table with types and descriptions. To see the shape of the input against a real policy, inspect a system policy, which reads the same document: ```shell chainctl policies describe --policy=no-eol -o json ``` ### Why was my Rego rejected at write time? Chainguard validates every expression before storing it, so a policy that would have been broken at pull time is rejected at authoring time instead. Errors come back with the line and column in your Rego source. The causes, in rough order of frequency: **Wrong package name.** The expression must declare `package chainguard.policies`. Any other package is rejected. **No `allow` rule.** The expression must define a rule named `allow`. It is the only rule Chainguard reads. **A reference to an undeclared parameter.** Referencing `input.parameters.days` when your manifest does not declare a `days` parameter is an error. This is strict in both directions: a parameterless policy may not reference `input.parameters` at all. Add the declaration to the manifest in the same change as the reference. **A typo in a top-level input field.** `input.crete_time` and `input.mian_package` both fail validation. Only the top level is checked this way — misspellings inside `input.main_package_version` are not caught. **A blocked builtin.** Some OPA builtins cannot be used in a custom policy. Refer to [The available Rego subset](#the-available-rego-subset) for the full list and what remains available. **An ordinary syntax or type error.** Standard Rego compile errors surface the same way, with line and column. A manifest can also be rejected before the expression is ever compiled, if the YAML itself is wrong. The manifest is parsed strictly, so an unrecognized key is an error naming the offending field rather than being silently ignored. If you passed a raw `.rego` file to `--file`, the error suggests the fix: `--file` expects a full YAML manifest, and `--expression` is the flag for a bare Rego file. Validation errors are aggregated, so the command reports every problem it found across both the name and the expression rather than making you fix them one at a time. ### How do I test a policy before committing to it? There are two levels of checking, from fastest to most realistic. #### Validate the manifest `chainctl policies custom validate` runs the same checks that `create` and `update` apply, without persisting anything: ```shell chainctl policies custom validate --file policy.yaml ``` While iterating on just the Rego, skip the manifest: ```shell chainctl policies custom validate --expression policy.rego ``` This is the authoritative check. A manifest that validates cleanly will not be rejected on create for expression reasons. #### Stage it in DRY_RUN against real traffic The most realistic check is the platform itself. Create the policy and enable it in `DRY_RUN` mode, which records outcomes without blocking any pulls: ```shell chainctl policies custom create --file policy.yaml chainctl policies enable --policy=lts-only --mode=DRY_RUN ``` Check a specific image immediately: ```shell chainctl policies check cgr.dev/$ORGANIZATION/python:latest ``` Then let normal pull traffic run for a representative period and review what the policy would have blocked: ```shell chainctl policies decision list --policy=lts-only --result=DENIED --since=7d ``` Every `DENIED` row is a pull that would have failed under `ENFORCE`. Promote the binding only once those results match your expectations. ### Can a custom policy allow something a system policy denies? No. Every active policy is evaluated independently, and an image is allowed only when all of them allow it. A single `ENFORCE` denial blocks the pull regardless of what other policies decided, so a custom policy cannot loosen a system policy. To let a specific image through a policy that denies it, use an [override](#granting-an-exception-with-an-override), which waives one policy for one digest. To stop a system policy from applying at all, disable its binding. ### Can I change a policy's resource type? No. The supported resource type is fixed when the policy is created. A `--file` update whose manifest declares a different resource type is rejected. To move a rule to a different resource type, create a new policy with the new type and delete the old one. Note that deleting cascades — the old policy's bindings and overrides go with it, so recreate those against the new policy. ### Why do I have two policies with the same name? Policy names are unique per resource type, not per organization, so a `Repo` policy and a `Python` policy can both be called `minimum-version`. When a name is ambiguous, `chainctl policies custom update` and `delete` accept `--resource-type` to disambiguate: ```shell chainctl policies custom delete --policy minimum-version --resource-type Python ``` The flag is ignored when `--policy` is given as a UIDP, which already identifies a single policy. ### I changed a policy's parameters and its bindings stopped working Updating a policy from a manifest is a full replacement, so a policy can gain, lose, or change parameters between versions. Existing bindings are **not** re-validated against the new schema — a binding created against the old parameter shape stays exactly as it was. `chainctl` warns you when an update changes the parameter schema. Review your bindings afterwards: ```shell chainctl policies binding list ``` Re-enable any binding whose parameters no longer match the policy's schema, supplying the current parameters with `--param=KEY=VALUE`. ## Known issues Policies are in open beta, and we are continuously working to improve the experience. The items below are current limitations you may run into when working with overrides, along with the workaround for each. ### An override/binding mutation is not effective immediately After you create an override, it does not take effect right away. The platform caches policy decisions, and it takes a short while for that cache to refresh before the override is applied. If a pull is still blocked immediately after you create an override, wait a moment and try again. The same delay applies to binding mutations, such as updating a policy's parameters. After you change a binding (for example, adjusting the `days` parameter on a `cooldown` policy), wait a moment before the new configuration takes effect. ### A single image can require two overrides Pulling an image with a client such as Docker involves two separate requests: a manifest pull followed by an image pull. Both requests are now evaluated against your policies, and they resolve to different digests, so a single `docker pull` can produce two `DENIED` decisions. An override targets one digest, so waiving only the first digest still leaves the second one blocked. To work around this, create an override for each denied digest. First attempt the pull so both decisions are recorded, then use [policy decisions](#policy-decisions) to find the digests that were blocked: ```shell chainctl policies decision list --result=DENIED ``` ```output REPOSITORY | ARTIFACT ID | POLICY | MODE | RESULT | REASON | PULLED ON ------------|----------------|----------|----------|--------|---------------------------------------|------------ curl | sha256:609aeb… | cooldown | ENFORCED | DENIED | denied: image built within the 7-…, + | 2026-07-02 curl | sha256:db532b… | cooldown | ENFORCED | DENIED | denied: image built within the 7-…, + | 2026-07-02 ``` Create an override for each of the denied digests: ```shell chainctl policies override create \ --policy=cooldown \ --artifact_id=sha256:609aeb... \ --reason="approved exception, ticket OPS-42" chainctl policies override create \ --policy=cooldown \ --artifact_id=sha256:db532b... \ --reason="approved exception, ticket OPS-42" ``` With both digests waived, the pull is allowed. Remember that the override is subject to the cache refresh described above, so allow a short delay before retrying the pull. Refer to `chainctl policies --help` or the [chainctl reference pages](/platform/chainctl/) for more information. --- ### Installing APK packages in distroless variants URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/building-and-modifying/install-apks-in-distroless-variants.md Last Modified: October 8, 2026 Tags: Chainguard Containers This page documents workflows for installing APK packages in [distroless variants](/chainguard/containers/concepts/getting-started-distroless/) of Chainguard container images, such as most builds tagged `:latest`. We copy a filesystem from a distroless container image to a build image, install APKs to it using `chroot`, then copy the modified filesystem back to the distroless image in the final step. ## Overview: Installing packages in distroless containers > **Note**: The chroot workflow on this page is one of several ways to add a package to a Chainguard Container, and it's the most involved. For a comparison of all of them, see [Adding a package to a Chainguard Container](/chainguard/containers/building-and-modifying/adding-packages/). The distroless variants of Chainguard Containers do not contain shells or package managers by design. This reduces attack surface and exploitability for these images. In cases where additional packages are required, we typically recommend the following: - If packages for the language runtime (such as those installed with Python’s pip or Node’s npm) are required and no additional system-level (APK) dependencies are needed, we recommend following one of our language-specific multi-stage build tutorials ([Python](https://edu.chainguard.dev/chainguard/containers/getting-started/languages-and-runtimes/python/), [Node](https://edu.chainguard.dev/chainguard/containers/migration/migration-guides/node/), [PHP](https://edu.chainguard.dev/chainguard/containers/migration/migration-guides/php/)). - Use Chainguard's [Custom Assembly](https://edu.chainguard.dev/chainguard/containers/custom-assembly/) tool to create an image with additional packages added. - For some use cases, consider running our variant tagged `:latest-dev` in production. These images are also low-to-zero CVE and are considered production-ready. - Consider requesting a custom image from Chainguard. However, we understand that there are specific use cases that require installation of system-level APK packages in distroless variants, such as maintaining internal build environments requiring application- or team-based packaging. In these cases, you may wish to implement the approach described in this document. ## Comparing workflow options Why not just directly copy individual APK files to a distroless image? If you’re familiar with the package or packages that need to be installed to the distroless image, this *may* work. However, this approach has a number of disadvantages: - Scanners will typically not detect the copied files as a system-level package. - You will also need to know the locations of all relevant files and configurations in order to copy these resources manually. - This method can be verbose in cases where you need many packages or where packages install many files. The chroot approach typically registers installed packages correctly, making them visible to scanners—one of the key reasons we recommend it. Here are three options that you may be considering and our thoughts about them before we focus solely on chroot: - **Copying** — Copying individual package files to a distroless image has the drawbacks mentioned. - **Bind mount** — In a multi-stage build, mount APK and other required development resources from a development image, then install needed APKs using these mounted resources. This approach can work for a single point in time but can be difficult to maintain as the APK resources needed to copy over change over time. - **Chroot (recommended)** — In a multi-stage build, install any build-time dependencies and build any required software artifacts such as binaries. Use a second base image to install runtime dependencies to a directory specified with chroot for later copying. In a final step, copy built software artifacts and the chroot installation directory to scratch and set the desired entrypoint. This method is recommended. ## Using chroot > **Note**: If you’re working to a deadline, you can skip to this short [appendix with full code example](#appendix-b-example-code-for-chroot-method-c-binary). This workflow allows you to install APKs to a distroless image during a Dockerfile build by generating required software artifacts in a development environment, preparing a directory structure with runtime dependencies installed using chroot, then assembling these components back in the distroless image. The steps are as follows: 1. Pull a distroless base image for use as a filesystem reference image and as the base image for the final step. This image should be as close as possible to your desired runtime environment. 2. Pull an image with package managers and shells (typically tagged `latest-dev`) with the desired environment for building your software artifacts. 3. Install any build time dependencies and build your software artifacts, such as virtual environments or binaries. 4. Copy the entire filesystem from the reference image to a directory on the build image. Install any desired runtime APKs to this directory using chroot. 5. Switch to the original distroless image for the final assembly. 6. Copy the directory with installed runtime dependencies from the build image to root on the distroless image. Copy any built software artifacts from the build image to the distroless image. 7. Set the desired entrypoint. In short, we pull a distroless image, replicate its file structure as a folder on the build image, install APKs to that file structure, build our artifacts on the build image, then put the customized file structure and artifacts into the distroless image. ### Considerations - You should build with the \`--no-cache\` flag enabled to ensure the latest remediated binaries are used and \`–pull\` to pull the most recent images. - Images built using this approach should be rebuilt periodically, even when reference base & build images did not change, to get updates of custom packages. - Scanners should correctly register packages in the final image. - If you build with [Kaniko](https://github.com/chainguard-forks/kaniko) rather than Docker/BuildKit, two lines in the examples on this page need to change. See [Appendix C](#appendix-c-example-code-for-chroot-method-kaniko). ### Example A: Preparing a Python virtual environment with APK install and runtime dependencies In this example, we prepare a virtual environment that will support the Python module for MariaDB, create a customized distroless file structure with needed APKs, and assemble these components in a distroless image. Creating this virtual environment (installing from pip) requires specific APKs be available at both install time and runtime. Note that, in this example, the same APKs are installed at install and runtime. In some cases, the packages required at install and runtime will not be the same. Many or most packages in interpreted language ecosystems like Python will only require system-level APKs to be present at runtime, and in these cases you can leave out adding APKs before installation. In the example case, system-level packages are required to install MariaDB using pip as well as at runtime. Let's get started. 1. Create a `run.py` file that will import the `_mysql` object from the `MySQLdb` module and print the version: ```python from MySQLdb import _mysql print(_mysql.__version__) ``` 1. Create a `requirements.txt` file with `mysqlclient` as the only listed Python dependency: ```plaintext mysqlclient ``` 1. Create a Dockerfile. Here's the full file, followed by line-by-line explanations. ```Dockerfile # syntax=docker/dockerfile:1 FROM cgr.dev/chainguard/python:latest AS base FROM cgr.dev/chainguard/python:latest-dev AS build WORKDIR /app USER root RUN apk add --no-cache mariadb-connector-c-dev mariadb USER 65532 RUN python -m venv venv ENV PATH="/app/venv/bin":$PATH COPY requirements.txt /app/ RUN pip install --no-cache-dir -r /app/requirements.txt USER root COPY --from=base / /base-chroot RUN mkdir -p /base-chroot RUN apk add --no-cache --no-scripts --root /base-chroot mariadb-connector-c-dev mariadb && \ ldconfig -r /base-chroot FROM cgr.dev/chainguard/python:latest # Copy over the apks prep'ed at the end of the build stage (no apk-add in this image) COPY --link --from=build /base-chroot / WORKDIR /app COPY --from=build /app/venv /app/venv COPY run.py run.py ENV PATH="/app/venv/bin:$PATH" ENTRYPOINT ["python", "run.py"] ``` First, we pull the distroless image we wish to customize. This image will be used both as a reference filesystem we will customize with our chosen APKs and later as the base image for our final assembly. `FROM cgr.dev/chainguard/python:latest AS base` Next, we pull our build image. The following steps will run in this environment. `FROM cgr.dev/chainguard/python:latest-dev AS build` Create a working directory: `WORKDIR /app` Now let’s add dependencies needed to install `mysqlclient` using pip. For many libraries, dependencies are needed only for runtime, so installing APKs at this stage is not needed. Note that we need root access to install APKs. `USER root` `RUN apk add --no-cache mariadb-connector-c-dev mariadb` We now create a virtual environment, give this environment precedence on the path, and install Python dependencies, in this case only `mysqlclient`. Installation by pip takes place as a nonadministrative user. `USER 65532` `RUN python -m venv venv` `ENV PATH="/app/venv/bin":$PATH` `COPY requirements.txt /app/` `RUN pip install --no-cache-dir -r /app/requirements.txt` We have now created the software artifact that will be copied into our final distroless image, in this case a Python virtual environment set up to run MariaDB. Now we will create a customized file structure that uses the filesystem from our distroless image as a base and includes required additional APKs needed during runtime. First, copy the full contents of the distroless image we pulled at the beginning (labeled “base”) to a folder on our build image: `USER root` `COPY --from=base / /base-chroot` `RUN mkdir -p /base-chroot` Now install APKs to this copied folder using chroot. We add `--no-scripts` and `ldconfig -r /base-chroot` to skip the pre- and post-install scripts in the APKs which would fail in a distroless environment and then regenerate the cache after installation: `RUN apk add --no-cache --no-scripts --root /base-chroot mariadb-connector-c-dev mariadb && ldconfig -r /base-chroot` We’ve now prepared two components on our build image: the required software artifacts (a Python virtual environment) and a file structure customized with installed APKs needed for runtime. We will now assemble these components in the distroless image. Switch to the distroless image: `FROM cgr.dev/chainguard/python:latest` Copy the customized folder structure we created on the build image, replacing the root of our distroless image: `COPY --link --from=build /base-chroot` Note here that we have chosen to use the [`--link` flag](https://docs.docker.com/reference/dockerfile/#copy---link). This adds an independent layer with the copied files and replaces the filesystem without reference to existing files, resulting in a more complete replacement. However, use of this flag can increase image size, so you may wish to experiment with disabling this flag in your build. Next, we copy our virtual environment and run script: `WORKDIR /app` `COPY --from=build /app/venv /app/venv` `COPY run.py run.py` Set the path so that the virtual environment has precedence: `ENV PATH="/app/venv/bin:$PATH"` Finally, set the entrypoint: `ENTRYPOINT ["python", "run.py"]` 1. Once you have your Dockerfile, `run.py`, and `requirements.txt`, build the image: `docker build . --pull --no-cache -t mariadb-distroless` Here, `--pull` ensures we receive the latest images, even if one is locally stored, and `--no-cache` ensures that we receive the latest versions of packages. 1. Once the build completes, run the container: `docker run mariadb-distroless` The output should be the version of the `MySQLdb` module (from the output of `run.py`). ### Example B: Binary compilation with local build and runtime dependencies In this example, the work is done in our Dockerfile, which is presented in full here and then annotated below. ```Dockerfile FROM cgr.dev/chainguard/glibc-dynamic:latest AS base FROM cgr.dev/chainguard/gcc-glibc:latest-dev AS build COPY < #include void main(){ printf("%s\\n", curl_version()); } EOF RUN apk add pc:libcurl RUN gcc test.c `pkg-config --cflags --libs libcurl` -o dynamic-binary COPY --from=base / /base-chroot RUN apk add --no-scripts --root /base-chroot so:libcurl.so.4 && \ ldconfig -r /base-chroot FROM scratch COPY --from=build /base-chroot / COPY --from=build /work/dynamic-binary /usr/bin/dynamic-binary ENTRYPOINT ["/usr/bin/dynamic-binary"] ``` In this Dockerfile, we start by pulling a reference image as similar as possible to our desired runtime environment. In this case, we pull the distroless `glibc-dynamic` Chainguard Image suitable for running compiled C binaries. `FROM cgr.dev/chainguard/glibc-dynamic:latest AS base` Next, pull a build image with shell, APK, and needed toolchains for binary compilation or other build process: `FROM cgr.dev/chainguard/gcc-glibc:latest-dev AS build` Copy source file(s) for our desired artifact. This example will use a [here document](https://en.wikipedia.org/wiki/Here_document) with a short example depending on libcurl. `COPY <` `#include ` `void main(){` `printf("%s\\n", curl_version());` `}` `EOF` Install any needed build-time dependencies using APK: `RUN apk add pc:libcurl` Build the binary: ``RUN gcc test.c `pkg-config --cflags --libs libcurl` -o dynamic-binary`` We now have the binary we’ll run in the final image. Next, we take steps to add runtime dependency APKs. We’ll first copy the entire filesystem of our reference distroless runtime image (here labeled “base”) pulled above to a directory on our build image. We will then install APKs to this directory. In the final step, this directory will be used as the filesystem in our assembled output image. Copy the filesystem of our reference image (“base”)to a directory on our build image: `COPY --from=base / /base-chroot` Install APKs to the copied folder using chroot. We add `--no-scripts` and `ldconfig -r /base-chroot` to skip the pre- and post-install scripts in the APKs which would fail in a distroless environment and then regenerate the cache after installation: `RUN apk add --no-scripts --root /base-chroot so:libcurl.so.4 && ldconfig -r /base-chroot` We now have our needed components, the compiled software artifact (in this case, a binary depending on libcurl) and a directory structure customized without runtime dependencies. We will now assemble these components in scratch. First, pull scratch: `FROM scratch AS custom-production-image` Copy the customized file structure to root on the scratch image (“custom-production-image”): `COPY --from=build /base-chroot /` Copy the binary: `COPY --from=build /work/dynamic-binary /usr/bin/dynamic-binary` In the last line of the Dockerfile, set the entrypoint: `ENTRYPOINT ["/usr/bin/dynamic-binary"]` We will now build the image from this Dockerfile: `docker build . --pull --no-cache -t dynamic-binary` Finally, run our image: `docker run dynamic-binary` You should see output similar to the following: ``` libcurl/8.12.1 OpenSSL/3.4.1 zlib/1.3.1 brotli/1.1.0 libpsl/0.21.5 nghttp2/1.64.0 OpenLDAP/2.6.9 ``` You have now created a production image based on a minimal distroless Chainguard Image customized with additional runtime dependencies as installed APKs. ## Appendix A: Example code for chroot method (Python virtualenv) `Dockerfile`: ```Dockerfile # syntax=docker/dockerfile:1 FROM cgr.dev/chainguard/python:latest AS base FROM cgr.dev/chainguard/python:latest-dev AS build WORKDIR /app USER root RUN apk add --no-cache mariadb-connector-c-dev mariadb USER 65532 RUN python -m venv venv ENV PATH="/app/venv/bin":$PATH COPY requirements.txt /app/ RUN pip install --no-cache-dir -r /app/requirements.txt USER root COPY --from=base / /base-chroot RUN mkdir -p /base-chroot RUN apk add --no-cache --no-scripts --root /base-chroot mariadb-connector-c-dev mariadb && \ ldconfig -r /base-chroot FROM cgr.dev/chainguard/python:latest # Copy over the apks prep'ed at the end of the build stage (no apk-add in this image) COPY --link --from=build /base-chroot / WORKDIR /app COPY --from=build /app/venv /app/venv COPY run.py run.py ENV PATH="/app/venv/bin:$PATH" ENTRYPOINT ["python", "run.py"] ``` --- `run.py`: ```python from MySQLdb import _mysql print(_mysql.__version__) ``` --- `Requirements.txt`: ```plaintext mysqlclient ``` ## Appendix B: Example code for chroot method (C binary) Annotated `Dockerfile`: ```Dockerfile # SPDX-FileCopyrightText: 2025 Chainguard, Inc. # SPDX-License-Identifier: Apache-2.0 # Pull unmodified base image FROM cgr.dev/chainguard/glibc-dynamic:latest AS base # Pull build container with toolchains and apk FROM cgr.dev/chainguard/gcc-glibc:latest-dev AS build # Get workload source code COPY < #include void main(){ printf("%s\\n", curl_version()); } EOF # Install build-time dependency RUN apk add pc:libcurl # Compile workload code RUN gcc test.c `pkg-config --cflags --libs libcurl` -o dynamic-binary # Copy base image contents to a subfolder COPY --from=base / /base-chroot # Customize base image chroot RUN apk add --no-scripts --root /base-chroot so:libcurl.so.4 && \ ldconfig -r /base-chroot # Create customized production image from scratch FROM scratch AS custom-production-image # Copy customized base image COPY --from=build /base-chroot / # Copy workload binary COPY --from=build /work/dynamic-binary /usr/bin/dynamic-binary ENTRYPOINT ["/usr/bin/dynamic-binary"] ``` Build the image: `docker build . --no-cache -t dynamic-binary` Run the image: `docker run dynamic-binary` If the build was successful, you should see version information from libcurl as `output.t`. ## Appendix C: Example code for chroot method (Kaniko) [Kaniko](https://github.com/chainguard-forks/kaniko) builds container images from a Dockerfile without a Docker daemon and is common in Kubernetes-based CI systems. Unlike Docker/BuildKit, Kaniko runs each Dockerfile instruction directly on the root filesystem of its executor container rather than in a per-stage snapshot. Because of this, two lines from the examples above fail under Kaniko: - `COPY --from=base / /base-chroot` fails with `could not save file: copying file: read /proc/1/attr/current: invalid argument`. When a later stage references an earlier stage, Kaniko saves that stage's files by copying them from the executor's live root filesystem, and `/` includes the executor's own `/proc`. - `COPY --from=build /base-chroot /` fails with `copying dir: chown /sys: read-only file system`. Chainguard's container images include empty `/dev`, `/proc`, and `/sys` directories, which are carried into the chroot along with the rest of the base image. When Kaniko copies the chroot onto `/`, it attempts to change ownership of the executor's real `/sys`. > **Note**: Kaniko's `--ignore-path` flag does not affect either failure. It applies only when Kaniko snapshots the filesystem into a layer or extracts a base image, and Kaniko already ignores mount points such as `/proc`, `/sys`, and `/dev` in those steps. Two small changes to the Dockerfile avoid both failures. The result also builds under Docker/BuildKit, so a single Dockerfile can serve both builders. Annotated `Dockerfile`: ```Dockerfile # Pull unmodified base image FROM cgr.dev/chainguard/python:latest AS base # Pull build container with shell and apk FROM cgr.dev/chainguard/python:latest-dev AS build USER root # Copy base image contents to a subfolder, referencing the image rather than the "base" stage COPY --from=cgr.dev/chainguard/python:latest / /base-chroot # Customize base image chroot, then remove the kernel mount point directories RUN apk add --no-cache --no-scripts --root /base-chroot openssh-client && \ ldconfig -r /base-chroot && \ rm -rf /base-chroot/dev /base-chroot/proc /base-chroot/sys # Create customized production image FROM base # Copy customized base image COPY --from=build /base-chroot / ENTRYPOINT ["ssh"] CMD ["-V"] ``` Only two lines differ from the examples above. First, copy the filesystem of our reference image to a directory on the build image. Rather than the stage label (“base”) used above, reference the image directly: `COPY --from=cgr.dev/chainguard/python:latest / /base-chroot` Kaniko treats an image reference in `--from` as an additional stage and extracts it into its own directory, so the copy never touches the executor's root filesystem. Kaniko pulls the image a second time for this, which adds a few seconds to the build. Next, install APKs to the copied folder using chroot as before, then remove the `/dev`, `/proc`, and `/sys` directories from the chroot. The final image already has these directories from its base layer, so nothing is lost. We do this in the same `RUN` instruction as `apk add` to avoid an extra layer: `RUN apk add --no-cache --no-scripts --root /base-chroot openssh-client && ldconfig -r /base-chroot && rm -rf /base-chroot/dev /base-chroot/proc /base-chroot/sys` The final assembly is the same as in the examples above. The same two changes apply when assembling from `scratch` as in Appendix B. In that case, the final image will not contain `/dev`, `/proc`, or `/sys` directories at all, which is fine because the container runtime mounts them. Build the image. This example runs the Kaniko executor locally with Docker and writes the result to a tarball instead of pushing it to a registry: ```sh docker run --rm \ -v "$PWD":/workspace \ cgr.dev/chainguard/kaniko:latest \ --dockerfile /workspace/Dockerfile \ --context dir:///workspace \ --no-push \ --tar-path /workspace/ssh-distroless.tar \ --destination ssh-distroless ``` In CI, remove `--no-push` and `--tar-path` and point `--destination` at your registry. If your base images come from your organization's private repository on `cgr.dev`, Kaniko needs a pull token in `/kaniko/.docker/config.json`; see [Authenticate to Chainguard's Registry](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token). Load and run the image: `docker load -i ssh-distroless.tar` `docker run --rm ssh-distroless` If the build was successful, you should see version information from OpenSSH, similar to the following: ``` OpenSSH_10.5p1, OpenSSL 3.6.4 25 Aug 2026 ``` The `python:latest` image does not include `ssh`, so this output confirms that the packages installed in the chroot were carried into the final image. --- ### Chainguard OS Packages URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-os/chainguard-os-packages.md Last Modified: September 28, 2026

Note: Chainguard OS Packages is in beta. Contact your Chainguard account team to enable it for your organization.

Chainguard OS Packages expands the packages available to your [private APK repository](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) by giving you access to the full set of 30,000 enterprise-grade, zero-CVE packages built as part of Chainguard OS and Wolfi. It also includes a small set of Chainguard base images, for example, `chainguard-base`. Chainguard OS Packages is designed for larger customers who already build their own images from packages using tools like Bazel, Dockerfiles, and rules\_apko, and want to use a wider set of packages from Chainguard. Because you are creating custom builds, you are responsible for the image builds, the build tooling, validation, and compatibility. You still benefit from the fact that Chainguard builds the packages in the Chainguard Factory with complete SBOMs and our standard enterprise-grade, zero-CVE process. If you need to achieve FIPS compliance, the FIPS variant of Chainguard OS Packages includes access packages with the latest versions of Chainguard’s [FIPS-validated modules](https://www.chainguard.dev/legal/fips-commitment). Chainguard OS Packages is not compatible with [Chainguard Custom Assembly](/chainguard/containers/custom-assembly/overview/). --- ### Chainguard VMs compliance features URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/vms/compliance-features.md Last Modified: October 8, 2026 Tags: Chainguard VMs, Compliance Chainguard VMs provides pre-hardened, audit-ready Linux virtual-machine images designed for regulated and high-assurance environments (federal, defense, healthcare, financial services, and suppliers to those sectors). These images combine the following features: | Feature | Description | |:------------------------------------| :---- | | **FIPS 140-3 validated cryptography** | [NIST](https://www.nist.gov/) CMVP-validated software modules and [SP 800-90B](https://csrc.nist.gov/pubs/sp/800/90/b/final) compliant entropy, with runtime guardrails blocking non-FIPS crypto. | | **STIG hardening** | Pre-configured to DISA [STIG](https://edu.chainguard.dev/chainguard/containers/security-and-compliance/stigs/) controls, delivered as production-ready images. | | **CIS benchmark compliance** | [CIS](https://www.cisecurity.org/cis-benchmarks/cis-benchmarks-faq) Level 1 hardened variants, hybrid STIG \+ CIS baseline. | | **Secure Boot** | Secure Boot enabled by default across AWS, Azure, GCP, and on-prem. | | **Compliance evidence & reporting** | FIPS certificates, OpenSSL docs, Security Content Automation Protocol (SCAP) scan results, and POA\&M-ready artifacts. | | **CVE remediation SLA** | 7 days for critical CVEs, 14 days for high, medium, and low. | Chainguard FIPS 140-3 validated and hardened VM images serve as ready-to-use replacements for standard operating systems across AWS, Azure, and GCP, allowing organizations to maintain existing infrastructure and workflows while achieving immediate compliance. This guide outlines the compliance features of Chainguard VMs and how they can help reduce engineering toil for your organization. ## FIPS 140-3 validated cryptography Chainguard's virtual-machine images include FIPS 140-3 validated software cryptographic modules, backed by a NIST Cryptographic Module Validation Program (CMVP) certificate. * **Validated modules** * Cryptographic modules are validated under FIPS 140-3 and integrated directly into the base image. * The images contain SP 800-90B compliant entropy sources for strong, standards-aligned randomness. * **Runtime guardrails** * Guardrails prevent the use of non-FIPS-approved cryptography at runtime (for example, enforcing FIPS-approved ciphers and modes only). * **Documentation for auditors** * FIPS integration documentation, including OpenSSL certificates and details on module configuration, is shipped as part of the deliverables to streamline audit review and ATO packages. This setup allows teams to consume an OS image that is already FIPS-conformant at the platform layer rather than building and validating crypto modules in-house. ## STIG hardening Chainguard VMs provides variants hardened to DISA Security Technical Implementation Guide (STIG) requirements which are used across U.S. federal and defense environments. * 2000+ relevant system controls are pre-configured and validated in the image, not delivered as a checklist that the customer must implement. * Images are designed as ready-to-use replacements for existing Linux OS images, enabling STIG-compliant baselines without re-architecting existing infrastructure. Chainguard can also provide SCAP scan outputs aligned with STIG requirements, helping teams demonstrate compliance with control requirements during audits. ## CIS Benchmark compliance For organizations standardizing on CIS controls, Chainguard offers images hardened to **CIS Level 1** benchmarks. These images use a hybrid baseline combining CIS Level 1 benchmarks with STIG requirements and industry-recognized secure defaults to provide defense-in-depth hardening. This allows security and Governance, Risk, and Compliance teams to map infrastructure posture to both internal CIS-based policies and external STIG-based requirements without maintaining parallel baselines. ## Secure Boot All Chainguard VM images support **Secure Boot enabled by default** across: * AWS * Microsoft Azure * Google Cloud Platform * On-premises platforms supporting Secure Boot Secure Boot ensures only cryptographically signed and trusted components participate in the boot chain, preventing tampering with early-boot components such as the bootloader and kernel. ## Compliance evidence and reporting Chainguard VMs is designed to simplify the generation of compliance artifacts often required in audits, ATO processes, and customer security reviews. Available artifacts include: * FIPS module documentation and integration details, including OpenSSL certificate documentation. * SCAP/OpenSCAP scan results for STIG and CIS benchmarks. * Plans of Action and Milestones (POA\&M) to support risk tracking and remediation planning. By shipping this evidence with the images, Chainguard significantly shortens the time required to build audit packages and meet regulatory reporting needs. ## Vulnerability management and lifecycle Chainguard's virtual-machine images are built and maintained with an explicit [**CVE remediation SLA**](https://www.chainguard.dev/legal/cve-policy): * **Critical CVEs**: patched within **7 days** * **High, medium, and low CVEs**: patched within **14 days** Chainguard VM images are updated regularly and made available to customers within these SLA windows, leaving them with a minimal, hardened footprint which reduces the volume of installed software and minimizes the attack surface. This leaves customers with a much smaller and more manageable CVE count. This lifecycle management shifts ongoing compliance from a perpetual engineering project to a managed image-consumption model. --- ### Getting started with the MinIO Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/web-and-data-services/minio.md Last Modified: September 28, 2026 Tags: Chainguard Containers MinIO is a high-performance, S3-compatible object storage system that has become widely adopted across the cloud-native ecosystem, with over 1 billion pulls on Docker Hub. It's used for testing, local development, and production deployments across on-premises and cloud environments. MinIO's solid S3 compatibility has made it a common choice for developers who need S3-compatible storage without AWS dependencies, and it's integrated into popular open source projects like Trino and Apache Spark for backup and archival, AI/ML workloads, data lakes, and application data storage. On October 23, 2025, the MinIO project stopped publishing the free, community edition of their Docker container image to Docker Hub and Quay repositories, requiring developers to build and maintain their own containers from source code. This change has impacted countless build pipelines and CI/CD workflows. Additionally, a new vulnerability, [CVE-2025-62506](https://www.cve.org/CVERecord?id=CVE-2025-62506), was reported in MinIO containers, which the maintainers declined to patch in the community edition. To help the community maintain secure and up-to-date MinIO deployments without additional complexity, Chainguard has made our [MinIO container image](https://images.chainguard.dev/directory/image/minio/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-minio) publicly available and free to all users. Built with a distroless design and minimal attack surface, Chainguard's production-ready MinIO image is continuously rebuilt from source in our SLSA L3 hardened infrastructure, providing zero-CVE images that are updated daily with new releases and security fixes. In this guide, we'll demonstrate how to deploy and use MinIO with Chainguard Containers through two practical examples. These examples show you how to replace existing MinIO deployments with Chainguard's more secure alternative while maintaining compatibility with your existing S3 workflows. The first example covers basic deployment for testing and development environments. The second example demonstrates how to build applications that interact with MinIO programmatically, which is relevant for CI/CD pipelines, automated backup systems, data processing workflows, and any application that requires object storage capabilities.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Preparation This tutorial requires Docker to be installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/). ### Cloning the demos repository Start by cloning the demos repository to your local machine: ```shell git clone https://github.com/chainguard-dev/edu-images-demos.git ``` Navigate to the `minio` folder in the repository: ```shell cd edu-images-demos/minio ``` This directory contains Docker Compose configurations and a sample Python application that we'll use in this guide. ## Example 1: Running MinIO with Docker Compose This example demonstrates how to deploy MinIO for local development and testing environments. This setup is useful when you need S3-compatible storage for developing applications locally, running integration tests, or testing data pipelines before deploying to production. By using Docker Compose, you can spin up a complete object storage environment that mimics production S3 behavior without requiring cloud resources or complex configuration. The MinIO Chainguard Container can be used directly without requiring a custom Dockerfile. The image comes pre-configured and ready to run with minimal setup. For reference, here is the relevant section from the `docker-compose.yml` file: ```yaml services: minio: image: cgr.dev/chainguard/minio:latest container_name: minio-server ports: - "9000:9000" - "9001:9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin123 command: server /data --console-address ":9001" volumes: - minio-data:/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 10s retries: 3 start_period: 10s networks: - minio-net volumes: minio-data: driver: local networks: minio-net: driver: bridge ``` This configuration will: 1. Pull the `cgr.dev/chainguard/minio:latest` image directly from the Chainguard registry; 2. Expose port 9000 for the MinIO API and port 9001 for the web console; 3. Set up admin credentials using environment variables; 4. Configure MinIO to serve data from the `/data` directory with the console on port 9001; 5. Create a persistent volume to store MinIO data; 6. Include a health check to monitor the server status. To start the MinIO server, run: ```shell docker compose up -d minio ``` Once the service is running, you can access: - **MinIO API**: http://localhost:9000 - **MinIO Console**: http://localhost:9001 Open your web browser and navigate to http://localhost:9001. Log in using the credentials defined in the compose file: - **Username**: `minioadmin` - **Password**: `minioadmin123` The MinIO console provides a graphical interface where you can create buckets, upload files, and manage your object storage. You can also interact with MinIO programmatically using the S3 API, which we'll demonstrate in the next example. To stop the MinIO server, run: ```shell docker compose down ``` Note that the data will persist in the `minio-data` volume even after stopping the container. To remove the volume and all stored data, add the `-v` flag: ```shell docker compose down -v ``` ## Example 2: Building a Python client application with multi-stage build This example demonstrates how to build containerized applications that interact with MinIO programmatically using the S3 API. This pattern is common in production systems for automated workflows such as CI/CD pipelines that upload build artifacts, data processing jobs that read and write large datasets, backup systems that archive files to object storage, or microservices that store and retrieve user-generated content. The example uses a multi-stage Docker build with Chainguard Python images, which reduces the final container size and attack surface by separating build dependencies from runtime requirements. This approach is recommended for production deployments where security and efficiency are priorities. The demo includes a Python script that performs the following operations: - Connecting to the MinIO server with retry logic - Creating buckets - Uploading objects (files and data) - Listing objects in a bucket - Downloading objects - Deleting objects ### Step 1: Understanding the application structure The application consists of three main files: 1. **requirements.txt** - Python dependencies: ``` minio==7.2.8 urllib3==2.2.3 ``` 2. **main.py** - The Python application demonstrating MinIO operations 3. **Dockerfile** - Multi-stage build configuration Let's examine the key parts of the Python application. The script uses the MinIO Python SDK to interact with the object storage server: ```python from minio import Minio from minio.error import S3Error def get_minio_client(): """Create and return a MinIO client instance""" endpoint = os.getenv("MINIO_ENDPOINT", "localhost:9000") access_key = os.getenv("MINIO_ACCESS_KEY", "minioadmin") secret_key = os.getenv("MINIO_SECRET_KEY", "minioadmin123") secure = os.getenv("MINIO_SECURE", "false").lower() == "true" return Minio( endpoint, access_key=access_key, secret_key=secret_key, secure=secure ) ``` The application includes functions for common operations like creating buckets, uploading objects, and listing contents. It demonstrates best practices such as connection retry logic and proper error handling. ### Step 2: Understanding the multi-stage Dockerfile The `Dockerfile` uses a multi-stage build to keep the final image minimal and secure: ```Dockerfile # Multi-stage build using Chainguard images # Stage 1: Build stage with Python dev image to install dependencies FROM cgr.dev/chainguard/python:latest-dev as builder WORKDIR /app # Copy requirements and install dependencies COPY requirements.txt . RUN pip install --user --no-cache-dir -r requirements.txt # Stage 2: Runtime stage with minimal Python image FROM cgr.dev/chainguard/python:latest WORKDIR /app # Copy installed packages from builder COPY --from=builder /home/nonroot/.local/lib/python3.*/site-packages /home/nonroot/.local/lib/python3.13/site-packages # Copy application code COPY main.py . # Run as non-root user (already set in base image) USER nonroot # Set Python path to include user-installed packages ENV PYTHONPATH=/home/nonroot/.local/lib/python3.13/site-packages CMD ["main.py"] ``` This Dockerfile will: 1. Start a build stage based on the `python:latest-dev` container image with build tools and pip; 2. Install the MinIO Python SDK and its dependencies; 3. Start a new runtime stage based on the minimal `python:latest` distroless image; 4. Copy only the installed packages and application code from the builder stage; 5. Run as a non-root user for enhanced security; 6. Set the application as the container's entry point. ### Step 3: Running the complete example The `docker-compose.yml` file includes both the MinIO server and the Python application: ```yaml services: minio: image: cgr.dev/chainguard/minio:latest container_name: minio-server ports: - "9000:9000" - "9001:9001" environment: MINIO_ROOT_USER: minioadmin MINIO_ROOT_PASSWORD: minioadmin123 command: server /data --console-address ":9001" volumes: - minio-data:/data networks: - minio-net app: build: context: ./app dockerfile: Dockerfile container_name: minio-app depends_on: - minio environment: MINIO_ENDPOINT: minio:9000 MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin123 MINIO_SECURE: "false" networks: - minio-net ``` First, ensure the MinIO server is running: ```shell docker compose up -d minio ``` Wait a few seconds for MinIO to fully start, then build and run the Python application: ```shell docker compose up app ``` You should see output similar to this: ``` ============================================================ MinIO Sample Application with Chainguard Container ============================================================ Waiting for MinIO server to be ready... MinIO server is ready! 1. Creating bucket 'demo-bucket'... ✓ Created bucket: demo-bucket 2. Listing all buckets... - demo-bucket (created: 2025-10-27 14:23:45.123456+00:00) 3. Uploading sample objects... ✓ Uploaded object: hello.txt to bucket: demo-bucket ✓ Uploaded object: data.json to bucket: demo-bucket ✓ Uploaded object: info.txt to bucket: demo-bucket 4. Listing objects in bucket 'demo-bucket'... - hello.txt (size: 18 bytes) - data.json (size: 62 bytes) - info.txt (size: 53 bytes) 5. Downloading and displaying 'hello.txt'... ✓ Downloaded object: hello.txt from bucket: demo-bucket Content: Hello from MinIO! 6. Deleting 'info.txt'... ✓ Deleted object: info.txt from bucket: demo-bucket 7. Listing objects in bucket 'demo-bucket' after deletion... - hello.txt (size: 18 bytes) - data.json (size: 62 bytes) ============================================================ Demo completed successfully! ============================================================ ``` The application demonstrates a complete workflow for working with MinIO object storage. You can also view the created bucket and uploaded objects through the MinIO console at http://localhost:9001. To stop all services, run: ```shell docker compose down ``` ### Step 4: Extending the application for your use case This demo application provides a foundation for building production object storage workflows. You can extend it to meet specific requirements such as: - Uploading files from disk for backup or archival systems - Implementing bucket policies and access control for multi-tenant environments - Adding versioning and lifecycle management for compliance requirements - Integrating with data processing pipelines or application services The MinIO Python SDK supports the full S3 API, enabling you to implement any S3-compatible functionality required for your use case. This makes it a suitable replacement for AWS S3 in development environments, or for production deployments where you need control over your storage infrastructure. ## Using MinIO images from Docker Hub The demo repository includes alternative configurations using the older Docker Hub images (`minio/minio:latest` and `python:3.13-slim`) for comparison purposes. However, these images are no longer recommended for production use. To use the Docker Hub images: ```shell docker compose -f docker-compose.dockerhub.yml up ``` **Important Note**: As of October 23, 2025, MinIO stopped publishing container images to Docker Hub and Quay. The images that remain are no longer maintained and contain known vulnerabilities, including CVE-2025-62506, which the maintainers have declined to patch. We recommend using Chainguard's MinIO image instead, which provides: - **Minimal attack surface** - Distroless design with only essential components - **Zero CVEs** - Regular security updates to maintain a clean vulnerability profile - **Non-root by default** - Enhanced security posture - **Smaller image size** - Faster deployments and reduced storage requirements - **SBOM included** - Complete software bill of materials for compliance - **Free and publicly available** - No authentication required to pull the image ## Video walkthrough: Multi-stage build and image scanning This video demonstrates the workflow covered in Example 2, showing how to build the Python client application using a multi-stage Docker build with Chainguard images. The walkthrough also includes scanning both the Chainguard MinIO image and the Docker Hub alternative with Grype to compare their security profiles, illustrating the vulnerability differences between maintained and unmaintained container images. [Watch the video on YouTube](https://www.youtube.com/watch?v=iaEvQdZ9gh4) --- ### Build Go containers with Ko URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/building-and-modifying/build-tools/building-go-containers-with-ko.md Last Modified: October 8, 2026 Tags: Chainguard Containers [Ko](https://ko.build/) is a tool for building Go applications into container images without using Dockerfiles. When combined with Chainguard's minimal containers, Ko creates smaller and more secure container runtimes with only your application and its essential dependencies. This tutorial will guide you through installing Ko and using it to containerize Go applications with Chainguard Containers. By the end of this tutorial, you'll understand how to build, configure, and deploy secure, custom containers with your Go-based application using Ko's streamlined workflow. ## Prerequisites To follow along with this tutorial, you will need: - A working Go development environment to set up the demo - Docker or another OCI-compatible container runtime installed and running, so that you can test the resulting image - Ko installed following the [official installation instructions](https://ko.build/install/) Once you're finished installing Ko, check that it is functional by running: ```shell ko version ``` You should get output similar to the following: ```shell v0.18.0 ``` ## Step 1: Creating a sample Go application To demonstrate Ko's capabilities, we'll create a simple Go web server that runs a minimal web application. Later on, you’ll be able to containerize it. Create a new directory for your project: ```shell mkdir ko-demo && cd ko-demo ``` Initialize a new Go module: ```shell go mod init ko-demo ``` Use the following command to create a `main.go` file containing the logic for a basic web server application: ```shell cat > main.go < has an invalid image , allowed images are ["cgr.dev/*"] ``` This example tries to create a pod using a container image downloaded from the Docker Hub registry, not Chainguard's registry. As this output indicates, attempting to create a non-compliant pod resulted in an error, and the request was denied. ## Ensure images are referenced by digest Chainguard updates its container images frequently to incorporate CVE fixes and package updates. The tags for Chainguard's container images are highly mutable, meaning that the underlying image changes frequently, even for very specific tags like `v1.2.3-r1`. To prevent the risk of updates introducing breaking changes, you can pull by digest to ensure the use of a specific image. The [`K8sImageDigests` constraint](https://github.com/open-policy-agent/gatekeeper-library/tree/master/library/general/imagedigests) from the [Gatekeeper Library](https://github.com/open-policy-agent/gatekeeper-library) can be used to mandate this practice inside a Kubernetes cluster. Add the template to your cluster: ```shell kubectl create -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper-library/refs/heads/master/library/general/imagedigests/template.yaml ``` Then create the constraint: ```shell kubectl create -f - < uses an image without a digest ``` This example attempts to create a pod using the `nginx` Chainguard container image, but does not pull the image by its digest as required by the constraint. As the output indicates, the attempt resulted in an error and the request was denied. ## Warn first, deny later When introducing new constraints into a cluster, it is a good idea to initially configure them with [`enforcementAction: warn`](https://open-policy-agent.github.io/gatekeeper/website/docs/violations/#warn-enforcement-action) so as to avoid blocking existing workloads. This way, when a user creates a non-compliant resource, they will get a warning like the following example. This gives the user a signal that they should update their configuration. ```output Warning: [container-image-must-have-digest] container uses an image without a digest ``` You can also find non-compliant resources that exist in the cluster by reviewing the constraint's violations: ```shell kubectl get k8simagedigests container-image-must-have-digest -o json | jq -r '.status.violations[]' ``` ```output { "enforcementAction": "warn", "group": "", "kind": "Pod", "message": "container uses an image without a digest ", "name": "nginx-disallowed", "namespace": "default", "version": "v1" } ``` Once all the violations have been addressed, you can remove `enforcementAction: warn` and Gatekeeper will start to block the creation of resources that violate the constraint. ## Ensure images are signed by Chainguard This will require having [Gatekeeper](https://open-policy-agent.github.io/gatekeeper/website/) external data enabled. ### Install Ratify ```shell helm repo add ratify https://notaryproject.github.io/ratify # download the notary verification certificate curl -sSLO https://raw.githubusercontent.com/deislabs/ratify/main/test/testdata/notation.crt helm install ratify \ ratify/ratify \ --namespace gatekeeper-system \ --set-file notationCerts={./notation.crt} \ --set featureFlags.RATIFY_CERT_ROTATION=true \ --set policy.useRego=true ``` ### Create a verifier The [verifier](https://ratify.dev/docs/reference/custom%20resources/verifiers) will set up the cosign verification. This example uses the public Chainguard images. > **Note**: If you want to use a private registry, you can follow the identity patterns laid out [for private and dedicated registries](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/#privatededicated-registry) ```yaml apiVersion: config.ratify.deislabs.io/v1beta1 kind: Verifier metadata: name: verifier-cosign-chainguard spec: name: cosign artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json parameters: trustPolicies: - name: chainguard-public scopes: - "cgr.dev/chainguard/*" tLogVerify: true keyless: ctLogVerify: true certificateOIDCIssuer: "https://token.actions.githubusercontent.com" certificateIdentity: "https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main" ``` To use a private registry, you need a couple more steps. First set a variable for your org ```shell PARENT=your-organization ``` Next, create two more variables to hold the UIDPs of your organization’s `catalog_syncer` and `apko_builder` identities, respectively: ```shell CATALOG_SYNCER=$(chainctl iam account-associations describe $PARENT -o json | jq -r '.[].chainguard.service_bindings.CATALOG_SYNCER') APKO_BUILDER=$(chainctl iam account-associations describe $PARENT -o json | jq -r '.[].chainguard.service_bindings.APKO_BUILDER') ``` Then your verifier would use those variables for your `certificateOIDCIssuer` and `certificateIdentityRegexp`. Substitute `${PARENT}`, `${CATALOG_SYNCER}` and `${APKO_BUILDER}` with the literal values. ```yaml apiVersion: config.ratify.deislabs.io/v1beta1 kind: Verifier metadata: name: verifier-cosign-chainguard spec: name: cosign artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json parameters: trustPolicies: - name: chainguard-private scopes: - "cgr.dev/${PARENT}/*" tLogVerify: true keyless: ctLogVerify: true certificateOIDCIssuer: "https://issuer.enforce.dev" certificateIdentityRegExp: "https://issuer.enforce.dev/(${CATALOG_SYNCER}|${APKO_BUILDER})" ``` ### Create the policy Create the Ratify [policy](https://ratify.dev/docs/reference/custom%20resources/policies/#policy). This defines a policy evaluating the verification results for a subject. ```yaml apiVersion: config.ratify.deislabs.io/v1beta1 kind: Policy metadata: name: ratify-policy spec: type: config-policy parameters: artifactVerificationPolicies: "application/vnd.dev.cosign.artifact.sig.v1+json": "any" default: "any" ``` ### Create the constraint template By default, a Gatekeeper [constraint template](https://open-policy-agent.github.io/gatekeeper/website/docs/constrainttemplates) uses Rego v0 syntax. This enables v1 syntax. If you want to use v0 syntax the policy will need to be updated. This example also adds an exempt images array to allow specific non-signed images. ```yaml apiVersion: templates.gatekeeper.sh/v1 kind: ConstraintTemplate metadata: name: k8srequiredimagesignatures spec: crd: spec: names: kind: K8sRequiredImageSignatures validation: openAPIV3Schema: type: object properties: exemptImages: type: array items: type: string targets: - target: admission.k8s.gatekeeper.sh code: - engine: Rego source: version: "v1" rego: | package k8srequiredimagesignatures default exemptions := [] exemptions := input.parameters.exemptImages all_images contains val.image if { containers := object.get(input.review.object.spec.template.spec, "containers", []) initContainers := object.get(input.review.object.spec.template.spec, "initContainers", []) ephContainers := object.get(input.review.object.spec.template.spec, "ephemeralContainers", []) vals := array.concat(containers, array.concat(initContainers, ephContainers)) some val in vals } ratify_response(image) = resp if { resp := external_data({ "provider": "ratify-provider", "keys": [image], }) } responses contains {"image": resp[0], "data": resp[1]} if { some image in all_images not image in exemptions rat_resp := ratify_response(image) resp := rat_resp.responses[_] } violation contains {"msg": msg} if { some resp in responses resp.data.system_error != "" msg := sprintf("image %q verification system error: %v", [resp.image, resp.data.system_error]) } violation contains {"msg": msg} if { some resp in responses count(resp.data.responses) == 0 msg := sprintf("image %q returned no verification response", [resp.image]) } violation contains {"msg": msg} if { some resp in responses not resp.data.isSuccess reason := object.get(resp.data, "message", "verification failed") msg := sprintf("image %q is not signed by Chainguard: %v", [resp.image, reason]) } violation contains {"msg": msg} if { some resp in responses err := resp.data.errors[_] err[0] == resp.image msg := sprintf("image %q verification error: %v", [resp.image, err[1]]) } ``` ### Create the constraint The constraint ties the constraint template to the Kubernetes kinds you define. ```yaml apiVersion: constraints.gatekeeper.sh/v1beta1 kind: K8sRequiredImageSignatures metadata: name: require-signed-images spec: match: kinds: - apiGroups: [""] kinds: ["Pod"] - apiGroups: ["apps"] kinds: ["Deployment", "StatefulSet", "DaemonSet", "ReplicaSet"] parameters: exemptImages: - "registry.k8s.io/pause:3.9" ``` ### Try deploying a Chainguard image ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-chainguard spec: replicas: 1 selector: matchLabels: app: nginx-chainguard template: metadata: labels: app: nginx-chainguard spec: containers: - name: nginx image: cgr.dev/chainguard/nginx:latest ports: - containerPort: 8080 ``` This should successfully create a deployment. ### Try deploying a non-Chainguard image ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-unsigned spec: replicas: 1 selector: matchLabels: app: nginx-unsigned template: metadata: labels: app: nginx-unsigned spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 ``` You will get an error like this explaining that the deployment was blocked because the image was not signed by Chainguard. ```output Error from server (Forbidden): error when creating "bad-deployment.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [require-signed-images] image "docker.io/library/nginx@sha256:7150b3a39203cb 5bee612ff4a9d18774f8c7caf6399d6e8985e97e28eb751c18" is not signed by Chainguard: verification failed ``` ## Learn more By combining OPA Gatekeeper with Chainguard container images, you gain a powerful way to enforce security and compliance across your Kubernetes clusters. Gatekeeper ensures that only container images meeting your defined policies are deployed, while Chainguard Containers provides a minimal, hardened foundation to reduce risk from the start. Together, they help teams ship software more securely and confidently, without slowing down development. If you'd like to learn more about Gatekeeper, we encourage you to refer to the [official documentation](https://open-policy-agent.github.io/gatekeeper/website/docs/). --- ### Chainguard container catalog pricing URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/reference/pricing.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard offers Catalog Pricing for our library of secure container images, providing access to the full catalog of Chainguard Containers. Catalog Pricing enables you to add individual images from the wider Chainguard catalog to your organization's repository using the Self-Serve Catalog Experience. This article highlights the benefits of the Catalog Pricing plan and outlines how you can provision container images through the Self-Serve Experience. ## Catalog pricing The Catalog Pricing model provides a single subscription that grants unlimited access to the full catalog of container images maintained by Chainguard. This model removes the need for per-repository licensing and offers predictable monthly or annual costs. Subscriptions can include FIPS-compliant images, depending on the selected tier. Chainguard's catalog contains more than a thousand secure-by-default container images that are continuously rebuilt and maintained under a [CVE SLA](https://www.chainguard.dev/legal/cve-policy). With Catalog Pricing, organizations can use any image in the catalog without having to manage individual licenses or track image usage. You can also add multiple instances of the same container image to your organization. Note that, as of this writing, organizations are limited to a maximum of 2500 container image repositories. This model is best suited for organizations that use many different Chainguard images across teams or projects. It allows platform and security teams to standardize on Chainguard images while giving developers unrestricted access to pull the images they need. Once a subscription is in place, any image from the catalog can be pulled and used without additional approvals or per-image fees. ## Self-serve catalog experience Organizations that have signed up for Catalog Pricing can add container images through [Chainguard's Self-Serve Catalog Experience](http://chainguard.dev/unchained/introducing-the-self-serve-catalog-experience). This allows customers to independently add Chainguard Containers to their organization's catalog directly within the console, without needing to file support tickets or wait for Chainguard to provision resources manually. In order for a user to add images to their organization through the Self-Serve Experience, they must be bound to a role with the `repo (create, list, update)` capabilities. Additionally, it may be helpful for users working with the Self-Serve Experience to have the `registry.entitlement (list)` capability in order to understand their organization's registry access entitlements. The only built-in role with all of these capabilities is the `owner` role. Users intending to work with the self-serve catalog experience should be bound to the `owner` role or a custom role with the appropriate capabilities. Refer to our documentation [Roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/) for more details. To add a container to your organization through the Self-Serve Experience, start by logging in to the [Chainguard Console](https://console.chainguard.dev). After logging in, click **Images** in the left-hand navigation. By default, this will take you to the **Organization** tab, where you'll find your organization's list of container images. Above the list of container images, to the right of the search box, there will be an **Add image** button. Click this button to open a window containing a list of Chainguard Containers you can add to your organization. Within this window, you can scroll through the entire list of container images available for you to add to your organization. You can also use the search box within the window to search for a specific Chainguard Container to add. After finding the container image you want to add to your organization, click on it. This changes the window to include a text field where you can optionally set a different name for the image. This is the name that your users and tooling will use to refer to the container image, such as in a `docker pull` command. Note that this is required if you're adding another instance of an image already included in your organization's catalog. Finally, click the **Add image** button in the window, and the container image will appear within your organization's catalog after a few minutes. You can also add container images from the **Chainguard catalog** tab. This tab contains a list of every container image Chainguard has to offer, not just those that have already been added to your organization's catalog. Within this tab, scroll or search for whatever image you want to add to your organization. At the right end of its row will be a button labeled **Add to org**. Click the **Add to org** button to add the image to your organization; this will follow the same process as outlined previously. As before, you have the option to set a new name for the container image before clicking the **Add image** button. After provisioning your new container image through Self-Serve, you and other members of your organization will be able to access it from the Console, with `chainctl`, or through Chainguard's API. If there comes a point where you no longer need the image, you can remove it. Navigate to the image's page within the Console and click the **More** button in the upper-right corner. Click **Delete** and enter the name of the container image to confirm that you want to remove it. ### Updating container image names with `chainctl` You can also use [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/), Chainguard's command-line interface, to change the name of a container image that has already been added to your organization. To begin, run a command like the following to check whether the container image you want to add is already in your organization. This example checks whether the `php` container image is included: ```shell chainctl images repos list --repo=php -o json | jq -r '.items[0].id' ``` ```Output b41c4dd8fa86b9f0174b2ad10133cb506778aee4/2750044fc718841c ``` Note that this example uses [`jq`](https://jqlang.org/), a lightweight command-line JSON processor, to filter the output to only show the image's unique ID within the `example.com` organization. Knowing that the image is included in the organization, you can rename it. This example renames the `php` container image to `php-new`: ```shell chainctl images repo update php --name=php-new ``` ```Output ID: b41c4dd8fa86b9f0174b2ad10133cb506778aee4/2750044fc718841c Name: php-new Tier: BASE ``` After making this change, references to the previous name will no longer work for this image. This applies to any clients referencing this image by name. If you happen to rename an image in error, you can change it back using the same command, swapping the old name and new name: ```shell chainctl images repo update php-new --name=php ``` ## Learn more Chainguard's Catalog Pricing provides access across Chainguard's library of container images. To learn more about the Catalog Pricing model, we encourage you to [reach out for more information](https://www.chainguard.dev/contact?utm=docs). Additionally, you may find the following resources to be helpful: - [Blog announcement: Unlock the Full Chainguard Containers Catalog – Now with a Catalog Pricing Option](https://www.chainguard.dev/unchained/unlock-the-full-chainguard-containers-catalog-now-with-a-catalog-pricing-option?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) - [Blog announcement: Introducing the self-serve catalog experience](http://chainguard.dev/unchained/introducing-the-self-serve-catalog-experience) - [Chainguard Pricing](https://www.chainguard.dev/pricing?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) --- ### How to use Chainguard iamguarded Helm charts URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-iamguarded-helm-charts.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Helm charts, iamguarded, Product [Helm](https://helm.sh) is a package manager for Kubernetes that simplifies the installation and management of applications by automating the creation of Kubernetes resources. Helm charts are reusable, versioned packages that define a collection of Kubernetes resources required to run an application or service. You use Helm to define, install, and perform upgrades to your applications on Kubernetes. Chainguard offers this limited iamguarded set of Helm charts to go with a set of Chainguard-created containers labeled as iamguarded, designed specifically to support organizations migrating off of Bitnami. The iamguarded charts are forked from upstream Bitnami charts, but now configured out-of-the box for use with Chainguard’s hardened container images. These charts only receive edits necessary to make them work with Chainguard container images and retain the intended functionality of the originals they are based on. Because the iamguarded charts are forks, they may be susceptible to breaking changes introduced by the upstream. In such cases, customers should plan to transition to a community-provided alternative (or an equivalent one from Chainguard) where possible. > For organizations looking to deploy their Chainguard container images with Helm and who don't need or want the iamguarded charts, Chainguard provides upstream-produced Helm charts. Learn more about these in [How to use Chainguard Helm charts](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts/). These iamguarded charts have been tested by Chainguard to confirm they produce expected deployment results using the following policies: - We only build the latest/mainline versions of iamguarded images and test them against the latest version of the corresponding iamguarded Helm charts. Chainguard makes the provenance of these charts clear. Helm charts are packaged as [OCI artifacts](https://www.chainguard.dev/supply-chain-security-101/what-are-oci-artifacts) using the upstream version adding an appended revision suffix for updates that include material changes to the chart; otherwise, tags will float based as their dependent images update. The OCI artifacts are signed and generate provenance attestations that link to the exact image digests used to ensure that all artifacts are cryptographically verifiable end-to-end for integrity and origin. You can find iamguarded Helm charts in the [Chainguard Console](/platform/console/images-directory/#find-helm-charts-in-the-chainguard-console) and in the [Chainguard Directory](/chainguard/containers/registry/chainguard-directory/#find-helm-charts-in-the-chainguard-directory). The following is an instructional guide for Chainguard users that are looking for Helm charts to use with their iamguarded Chainguard container images. If there is a FIPS version of the iamguarded image the chart needs, you can use these Helm charts with Chainguard FIPS container images, but you will need to set the image in the values because they use the non-FIPS images by default. We build a single chart per application and validate that both FIPS and non-FIPS Chainguard container images work with it. If there is no appropriate iamguarded-fips image then you cannot use a non-iamguarded FIPS image. ## Configuration requirements If you are pulling container images directly from Chainguard, then you must set a `global.org` value. You don't need this if you are pulling from your own internal registry. You can set a `global.org` value using a `--set` flag during installation, as shown in this example: ```sh helm install rabbitmq oci://cgr.dev/$ORGANIZATION/iamguarded-charts/rabbitmq \ --set "global.org=$ORGANIZATION" ``` Alternately, you can set this value in a YAML file: ```yaml global: org: $ORGANIZATION ``` In addition, users who mirror images to custom repositories should use `global.imageRegistry` to override the default `cgr.dev`. If you have a complex mirroring strategy, consult the chart’s `values.yaml` for individual image configuration options including `registry`, `repository`, `tag` and `digest`. Here’s a sample from the RabbitMQ `iamguarded` chart documentation: ```yaml image: registry: myregistry.example.com repository: mirrored/rabbitmq-iamguarded digest: sha256:... # Use specific digest instead of tag # OS Shell image for volume permissions volumePermissions: enabled: true image: registry: myregistry.example.com repository: mirrored/os-shell-iamguarded digest: sha256:... # Use specific digest instead of tag ``` ## Authentication You will need to authenticate to pull charts. These instructions explain how to use charts and images with the `cgr.dev` repository. If you have mirrored or copied the charts and images to an organization-specific registry, you will need to adapt these instructions to authenticate to your registry, as appropriate. This section presents multiple authentication methods: - Use Helm values with `global.imagePullSecrets` - Deploy a Chainguard Helm chart using a Kubernetes pull secret - Use cluster node-scoped registry permissions ### Use Helm values with `global.imagePullSecrets` When performing authentication via a `global.imagePullSecrets` key-value pair, include the following in your `values.yaml` file. ```yaml global: imagePullSecrets: - name: chainguard-pull-secret ``` ### Deploy a Chainguard Helm chart using a Kubernetes pull secret To begin, generate a pull token. ```sh chainctl auth configure-docker --pull-token --save --ttl=24h ``` You don't need to run `chainctl auth login` first; `configure-docker` authenticates you when no valid token is available. This token expires in 24 hours by default, which can be modified using the `--ttl` flag. It sets the duration for the validity of the token. The maximum valid value is `8760h` (equivalent to 365 days), Valid unit strings range from nanoseconds to hours and are `ns`, `us`, `ms`, `s`, `m`, and `h`, for example `--ttl=24h`. Find the username and password that are contained in the pull token, as in this sample output: ```sh chainctl auth configure-docker --pull-token --save --ttl=24h ✔ Selected folder chainguard.edu. To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" Configuring identity "" for pulls from cgr.dev (expires 2025-06-12T09:27:45-05:00). Overwriting existing credentials. ``` Save the credentials as variables, like this. ```sh HELMUSER="" HELMPASS="" ``` Create your Kubernetes secret using the variables you just created. ```sh kubectl create secret docker-registry chainguard-pull-secret \ --docker-server=cgr.dev \ --docker-username=$(echo $HELMUSER) \ --docker-password=$(echo $HELMPASS) \ -n ``` Log in to the `cgr.dev` Helm registry. The secret you just created covers the image pulls your pods make, but it has no effect on the Helm client, which authenticates on its own to fetch the chart. ```sh helm registry login cgr.dev \ --username=$HELMUSER \ --password=$HELMPASS ``` Reference the secret in your Helm installation: ```sh helm install rabbitmq oci://cgr.dev/$ORGANIZATION/iamguarded-charts/rabbitmq \ --set "global.org=$ORGANIZATION" \ --set "global.imagePullSecrets[0].name=chainguard-pull-secret" ``` When the install is successful, it returns a confirmation message, like this: ```sh Pulled: cgr.dev/chainguard-private/iamguarded-charts/rabbitmq:16.0.2 Digest: sha256:cfc18fe651760c91c7596f7d9d44512b162f19e4bfd8ba81b5ff6cb6d8e61d6d NAME: rabbitmq LAST DEPLOYED: Mon Jun 9 08:35:39 2025 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: CHART NAME: rabbitmq CHART VERSION: 16.0.2 APP VERSION: 4.1.0** Please be patient while the chart is being deployed ** Credentials: echo "Username : user" echo "Password : $(kubectl get secret --namespace default rabbitmq -o jsonpath="{.data.rabbitmq-password}" | base64 -d)" echo "ErLang Cookie : $(kubectl get secret --namespace default rabbitmq -o jsonpath="{.data.rabbitmq-erlang-cookie}" | base64 -d)" Note that the credentials are saved in persistent volume claims and will not be changed upon upgrade or reinstallation unless the persistent volume claim has been deleted. If this is not the first installation of this chart, the credentials may not be valid. This is applicable when no passwords are set and therefore the random password is autogenerated. In case of using a fixed password, you should specify it when upgrading. More information about the credentials may be found at https://docs.iamguarded.com/general/how-to/troubleshoot-helm-chart-issues/#credential-errors-while-upgrading-chart-releases. RabbitMQ can be accessed within the cluster on port 5672 at rabbitmq.default.svc.cluster.local To access for outside the cluster, perform the following steps: To Access the RabbitMQ AMQP port: echo "URL : amqp://127.0.0.1:5672/" kubectl port-forward --namespace default svc/rabbitmq 5672:5672 To Access the RabbitMQ Management interface: echo "URL : http://127.0.0.1:15672/" kubectl port-forward --namespace default svc/rabbitmq 15672:15672 WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs: - resources +info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ ``` ### Use cluster node-scoped registry permissions If you manage access and permissions at cluster-wide and node-specific levels, these are some best practices to consider. **Use Image Pinning:** All `iamguarded` charts pin images to specific **digests** that have been tested for compatibility, ensuring reliable deployments. **Pin to Digest:** While charts follow the same tagging scheme as Chainguard images, always pin to a specific chart **digest** to prevent unexpected updates: ```sh helm install rabbitmq \ oci://cgr.dev/$ORGANIZATION/iamguarded-charts/rabbitmq@sha256:DIGEST \ --set "global.org=$ORGANIZATION" ``` **Review Default Values:** The chart provides security-minded defaults that are sensible but may not suit all use cases. Review the chart's `values.yaml` for the full range of configuration options and adjust as needed. ## Helm chart usage examples ### Install with details passed in a flag To install a Helm chart using standard Helm commands and passing details as flag values in the command itself, add the flags and values at the end like this example, substituting your organization for `$ORGANIZATION`. ```sh helm install rabbitmq oci://cgr.dev/$ORGANIZATION/iamguarded-charts/rabbitmq \ --set "global.org=$ORGANIZATION" ``` ### Install with details in a file Alternatively, you can put values in a file, such as our `values.yaml` sample, and then refer to the file in your install command. ```sh image: registry: cgr.dev repository: $ORGANIZATION/rabbitmq # replace $ORGANIZATION tag: latest # pin to specific version instead of latest ``` Then you refer to the file like this: ```sh helm install test oci://cgr.dev/chainguard-private/iamguarded-charts/rabbitmq --values ./values.yaml ``` ### Install on AWS Elastic Kubernetes Service (EKS) auto mode When installing on EKS Auto Mode, you may need to create a storage class for the Helm chart's pod(s). This can be done by creating a storage class: ```sh cat <

Note: This overview highlights using the Chainguard console’s UI to interact with Custom Assembly resources. However, you can also interact with Custom Assembly using chainctl, Chainguard’s command-line interface tool, as well as the Chainguard API.

## Selecting packages and building a customized container After logging in to the [Chainguard console](https://console.chainguard.dev/auth/login), the console opens to your account overview page. If you belong to more than one organization, be sure to select an organization with access to Custom Assembly from the drop-down menu in the top-left corner. Click on **Images** and scroll or search for the container image that you want to customize. Note that you can use Custom Assembly to customize any Chainguard Container that your organization has access to. Click your chosen container image to open its entry in the console. In the upper right corner of this page, you'll find the **Customize image** button and a **More** menu. Click **Customize image** to open a window displaying a list of all of the packages available to be added or removed from your selected container image. This list of packages includes all the packages your organization is entitled to. If there's a package you'd like to include in your image but it isn't available in this list, please open a Chainguard support ticket. You can scroll through the list and select or deselect packages to tailor the image to your needs by checking their respective boxes. Alternatively, you can use the search box to filter for the packages you're looking for. After selecting your chosen packages, click the **Continue** button. Custom Assembly then prompts you to select one of the following two options for how you want to apply the customizations: * **Create a new image**: This option creates a new container image, based on the current image you've chosen to customize. * This option requires you to select a new name for the container image. Note that whatever name you select can only contain lowercase alphanumeric characters, `-`, or `_`. * **Customize current image**: This option overrides the existing container image with your customizations. Note that any customizations applied to this image also apply to any users in your organization who are already consuming it. After selecting one of these options, click the **Preview changes** button to view all the packages you've selected for the customized image. If you'd like to make further changes, click the **Back** button to return to the package selection. If you're satisfied with the selection of packages, click the **Apply changes** button to build the new customized image. If you opted to create a new image, this button instead says **Create $IMAGE_NAME**. A confirmation message at the top of the Customize Container display tells you that the image was successfully customized. If a build fails, you'll need to make the appropriate changes before attempting another build. You can check the build's logs for information about what went wrong and what to fix. ## Listing builds and viewing logs You can view a list of all the available builds of your customized container image by clicking the customized image's **Builds** tab in the console. The table in the Builds tab has six columns: * **Status**: The status of the given build. When a build is successful, this column shows a green check inside of a circle. When a build has failed, this column displays a red exclamation mark in a triangle. * **ID**: A unique identifier representing a specific customized container image build. * **Tag**: The container image version the build represents. * **Digest**: A unique, content-based hash representing the given container image build. * **Duration**: The amount of time it took to build the container image. * **Created**: How long it's been since the build was created. Note that if you only recently customized the container image it may take a few minutes for the latest builds to populate. Additionally, builds stay listed in the console for only 24 hours. This is because Chainguard Containers, including Custom Assembly container images, are rebuilt frequently and would quickly congest the user interface. You can click on the row of any build listed in the Builds tab to access its logs. A window opens from the right with more details about the build, including build failures. ## Making changes to a customized container image If you need to make further modifications to a customized image, or revert changes you've already made, you can do so with just a few clicks in the Chainguard console. Going back to the container image you just customized, click **Customize image** again. The panel where you added packages lists the packages added to the customized image, below the **Search package names** box. You can add more packages to the customized image by following the process outlined previously. To remove a package from the container image, click its **X** symbol. To remove all of the container image's customizations, click **More** at the top right of the customize panel, then select **Remove customizations**. Removing all the added packages returns the image to its original state. Note that you can also edit the packages in a customized image [using the `chainctl images repos build edit` command](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#adding-packages-to-a-customized-container-image). If you elected to create a new container image with Custom Assembly, you can rename it from the image's page. Click **More** next to the **Customize image** button. Select **Rename** and enter a new name for the image. Note that after you rename the customized image, any references to the previous name stop working. You can also delete new container images that you've created with Custom Assembly. To do so, click **More** (again, next to the **Customize image** button) and select **Delete**. This opens a window that prompts you to enter the name of the container image to confirm that you want to delete it. ## Learn more You can also use the Chainguard Console to add Chainguard-managed certificates to Custom Assembly images. Refer to our guide on [Adding custom certificates with Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/#chainguard-managed-certificate-bundles) for more information. For more advanced workflows or automation, consider exploring the [`chainctl` CLI tool](/chainguard/containers/custom-assembly/custom-assembly-chainctl/) or the [Chainguard API](/chainguard/containers/custom-assembly/custom-assembly-api-demo/) for programmatic access to Custom Assembly features. --- ### Chainguard Libraries verification URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/policies-and-security/verification.md Last Modified: October 8, 2026 Tags: Chainguard Libraries Chainguard's `chainctl` tool with the command [`libraries verify`](/platform/chainctl/chainctl-docs/chainctl_libraries_verify/) verifies which of your language ecosystem dependencies were built by Chainguard, providing critical visibility into your software supply chain security. By verifying binary artifacts across your projects and repositories, you can confirm which dependencies came from Chainguard's hardened build environment, identify opportunities to improve security posture, and maintain compliance with supply chain security policies. For packages that aren't built by Chainguard, you can enable [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) to apply additional configurable security controls. Command characteristics: - Uses a signature-based binary identification and a checksum fallback. - Supports different binary formats, including JAR, WAR, EAR, ZIP, TAR, WHL, APK, and npm tarballs (.tgz), as well as container images. - Allows analysis of directories and nested archive files. - Creates output in text, json, yaml, and CSV formats. ## Requirements Before using `chainctl` to verify libraries, ensure you have the following installed and available on your path: - [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) — Chainguard-maintained tool that includes the `libraries verify` command, details also in the [reference documentation](/platform/chainctl/chainctl-docs/chainctl_libraries_verify/). `chainctl libraries verify` checks signatures in-process. To fetch the Sigstore trust root, the command needs network access to `tuf-repo-cdn.sigstore.dev`. Refer to [network requirements](/chainguard/libraries/introduction/network-requirements/) for the full list of domains. You also need: - A Linux, macOS, or Windows system (x86_64 or arm64) - Sufficient [network access](/chainguard/libraries/introduction/network-requirements/) - Your organization [must include entitlement for access to Chainguard Libraries](/chainguard/libraries/introduction/access/#entitlement) - You must have one of the `libraries.java.pull`, `libraries.javascript.pull`, or `libraries.python.pull` permissions, or the Owner role. Confirm that `chainctl` is installed and available on the `PATH`: ```sh chainctl version ``` ## Authentication and configuration You can authenticate with your Chainguard organization using `chainctl`. First, initiate the login flow: ```sh chainctl auth login ``` If you are a member of one organization only, you can proceed to use `libraries verify` and other commands. If you are a member of multiple organizations, you must provide the name of your organization using the `--parent` flag as follows, replacing `` with the name of your organization, with every command: ```sh chainctl libraries verify --parent /path/to/artifact.jar ``` To avoid the need for the additional parameter, you can configure a default organization with the following steps. Find your organization name with the entitlement: ```sh chainctl iam organizations list ``` Set the configuration for the default group: ```sh chainctl config set default.group ``` Verify the configuration: ```sh chainctl config view ``` Ensure that you use this configuration or add the `--parent` parameter in all the following examples as necessary. ## File analysis To save available Software Bills of Materials (SBOMs) and attestations while verifying Java artifacts, JavaScript packages, or Python wheels, add `--output-attestations`. Use `--output-dir` to choose the base directory, which defaults to the current directory. Only packages verified as Chainguard builds produce companion files. The verification report lists the saved files and their trust status in text, JSON, YAML, and CSV output. > **Note**: Running `chainctl libraries verify` requires one of the `libraries.java.pull`, `libraries.javascript.pull`, or `libraries.python.pull` permissions, or the Owner role. ### Analyze a Python wheel file Analyze a Python wheel file in the current directory: ```sh chainctl libraries verify flask-3.0.1-py3-none-any.whl ``` The analysis of wheel files is fast because the provenance information is available within the archive. Python development tools often unpack the wheel file and you can also scan these extracted packages. For example, if you create a virtual environment in your Python project, you can subsequently analyze the package in the virtual environment: ```sh python3 -m venv venv source ./venv/bin/activate pip3 install -r requirements.txt chainctl libraries verify --detailed ./venv/ ``` If you use Poetry and the virtual environment is not in the project directory, verify the environment returned by Poetry: ```bash chainctl libraries verify --detailed "$(poetry env info --path)" ``` For CI/CD, use JSON output to save a machine-readable report: ```bash chainctl libraries verify --detailed -o json .venv/ > provenance-report.json ``` #### Download Python SBOMs and attestations Save the available companions for a verified wheel: ```sh chainctl libraries verify flask-3.0.1-py3-none-any.whl \ --output-attestations --output-dir attestations ``` Files are saved under the normalized package name and version. For the preceding command, the directory is `attestations/flask/3.0.1/`, with these files when available: - `flask-3.0.1-py3-none-any.whl.signature-bundle.json` — The exact Sigstore bundle used to verify the wheel. - `flask-3.0.1-py3-none-any.whl.pep740-attestation.json` — PEP 740 provenance. Before saving it as trusted, `chainctl` independently verifies its signature, the Chainguard Python signer identity and issuer, and the wheel's SHA-256 digest as an attested subject. - `flask-3.0.1-py3-none-any.whl.sbom.spdx.json` — The SPDX SBOM extracted from the verified wheel's top-level `.dist-info/sboms/` directory. The full wheel filename distinguishes companions for different platform and Python builds of the same package version. Remediated versions with a `+cgr` suffix also support downloads. Legacy signature bundles served from the provenance endpoint do not produce a PEP 740 file. Downloads require a wheel file. An installed Python environment or a source distribution does not produce companion files. For details about the formats, refer to [SBOM and attestation files](/chainguard/libraries/python/overview/#sbom-and-attestation-files). ### Analyze a Java JAR file Analyze a Java `.jar` file: ```sh chainctl libraries verify commons-lang3-3.17.0.jar ``` Verifying a JAR file is performed by looking up checksums and provenance information from the Chainguard repositories. This requires network access and can take longer if you analyze multiple files or archives that contain multiple libraries. Typically, you find the JAR files in the local Maven repository cache in `~/.m2/repository`. For best results, verify individual JAR files from this cache before packaging your application. Refer to [Java fat JAR limitations](#java-fat-jar-limitations) for more details. Analyze a deployment archive for your custom application that contains other libraries: ```sh chainctl libraries verify example-application.tar.gz ``` Note that if your deployment archive is a fat JAR, uber JAR, or shaded JAR, verification returns 0% coverage. This is expected behavior; refer to [Java fat JAR limitations](#java-fat-jar-limitations) for the recommended verification approach. For other archive types such as tarballs that contain individual unmodified JAR files, scanning can take a significant amount of time if numerous libraries are included. Consider detailed output with the `--detailed` flag for more information about the performed verification steps, and potentially pipe the output into a file. ```sh chainctl libraries verify --detailed commons-lang3-3.17.0.jar > run.log ``` Use the `--verbose` flag for even more details. Analyze multiple artifacts output: ```sh chainctl libraries verify artifact1.jar artifact2.zip ``` Analyze a file and create JSON output: ```sh chainctl libraries verify -o json commons-lang3-3.17.0.jar ``` #### Recommended build-time workflow for Maven For Maven-based applications, a recommended workflow is to copy only runtime dependencies into a dedicated directory then verify those files, allowing you to avoid noise from unrelated artifacts in `~/.m2/repository`. For example: ```bash mvn -U -q -s settings.xml dependency:copy-dependencies \ -DincludeScope=runtime \ -DoutputDirectory=target/chainguard-verify chainctl libraries verify -o json --detailed target/chainguard-verify/*.jar \ > provenance-report.json ``` > Note: If you belong to multiple Chainguard organizations, include the `--parent=` flag in the command. It can take up to 5 minutes for this command to return results. It returns output similar to the following: ```bash { "artifactVerificationCoverage": 74.19354838709677, "verifiedItems": 23, "totalItems": 31, "artifactsSummary": { "totalArtifacts": 31, "fullyVerified": 23, "partiallyVerified": 0, "notVerified": 8, "verifiedPercent": 74.19354838709677 }, "results": [ { "artifact": "target/chainguard-verify/jakarta.validation-api-3.0.2.jar", "artifactVerificationCoverage": 100, "details": "Fully verified by Chainguard (signature verified)\nMaven artifact: jakarta.validation:jakarta.validation-api:3.0.2" }, ... ``` In this example, out of 31 `totalItems`, 23 were verified. The `arfifactVerificationCoverage` percent is 74. #### Java fat JAR limitations The fat JAR packaging approach merges the class files from all dependency JARs into one combined archive, which means the original JAR boundaries are lost. Because `chainctl libraries verify` identifies libraries by checking checksums and provenance information against individual JAR files, it cannot trace merged class files back to their source JARs. As a result, running `chainctl libraries verify` against a fat JAR returns 0% coverage, even if the dependencies inside it were sourced from Chainguard Libraries. #### Recommended verification approach for fat JARs To verify that your Java dependencies come from Chainguard Libraries, run `chainctl libraries verify` during your build process against the individual JAR files in your local Maven repository cache, **before** fat JAR assembly. After resolving dependencies with Maven, the individual JAR files are available in `~/.m2/repository`. The following example uses `net.logstash.logback:logstash-logback-encoder:8.1` as the library, but you can replace the path with the specific JAR you want to verify: ```sh chainctl libraries verify ~/.m2/repository/net/logstash/logback/logstash-logback-encoder/8.1/logstash-logback-encoder-8.1.jar ``` To integrate this into your build pipeline, add the verification step after dependency resolution and before the packaging phase. #### Download SBOMs and attestations During verification, `chainctl` fetches the SBOM and SLSA provenance published alongside a Chainguard-built Java artifact and discards them after the check. Add the `--output-attestations` flag to save these files to disk instead: ```sh chainctl libraries verify commons-lang3-3.17.0.jar --output-attestations ``` Use `--output-dir` to set the base directory, which defaults to the current directory. Files are written under each artifact's Maven repository path, `group/artifact/version`, matching the layout of a local Maven repository: ```sh chainctl libraries verify commons-lang3-3.17.0.jar \ --output-attestations --output-dir repo ``` For the preceding command, the files land under `repo/org/apache/commons/commons-lang3/3.17.0/`: - `commons-lang3-3.17.0.spdx.json` — SPDX SBOM, saved as a trusted Chainguard attestation - `commons-lang3-3.17.0.slsa-attestation.json` — SLSA provenance, also saved as trusted - `commons-lang3-3.17.0-cyclonedx.json` and `commons-lang3-3.17.0-cyclonedx.xml` — CycloneDX SBOMs, saved when present but marked unverified, since Chainguard does not vouch for their contents Only an artifact that verifies as a Chainguard build saves anything. Upstream or tampered bytes save nothing. Inside a fat JAR, each embedded library is verified and saved individually, so only the verified libraries contribute files. The saved files are listed in the text, JSON, YAML, and CSV output, with unverified files clearly marked. For a description of these files and the alternative of downloading them directly, refer to [SBOM and attestation files](/chainguard/libraries/java/overview/#sbom-and-attestation-files). ### Analyze JavaScript packages `chainctl libraries verify` can scan local package manager caches and stores to confirm that your installed JavaScript packages were built by Chainguard. It supports the following JavaScript package managers: - pnpm store: auto-detected by `v10/index/` or `v11/index/` structure (pnpm v10 and v11 supported) - npm cache: auto-detected by `_cacache/index-v5/` structure - Yarn Classic: v1.x, requires `yarn:` prefix #### Analyze an npm tarball Verify an npm package tarball to confirm it was built by Chainguard: ```sh chainctl libraries verify PACKAGE-VERSION.tgz ``` Replace `PACKAGE` and `VERSION` with the package name and version (for example, `@eslint-js` and `9.0.0`) Verification uses SLSA provenance attestations. `chainctl` computes a SHA-512 digest of the tarball locally, fetches the signed attestation bundle, and verifies in-process that the signature is valid, the certificate chains to the Sigstore root, the signer identity matches the Chainguard JavaScript builder, and the digest matches what was attested at build time. #### Verify an npm cache Verify your npm cache: ```sh chainctl libraries verify "$(npm config get cache)" ``` #### Verify a pnpm store Verify your pnpm store. Use `--store-dir` to install to an explicit path and verify that location: ```sh pnpm install --store-dir /tmp/my-pnpm-store chainctl libraries verify /tmp/my-pnpm-store ``` pnpm v9 and earlier are not supported. Verification works by comparing the tarball hash recorded in your local store against the hash in Chainguard's signed SLSA attestation. pnpm v10 records this hash in the index file path; pnpm v9 does not. Note that in `pnpm-lock.yaml`, packages resolved from Chainguard have only a `resolution:` entry with an integrity hash: ```yaml supports-color@7.2.0: resolution: {integrity: sha512-LPhWJX...} ``` Packages that are pulled from the upstream fallback include an explicit `tarball:` URL pointing to `javascript-upstream`. For example: ```yaml tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwm..., tarball: https://libraries.cgr.dev/javascript-upstream/tar-fs/-/tar-fs-2.1.4.tgz} ``` #### Verify a Yarn Classic cache Verify a Yarn Classic (v1) cache: ```sh chainctl libraries verify yarn: ``` To specify a non-default cache location: ```sh chainctl libraries verify yarn:~/Library/Caches/Yarn/v6 ``` Unlike npm and pnpm, Yarn Classic requires the `yarn:` prefix because its cache directory layout cannot be reliably auto-detected. #### Verify a `node_modules` directory Verify npm packages installed in a `node_modules` directory: ```sh chainctl libraries verify ./node_modules ``` If `.package-lock.json` is not present, the directory is not recognized as an npm tree and verification will not run. #### Verify JavaScript lockfiles without installing dependencies > **Note**: This command requires `chainctl` v0.2.354 or later. Verify a JavaScript lockfile directly, without running an install and without requiring `node_modules` or a persistent package manager cache: ```sh chainctl libraries verify package-lock.json ``` Supported lockfiles include `package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, and `bun.lock`. This command verifies what the lockfile claims will be installed. It does not verify the package bytes present on disk. #### Download JavaScript SBOMs and attestations Save the available companions for a verified npm tarball: ```sh chainctl libraries verify lodash-4.17.21.tgz \ --output-attestations --output-dir attestations ``` Files are saved under `name/version/`, or `@scope/name/version/` for scoped packages. For the preceding command, the directory is `attestations/lodash/4.17.21/`, with these files when available: - `lodash-provenance-4.17.21.sigstore` — The exact signed SLSA provenance bundle verified against the package's integrity digest and Chainguard signer identity. - `lodash-sbom-4.17.21.spdx.json` — The SPDX SBOM retrieved from the authenticated Chainguard SBOM endpoint. `chainctl` reports it as trusted based on that endpoint. The npm provenance does not cryptographically bind the SBOM's bytes. The flags also work for npm caches, pnpm stores, Yarn Classic caches, installed `node_modules` directories, and supported JavaScript lockfiles. For example, save companions while verifying the dependencies recorded in a lockfile: ```sh chainctl libraries verify package-lock.json \ --output-attestations --output-dir attestations ``` For lockfiles, downloads require verified provenance that identifies the package the lockfile will install. Ambiguous entries with multiple candidate integrity hashes do not produce companion files unless the resolved URL identifies the Chainguard build. Missing SBOMs are skipped. If the verified provenance bundle cannot be read, `chainctl` warns and skips downloads for that package. If saving a later companion fails, the report still lists successfully saved files with a warning. For manual retrieval, refer to [Provenance and attestations](/chainguard/libraries/javascript/overview/#provenance-and-attestations). #### Verify a container image Verify JavaScript packages inside a container image: ```sh chainctl libraries verify IMAGE:TAG ``` Coverage is reported as the percentage of JavaScript packages in the image that are confirmed Chainguard-rebuilt libraries. Images built with npm versions earlier than v7, or where `.package-lock.json` was removed during the build, cannot be verified this way. ### Other bundled artifact formats The same limitation applies to other ecosystems where dependencies are bundled into a single output artifact, such as JavaScript bundles and Python applications packaged with tools that inline dependencies. Dependencies may also be minified, partially copied, or otherwise transformed during the build process. In all of these cases, verification should also be performed against the original package files before bundling rather than against the final output artifact. ## Verify artifacts in a repository manager You can verify what artifacts are retrieved from the Chainguard Libraries repository on a global level: - **Artifactory and Nexus**: Browse the `chainguard` proxy repository on your repository manager server. - **Cloudsmith**: Access the **Packages** tab of the repository on your Cloudsmith instance. Filter the package list with the tag value with the name for your upstream proxy for Chainguard, for example `tag:chainguard`. The tag uses the name of the upstream proxy, with spaces replaced with dashes. Use the browsing access to locate specific artifacts and identify their name, file size, checksum values, timestamp and other identifiers. With these details you can verify your libraries use in the following locations: - Local cache repositories on developer workstation - Cache repositories in your CI pipeline - Libraries in your application bundles - Installed applications on your hosts or in your container images A uniquely identifying characteristic of library artifacts are their checksums. Contrary to filenames and timestamps, checksums do not change in the use of libraries during an application build or the assembly of a deployment artifact like a tarball or container. This allows you to identify a library artifact by determining the checksum and then locating it in your repository manager. Calculate the different commonly used sums for a file `example.jar` with the following commands and output examples: ``` $ sha1sum example.jar aea83e64ebec6a37e0be100f968a55fb381143c2 example.jar $ sha256sum example.jar 87a25c44e0fdb0c71e898c57f67b236d2205bfa76a25dbbb9779ebe2f93e787e example.jar $ md5sum example.jar fefd660ddc795900d48bdf49c17b3135 example.jar ``` Use the search features in your repository manager to locate the library. For the specific example, you find that the checksums correspond to the file `junit-4.13.2.jar` found in `junit/junit/4.13.2/` and that the artifact is found in the `chainguard` proxy repository. You can therefore conclude that the `example.jar` file originates from Chainguard, was built in the Chainguard Factory from source, and is available at `https://libraries.cgr.dev/java/junit/junit/4.13.2/junit-4.13.2.jar`. You can [manually download the file to compare](/chainguard/libraries/java/overview/#manual-access), if desired. ## Container analysis You can also analyze container images to verify the libraries contained within the container. Note that this requires more time to verify depending on the container size, and the number and type of included libraries. Analyze a container image: ```sh chainctl libraries verify cgr.dev/chainguard/maven:latest ``` Note that the analysis separately downloads the container tarball and analyzes it, rather than any container available in your local container setup. Analyze a local image with localhost prefix: ```sh chainctl libraries verify localhost/myapp:latest ``` ## Other examples The following examples use Maven Central and PyPI URLs and returns a negative result, because packages were not built by Chainguard. A practical use of this functionality points to an internal repository manager with a mixture of artifacts from Chainguard and elsewhere. Note that authentication to the repository is not supported and you must download artifacts to a local directory as an alternative method to verify them. Analyze a remote artifact on Maven Central: ```sh chainctl libraries verify remote:repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.17.0/commons-lang3-3.17.0.jar ``` Analyze a remote artifact on PyPI: ```sh chainctl libraries verify remote:files.pythonhosted.org/packages/...../requests-2.31.0-py3-none-any.whl ``` ## Built-in help Use the `help` command for more command options and details for the `verify` command: ```sh chainctl help libraries verify ``` ## Troubleshooting ### Why might I see 0% coverage when verifying Java artifacts? A 0% coverage result is expected when verifying a fat JAR, uber JAR, or shaded JAR. Those packaging formats merge dependency contents into a single archive, so `chainctl libraries verify` cannot trace merged classes back to the original JARs. ## Resources - [Chainguard Libraries overview](/chainguard/libraries/introduction/overview/) - [Chainguard Libraries authentication](/chainguard/libraries/introduction/access/) - [`chainctl libraries verify` reference documentation](/platform/chainctl/chainctl-docs/chainctl_libraries_verify/) - [ Learning Lab: Chainguard Libraries for Java](/software-security/learning-labs/ll202505/) - [ Learning Lab: Chainguard Libraries for Python](/software-security/learning-labs/ll202506/) --- ### Global configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/javascript/global-configuration.md Last Modified: October 1, 2026 Tags: Chainguard Libraries, JavaScript JavaScript and npm package consumption in a large organization is typically managed by a repository manager. Commonly used repository manager applications are [JFrog Artifactory](https://jfrog.com/artifactory/), [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository), and others. The repository manager acts as a single point of access for developers and development tools to retrieve the required libraries. If your organization uses the [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) feature of Chainguard Repository, you can configure your repository manager with a single upstream pointed at `https://libraries.cgr.dev/javascript/`. This is the recommended setup. The Chainguard Repository handles fallback and policy enforcement; your repository manager handles local caching and access control. Chainguard also retrieves packages from the public npm Registry on your behalf when upstream fallback is enabled. This includes protections such as malware detection and a cooldown period for newly published packages. The same `https://libraries.cgr.dev/javascript/` upstream also serves remediated `-cgr.N` artifacts, so you do not configure a separate remediated repository in your repository manager. Selecting remediated versions happens in the project with `chainctl libraries remediate --apply`, not through your repository manager configuration. Refer to [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/) for details. At a high level, adopting the use of Chainguard Libraries consists of the following steps: * Configure your environment to use `https://libraries.cgr.dev/javascript/` as the single upstream source for JavaScript package retrieval. This can be done either: * As a remote repository in your repository manager, or * Directly in your JavaScript build configuration (for example, npm, pnpm, or yarn). * Additional steps depend on your visibility and validation goals and can include the following optional measures: * Remove all cached libraries in existing proxy repositories. This step allows you to validate which libraries are not available from Chainguard Libraries and ensures they are retrieved through Chainguard for evaluation. * Remove any repositories that are no longer desired or necessary. Depending on your library requirements, this step can result in removal of some proxy repositories or simplification of your repository configuration. Adopting the use of a repository manager is the recommended approach to minimize complexity. If your organization does not use a repository manager, refer to the [direct access documentation](/chainguard/libraries/javascript/build-configuration/) for build tools. ## Manually managing fallback Chainguard recommends using the Chainguard Repository's built-in [upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) rather than configuring a public registry fallback in your repo manager. Configuring your own fallback bypasses the protection that the Chainguard Repository provides. However, if upstream fallback is not enabled or you prefer to manage your own fallback ordering, you can configure `https://libraries.cgr.dev/javascript/` as a remote repository alongside your npm upstream, and combine them in a virtual or group repository with Chainguard as the first priority. The per-tool instructions on this page follow this pattern. ### Updating lockfile hashes If you are migrating an existing JavaScript project to Chainguard Libraries through a repository manager, your lockfile likely contains integrity hashes generated against packages previously downloaded from npm or through your repository manager. The [`chainctl libraries update-hashes` command](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/) automates lockfile hash updates for all supported JavaScript lockfile formats. When you are using a repository manager, pass the full repository manager URL with `--registry-url` and authenticate with one of the supported methods: `--username` and `--password`, `--token`, or a `.netrc` entry for the registry host. For example: ```bash chainctl libraries update-hashes \ --registry-url https://repo.example.com:8443/repository/javascript-all/ \ --token "$REPO_TOKEN" ``` > **Note**: Running `chainctl libraries update-hashes` requires the `libraries.javascript.pull` permission or the Owner role. After updating the lockfile, keep your repository manager configuration in place and reinstall through the same repository manager endpoint to apply the updated hashes. Learn more in the [Build configuration page](/chainguard/libraries/javascript/build-configuration/#updating-lockfile-hashes) and in the [chainctl docs](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/). ## Cloudsmith [Cloudsmith](https://cloudsmith.com/) supports npm registries for proxying and hosting. Refer to the [npm registry documentation](https://help.cloudsmith.io/docs/npm-registry) and the [npm Upstream documentation](https://help.cloudsmith.io/docs/upstream-proxying-caching#create-a-npm-upstream) for Cloudsmith for more information. Cloudsmith supports combining repositories by defining multiple upstream repositories. The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single upstream proxy pointed at `https://libraries.cgr.dev/javascript/` rather than adding a separate public npm proxy. Refer to [Manually managing fallback](#manually-managing-fallback) if you need to control fallback ordering yourself. ### Initial configuration Use the following steps to configure a repository with the Chainguard Libraries for JavaScript repository as an upstream. Configure a *javascript-all* repository. This repository acts as a single access point for JavaScript packages and may also include private packages or additional upstream sources, depending on your configuration. 1. Log in as a user with administrator privileges. 1. Select the **Repositories** tab near the top of the screen. 1. On the **Repositories** page, click **+ New repository**. 1. Enter the name *javascript-all* for your new repository. The name should include *javascript* to identify the ecosystem. This convention helps avoid confusion since repositories in Cloudsmith are multi-format. 1. Select a storage region that is appropriate for your organization and infrastructure. 1. Click **+ Create Repository**. Configure an upstream proxy for the Chainguard Libraries for JavaScript repository: 1. Click the name of the new *javascript-all* repository on the repositories page to configure it. 1. Access the **Upstreams** tab and click **+ Add Upstream Proxy**. 1. Configure an upstream proxy with the format **npm** and the following details: * **Name** *javascript-chainguard* * **Priority** *1* * **Proxy URL** `https://libraries.cgr.dev/javascript/` * **Mode** *Cache and Proxy* * Add the **Username** and **Password** value from [Chainguard Libraries access](/chainguard/libraries/introduction/access/) in **Authentication Settings** 1. Click **Create Upstream Proxy**. If you are manually managing fallback, you can add an additional upstream proxy for the public npm registry with a lower priority than `javascript-chainguard`. Use this setup for initial testing with Chainguard Libraries for JavaScript. For production usage, add the `javascript-chainguard` upstream proxy to your production repository. ### Build tool access The following steps allow you to determine the URL and authentication details for accessing the repository: 1. Select the **Packages** tab. 1. Click **Push/Pull Packages**. 1. Choose the format **NPM**. 1. Refer to the **Pull Package** tab. 1. Note the registry URL and syntax from the code snippets for npm. For example, the URL for the registry in the `example` organization is `https://npm.cloudsmith.io/example/javascript-all/`. 1. Note that authentication is using an authentication token and the syntax for npm in the `example` organization is `//npm.cloudsmith.io/example/javascript-all/:_authToken=YOUR-API-KEY` Use the provided code snippets directly for your use with npm, or adjust as necessary for other JavaScript build and packaging tools. Find relevant details in the [Build Configuration](/chainguard/libraries/javascript/build-configuration/) and specific packaging tool documentation. Use the following steps to retrieve the necessary API key as an authentication token for the registry access: 1. Click on your user name at the top right corner. 1. Select **Personal API keys**. 1. Authenticate again in the **Confirm access** dialog. 1. Create a new token or refresh the existing one in case you lost the token value. ## JFrog Artifactory [JFrog Artifactory](https://jfrog.com/artifactory/) supports npm repositories for proxying and hosting, and virtual repositories to combine them. Refer to the [npm Repository documentation for Artifactory](https://docs.jfrog.com/artifactory/docs/npm-repositories) for more information. If you follow the recommended approach to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), disable or remove any existing Artifactory remote repository that points at the public npm registry, and remove it from the virtual repository your builds resolve against. A remote pointing directly at the public upstream bypasses those protections. Since Artifactory resolves through the virtual repository in order, a misconfiguration can result in Artifactory serving an unprotected package. ### Initial configuration Use the following steps to add Chainguard Libraries for JavaScript as a remote repository: 1. Log in as a user with administrator privileges. 1. Click **Administration** in the top navigation bar. 1. Select **Repositories** in the left hand navigation. Configure a remote repository for the Chainguard Libraries for JavaScript repository: 1. Click **Create a Repository** and choose the **Remote** option. 1. Select *Npm* as the **Package type**. 1. Set the **Repository Key** to `javascript-chainguard`. 1. Set the **URL** to `https://libraries.cgr.dev/javascript/`. 1. Set **User Name** and **Password / Access Token** to the [values as retrieved with chainctl](/chainguard/libraries/introduction/access/). * Note: The **Test** button is not a reliable indicator; to verify your setup, refer to the [validation steps](#validate-the-remote-repository) later on this page. 1. Click the **Advanced** configuration tab, then configure the following settings: * In the **Network** section: * Confirm **Lenient Host Authentication** is unchecked, so that your credentials are not forwarded across the redirect. * Optionally check **Enable Cookie Management**. JFrog recommends this for remote repositories that involve redirects. * In the **Others** section: * Check **Bypass HEAD Requests**, so that Artifactory retrieves each tarball with a GET request instead of probing with a HEAD request first. * Uncheck **Block Mismatching Mime Types**. * Check **Disable URL Normalization**, so that Artifactory does not rewrite the pre-signed redirect URL. 1. Click **Create Remote Repository**. These settings are required because Chainguard Libraries stores artifacts in Cloudflare R2. A tarball download from `libraries.cgr.dev` returns a 302 redirect to a pre-signed URL on a different host, and the redirect response itself is an HTML document. Without these settings, Artifactory may rewrite the pre-signed URL, forward your credentials across the redirect, or cache the redirect response in place of the tarball. A cached redirect response fails npm's integrity checksum check at install time. Create a virtual repository, or add the remote repository to an existing virtual repository used for npm packages, to give your build tools a single access point. A virtual repository may also include your own private npm packages. 1. Click **Create a Repository** → **Virtual**. 1. Select **Npm**. 1. Set key to *javascript-all*. 1. Add `javascript-chainguard`. 1. Click **Create Virtual Repository**. If you are manually managing fallback, rather than following the recommended approach to use Chainguard's upstream fallback, you can configure an additional npm remote repository with lower priority. ### Validate the remote repository After creating and configuring the `javascript-chainguard` remote repository, validate that Artifactory is successfully proxying through to Chainguard before proceeding. A misconfigured remote repository fails silently; if any remote pointing at the public npm registry is still present, Artifactory resolves through it instead and the build succeeds with no visible error. This can result in pulling an unprotected package. Common sources of misconfiguration include invalid or expired credentials, an incorrect or incomplete URL, and misconfigured [settings in the Advanced tab](#initial-configuration-1). The Artifactory **Test** button on the repository configuration screen is not a reliable indicator; it may fail for a correctly configured repository, and may pass for an incorrectly configured one. Instead, use the following steps to verify that fetching an artifact through Artifactory produces the same checksum as fetching it directly from `libraries.cgr.dev`. 1. Fetch the artifact directly from Chainguard and compute its checksum, using the same credentials you configured in Artifactory. This example uses `picocolors-1.1.1`. You can substitute any artifact you know to be available. ```bash curl -sSf -L \ -u "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" \ https://libraries.cgr.dev/javascript/picocolors/-/picocolors-1.1.1.tgz \ | openssl dgst -sha512 -binary | base64 ``` 1. Fetch the same artifact through the Artifactory remote repository and compute its checksum: ```bash curl -sSf -L \ -H "Authorization: Bearer ${ARTIFACTORY_TOKEN}" \ https:///artifactory/api/npm/javascript-chainguard/picocolors/-/picocolors-1.1.1.tgz \ | openssl dgst -sha512 -binary | base64 ``` Replace `artifactory-host` with your Artifactory instance hostname, and replace `${ARTIFACTORY_TOKEN}` with your Artifactory identity token. 1. Test the virtual repository your build tools resolve against as well, so that you catch a stray remote still present in it: ```bash curl -sSf -L \ -H "Authorization: Bearer ${ARTIFACTORY_TOKEN}" \ https:///artifactory/api/npm/javascript-all/picocolors/-/picocolors-1.1.1.tgz \ | openssl dgst -sha512 -binary | base64 ``` The checksums returned by the commands must match. If the checksum from the Artifactory remote or virtual repository differ from the direct fetch, or if the Artifactory fetch fails entirely, review the following before proceeding: * URL: The remote repository URL must be set to `https://libraries.cgr.dev/javascript/`. * Credentials: You may need to regenerate your pull token with `chainctl auth pull-token --repository=javascript` and update the Artifactory repository credentials. Expired tokens fail silently. * Advanced configuration: Ensure all recommended Advanced settings from the [initial configuration steps](#initial-configuration-1) have been applied. * Corrupted cached tarballs: if the repository previously ran without these settings, Artifactory may still be serving a cached redirect response. In Artifactory, browse the `javascript-chainguard` remote cache and locate the affected `.tgz` artifacts. Right-click each artifact, select **Delete content**, then re-run your install. Do not proceed to virtual repository setup or build configuration until the checksums match. ### Build tool access The following steps allow you to determine the URL and authentication details for accessing the repository: 1. Click **Administration** in the top navigation bar. 1. Select **Repositories** in the left hand navigation. 1. Select the **Virtual** tab in the repositories view. 1. Locate the *javascript-all* repository. 1. Hover over the row and click the **...** in the last column on the right. 1. Select **Set Me Up** in the dialog. 1. Click **Generate Token & Create Instructions**. 1. Copy the generated token value to use as the password for authentication. 1. Click **Generate Settings**. 1. Copy the value from a **url** field. They are all identical. For example, `https://exampleorg.jfrog.io/artifactory/javascript-all/` with `exampleorg` replaced with the name of your organization. Use the URL of the virtual repository in the [build configuration](/chainguard/libraries/javascript/build-configuration/) and build a first test project. In a working setup the chainguard remote repository contains all libraries retrieved from Chainguard. ## Sonatype Nexus Repository [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) allows for merging multiple remote repositories as a repository group. The instructions on this page are based on the [Nexus documentation for npm](https://help.sonatype.com/en/npm-registry.html). The recommended approach is to use the Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). If you are instead configuring your own fallback in your repo manager, for initial testing it is advised to create a separate proxy repository for the npm Registry, a separate proxy repository Chainguard Libraries for JavaScript repository, and a separate repository group. ### Initial configuration Create a separate proxy repository for the Chainguard Libraries for JavaScript repository, and include it in a repository group: 1. Log in as a user with administrator privileges. 1. Access the **Server administration** and configuration section with the gear icon in the top navigation bar. Configure a proxy repository for the Chainguard Libraries for JavaScript repository: 1. Select **Repository - Repositories** in the left hand navigation. 1. Click **Create repository**. 1. Select the **npm (proxy)** recipe. 1. Configure the following: * **Name**: `javascript-chainguard` * **Proxy - Remote storage**: Add the URL `https://libraries.cgr.dev/javascript/`. * **HTTP - Authentication**: Select `Username` as the Authentication type, and provide the [username and password values as retrieved with chainctl](/chainguard/libraries/introduction/access/). 1. Click **Create repository**. Create a repository group, or add to an existing repository group: 1. Select **Repository - Repositories** in the left hand navigation. 1. Click **Create repository**. 1. Select the **npm (group)** recipe. 1. Configure the following: * **Name**: `javascript-all` * Under **Group - Member repositories**, move the new repository `javascript-chainguard` to the right to include it in the group. Position `javascript-chainguard` at the top of the list using the arrow controls. Repository groups can include multiple repositories, such as hosted repositories for private packages or additional proxy repositories. In a typical configuration, the Chainguard repository is placed first to ensure packages are retrieved through Chainguard when available. If you are manually managing fallback, you can configure an additional npm proxy repository and add it to the group after `javascript-chainguard`. ### Build tool access The following steps allow you to determine the URL and authentication details for accessing the repository: 1. Click **Browse** in the **Welcome** view or the browse icon (cube) in the top navigation bar. 1. Locate the **URL** column for the `javascript-all` repository group and click **copy**. For example, `https://repo.example.com/repository/javascript-all/` with `repo.example.com` replaced with the hostname of your repository manager. 1. Copy the URL in the dialog. 1. Use your configured username and password, unless **Security** > **Anonymous Access** > **Access** > **Allow anonymous users to access the server** is activated. Details vary based on your configured authentication system. Use the URL of the repository group, such as `https://repo.example.com/repository/javascript-all/` in the [build configuration](/chainguard/libraries/javascript/build-configuration/) and build a first test project. In a working setup the `javascript-chainguard` proxy repository contains all libraries retrieved from Chainguard. ## Google Artifact Registry Google Artifact Registry (GAR) is not an officially supported repository manager for Chainguard Libraries for JavaScript. However, it has been shown to work with the following configuration. [Google Artifact Registry](https://cloud.google.com/artifact-registry) supports the npm format for hosting artifacts in **Standard** repositories and proxying artifacts from public repositories in **Remote** repositories. Use **Virtual** repositories to combine them for consumption with npm and other build tools. The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single remote repository pointed at `https://libraries.cgr.dev/javascript/` rather than adding a separate public npm remote. A single remote on the plain `javascript/` path serves both Chainguard-built packages and policy-protected upstream packages through Chainguard's built-in fallback, so there is no need for a second remote or for disabling upstream validation. Refer to [Manually managing fallback](#manually-managing-fallback) if you need to control fallback ordering yourself. ### Initial configuration Use the following steps to add the Chainguard Libraries for JavaScript repository as a remote repository and expose it through a virtual repository. 1. Log in to the Google Cloud console as a user with administrator privileges. 1. Navigate to your project and find the **Artifact Registry** with the search. 1. Activate Artifact Registry if necessary. 1. Navigate to your project and find the **Secret Manager** with the search. 1. Activate **Secret Manager** if necessary. Before configuring the repositories, you must create a secret with the [password value as retrieved with chainctl](/chainguard/libraries/introduction/access/): 1. Navigate to the **Secret Manager** 1. Click **Create secret**. 1. Set the **Name** to `chainguard-libraries-javascript`. 1. Use the **Password** from chainctl output to set the **Secret value**. 1. Click **Create secret**. Navigate to Artifact Registry and select **Repositories** in the left hand navigation under the **Artifact Registry** label to configure a remote repository for Chainguard Libraries for JavaScript: 1. Click **+Create a Repository**. 1. Configure the repository: 1. **Name**: `javascript-chainguard` 1. **Format**: `npm` 1. **Mode**: `Remote` 1. **Remote repository source**: `Custom`. Set the URL for the Custom repository to `https://libraries.cgr.dev/javascript/`. 1. **Remote repository authentication mode**: Select `Authenticated`. 1. Set **Username for the upstream repository** to the [value as retrieved with chainctl](/chainguard/libraries/introduction/access/). 1. Select the *chainguard-libraries-javascript* secret in the list for the **Secret** input. 1. Choose a **Region** for your development in **Location type**. 1. Click **Create**. If you are manually managing fallback rather than using the [Chainguard Repository's built-in fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configure an additional remote repository `javascript-public` for the public npm registry. Combine the `javascript-chainguard` repository into a new virtual repository: 1. Click **+** to add another repository. 1. Set the **Name** to `javascript-all`. 1. Set the **Format** to `npm`. 1. Set the **Mode** to `Virtual`. 1. Click **Add upstream repository** in **Virtual upstream repositories**. 1. Click **Browse**, then locate and select the `javascript-chainguard` repository as **Repository 1** and set the **Policy name 1** to `javascript-chainguard`. 1. Add the public npm remote repository (`javascript-public`) as a second remote repository. Ensure `javascript-chainguard` maintains a higher priority. 1. Choose a **Region** for your development in **Location type**. 1. Click **Create**. ### Build tool access Use the URL of the virtual repository in the [build configuration](/chainguard/libraries/javascript/build-configuration/) and build a first test project. In a working setup the `javascript-chainguard` remote repository contains all libraries retrieved from Chainguard. ## AWS CodeArtifact AWS CodeArtifact is not an officially supported repository manager for Chainguard Libraries for JavaScript. However, it has been shown to work with the following configuration. Because CodeArtifact doesn't support proxying authenticated upstream repositories, these instructions show how to mirror JavaScript packages from Chainguard Libraries into CodeArtifact. After configuring a mirror, configure developers and CI systems to install packages from your private CodeArtifact repository. If you use Chainguard Repository with upstream fallback enabled, npm clients can point directly to the Chainguard endpoint and retrieve either Chainguard-built packages or policy-protected upstream packages through that single registry. Use the CodeArtifact mirroring workflow only if your organization needs to keep AWS CodeArtifact as the registry your developers install from. ### Prerequisites You will need the following: * An AWS account with the following IAM permissions: * `codeartifact:PublishPackageVersion` * `codeartifact:PutPackageMetadata` * `codeartifact:GetAuthorizationToken` * `codeartifact:GetRepositoryEndpoint` * A Chainguard account with entitlement to Chainguard Libraries for JavaScript and credentials you can use to authenticate to the registry. * The AWS CLI, Node.js and npm, `jq`, and `bash`. If you use pnpm lockfiles, you also need the Go `mikefarah` build of `yq` v4. This workflow supports both `package-lock.json` and `pnpm-lock.yaml`. For pnpm, the parser supports lockfile versions v5, v6, and v9. Scoped packages such as `@types/node` and `@babel/core` are also supported. > Note: This workflow preserves standard npm attestations embedded in tarballs, but Chainguard-specific registry metadata is not preserved in the mirrored repository. Because AWS CodeArtifact charges for storage and requests, you should monitor repository growth over time and clean up unused package versions as needed. ### Step 1: Create a CodeArtifact domain and repository You can create the CodeArtifact resources manually with the AWS CLI: ```bash export AWS_REGION="us-east-1" aws codeartifact create-domain \ --domain npm-mirror-test \ --region $AWS_REGION aws codeartifact create-repository \ --domain npm-mirror-test \ --repository cg-npm-packages \ --description "npm packages mirror from Chainguard" \ --region $AWS_REGION ``` After setup, export the values the mirroring workflow uses: ```bash export AWS_REGION="us-east-1" export CODEARTIFACT_DOMAIN="npm-mirror-test" export CODEARTIFACT_REPOSITORY="cg-npm-packages" export CGR_USER="your-chainguard-identity" export CGR_TOKEN="your-chainguard-token" ``` `CODEARTIFACT_DOMAIN_OWNER` is optional. If you do not set it, the workflow can use your current AWS account ID. ### Step 2: Mirror packages from a lockfile This guide uses a script named `npm-codeartifact-mirror.sh`. Access the bash script and learn more about it in [Chainguard's example on GitHub](https://github.com/chainguard-demo/platform-examples/tree/main/library-copy-aws-code-artifact). Before running the script, save it to a local working directory and make it executable: ```bash chmod +x npm-codeartifact-mirror.sh ``` The mirroring workflow reads a project lockfile, extracts package names and versions, checks whether each version already exists in CodeArtifact, downloads missing packages from Chainguard Libraries, and publishes them into your private CodeArtifact repository. Run the script against either an npm or pnpm lockfile: ```bash ./npm-codeartifact-mirror.sh ./package-lock.json ``` ```bash ./npm-codeartifact-mirror.sh ./pnpm-lock.yaml ``` If you do not pass a file path, the script automatically looks for `package-lock.json` and then `pnpm-lock.yaml` in the current directory. ### How mirroring behaves The mirroring workflow runs in multiple passes. On the first pass, it skips versions that already exist in CodeArtifact and attempts to download and publish everything else. If a package is not yet available because Chainguard is still ingesting it from upstream, the workflow queues that package for a later retry. By default, the workflow retries unavailable packages up to four times with a 30-second delay between passes. You can tune this behavior with `INGEST_MAX_PASSES` and `INGEST_RETRY_DELAY`. For transient npm transport failures such as socket timeouts, rate limits, or 5xx responses, you can also tune `NPM_FETCH_RETRIES` and `NPM_FETCH_TIMEOUT`. ### Step 3: Verify mirrored packages After the mirror completes, use the AWS CLI to verify that packages were published to CodeArtifact: ```bash aws codeartifact list-packages \ --domain $CODEARTIFACT_DOMAIN \ --repository $CODEARTIFACT_REPOSITORY \ --format npm aws codeartifact list-package-versions \ --domain $CODEARTIFACT_DOMAIN \ --repository $CODEARTIFACT_REPOSITORY \ --package lodash \ --format npm ``` For scoped packages, use the `--namespace` parameter: ```bash aws codeartifact list-package-versions \ --domain $CODEARTIFACT_DOMAIN \ --repository $CODEARTIFACT_REPOSITORY \ --package fs-minipass \ --namespace isaacs \ --format npm ``` If any packages could not be mirrored, the workflow writes an unresolved package report. Each line includes the reason and package version, such as `404`, `ETARGET`, or `AUTH`. ### Step 4: Configure npm clients to install from CodeArtifact Once the packages are mirrored, authenticate to CodeArtifact and point npm at your repository endpoint: ```bash export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \ --domain $CODEARTIFACT_DOMAIN \ --query authorizationToken \ --output text \ --region $AWS_REGION) export CODEARTIFACT_REGISTRY=$(aws codeartifact get-repository-endpoint \ --domain $CODEARTIFACT_DOMAIN \ --repository $CODEARTIFACT_REPOSITORY \ --format npm \ --query repositoryEndpoint \ --output text \ --region $AWS_REGION) npm config set registry $CODEARTIFACT_REGISTRY npm config set //$(echo $CODEARTIFACT_REGISTRY | sed 's|https://||')/:_authToken $CODEARTIFACT_AUTH_TOKEN ``` You can then install packages as usual: ```bash npm install ``` As an alternative, you can use `aws codeartifact login` for npm: ```bash aws codeartifact login \ --tool npm \ --domain $CODEARTIFACT_DOMAIN \ --repository $CODEARTIFACT_REPOSITORY \ --region $AWS_REGION ``` ### Troubleshooting #### Authentication and AWS permissions If you cannot get a CodeArtifact authorization token or publish packages, verify your AWS credentials and IAM permissions. The workflow specifically relies on permissions such as `codeartifact:PublishPackageVersion`, `codeartifact:PutPackageMetadata`, `codeartifact:GetAuthorizationToken`, and `codeartifact:GetRepositoryEndpoint`. CodeArtifact tokens expire after 12 hours. If authentication starts failing later, request a new token and retry. #### Packages are still unavailable If packages remain unavailable after all retry passes, Chainguard may still be ingesting them, they may be in a cooldown period, they may be blocked for security reasons, or the requested version may not exist. Re-running the workflow later, or increasing `INGEST_MAX_PASSES` and `INGEST_RETRY_DELAY`, can help pick up packages that were still within the ingestion window during an earlier run. #### pnpm parsing issues If you use pnpm and the workflow reports zero packages or fails because of `yq`, make sure you are using the Go `mikefarah` build of `yq` v4 rather than the Python `kislyuk` implementation. --- ### Chainguard end-of-life grace period for containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period.md Last Modified: October 15, 2026 Tags: Chainguard Containers Typically, specific versions of software receive updates on a schedule for a set amount of time. Eventually, though, every version of software will stop receiving support. When project maintainers stop providing updates, it's known as the *End-of-Life* (EOL) stage. It's recommended that when a software version reaches the EOL phase, users should migrate their projects to a later version, as [EOL software is known to accumulate vulnerabilities](https://www.chainguard.dev/unchained/why-end-of-life-software-means-400-cves-per-year). However, there are cases where an organization may want to continue using a container image after it has reached end-of-life. This could be because an image reaches EOL before the organization's release schedule, or perhaps later image versions have one or more issues that prevent the organization from upgrading. To help in situations like this, Chainguard offers an end-of-life grace period for eligible Containers to all Chainguard Containers customers. This article provides an overview of Chainguard's EOL grace period, and also includes a brief introduction to using Chainguard's API to retrieve information about an image's EOL grace period status. ## Understanding Chainguard's EOL grace period Chainguard's EOL grace period gives customers access to new builds of container images whose primary package has entered its end-of-life phase for up to six months after they have reached EOL. During this time, Chainguard will address vulnerabilities and update any non-EOL packages within the container image (other than the image's primary package). Chainguard will continue to rebuild the image a maximum of six months after the primary package enters its EOL phase or until the build fails.
Diagram representing the lifecycle of an example successful build under the EOL Grace Period. A Ruby image whose primary package has reached EOL goes through an automated remediation and rebuild process, resulting in a successful build. Following that, the package dependencies have been successfully updated, although the image still contains the EOL version of Ruby. Because the build was successful, the EOL grace period continues.

> **Note**: Chainguard is **not** able to offer any exceptions to the 6 month limit for the EOL grace period. You will be able to find the end date of a given container image version's grace period in the [Chainguard Console](https://console.chainguard.dev/). From **Images**, select an image on the **Organization** tab. You'll be taken to that container image's **Tags** tab, and the end date of each grace period will be listed under the respective version:
Screenshot of a portion of an image's 'Tags' tab, showing the Grace Period end dates for several versions of the image.

As of this writing, a container image must meet four key requirements to be eligible for coverage under the EOL grace period: 1. It is listed as part of the current available or EOL versions for a version stream package present in our catalog 2. Has [multiple release tracks](/chainguard/containers/concepts/lifecycle-and-eol/versions/#how-versions-are-maintained) 3. Is within six months of their official EOL date (as declared by upstream project maintainers) 4. Its release and EOL dates are available on the [`endoflife.date`](https://endoflife.date/) website Be aware that the following are not covered by Chainguard's EOL grace period: * Updating an image’s EOL primary package. * Backporting or cherry-picking individual commits or patches to the EOL primary package. * Installing updated versions of packages in instances where the new package version no longer supports the EOL version of the image's main package (refer to the note). * Any package labeled end-of-life for more than 6 months by its open source creators or maintainers. Additionally, if a container image fails to build because underlying dependencies conflict with the primary package, it will no longer be supported. A failed build signals the end of support for that image.

Note: When an image’s primary package reaches EOL, newer versions of some dependency packages may no longer support it. In these cases, Chainguard does not update those packages solely to remediate CVEs, because doing so could introduce compatibility issues or result in a broken image. Instead, the existing package versions are retained to preserve image functionality while providing additional time to migrate to a supported image. You can attempt to install newer package versions at your own risk, using apk upgrade.

Diagram representing the lifecycle of an example unsuccessful build under the EOL Grace Period. A Python image whose primary package has reached EOL goes through an automated remediation and rebuild process, resulting in an unsuccessful build because the updated dependencies break the primary package. Because the build was unsuccessful, the EOL grace period ends and the customer should migrate to a newer package version.

If a dependency conflict prevents an image version from building successfully, the grace period will end immediately for that version. Chainguard will not attempt further updates or CVE remediations after a build failure. Additionally, If a container image fails to build due to dependency conflicts, its grace period ends immediately. This means no further updates or CVE remediations will be provided for that image version. After a grace period ends, your organization will retain access to the last successful build of the image. Chainguard will stop providing updates and CVE remediations; although the image will remain usable, it won't receive any ongoing security maintenance. Once an image's grace period has ended, we strongly recommend upgrading to a supported version as soon as possible. ### Planning for and managing an EOL grace period lifecycle To maximize the value of a grace period, we recommend the following: * **Before the grace period starts**: * Identify all dependent applications and services using the container image * Create an upgrade plan with realistic timelines * Document any known issues or compatibility requirements * Set up monitoring for the container images * **During the grace period**: * Test newer versions of the image in a development environment * Track and resolve any compatibility issues * Begin deploying updated versions to non-critical environments * Monitor for any build failures or dependency conflicts * **Before expiration**: * Complete all necessary testing of the new version * Schedule the production upgrade * Document any configuration changes needed * Prepare rollback procedures if needed ## Using the EOL grace period API Although the Chainguard Console is a useful interface, many customers would prefer to integrate EOL data with their preferred tools for faster, more convenient monitoring. For this reason, Chainguard has developed an API to serve customers with EOL data sufficient for monitoring the lifecycle of their images. The API endpoint you can reach for EOL data is [`Registry_ListEolTags`](/platform/api/spec/#tag/registry/GET/registry/v1/eoltags). This section outlines how you can use `curl` to make a call to this API endpoint. To follow along, you'll need to know the unique ID path (UIDP) of the container image repository you'd like to retrieve end-of-life data for. You can find this with the following `chainctl` command: ```shell chainctl images repos list -o wide ``` This command will return a table showing the UIDPs of every Chainguard Container your organization has access to: ```output ID | REGISTRY | REPO | BUNDLES | TIER -----------------------------------+---------------------+----------+-----------------------+-------------- ORGANIZATION_ID/165aEXAMPLE5b7ae | cgr.dev/example.com | nginx | application, featured | APPLICATION ORGANIZATION_ID/4408EXAMPLE4131a | cgr.dev/example.com | node | base | UNKNOWN ORGANIZATION_ID/37a2EXAMPLE0d419 | cgr.dev/example.com | python | base, featured | UNKNOWN ``` With the image repository ID, you can make a request to the API endpoint with a command like the following. Make sure to replace `$ORGANIZATION_ID/4408EXAMPLE4131a` with the container image repository UIDP you just found: ```shell curl -H "Authorization: Bearer $(chainctl auth token)" 'https://console-api.enforce.dev/registry/v1/eoltags?uidp.childrenOf=$ORGANIZATION_ID/4408EXAMPLE4131a' | jq . ``` Note that this example includes the `-H` argument to pass an authorization header to the API. This header is constructed with the `chainctl auth token` command which prints the local Chainguard token, allowing you to authenticate to the API. It also pipes the `curl` command's output into `jq`, a lightweight JSON processor, in order to make it easier to read. This command will return EOL data for each image within the repository, which will be delivered in the following format: ```output { "items": [ . . . { "id": "ORGANIZATION_ID/4408EXAMPLE4131a/9ef6EXAMPLE6265c", "name": "18.20.8-slim", "mainPackageName": "nodejs", "tagStatus": "TAG_IN_GRACE", "mainPackageVersion": { "eolDate": "2025-04-30", "exists": true, "fips": false, "lts": "2022-10-25", "releaseDate": "2022-04-19", "version": "18", "eolBroken": false }, "graceStatus": "GRACE_ACTIVE", "gracePeriodExpiryDate": "2025-10-30T00:00:00Z" }, . . . ] } ``` This example output is derived from an API call made on a `node` image repository, and the data returned presents a lot of useful information, some of which is highlighted here: * `id`: this is the UID of the specific image this block of data represents * `name`: the given container image's tag * `tagStatus`: whether the tag can support a grace period, with the following possible statuses: * `TAG_ACTIVE`: the tag is continuing to be built, but is not in a grace period * `TAG_IN_GRACE`: tag is in a grace period * `TAG_INACTIVE`: the tag is not in a grace period and no longer being built * `mainPackageVersion`: this section shows some information about the main package itself: * `eolDate`: the date on which the image's main package reached EOL * `lts`: the date on which this version of the main package entered its long-term support period * `releaseDate`: the date on which the main package's version was released * `graceStatus`: the status of whether or not the given container image is in an active grace period * `GRACE_ACTIVE`, as shown in this example, indicates the image is in an active grace period * any images that are not currently in a grace period but may be in the future will show `GRACE_ELIGIBLE` * any container images that will never enter a grace period will show `GRACE_NOT_ELIGIBLE` * `gracePeriodExpiryDate`: the date on which the image's grace period will end Of course, you won't use `curl` to interact with the Chainguard API in most scenarios. Instead, you'll likely have some kind of application that can ingest and process this EOL data. For example, your organization could create a Slackbot that fetches data from the Chainguard EOL grace period API and posts messages about EOL tags approaching their grace period expiration to a specified Slack channel. Chainguard's [API documentation](/platform/api/spec/) includes request samples for many languages and platforms, including Go, Python, and Java. ## Learn more Chainguard's EOL grace period gives customers the opportunity to continue to receive best-effort CVE remediated updates on EOL images, while they work on transitioning to a newer upstream version. For more information on the EOL grace period, [please contact us](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). Additionally, our doc outlining the [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/) can be helpful for understanding Chainguard’s approach to updates, releases, and versions within Chainguard Containers. Finally, our blog post [Why end-of-life software means 400+ CVEs per year](https://www.chainguard.dev/unchained/why-end-of-life-software-means-400-cves-per-year) explains how end-of-life software accumulates vulnerabilities. --- ### Understanding Chainguard's container image categories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/container-categories.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard Containers is a collection of curated, distroless container images designed with a focus on software supply chain security. Chainguard's container images are designed to be slim runtimes for production environments, emphasizing security and efficiency by removing unnecessary elements. Additionally, the images are designed to be easily integrated into existing workflows, helping organizations to build better, more secure software. Within the [Chainguard Containers Directory](https://images.chainguard.dev/), images are organized into five general categories (with some falling into multiple categories): * **Free** * **Base** * **Application** * **FIPS** * **AI** This conceptual article will outline each of these categories in turn, including their uses as well as examples of images from each category. It will also highlight important considerations one should make when using images from these categories. ## Free containers Chainguard offers a set of container images that are publicly available and don’t require authentication for download and use; they are free to use for everyone. We refer to these as our *Free Containers*, and they cover several use cases for different language ecosystems. Free Containers are limited to the latest build of a given image, and are always tagged as `latest` and `latest-dev`. You can access these images directly from Chainguard's registry from the `chainguard` repository. For example, to download the cURL Free image, you could run a command like the following: ```shell docker pull cgr.dev/chainguard/curl ``` To access any other image, you will need to do so through your organization's private Chainguard repository. The following example will pull the `chainguard-base` image from the `chainguard.edu` organization's repository: ```shell docker pull cgr.dev/chainguard.edu/chainguard-base ``` Note that you won't have access to the organization's repository used in this example, but if your organization has access to the `chainguard-base` image you will be able to pull this image using your organization's repository name in place of `chainguard.edu`. Chainguard's registry provides public access to all Free images, and provides customer access for Production images after logging in and authenticating. For a complete list of Free images that are currently available, check out the [**Free** category on Chainguard Containers Directory](https://images.chainguard.dev/?category=free). Registered users can also access all Free and available Production images in the [Chainguard Console](https://console.chainguard.dev/overview). After logging in you will be able to find all the currently available Free Containers in the **Public images** tab. ### Production containers The rest of Chainguard's container images, those that **are not** Free images and not included in the free tier of images, are referred to as *Production Containers*. Production images are enterprise-ready images that come with patch SLAs and features such as Federal Information Processing Standard (FIPS) readiness and unique time-stamped tags. Unlike Free images, which are typically paired with only the latest version of an upstream package, Production images offer specific major and minor versions of open source software. As with the Free Container category, any container image considered a Production image will also fall into at least one of the other categories listed in this guide. To view the Production container images that your organization has access to, select the appropriate organization in the drop-down menu above the left-hand navigation and then click the **Organization images** tab. ## Base containers Base Containers are meant to be extended by users with their own packages and applications. Examples include [chainguard-base](https://images.chainguard.dev/directory/image/chainguard-base/overview), [Go](https://images.chainguard.dev/directory/image/go/overview), and [Python](https://images.chainguard.dev/directory/image/python/overview). Chainguard is responsible for releasing fully-patched toolchains and Base Containers, while customers are responsible for patching any applications and dependencies they add to a Chainguard Container. It is recommended to use a fully-patched Chainguard toolchain image to build the application, and a fully-patched Chainguard Base container image to layer the final application on. When migrating to a Chainguard Base container image you should first check the images’s overview page on the Containers Directory for usage details and any compatibility notes. You should understand the libraries, runtime requirements, and operating system dependencies of the applications you plan to have running on the Base container image. It is a best practice to use the same versions of any languages or applications that will be running on the Chainguard Base container image as what is currently running in your environment. Do not upgrade language or application versions at the same time that you migrate. Following the migration, you should thoroughly test and monitor your application. If you need a package to use with your Chainguard Base Container, Wolfi packages are available using `apk`. Ensure you only use Wolfi packages, as Alpine APK’s are not compatible with Wolfi. Additionally, it is important to note that vendor-provided packages need to be glibc-based and their functionality should be fully tested along with the application. For additional tips, please refer to our guide on [Troubleshooting apko builds](/open-source/build-tools/apko/troubleshooting/). > **Note**: Base Containers often require more customization by the user. Be aware that Chainguard offers a customization platform called [Custom Assembly](/chainguard/containers/custom-assembly/overview/) to streamline this requirement without customers having to stand up their own custom pipelines. ## Application containers In contrast with Base container images, which are intended to be built upon, Application Containers are designed to be used directly, often by plugging into systems like Helm. Some examples of Chainguard's Application images include [nginx](https://images.chainguard.dev/directory/image/nginx/overview), [Fulcio](https://images.chainguard.dev/directory/image/fulcio/overview), and [apko](https://images.chainguard.dev/directory/image/apko/overview). When it comes to maintaining Application container images, Chainguard is responsible for rebuilding the upstream project with the latest toolchain and patching static and dynamic dependencies where such a change is non-breaking. Customers are responsible for tracking a supported version of the Chainguard Container. When migrating to a Chainguard Application container image you should first check the image’s overview page on the Containers Directory for usage details and any compatibility notes. There may be user ID, permissions, or volume path differences with the Chainguard image that you should be aware of. It is a best practice to use the same version of the Chainguard Application container image as what is currently running in your environment. ## AI container Artificial intelligence and machine learning (AI/ML) systems are used in a wide variety of high-stakes applications, including information retrieval, medical research, fraud identification, military operations, autonomous vehicle navigation, and more. If compromised by malicious actors, the consequences could be disastrous and far reaching. Due to their unique features and uses, these systems often pose a greater risk than traditional software systems. Chainguard offers a suite of CPU- and GPU-enabled AI Containers which can help to mitigate these risks. Some of these AI container images include [NeMo](https://images.chainguard.dev/directory/image/nemo/overview), [PyTorch](https://images.chainguard.dev/directory/image/pytorch/overview), and [TensorFlow](https://images.chainguard.dev/directory/image/tensorflow/overview). These images are hardened, minimal, and optimized for efficient AI development and deployment. By leveraging Chainguard AI Containers, organizations can confidently secure their AI infrastructure, streamline vulnerability management, and maintain high performance with low-to-zero vulnerabilities. Rather than starting with tens, dozens, or hundreds of CVEs in your application or pipeline, you start with a clean slate. To learn more about Chainguard's AI Containers and their uses, we encourage you to check out our course on [Securing the AI/ML Supply Chain](https://courses.chainguard.dev/securing-ai). ## FIPS containers FIPS — or, Federal Information Processing Standards — are publicly announced standards developed by the National Institute of Standards and Technology (NIST). Chainguard offers images that use FIPS-validated cryptographic software modules to help users ensure that their applications meet FIPS standards. Chainguard offers FIPS versions of many of its container images, so FIPS Containers will fall into more than one category. Some Chainguard container images with FIPS variants are [nginx](https://images.chainguard.dev/directory/image/tensorflow/overview), [PHP](https://images.chainguard.dev/directory/image/php-fips/overview), and [PyTorch](https://images.chainguard.dev/directory/image/pytorch-fips/overview). For more information, please refer to our conceptual article on [FIPS Chainguard Containers](/platform/fips/fips-images/). ## Container image type considerations There is some overlap between the different container image categories outlined in this guide. For example, the [PyTorch](https://images.chainguard.dev/directory/image/pytorch/overview) image is an AI image, but it is also part of our free tier, meaning it's also a Free image. Many customers use both Application and Base Containers. Note that it often takes more time to migrate your applications to a Base container image in comparison to an Application image due to the complexity of coordinating multiple teams, testing, and release schedules. We recommend starting with and migrating to Application container images first while your teams get trained and onboarded with Base container images. A common requirement for many customers is to add a company-specific certificate or other security related content. The three most common ways to accomplish this are: 1. Using [incert](/chainguard/containers/custom-assembly/incert-custom-certs/) 2. Running the `update-ca-certificates` utility within a Dockerfile 3. Using the Java `keytool` utility within a Dockerfile The process of adding or updating certificates, configuring APK repositories, and implementing other organization-specific customizations into an image is commonly known as creating a "Golden Image". This approach enables these standard modifications to be applied once and then distributed across all teams, thereby reducing the risk of errors and minimizing friction during the migration process. ## Learn more By reading this guide, you should have a better understanding of how Chainguard categorizes its container images and what these categories mean. To recap: * **Free**: Chainguard's free tier of container images * **Base**: Containers meant to be built upon * **Application**: Containers meant to be run directly * **FIPS**: Contain FIPS-validated cryptographic modules * **AI**: Containers for running AI/ML workloads securely For more information on Chainguard Containers, please refer to the other resources in the [About section](/chainguard/containers/concepts/). In particular, you may find our conceptual article on [Chainguard's Shared Responsibility Model](/chainguard/containers/concepts/shared-responsibility-model/) to be of interest. --- ### Global configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/java/global-configuration.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Java Java and JVM library consumption in a large organization is typically managed by a repository manager. Commonly used repository manager applications are [Cloudsmith](https://cloudsmith.com/), [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs), [JFrog Artifactory](https://jfrog.com/artifactory/), and [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository). The repository manager acts as a single point of access for developers and development tools to retrieve the required libraries. The recommended approach is to use the [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) feature of Chainguard Repository, which allows you to configure your repository manager with a single upstream pointed at `https://libraries.cgr.dev/java/` without a separate fallback to Maven Central. The Chainguard Repository handles fallback and policy enforcement; your repository manager handles local caching and access control. Chainguard retrieves packages from the public Maven Central repository on your behalf when upstream fallback is enabled. This includes protections such as malware scanning and a cooldown period for newly published packages. At a high level, adopting the use of Chainguard Libraries consists of the following steps: * Configure your environment to use `https://libraries.cgr.dev/java/` as the single upstream source for Java package retrieval. This can be done either: * As a remote repository in your repository manager, or * Directly in your Java build configuration (for example, Maven or Gradle). * Additional steps depend on your specific environment and preferences, and can include the following optional measures: * Remove all cached artifacts for Maven Central. This step ensures that any libraries you pull are from Chainguard Libraries, and not existing cached artifacts from upstream. * Remove any repositories that are no longer desired or necessary, depending on your organization's preferences. This page explains how to use Chainguard Libraries for Java with a repository manager. If your organization does not use a repository manager, you can pull directly from Chainguard. Refer to the [direct access documentation for build tools](/chainguard/libraries/java/build-configuration/#direct-access) for more information. ## Configure access with a repository manager Refer to the Build configuration docs for instructions on configuring repository manager access for [Maven](/chainguard/libraries/java/build-configuration/#repo-manager-maven), [Gradle](/chainguard/libraries/java/build-configuration/#repo-manager-gradle), and [Bazel](/chainguard/libraries/java/build-configuration/#repo-manager-bazel). ## Manually managing fallback Chainguard recommends using the Chainguard Repository's built-in [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) rather than configuring a fallback to the public Maven Central registry in your repo manager. Configuring your own fallback bypasses the protection that the Chainguard Repository provides. However, if you intentionally want to manage fallback ordering yourself, you can configure `https://libraries.cgr.dev/java/` as a remote repository alongside your Maven upstream, and combine them in a virtual or group repository with Chainguard as the first priority. If you configure a fallback to Maven Central, packages sourced from that registry are not covered by Chainguard's malware-resistance guarantees. The per-tool instructions on this page vary by repository manager. The JFrog Artifactory and Sonatype Nexus Repository sections use the recommended upstream fallback approach, which Chainguard has tested with those tools. The Cloudsmith and Google Artifact Registry sections use the manual Maven Central fallback pattern; Chainguard has not yet tested the upstream fallback with those tools. ## Cloudsmith [Cloudsmith](https://cloudsmith.com/) supports Maven repositories for proxying and hosting. Refer to the [Maven Repository documentation](https://help.cloudsmith.io/docs/maven-repository) and the [Maven Upstream documentation](https://help.cloudsmith.io/docs/upstream-proxying-caching#create-a-maven-upstream) for Cloudsmith for more information. Cloudsmith supports combining repositories by defining multiple upstream repositories. > **Note:** Chainguard has not yet tested the recommended [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) approach — a single upstream pointed at `https://libraries.cgr.dev/java/` — with Cloudsmith. The following steps instead configure Maven Central as an upstream fallback in your repository manager. Packages sourced from Maven Central are not covered by Chainguard's malware-resistance guarantees. ### Initial configuration Use the following steps to add a repository with the Maven Central Repository and the Chainguard Libraries for Java repository as Maven upstream repositories. Configure a `java-all` repository: 1. Log in as a user with administrator privileges. 1. Select the **Repositories** tab near the top of the screen. 1. On the **Repositories** page, click the **+ New repository** button. 1. Enter the name `java-all` for your new repository. The name should include `java` to identify the ecosystem. This convention helps avoid confusion since repositories in Cloudsmith are multi-format. 1. Select a storage region that is appropriate for your organization and infrastructure. 1. Click **+Create Repository**. Configure an upstream proxy for the Maven Central Repository: 1. Click the name of the new `java-public` repository on the repositories page to configure it. 1. Click the **Upstreams** tab, then click **+Add Upstream Proxy**. 1. Configure an upstream proxy with the format **Maven**. 1. Configure another upstream proxy with the following: * **Name**: `java-public` * **Priority**: `2` * **Upstream URL**: `https://repo1.maven.org/maven2/` * **Mode**: Cache and Proxy 1. Click **Create Upstream Proxy**. Configure an upstream proxy for the Chainguard Libraries for Java repository: 1. Click the name of the new `java-chainguard` repository on the repositories page to configure it. 1. Click the **Upstreams** tab, then click **+Add Upstream Proxy**. 1. Configure an upstream proxy with the format **Maven** and the following details: * **Name**: `java-chainguard` * **Priority**: `1` * **Proxy URL**: `https://libraries.cgr.dev/java/` * **Mode**: Cache and Proxy * **Authentication Settings**: Enter the **Username** and **Password** value from [Chainguard Libraries access](/chainguard/libraries/introduction/access/). 1. Click **Create Upstream Proxy**. 1. If you are using the separate repository with remediated Java libraries, repeat the preceding steps to create remote repository named `java-chainguard-remediated` with a URL set to `https://libraries.cgr.dev/java-remediated/`. Use the same authentication details. Use this setup for initial testing with Chainguard Libraries for Java. For production usage, add the `java-chainguard` upstream proxy to your production repository. ### Build tool access The following steps allow you to determine the URL and authentication details for accessing the repository: 1. Select the **Packages** tab. 1. Click **Push/Pull Packages**. 1. Choose the format **Maven**. 1. Copy the value in the `` tag from the XML snippet with the `` entry. For example, `https://dl.cloudsmith.io/basic/exampleorg/java-all/maven/` with `exampleorg` replaced with the name of your organization. Note that the name of the repository `java-all` as well as `maven` as identifier for the format are part of the URL. 1. Copy the username and password values block from the second code snippet for authentication after choosing the desired authentication of **Default** or **API Key**. Choose a different format and the equivalent sections if you are using another build tools such as Gradle. Use the URL of the repository, the username, and the password for the server authentication block in the [build configuration](/chainguard/libraries/java/build-configuration/) and build a first test project. In a working setup, all libraries retrieved from Chainguard are tagged with the name of the upstream proxy. ## Google Artifact Registry [Google Artifact Registry](https://cloud.google.com/artifact-registry) supports the Maven format for hosting artifacts in **Standard** repositories and proxying artifacts from public repositories in **Remote** repositories. Use **Virtual** repositories to combine them for consumption with Maven and other build tools. Use the [Java package documentation for Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/java) as the starting point for more details. > **Note:** Chainguard has not yet tested the recommended [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) approach — a single upstream pointed at `https://libraries.cgr.dev/java/` — with Google Artifact Registry. The following steps instead configure Maven Central as an upstream fallback in your repository manager. Packages sourced from Maven Central are not covered by Chainguard's malware-resistance guarantees. ### Initial configuration Use the following steps to add the Maven Central Repository and the Chainguard Libraries for Java repository as remote repositories and combine them as a virtual repository: 1. Log in to the Google Cloud console as a user with administrator privileges. 1. Navigate to your project and find the **Artifact Registry** with the search. 1. Activate Artifact Registry if necessary. 1. Navigate to your project and find the **Secret Manager** with the search. 1. Activate **Secret Manager** if necessary. Before configuring the repositories, you must create a secret with the [password value as retrieved with chainctl](/chainguard/libraries/introduction/access/): 1. Navigate to the **Secret Manager** 1. Click **Create secret**. 1. Set the **Name** to `chainguard-libraries-java`. 1. Set the **Secret** value to the password from your `chainctl` output. 1. Click **Create secret**. Navigate to Artifact Registry and select **Repositories** in the left hand navigation under the **Artifact Registry** label to configure a remote repository for the Maven Central Repository: 1. Click **Create a Repository** (or the **+** button). 1. Configure the repository: * **Name**: `java-public` * **Format**: Maven * **Mode**: Remote * **Remote repository source**: Maven Central * **Location type > Region**: Select a region. 1. Click **Create**. Configure a remote repository for the Chainguard Libraries for Java repository: 1. Click **+** to add another repository. 1. Configure the repository: * **Name**: `java-chainguard` * **Format**: `Maven` * **Mode**: Remote * **Remote repository source**: Custom * **Custom repository URL**: `https://libraries.cgr.dev/java/` * **Remote repository authentication mode**: Authenticated * **Username for the upstream repository**: Set this to the [value as retrieved with chainctl](/chainguard/libraries/introduction/access/). * **Secret**: Select the `chainguard-libraries-java` secret in the list. * **Location type > Region**: Select the same region configured for your `java-public` repository. 1. Click **Create**. 1. If you are using the separate repository with remediated Java libraries, repeat the preceding steps to create remote repository named `java-chainguard-remediated` with a URL set to `https://libraries.cgr.dev/java-remediated/`. Use the same authentication details. Combine the repositories in a new virtual repository: 1. Click the **+** button to add another repository. 1. Configure the repository: * **Name**: `java-all` * **Format**: Maven * **Mode**: Virtual 1. Under **Virtual upstream repositories**, click **Add upstream repository**. 1. Use the **Browse** button to locate and select the `java-chainguard` repository as **Repository 1** and set the **Policy name 1** to `java-chainguard`. 1. Use the **Browse** button to locate and select the `java-public` repository as **Repository 1** and set the **Policy name 1** to `java-public`. 1. Under **Virtual upstream repositories**, click **Add upstream repository**. 1. Set the **Priority** value for the `java-chainguard` policy name to a higher value than the `java-public` priority value. * If you are using the remediated repository, add the `java-chainguard-remediated` repository and ensure it is the first in the displayed list. If not, ensure the `java-chainguard` repository is first. 1. Under **Location type**, choose the same suitable **Region** for your development as configured for the `java-public` repository. 1. Click **Create**. ### Build tool access The following steps allow you to configure your build tool for accessing the repository: 1. Navigate to Artifact Registry and select **Repositories** in the left hand navigation under the **Artifact Registry** label. 1. Click the `java-all` repository name in the list of repositories. 1. Click **Setup instructions** and follow the documentation. Note that you must add the extension `com.google.cloud.artifactregistry:artifactregistry-maven-wagon` to each project. In a working setup, the `chainguard` remote repository contains all artifacts retrieved from Chainguard. ## JFrog Artifactory [JFrog Artifactory](https://jfrog.com/artifactory/) supports Maven repositories for proxying and hosting, and virtual repositories to combine them. Refer to the [Maven Repository documentation for Artifactory](https://docs.jfrog.com/artifactory/docs/maven-repositories) for more information. If you follow the recommended approach to rely on Chainguard Repository's upstream fallback, disable or remove any existing Artifactory remote repository that points at Maven Central, and remove it from the virtual repository your builds resolve against. A remote pointing directly at Maven Central bypasses those protections. Since Artifactory resolves through the virtual repository in order, a misconfiguration can result in Artifactory serving an unprotected package. ### Initial configuration Use the following steps to add Chainguard Libraries for Java as a remote repository: 1. Log in as a user with administrator privileges. 1. Click **Administration** in the top navigation bar. 1. Select **Repositories** in the left hand navigation. Configure a remote repository for the Chainguard Libraries for Java repository: 1. Click **Create a Repository** and choose the **Remote** option. 1. Configure the repository: * **Package type**: Maven * **Repository Key**: `java-chainguard` * **URL**: `https://libraries.cgr.dev/java/` * **User Name** and **Password / Access Token**: Set to the [values as retrieved with chainctl](/chainguard/libraries/introduction/access/). * Deactivate **Maven Settings - Handle Snapshots**. 1. Note: The **Test** button is not a reliable indicator; to verify your setup, refer to the [validation steps](#validate-the-remote-repository) later on this page. 1. Click the **Advanced** configuration tab, then configure the following settings: * In the **Network** section: * Confirm **Lenient Host Authentication** is unchecked, so that your credentials are not forwarded across the redirect. * Optionally check **Enable Cookie Management**. JFrog recommends this for remote repositories that involve redirects. * In the **Others** section: * Check **Bypass HEAD Requests**, so that Artifactory retrieves each artifact with a GET request instead of probing with a HEAD request first. * Uncheck **Block Mismatching Mime Types**. * Check **Disable URL Normalization**, so that Artifactory does not rewrite the pre-signed redirect URL. 1. Click **Create Remote Repository**. These settings are required because Chainguard Libraries stores artifacts in Cloudflare R2. An artifact download from `libraries.cgr.dev` returns a 302 redirect to a pre-signed URL on a different host, and the redirect response itself is an HTML document. Without these settings, Artifactory may rewrite the pre-signed URL, forward your credentials across the redirect, or cache the redirect response in place of the POM, JAR, or checksum file. A cached redirect response fails checksum verification at build time. If you are manually managing fallback, you can configure an additional remote repository for Maven Central with lower priority. Make sure to deactivate **Maven Settings - Handle Snapshots** in the remote repository. Create a virtual repository to give your build tools a single access point: 1. Click **Create a Repository** and choose the **Virtual** option. 1. Configure the repository: * **Package type**: Maven * **Repository Key**: `java-all` 1. Scroll down to the **Repositories** section. 1. Add the `java-chainguard` repository. 1. Click **Create Virtual Repository**. ### Add remediated libraries This section applies only if you use the separate repository of [remediated Java libraries](/chainguard/libraries/policies-and-security/cve-remediation/). It requires a second remote repository, added to the virtual repository ahead of `java-chainguard` so that remediated versions resolve first. 1. Repeat the [remote repository steps](#initial-configuration-2) to create a second remote repository, named `java-chainguard-remediated`, with the **URL** set to `https://libraries.cgr.dev/java-remediated/`. Use the same authentication details and the same Advanced settings. 1. Open the `java-all` virtual repository and scroll down to the **Repositories** section. 1. Add the `java-chainguard-remediated` repository, then drag it above `java-chainguard` so that it is first in the displayed list. Use the icon to the right of the repository name to reorder the list. 1. Click **Save**. ### Validate the remote repository After creating the `java-chainguard` remote repository, validate that Artifactory is successfully proxying through to Chainguard before proceeding. A misconfigured remote repository fails silently; if any remote pointing at Maven Central is still present, Artifactory resolves through it instead and the build succeeds with no visible error. This can result in pulling an unprotected package. Common sources of misconfiguration include invalid or expired credentials, or an incorrect or incomplete repository URL. The Artifactory **Test** button on the repository configuration screen is not a reliable indicator; it may fail for a correctly configured repository, and may pass for an incorrectly configured one. Instead, use the following steps to verify that fetching an artifact through Artifactory produces the same checksum as fetching it directly from `libraries.cgr.dev`. #### 1. Compute the artifact's checksum Fetch the artifact directly from Chainguard and compute its checksum. This example uses `junit-4.13.2.jar`. You can substitute any artifact you know to be available. ```bash curl -sSf -L \ -u "${CHAINGUARD_JAVA_IDENTITY_ID}:${CHAINGUARD_JAVA_TOKEN}" \ https://libraries.cgr.dev/java/junit/junit/4.13.2/junit-4.13.2.jar \ | sha256sum ``` #### 2. Compute the same artifact's checksum from Artifactory Fetch the same artifact through the Artifactory remote repository and compute its checksum: ```bash curl -sSf -L \ -u "${ARTIFACTORY_USER}:${ARTIFACTORY_TOKEN}" \ https:///artifactory/java-chainguard/junit/junit/4.13.2/junit-4.13.2.jar \ | sha256sum ``` Replace `artifactory-host` with your Artifactory instance hostname. The checksums returned by the commands must match. If the checksum from the Artifactory remote repository differs from the direct fetch, or if the Artifactory fetch fails entirely, review the following before proceeding: * URL: The remote repository URL must be set to `https://libraries.cgr.dev/java/`. * Credentials: You may need to regenerate your pull token with `chainctl auth pull-token --repository=java` and update the Artifactory repository credentials. Expired tokens fail silently. * Advanced configuration: Ensure all recommended Advanced settings from the [remote repository configuration steps](#initial-configuration-2) have been applied. * Corrupted cached artifacts: if the repository previously ran without these settings, Artifactory may still be serving a cached redirect response. In Artifactory, browse the `java-chainguard` remote cache and locate the affected files. Right-click each artifact, select **Delete content**, then re-run your build. Do not proceed to virtual repository setup or build configuration until the checksums match. ### Build tool access The following steps allow you to determine the URL and authentication details for accessing the repository: 1. Click **Administration** in the top navigation bar. 1. Select **Repositories** in the left hand navigation. 1. Select the **Virtual** tab in the repositories view. 1. Locate the `chainguard-maven` repository. 1. Hover over the row and click the **...** in the last column on the right. 1. Select **Set Me Up** in the dialog. 1. Click **Generate Token & Create Instructions**. 1. Copy the generated token value to use as the password for authentication. 1. Click **Generate Settings**. 1. Copy the value from a **url** field. They are all identical. For example, `https://exampleorg.jfrog.io/artifactory/java-all/` with `exampleorg` replaced with the name of your organization. Use the URL of the virtual repository in the [build configuration](/chainguard/libraries/java/build-configuration/) and build a first test project. In a working setup the chainguard remote repository contains all libraries retrieved from Chainguard. ## Sonatype Nexus Repository [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) includes a `maven-public` repository group out of the box. It groups access to the Maven Central Repository from the `maven-central` repository with the internal `maven-releases` and `maven-snapshot` repositories. Refer to the [Maven Repositories documentation for Nexus](https://help.sonatype.com/en/maven-repositories.html) for more information. If you are using this group, you can add a proxy repository for Chainguard Libraries for Java repository for production use. The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single proxy repository pointed at `https://libraries.cgr.dev/java/` rather than adding a separate Maven Central proxy. Refer to [Manually managing fallback](#manually-managing-fallback) if you need to control fallback ordering yourself. ### Initial configuration Use the following steps to add Chainguard Libraries for Java as a proxy repository. If you are configuring your own fallback rather than using upstream fallback, for initial testing create a separate proxy repository for the Maven Central Repository, a separate proxy repository for Chainguard Libraries for Java, and a separate repository group. 1. Log in as a user with administrator privileges. 1. Click the gear icon in the top navigation bar to access **Server administration**. Configure a remote repository for the Chainguard Libraries for Java repository: 1. Select **Repository - Repositories** in the left hand navigation. 1. Click **Create repository**, then select the `maven2 (proxy)` recipe. 1. Configure the repository: * **Name**: `java-chainguard` * **Maven 2 - Version policy**: `Release`. * **Proxy - Remote storage**: Add the URL `https://libraries.cgr.dev/java/` * **HTTP - Authentication** Select the `username` **Authentication type**, and provide the [username and password values as retrieved with chainctl](/chainguard/libraries/introduction/access/). 1. Click **Create repository**. 1. If you are using the separate repository with remediated Java libraries, repeat the preceding steps to create remote repository named `java-chainguard-remediated` with a URL set to `https://libraries.cgr.dev/java-remediated/`. Use the same authentication details. If you are manually managing fallback, you can configure an additional `java-public` remote repository for Maven Central with lower priority. ### Combine a new repository group Combine a new repository group and add the repositories: 1. Select **Repository - Repositories** in the left hand navigation. 1. Click **Create repository**, then select the `maven2 (group)` recipe. 1. Configure the repository: * **Name**: `java-all` * Under **Group - Member repositories**, move the new repository `java-chainguard` to the right. If you are using the remediated repository, move the `java-chainguard-remediated` repository to the right, above the `java-chainguard` repository. ### Build tool access The following steps allow you to determine the URL and authentication details for accessing the repository: 1. Click **Browse** in the **Welcome** view or the browse icon (cube) in the top navigation bar. 1. Locate the **URL** column for the `java-all` repository group and click **Copy**. * For example, `https://repo.example.com/repository/java-all/` (with `repo.example.com` replaced with the hostname of your repository manager). 1. Use your configured username and password, unless **Security > Anonymous Access > Access > Allow anonymous users to access the server** is activated. Details vary based on your configured authentication system. Use the URL of the repository group, such as `https://repo.example.com/repository/java-all/` or `https://repo.example.com/repository/maven-public/` in the [build configuration](/chainguard/libraries/java/build-configuration/) and build a first test project. In a working setup the `java-chainguard` proxy repository contains all libraries retrieved from Chainguard. --- ### Global configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/python/global-configuration.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Python Python library consumption in a large organization is typically managed by a repository manager. Commonly used repository manager applications are [Cloudsmith](https://cloudsmith.com/), [JFrog Artifactory](https://jfrog.com/artifactory/), and [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository). The repository manager acts as a single point of access for developers and development tools to retrieve the required libraries. The recommended approach is to use the [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) feature of Chainguard Repository, which allows you to configure your repository manager with a single upstream pointed at `https://libraries.cgr.dev/python/`. The Chainguard Repository handles fallback and policy enforcement; your repository manager handles local caching and access control. Chainguard proxies and serves packages from the public PyPI repository on your behalf when upstream fallback is enabled. All packages served from Chainguard are protected with malware scanning and a configurable cooldown policy. At a high level, adopting the use of Chainguard Libraries consists of the following steps: * Configure your environment to use `https://libraries.cgr.dev/python/` as the single upstream source for Python package retrieval. * Add the public [PyPI](https://pypi.org/) repository as a remote repository. * Create a group, virtual, or polyglot repository combining these repository sources with any desired internal repositories. Configure the Chainguard Libraries repository as the first choice for any library access after any desired internal repositories. You should also: * Remove all prior cached artifacts in the virtual server or proxy public repository. This step reduces confusion about the origin of libraries and assists technical evaluation and adoption of Chainguard Libraries. * Remove any repositories that are no longer desired or necessary. Depending on your library requirements, this step can result in removal of some proxy repositories or even removal of all proxy repositories. If your organization does not use a repository manager, you can still use Chainguard Libraries. However, this approach requires configuration of multiple build and development platforms and utilities to use Chainguard Libraries. For this reason, adopting the use of a repository manager is the recommended approach. Refer to the [direct access documentation for build tools](/chainguard/libraries/python/build-configuration/#direct-access) for more information. ## Manually managing fallback Chainguard recommends using the [Chainguard Repository built-in upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) rather than configuring a public PyPI fallback in your repository manager. Configuring your own fallback bypasses the protection and policy behavior provided by Chainguard Repository. However, if you intentionally want to manage fallback ordering yourself, you can continue using the repository manager patterns described on this page to combine Chainguard and PyPI sources. ### Updating lockfile hashes If you are migrating an existing Python project to Chainguard Libraries through a repository manager, your lockfile likely contains integrity hashes generated against packages previously downloaded from PyPI or through your repository manager. The [`chainctl libraries update-hashes` command](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/) automates lockfile hash updates for all supported Python lockfile formats. When you are using a repository manager, pass the full repository manager URL with `--registry-url` and authenticate with one of the supported methods: `--username` and `--password`, `--token`, or a `.netrc` entry for the registry host. For example: ```bash chainctl libraries update-hashes \ --registry-url https://repo.example.com:8443/repository/python-all/ \ --token "$REPO_TOKEN" ``` > **Note**: Running `chainctl libraries update-hashes` requires the `libraries.python.pull` permission or the Owner role. After updating the lockfile, keep your repository manager configuration in place and reinstall through the same repository manager endpoint to apply the updated hashes. Learn more in the [Build configuration page](/chainguard/libraries/python/build-configuration/#updating-lockfile-hashes) and in the [chainctl docs](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/). ## Cloudsmith [Cloudsmith](https://cloudsmith.com/) supports Python repositories for proxying and hosting and polyglot repositories that combine multiple repositories sources with compatible formats. Refer to the [Cloudsmith Python Repository documentation](https://help.cloudsmith.io/docs/python-repository) and the [Cloudsmith documentation for creating a repository](https://help.cloudsmith.io/docs/create-a-repository) for more information. The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single upstream proxy pointed at `https://libraries.cgr.dev/python/` rather than adding a separate public PyPI proxy. Refer to [Manually managing fallback](#manually-managing-fallback) if you need to control fallback ordering yourself. ### Initial configuration Use the following steps to add a repository with Chainguard Libraries for Python as the upstream source. First, create a repository: 1. Log in to your Cloudsmith instance as user with administrator privileges. 1. Select the **Repositories** tab near the top of the screen. 1. Navigate to the **Repositories Overview**, then select **+New repository**. 1. At the new repository form, enter the name *python-all* for your new repository. The name should include *python* to identify the repository format. This convention helps avoid confusion, since repositories in Cloudsmith are multi-format. 1. Select a storage region that is appropriate for your organization and infrastructure. 1. Select **+Create Repository**. Next, configure the upstream proxies: 1. Select the name of the new *python-all* repository on the repositories page to configure it. 1. Access the **Upstreams** tab and click **+ Add Upstream Proxy**. 1. Configure an upstream proxy with the format **python** and the following details: * **Name**: `python-chainguard` * **Priority**: `1` * **Upstream URL**: `https://libraries.cgr.dev/python/` * **Mode**: `Cache and Proxy` * Add the **Username** and **Password** value from [Chainguard Libraries access](/chainguard/libraries/introduction/access/) in **Authentication Settings** 1. Select **Create Upstream Proxy**. 1. If you want to use the separate repository with [remediated Python libraries](/chainguard/libraries/python/overview/#cve-remediation), repeat the preceding two steps with the name `python-chainguard-remediated`, the priority `2`, the same authentication details, and the URL `https://libraries.cgr.dev/python-remediated/`. 1. If you are manually managing fallback rather than using the [Chainguard Repository's built-in fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configure another upstream proxy with the following details: * **Name**: `python-public` * **Priority**: `3` * **Upstream URL**: `https://pypi.org/` * **Mode**: `Cache and Proxy` 1. Select **Create Upstream Proxy**. ### Build tool access Refer to the page on [build tool configuration for Chainguard Libraries for Python](/chainguard/libraries/python/build-configuration/#cloudsmith) for information on accessing credentials and setting up build tools. ## Google Artifact Registry [Google Artifact Registry](https://cloud.google.com/artifact-registry) supports the Python format for hosting artifacts in **Standard** repositories and proxying artifacts from public repositories in **Remote** repositories. Use **Virtual** repositories to combine them for consumption with `pip` and other build tools. Use the [Python package documentation for Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/python) as the starting point for more details. The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single remote repository pointed at `https://libraries.cgr.dev/python/` rather than adding a separate public PyPI remote. Refer to [Manually managing fallback](#manually-managing-fallback) if you need to control fallback ordering yourself. ### Initial configuration Use the following steps to add the Chainguard Libraries for Python repository as a remote repository and expose it through a virtual repository. 1. Log in to the Google Cloud console as a user with administrator privileges. 1. Navigate to your project and find the **Artifact Registry** with the search. 1. Activate Artifact Registry if necessary. 1. Navigate to your project and find the **Secret Manager** with the search. 1. Activate **Secret Manager** if necessary. Before configuring the repositories, you must create a secret with the [password value as retrieved with chainctl](/chainguard/libraries/introduction/access/): 1. Navigate to the **Secret Manager** 1. Click **Create secret**. 1. Set the **Name** to `chainguard-libraries-python`. 1. Use the **Password** from chainctl output to set the **Secret value**. 1. Click **Create secret**. Navigate to Artifact Registry and select **Repositories** in the left hand navigation under the **Artifact Registry** label to configure a remote repository for Chainguard Libraries for Python: 1. Click **+Create a Repository**. 1. Configure the repository: 1. **Name**: `python-chainguard` 1. **Format**: `Python` 1. **Mode**: `Remote` 1. **Remote repository source**: `Custom`. Set the URL for the Custom repository to `https://libraries.cgr.dev/python/`. 1. **Remote repository authentication mode**: Select `Authenticated`. 1. Set **Username for the upstream repository** to the [value as retrieved with chainctl](/chainguard/libraries/introduction/access/). 1. Select the *chainguard-libraries-python* secret in the list for the **Secret** input. 1. Choose the a **Region** for your development in **Location type**. 1. Click **Create**. 1. If you want to use the separate repository with [remediated Python libraries](/chainguard/libraries/python/overview/#cve-remediation) repeat the preceding steps with the name `python-chainguard-remediated`, the same authentication details, and the URL `https://libraries.cgr.dev/python-remediated/`. If you are manually managing fallback rather than using the [Chainguard Repository's built-in fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configure an additional remote repository for the public PyPI. Combine the `python-chainguard` repository, and optionally the `python-chainguard-remediated` repository, into a new virtual repository: 1. Click **+** to add another repository. 1. Set the **Name** to `python-all`. 1. Set the **Format** to `Python`. 1. Set the **Mode** to `Virtual`. 1. Click **Add upstream repository** in **Virtual upstream repositories**. 1. Click **Browse**, then locate and select the `python-chainguard` repository as **Repository 1** and set the **Policy name 1** to `python-chainguard`. 1. Choose a **Region** for your development in **Location type**. 1. Click **Create**. If you are manually managing fallback rather than using the recommended [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), add the public PyPI index as a second remote repository (`python-public`) and give the `python-chainguard` policy a higher priority than `python-public`. ## JFrog Artifactory [JFrog Artifactory](https://jfrog.com/artifactory/) supports PyPI repositories for proxying and virtual repositories to combine multiple sources into a single repository. The following instructions are based on the [PyPI Repository documentation for Artifactory](https://docs.jfrog.com/artifactory/docs/pypi-repositories). If you follow the recommended approach to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), disable or remove any existing Artifactory remote repository that points at the public PyPI index, and remove it from the virtual repository your builds resolve against. A remote pointing directly at the public upstream bypasses those protections. Since Artifactory resolves through the virtual repository in order, a misconfiguration can result in Artifactory serving an unprotected package. ### Initial configuration Use the following steps to add Chainguard Libraries for Python as a remote repository: 1. Log in as a user with administrator privileges. 1. Click **Administration** in the top navigation bar. 1. Select **Repositories** in the left hand navigation. Configure a remote repository for the Chainguard Libraries for Python index: 1. Select **Create a Repository** and choose the **Remote** option. 1. For **Package type**, select `PyPI`. 1. Set the **Repository Key** to `python-chainguard`. 1. Set the **URL** to `https://libraries.cgr.dev/`. * Do not include `/python` in the URL. Python's [Simple Repository API](https://peps.python.org/pep-0503/) keeps the package index on its own path which goes in the **PyPI Settings** fields below, and the base URL also needs to cover the `/python-upstream/` paths for upstream fallback packages. 1. Set **User Name** and **Password / Access Token** to the [values as retrieved with chainctl](/chainguard/libraries/introduction/access/). * Note: The **Test** button is not a reliable indicator; to verify your setup, refer to the [validation steps](#validate-the-remote-repository) later on this page. 1. Set the **PyPI Settings - Registry URL** to `https://libraries.cgr.dev/`. 1. Set the **PyPI Settings - Registry Index Location URL Suffix** to `python/simple`. 1. Click the **Advanced** configuration tab, then configure the following settings: * In the **Network** section: * Confirm **Lenient Host Authentication** is unchecked, so that your credentials are not forwarded across the redirect. * Optionally check **Enable Cookie Management**. JFrog recommends this for remote repositories that involve redirects. * In the **Others** section: * Check **Bypass HEAD Requests**, so that Artifactory retrieves each package file with a GET request instead of probing with a HEAD request first. * Uncheck **Block Mismatching Mime Types**. * Check **Disable URL Normalization**, so that Artifactory does not rewrite the pre-signed redirect URL. 1. Click **Create Remote Repository**. 1. If you want to use the separate repository with [remediated Python libraries](/chainguard/libraries/python/overview/#cve-remediation) repeat the preceding steps with the name `python-chainguard-remediated`, the same authentication details, and the following changes: * Set the **URL** to `https://libraries.cgr.dev/`. Do not include `/python-remediated` * Set the **PyPI Settings - Registry URL** to `https://libraries.cgr.dev/`. * Set the **PyPI Settings - Registry Index Location URL Suffix** to `python-remediated/simple`. These settings are required because Chainguard Libraries stores artifacts in Cloudflare R2. A package file download from `libraries.cgr.dev` returns a 302 redirect to a pre-signed URL on a different host, and the redirect response itself is an HTML document. Without these settings, Artifactory may rewrite the pre-signed URL, forward your credentials across the redirect, or cache the redirect response in place of the wheel or source distribution. A cached redirect response fails checksum verification at install time. If you are manually managing fallback, rather than using the recommended [Chainguard Repository built-in fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) approach, configure an additional remote repository for the public PyPI index. Create a virtual repository to give your build tools a single access point: 1. Click **Create a Repository** and choose the **Virtual** option. 1. Select `PyPI` as the Package type. 1. Set the **Repository Key** to `python-all`. 1. In the **Repositories** section, add `python-chainguard`. If you are using the remediated index, also add `python-chainguard-remediated` and ensure it is first in the displayed list, so that remediated versions resolve first. Use the icon on the right of the repository name to drag and drop repositories into the desired position. * If you are manually managing fallback, add the `python-public` repository and ensure it is last in the list. 1. Select **Create Virtual Repository**. At this point, you have a virtual repository set up in Artifactory that allows you or others in your organization to access Chainguard Libraries for Python, optionally including remediated versions, with your chosen tools. ### Validate the remote repository After creating the `python-chainguard` remote repository, validate that Artifactory is successfully proxying through to Chainguard before proceeding. A misconfigured remote repository fails silently; if any remote pointing at the public PyPI index is still present, Artifactory resolves through it instead and the build succeeds with no visible error. This can result in pulling an unprotected package. Common sources of misconfiguration include invalid or expired credentials, or an incorrect or incomplete repository URL. As noted in the configuration steps, the Artifactory **Test connection** button is not a reliable indicator; it fails for a correctly configured Chainguard repository, and it may pass for an incorrectly configured one. Use the following steps instead to verify that fetching an artifact through Artifactory produces the same checksum as fetching it directly from `libraries.cgr.dev`. 1. Find the direct URL for a specific package wheel from the Chainguard index. This example uses `urllib3`. You can substitute any artifact you know to be available. ```bash curl -sSf \ -u "${CHAINGUARD_PYTHON_IDENTITY_ID}:${CHAINGUARD_PYTHON_TOKEN}" \ https://libraries.cgr.dev/python/simple/urllib3/ \ | grep -o 'https://[^"]*\.whl' | head -1 ``` 1. Fetch a package file directly from `libraries.cgr.dev` and compute its checksum: ```bash curl -sSf -L \ -u "${CHAINGUARD_PYTHON_IDENTITY_ID}:${CHAINGUARD_PYTHON_TOKEN}" \ \ | sha256sum ``` 1. Fetch the same file through the Artifactory remote repository and compute its checksum: ```bash curl -sSfL \ -u "${ARTIFACTORY_USERNAME}:${ARTIFACTORY_TOKEN}" \ "https:///artifactory//${path-to-wheel}" \ | sha256sum ``` Replace `artifactory-host` with your Artifactory instance hostname and replace `python-remote-repository` with your remote repository name. Replace `path-to-wheel` with the path component of the URL from step 1 (for example: `/files/15f7d141c3b76b85/37e321caa85a8f41/urllib3/urllib3-1.26.9-py2.py3-none-any.whl`) The checksums returned by the commands must match. If the checksum from the Artifactory remote repository differs from the direct fetch, or if the Artifactory fetch fails entirely, review the following before proceeding: * URL: The remote repository URL must be set to `https://libraries.cgr.dev/`. * Credentials: You may need to regenerate your pull token with `chainctl auth pull-token --repository=python` and update the Artifactory repository credentials. Expired tokens fail silently. * Advanced Configuration: Ensure all recommended Advanced settings from the [initial configuration steps](#initial-configuration-2) have been applied. * Corrupted cached artifacts: if the repository previously ran without these settings, Artifactory may still be serving a cached redirect response. In Artifactory, browse the `python-chainguard` remote cache and locate the affected files. Right-click each artifact, select **Delete content**, then re-run your install. Do not proceed to virtual repository setup or build configuration until the checksums match. ### Build tool access Refer to the page on [build tool configuration for Chainguard Libraries for Python](/chainguard/libraries/python/build-configuration/#artifactory) for information on accessing credentials and setting up build tools. ## Sonatype Nexus Repository [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) allows for merging multiple remote repositories as a repository group. The below instructions are based on the [Nexus documentation for PyPI](https://help.sonatype.com/en/pypi-repositories.html) The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single proxy repository pointed at `https://libraries.cgr.dev/python/` rather than adding a separate public PyPI proxy. Refer to [Manually managing fallback](#manually-managing-fallback) if you need to control fallback ordering yourself. ### Initial configuration The following steps create a remote repository for Chainguard Libraries for Python and a repository group that exposes it to your build tools. First, log in to Sonatype Nexus as a user with administrator privileges and access the **Server administration** and configuration section within the gear icon in the top navigation bar. Next, configure a remote repository for Chainguard Libraries for Python repository: 1. Select **Repository - Repositories** in the left hand navigation. 1. Select **Create repository**. 1. Select the **PyPI (proxy)** recipe. 1. Provide a new name, such as `python-chainguard`. 1. In the **Proxy - Remote storage**field, add the following URL: `https://libraries.cgr.dev/python/`. 1. In **HTTP - Authentication**, set the **Authentication type** to *username* and enter the the [username and password values as retrieved with chainctl](/chainguard/libraries/introduction/access/). 1. Select **Create repository**. If you want to use the separate repository with [remediated Python libraries](/chainguard/libraries/python/overview/#cve-remediation) repeat the preceding steps with the name `python-chainguard-remediated`, the same authentication details, and the URL `https://libraries.cgr.dev/python-remediated/`. If you are manually managing fallback rather than using the [Chainguard Repository's built-in fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configure an additional remote repository for the public PyPI. Finally, create a new repository group and add the repositories: 1. Select **Repository - Repositories** in the left hand navigation. 1. Select **Create repository**. 1. Select the **PyPI (group)** recipe. 1. Provide a new name, such as `python-all`. 1. In the section **Group - Member repositories**, move the new repositories `python-chainguard-remediated` and `python-chainguard` to the right. Move the `python-chainguard-remediated` repository to the top of the list. ### Build tool access Refer to the page on [build tool configuration for Chainguard Libraries for Python](/chainguard/libraries/python/build-configuration/#nexus) for information on accessing credentials and setting up build tools. ## Troubleshooting and FAQ ### Sonatype build failures and 404 errors for uv and pip requests ending in `.whl.metadata` Builds fail intermittently when Sonatype Nexus is configured as a PyPI proxy in front of `libraries.cgr.dev`, even though authentication, entitlements, and network connectivity all check out. The failures look like missing packages, but the packages themselves are present and downloadable. The Nexus outbound request logs show 404 responses specifically for URLs ending in `.whl.metadata`, while the same URL without the `.metadata` suffix succeeds. Modern Python installers (pip 23.1+, uv) implement [PEP 658](https://peps.python.org/pep-0658/) and request a small per-package metadata file by appending `.metadata` to the wheel's download URL, ahead of downloading the wheel itself. When Nexus is serving a stale cached index page, it can point clients at a metadata URL format that `libraries.cgr.dev` no longer honors. Chainguard's upstream returns 400 for that request, which Nexus in turn surfaces to the client as a 404. The result presents as a missing package, when the underlying issue is a stale cached index. #### Fix: Recreate the proxy repository To fix this, delete and recreate the Chainguard PyPI proxy repository in Nexus. This clears all cached index pages and metadata assets for that repository, forcing Nexus to pull fresh copies on the next request. Alternatively, you could add a Nexus routing rule to block metadata requests outright. Clients fall back to standard resolution automatically when the metadata request fails cleanly, without surfacing an error: 1. In Nexus, go to **Administration** > **Routing Rules** and create a new rule. 1. Set the Mode to `Block`. 1. Set the matcher to `.*\.metadata$`. 1. Assign the rule to your Chainguard PyPI proxy repository. 1. Remove the routing rule once you've recreated the proxy repository, so clients can resume using PEP 658 metadata requests. --- ### Quickstart for Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/introduction/quickstart.md Last Modified: October 13, 2026 Tags: Chainguard Libraries Most supply chain attacks succeed the same way: malicious code is injected into a package after the source is written — either as a backdoored binary with no verifiable source, or as a malicious install-time script that runs the moment a dependency is pulled. Recent attacks on LiteLLM, Telnyx, and Axios all followed this pattern. Chainguard Libraries for Java, JavaScript, and Python are rebuilt from verified source in an isolated build environment, making them malware-resistant by design. When a package is available as a Chainguard-built library, that rebuilt package is served first. When you use the [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), the same ecosystem endpoint can also serve eligible upstream packages that Chainguard has not yet built, subject to configurable policy controls such as cooldown and malware scanning. This gives your engineers drop-in replacements for the packages they already use, with no breaking changes. This guide covers the high-level steps to get up and running with Chainguard Libraries. For full reference documentation on any step, follow the links provided throughout this page. For ecosystem-specific guides, refer to the following pages: * [Java migration guide](/chainguard/libraries/java/migration/) * [JavaScript migration guide](/chainguard/libraries/javascript/migration/) * [Python migration guide](/chainguard/libraries/python/migration/) ## Prerequisites Before getting started: * If you're not yet a Chainguard user, you must [create an account](https://console.chainguard.dev/auth/login). * Entitle access for yourself to Chainguard Libraries:

To create an entitlement and pull token in the Chainguard Console: while viewing a library ecosystem page, follow the prompts to create an access token.

Chainguard Libraries are available to Catalog Starter and Free tier users, and trial users. ## Step 1: Choose your access method There are two ways to access Chainguard Libraries: using an [artifact manager](#artifact-manager) or [direct access](#direct-access). ### Artifact manager Configure credentials once in a tool like JFrog Artifactory, Sonatype Nexus, or Cloudsmith. This centralizes policy, logging, and fallback behavior, and is the safest approach for organizations with multiple teams and applications. Learn how to configure a repository manager in the global configuration documentation for [Java](/chainguard/libraries/java/global-configuration/), [JavaScript](/chainguard/libraries/javascript/global-configuration/), and [Python](/chainguard/libraries/python/global-configuration/). ### Direct access Configure authentication directly in each project's build configuration. This option is faster to set up initially, but requires per-project and per-workstation configuration. This increases the risk of credentials being committed to source control or going stale. Learn how to set up direct access in the build configuration documentation for [Java](/chainguard/libraries/java/build-configuration/), [JavaScript](/chainguard/libraries/javascript/build-configuration/), and [Python](/chainguard/libraries/python/build-configuration/). ## Step 2: Create a pull token [Pull tokens](/chainguard/libraries/introduction/access/#creating-pull-tokens-for-libraries) are required for authentication. You can [create pull tokens via the Chainguard Console](/chainguard/libraries/introduction/access/#creating-pull-tokens-with-the-chainguard-console) under **Overview > Manage pull tokens > Create access token**. You can also [create one using `chainctl`](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token_create/):
chainctl auth pull-token create --repository=java --ttl=720h
> The default TTL is `720h` (30 days); the maximum is `8760h` (365 days). The command returns a username and password for basic authentication. Store these securely, as they won't be shown again. Learn more about pull tokens, and using environment variables for pull token credentials, in the [Libraries access documentation](/chainguard/libraries/introduction/access/). ## Step 3: Configure your build tools Once you have a pull token, you can configure your build tool. Configuration steps vary by build tool and ecosystem. Refer to the ecosystem-specific documentation pages for instructions. If you [configure upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), the same ecosystem endpoint can serve both: * Libraries rebuilt from source by Chainguard, and * Eligible packages from the upstream public registry when Chainguard has not built that package or version yet Upstream packages served through the Chainguard Repository are subject to configurable policy controls such as cooldown and malware protection. It is strongly recommended that you follow this approach.

Java

  • Repository manager: Configure your repository manager or build tool to use https://libraries.cgr.dev/java/ as the first repository for artifact resolution.
  • Direct access: Configure your tool to retrieve artifacts directly from the Chainguard Libraries for Java repository at https://libraries.cgr.dev/java/. Use direct access for small teams or evaluations, or when you have an existing repository configuration you can’t change yet.

In addition to malware-resistance, Chainguard Libraries for Java includes CVE remediation for select libraries. These patched versions help reduce known risk while you plan your next major version upgrade. You can view which libraries have CVE remediation available in the Chainguard Console.

Check out minimal example projects for Maven and Gradle to understand how to use these repositories.

> **Note on upstream fallback**: The upstream fallback is > available as an opt-in setting for both repository manager or direct access > approaches, and is turned off by default. Learn more about upstream > fallback policy and controls in the [Libraries > overview](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). ## Step 4: Verify your libraries After setup, you can verify which dependencies were built from source by Chainguard. This step requires [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/); if you entitled access through the Chainguard Console, install `chainctl` and log in with `chainctl auth login` before continuing. ```bash chainctl libraries verify /path/to/artifact ``` > **Note**: Running `chainctl libraries verify` requires one of the `libraries.java.pull`, `libraries.javascript.pull`, or `libraries.python.pull` permissions, or the Owner role. For JavaScript projects, you can also verify a lockfile directly, without installing dependencies. For example: `chainctl libraries verify package-lock.json`. Learn more in [Chainguard Libraries verification](/chainguard/libraries/policies-and-security/verification/). ## FAQs Refer to the [Chainguard Libraries FAQ page](/chainguard/libraries/troubleshooting/faq/) for common questions and issues. --- ### Using Chainguard Containers in Dev Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/dev-containers.md Last Modified: October 8, 2026 Tags: Chainguard Containers [Development Containers](https://containers.dev/) — sometimes known as "dev containers" — allow you to use a container as a development environment where you can run applications and separate tools, libraries, or runtimes. Dev containers can also help with testing and continuous integration. With a few changes, the images based on Wolfi and maintained by Chainguard provide distroless images that can be used as dev containers. This guide outlines how you can set up a Chainguard image as a dev container in VS Code.
What is distroless Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Prerequisites To follow along with this guide, you will need to have the following: * A compatible Integrated Development Environment (IDE) or other tool. Here is a list of [supported editors and tools](https://containers.dev/supporting). * Note that this guide was validated using [Visual Studio Code (VS Code)](https://code.visualstudio.com/). * A Docker server to connect to. A local installation of Docker Desktop will usually suffice for demonstration purposes, but you can find full instructions in this guide on [Developing inside a Container](https://code.visualstudio.com/docs/devcontainers/containers) in the VS Code documentation. * This guide's first example assumes that you have a GitHub repository named `empty`. You can follow GitHub's [Quickstart for repositories](https://docs.github.com/en/repositories/creating-and-managing-repositories/quickstart-for-repositories) for information on setting this up. ## What are Dev Containers? A development container is a container in which a user can develop an application. Development containers are isolated environments that allow developers to work on applications with all the necessary dependencies, tools, and configurations pre-packaged. These containers ensure that the development environment is consistent across different systems, avoiding the "works on my machine" problem. In order to run a dev container, the given project must contain a file named `devcontainer.json`. This is a special file defined by the [Development Container Specification](https://containers.dev/implementors/json_reference/) which holds all the metadata necessary to configure the dev container. Although there are many reasons why production images should be secure, the reasons for concern about the security of development environments are less clear. Put briefly: the code you've written and tested in your development environment will eventually make it into your production environment. If you've been hacked during development, then perhaps the hackers' code goes into production as well. Chainguard offers minimal runtime images designed for running production workloads, and development images that contain a shell and some development tooling. With that said, both development and production images are slimmed down and updated regularly to be free of CVEs. Because of their minimal and secure-by-default nature, Chainguard's container images are ideal for use in a secure development process. ## Building a Go Dev Container using an example repository The following is an example of how to set up a dev container using a Go project. Here, we will take the content of the `chainguard-go-devcontainer` directory in [Chainguard's demo GitHub repository](https://github.com/chainguard-dev/edu-images-demos) and push it to the root of an empty repository. Start by cloning the repository: ```shell git clone https://github.com/chainguard-dev/edu-images-demos.git ``` Then navigate into the `chainguard-go-devcontainer` example directory: ```shell cd edu-images-demos/chainguard-go-devcontainer ``` From there, Initialize a Git repository: ```shell git init . ``` Then add all the files there, including any hidden files: ```shell git add * .??* ``` Commit the changes: ```shell git commit -am "Initial Commit" ``` Add a GitHub repository that you have control over as a remote named `origin`. This example assumes that the repository is named `empty` but you can use any empty GitHub repository you have created: ```shell git remote add origin https://github.com/$YOUR-GITHUB-PROFILE/empty.git ``` Be sure to change `$YOUR-GITHUB-PROFILE` to reflect the name of your GitHub profile. Finally, push the commit to the remote repository you just configured: ```shell git push -fu origin main ``` Following that, if you open VS Code on this directory you will be prompted to open the project in a dev container.
Screenshot of a VS Code window showing the prompt to reopen the project in a dev container.

If you do reopen the project in a dev container it may take a minute or so to build the first time you use it. Open a terminal and you can run the sample project, even if you don't have Go installed on your local machine:
Screenshot of a VS Code window with the example Go program being run in the terminal.

If you run a webserver in your dev container you will be asked if you want to open the port in a local browser. Exactly as if you were running in a local container:
Screenshot of a VS Code window running helloserver and prompting the user to open the application in the browser.

## Building a Dev Container in other languages If you want to develop in languages other than Go, you'll need to use a different base image. Note that to do this, you'll need a working knowledge of Dockerfiles, Docker builds, JSON, as well as how your chosen language installs libraries or packages. There are a couple of different ways to set this up, and the exact configuration used in the following example is not strictly required as long as certain requirements are met. This example sets up a Python project and uses a `Dockerfile` and `devcontainer.json` in a `.devcontainer` directory. This has the advantage of keeping the code separate from the dev container configuration. Assuming you're starting from a project with no dev container, you'll first need to create the requisite files and folders. Start by creating a directory named `.devcontainer` and navigate into it: ```shell mkdir .devcontainer && cd $_ ``` Next, create a dev container configuration file named `devcontainer.json` with the following command: ```shell cat > devcontainer.json < Dockerfile <> /etc/sudoers && echo y | pwck -q || true ``` ## Usage notes [GitHub CodeSpaces](https://github.com/features/codespaces) support dev containers. However, at present they only work when the config is stored at the root directory. The IDE should pass your keys through to the dev container using `ssh-agent` and `gpg-agent`. For this reason, you may need to install GPG or SSH tools inside the container. Please refer to the VS Code page on [advanced usage](https://code.visualstudio.com/remote/advancedcontainers/sharing-git-credentials) for more information. ## Conclusion You should now have a development environment that does not require packages to be installed on your local machine and will run the same for anyone working on your project. You may want to add things like linting rules or other components to your dev container config. The [VS Code website](https://code.visualstudio.com/docs/devcontainers/containers) has many resources on working with dev containers which you may find useful. --- ### Chainguard's private APK repositories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/building-and-modifying/packages/private-apk-repos.md Last Modified: October 2, 2026 Tags: Chainguard Containers With Chainguard's private APK repositories, you can access packages that are included within your organization's container image entitlements. This allows you to build custom images based on components that are already part of your organization catalog. This guide provides a brief overview of Chainguard's private APK repositories and outlines different ways you can incorporate them into your organization's workflows.

Note: Private APK Repositories is in beta. Available to all Chainguard Containers customers.

## About private APK repositories Chainguard's private APK repos allow customers to pull secure apk packages from Chainguard. The list of packages available in an organization's private repository is based on the apk repositories that the organization already has access to. For example, say your organization has access to the [Chainguard MySQL container image](https://images.chainguard.dev/directory/image/mysql/versions). Along with `mysql`, this image comes with other apk packages, including `bash`, `openssl`, and `pwgen`. This means that you'll have access to these apk packages through your organization's private APK repository, along with any others that appear in Chainguard container images that your organization has access to. ## Contents of private APK repositories Your private APK repository contains all packages available in the container images your organization is entitled to, plus some extra utilities, like `curl`. Chainguard offers [Chainguard OS Packages](/chainguard/chainguard-os/chainguard-os-packages/), a beta feature that extends the set of available packages in your private APK repository to include the latest versions of all 30,000 packages Chainguard maintains as part of Chainguard OS and Wolfi. If you are a catalog customer, your private APK repository will contain the full set of packages built for Chainguard OS and Wolfi. If you're a per-image customer, your private APK repository will contain most of these packages, but won't contain any "main" packages for images you aren't entitled to, preventing you from recreating our images. If you're interested in expanding the set of available packages in your private APK repository and are a current customer, reach out to your account team to be added to the Beta. ## Your repository address Your private APK repository will be available at a URL like the following: `https://apk.cgr.dev/$ORGANIZATION` You will need to replace `$ORGANIZATION` with the name of your organization as it appears in the Chainguard Console. You can always find your private APK repository's address by logging into the [Chainguard Console](https://console.chainguard.dev/) and navigating to the **Settings** tab in the left-hand navigation menu. This will take you to the **General** section where you can copy the repository address. In order to use your private repository, you must add this URL to the list of apk repositories found in an `/etc/apk/repositories` file, and you'll also need to provide credentials to have access to this repo. To set this up, you must follow these general steps: 1. Set up an `HTTP_AUTH` environment variable with appropriate credentials 2. Add `https://apk.cgr.dev/ORGANIZATION` to `/etc/apk/repositories` 3. Update your apk cache 4. Install your desired packages In the following sections, this guide will outline how to implement this process in a live container and also when building container images from a Dockerfile or using apko. ## Authenticating to your private APK repository To access the apk packages in your private repository, you'll first need to authenticate. You can do so by setting up an `HTTP_AUTH` environment variable with an authentication string that uses `chainctl` to obtain an ephemeral token to access the registry. The `HTTP_AUTH` environment variable expects a string in the following format: ```example HTTP_AUTH=basic:apk.cgr.dev:user:password ``` You will need to replace `password` with an apk token which you can obtain using the following `chainctl` command: ```shell chainctl auth token --audience apk.cgr.dev ``` The following `docker run` command injects the `HTTP_AUTH` environment variable directly into the container by calling `chainctl` from the host machine: ```shell docker run -ti --rm \ -e "HTTP_AUTH=basic:apk.cgr.dev:user:$(chainctl auth token --audience apk.cgr.dev)" \ cgr.dev/$ORGANIZATION/$IMAGE ``` In this command and throughout the rest of this guide, be sure to change `$ORGANIZATION` to reflect the name of your organization's repository within Chainguard's registry. Additionally, this guide was validated using the [chainguard-base](https://images.chainguard.dev/directory/image/chainguard-base/overview) image, but you can use any container image that is part of your organization's image catalog. However, to follow along with every example this guide, you should use a container image that includes a shell, such as an image's `-dev` variant. Also, you can initialize these placeholder values by setting them as environment variables as in the following examples. This will make it easier to follow along: ```shell ORGANIZATION="chainguard.edu" IMAGE="chainguard-base" ``` This command will start an interactive container and open up a shell interface. From there, you can add your organization's private APK repository to the list of apk repositories in `/etc/apk/repositories`. First, you'll need to retrieve your organization's private repository address. Recall that you can find this in the **Settings** tab in the Chainguard Console. From the interactive container's shell, add the repository address to your list of apk repositories with a command like the following: ```container echo https://apk.cgr.dev/$ORGANIZATION > /etc/apk/repositories ``` Note that this command uses `>` to overwrite the contents of the `/etc/apk/repositories` file. You could instead append the private APK repository's address with `>>`, but here we overwrite the file to ensure that we're only installing packages from the private repo. Then update the apk cache to include the newly-added private repository: ```container apk update ``` Following that, you can proceed to search and install packages from your private APK repository. ## Searching for and installing packages As an example of how you can search for and install packages from these private repositories, this section will install `wget`. However, you could also try this out with any apk package that is included in any of the Chainguard container images your organization has access to. First, search for the `wget` package from the interactive container's shell: ```container apk search wget ``` You will receive output similar to the following: ```Output wget-1.25.0-r0 ``` You can call `apk policy` to find which repositories contains the package: ```container apk policy wget ``` This example output shows that it's available from the private APK repository: ```Output wget policy: wget policy: 1.24.5-r4: https://apk.cgr.dev/$ORGANIZATION 1.24.5-r5: https://apk.cgr.dev/$ORGANIZATION 1.25.0-r0: https://apk.cgr.dev/$ORGANIZATION ``` Install the package with `apk add`: ```container apk add wget ``` Finally, run the same `apk policy` command you ran previously: ```container apk policy wget ``` ```Output wget policy: 1.24.5-r4: https://apk.cgr.dev/$ORGANIZATION 1.24.5-r5: https://apk.cgr.dev/$ORGANIZATION 1.25.0-r0: lib/apk/db/installed https://apk.cgr.dev/$ORGANIZATION ``` This output shows that the `wget` package is now installed. ## Using private APK repositories with Dockerfiles So far, this guide has outlined how to manually fetch apk packages from a private repository. We'll now go over how to use a private APK repo within a Dockerfile workflow. We'll be using the same organization, container image, and private package used in the previous examples. If you haven't already done so, close the container you were running in the previous section. Then run the following command to create a Dockerfile. This Dockerfile uses [Docker Build Secrets](https://docs.docker.com/build/building/secrets/#using-build-secrets) to inject the credentials into a `RUN` command that will update the apk cache and another that will install the `wget` apk package: ```shell cat > Dockerfile < /etc/apk/repositories RUN --mount=type=secret,id=cgr-token sh -c "export HTTP_AUTH=basic:apk.cgr.dev:user:\$(cat /run/secrets/cgr-token) apk update && apk add wget" USER 65532 EOF ``` Again, this Dockerfile overwrites the contents of the `/etc/apk/repositories` file. This isn't necessary, but will force Docker to build the image and install the `wget` package without falling back to the default repositories. You may need to change the final `USER` statement to match the default user of the image you are extending. This can be found by running `docker inspect` or in the Specifications tab of the Chainguard Console entry for the image. Now you can build the image while passing along credentials obtained with `chainctl`. The following example builds an image named `my-custom-image`: ```shell CGR_TOKEN=$(chainctl auth token --audience apk.cgr.dev) \ docker build --secret id=cgr-token,env=CGR_TOKEN . -t my-custom-image ``` Following that, you can verify that the image has access to the private repository and contains the apk package that was installed at build time: ```shell docker run --rm my-custom-image apk policy wget ``` ```Output wget policy: 1.24.5-r4: https://apk.cgr.dev/$ORGANIZATION 1.24.5-r5: https://apk.cgr.dev/$ORGANIZATION 1.25.0-r0: lib/apk/db/installed https://apk.cgr.dev/$ORGANIZATION ``` As this output shows, the `wget` apk package is installed in the container. ## Using private APK repositories with apko builds You can also use your private APK repository with [apko](/open-source/build-tools/apko/) builds. One of the advantages of this method is that you can build distroless images that include only the apk packages you need in the final image. As with the previous examples, you'll need to provide the `HTTP_AUTH` environment variable containing your Chainguard token to the apko runtime building the image. The following Dockerfile includes the private APK repository used in previous examples and installs a single package (`wget`) in the image: ```shell cat > apko.yaml < apko.yaml < ## Pull token for authentication in automated workflows Use a pull token with a custom time to live (TTL) to authenticate to your private APK repository. The following example creates a pull token with a TTL of 2190 hours, equivalent to 3 months, and produces an output with `export` commands to set environment variables `CHAINGUARD_IDENTITY_ID` for username and `CHAINGUARD_TOKEN` for password values and basic authentication use. ```shell chainctl auth pull-token --repository=apk --ttl=2190h --output=env ``` ```output export CHAINGUARD_IDENTITY_ID= export CHAINGUARD_TOKEN= ``` * `--repository=apk`: create a role binding to bind the pull token identity the `apk.pull` role, enabling the identity to download packages from the private APK repository of the parent organization. * `--ttl=2190d`: set the duration for the validity of the token, defaults to `720h` (equivalent to 30 days), maximum valid value is `8760h` (equivalent to 365 days), valid unit strings range from nanoseconds to hours and are `ns`, `us`, `ms`, `s`, `m`, and `h`. * `--output=env`: set the command output to use export commands for environment variables. `apk` and `oci` pull tokens use the unqualified `CHAINGUARD_IDENTITY_ID` and `CHAINGUARD_TOKEN` names; library ecosystems use names such as `CHAINGUARD_JAVA_IDENTITY_ID`. Refer to [pull token output formats and credential names](/platform/chainctl-usage/pull-token-output/) for the full mapping. Each invocation of the command creates a new identity with access rights as a pull token. `chainctl` uses your organization automatically when you belong to only one; if you have access to more than one, add `--parent=ORGANIZATION`. Combine the call with `eval` to populate the environment variables directly by calling `chainctl`. The following example uses the default TTL value of 30 days, which is suitable for regular CI runs: ```shell eval $(chainctl auth pull-token --repository=apk --output env) ``` The generated pull token can be provided in the `HTTP_AUTH` environment variable for accessing the private APK repository: ```shell export HTTP_AUTH=basic::${CHAINGUARD_IDENTITY_ID}:${CHAINGUARD_TOKEN} ``` Now you can structure your CI workflow to utilize this variable for authentication against the APK repository. ## Troubleshooting You may receive a 403 error when pulling down a package: `403 FORBIDDEN caller does not have the required capabilities` This error may mean that your Chainguard identity doesn't have the proper capabilities to download the image. To pull an image, the identity will need a role with the `apk (list)` capability. The least privileged role with this capability is `apk.role`, though more privileged roles like `owner`, `editor`, and `viewer` also have this capability. You can check this and fix it by following these steps: 1. Run `chainctl auth status` and check the `Capabilities` field in the output. If you don't find the `apk.pull` role (or a more privileged role) for the organization you're trying to pull from, you will need to add the role. 2. Create the `apk.pull` role using the steps outlined in our [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/) resource. 3. Try pulling the package again. If you'd like to provide feedback or need further help troubleshooting, [reach out to our Customer Support team](https://www.chainguard.dev/contact?utm=docs). ## Conclusion Private APK repositories offer customers a convenient way to make use of the packages their organization has access to. By following this guide, you should have a good understanding of how you can use them in your particular workflows. Be aware that you can use private APK repositories to install packages within container images customized with Chainguard's Custom Assembly tool. Refer to our [Custom Assembly documentation](/chainguard/containers/custom-assembly/#installing-packages-from-a-chainguard-private-apk-repository) for more information. --- ### Migration best practices and checklist URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-checklist.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard container images are designed to be minimal and to include special features for increased security and provenance attestation. Depending on your current base image and customizations, you may need to make some adjustments when migrating your current workloads to use Chainguard Containers. This checklist provides a high-level overview of the steps you should consider when migrating to Chainguard Containers. > **Download the [PDF version](/downloads/migrating-to-chainguard-images.pdf) of this checklist [here](/downloads/migrating-to-chainguard-images.pdf)!** ## Important to know - Most of Chainguard's container images don’t have a package manager or a shell by default. These are **distroless** images intended to be used as slim runtimes for production environments. - For every version of an image, a complimentary **standard** image is provided with a shell and the apk package manager. These are identified by the `-dev` suffix and can be customized. - When possible, we recommend using multistage builds that combine a build stage based on a `-dev` variant and a runtime stage based on a distroless image. - Chainguard's container images typically don’t run as root, so a `USER root` statement may be required before installing software. - Chainguard's container images are based on **apk**. If you’re coming from Debian or Ubuntu you’ll need to replace `apt` commands with their `apk` equivalents. This also applies for other distros that are not based on **apk**. - Some images may behave differently than their equivalent in other distros, due to differences in entrypoint and shell availability. Always check the image documentation for usage details. - For a number of our most popular Containers, a **full** variant (tagged `-full`) maps to the upstream image to ease initial migration. It's a useful starting point if your pipeline depends on packages from your previous image, though we recommend moving to a slimmer variant once you've migrated. Refer to [Full container variants](/chainguard/containers/concepts/container-variants/#full-container-variants). ## Migration checklist - [ ] Check the image’s overview page on the [Containers Directory](https://images.chainguard.dev) for usage details and any compatibility remarks. - [ ] Replace your current base image with a standard `-dev` (such as `latest-dev`) variant as a starting point. - [ ] Add a `USER root` statement before package installations or other commands that must run as an administrative user. - [ ] Replace any instances of `apt install` (or equivalent) with `apk add`. - [ ] Use `apk search` on a running container or the [APK Explorer](https://apk.dag.dev/) tool to identify packages you need – some commands might be available with different names or bundled with different packages. - [ ] When copying application files to the image, make sure proper permissions are set. - [ ] Switch back to a non-root user so that the image does not run as root by default. - [ ] Build and test your image to validate your setup. - [ ] Optional: migrate your setup to a multi-stage build that uses a distroless image variant as runtime. Our [Getting started with distroless](https://edu.chainguard.dev/chainguard/containers/concepts/getting-started-distroless/) guide has detailed information on how to work with distroless images and multi-stage builds. For detailed migration guidance, please refer to our [Migration docs](https://edu.chainguard.dev/chainguard/containers/migration/) on Chainguard Academy. For troubleshooting, check our [Debugging distroless containers](https://edu.chainguard.dev/chainguard/containers/troubleshooting/debugging-distroless-images/) resource. --- ### Using Grype to scan software artifacts URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/working-with-scanners/grype-tutorial.md Last Modified: September 28, 2026 Tags: CVE, Chainguard Containers [Grype](https://github.com/anchore/grype) is a vulnerability scanner for container images and filesystems developed and maintained by [Anchore](https://anchore.com/) and written in the Go programming language. Grype can scan from Docker, OCI, Singularity, podman, image archives, and local directory. Grype is compatible with SBOMs generated by [Syft](https://github.com/anchore/syft), and Grype's [vulnerability database](https://github.com/anchore/grype-db) draws from a wide variety of sources. Grype is appropriate for one-off detection for manual CVE mitigation and in automated use in CI pipelines. Chainguard maintains a [low-to-no CVE Chainguard Image for Grype](https://images.chainguard.dev/directory/image/grype/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-scanners-grype-tutorial) based on our lightweight Wolfi distribution. ## Installation ### Container images Grype is readily available as a container image. To pull the low-to-no-CVE Chainguard Container for Grype and perform a scan on the official Docker nginx image, run the following: ```bash docker run -it cgr.dev/chainguard/grype nginx ``` Alternatively, you can scan using the official Grype Docker image: ```bash docker run -it anchore/grype:latest nginx ``` ### Binary installation Grype provides an installation script. To use it, change the path following the `-b` flag to a preferred installation location on your system path, such as `/usr/bin`. ```bash curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin ``` Alternatively, find and download the appropriate package file or binary from the [official releases page](https://github.com/anchore/grype/releases). Place the binary on your system path or install the package using the conventions for your OS and distribution. ### Install via package manager Grype can be installed using the following commands specific to your OS and distribution: #### Homebrew ```bash brew tap anchore/grype brew install grype ``` #### Chocolatey ```cmd choco install grype -y ``` ## Basic usage Throughout this tutorial, we'll use the `grype` command to run Grype. If you're running Grype as a container image, replace this command with the appropriate `docker run` command, such as `docker run -it cgr.dev/chainguard/grype`. ### Scan an image in a registry To run Grype on an image on Docker Hub, pass the image name as an argument: ```bash grype nginx ``` For images on other registries: ```bash grype cgr.dev/chainguard/nginx ``` ### Scan a .tar file To scan an image stored to `.tar`, pass the path to the archive file as an argument: ```bash docker pull cgr.dev/chainguard/nginx docker save cgr.dev/chainguard/nginx > nginx_chainguard_image.tar.gz grype nginx_chainguard_image.tar.gz ``` ### Scan a local directory Grype can scan local directories, such as Python virtual environments (venv) or `node_modules` folders. To try it out, start by creating a Python virtual environment: ```bash python -m venv venv ``` Add a few out-of-date packages that will show vulnerabilities: ```bash ./venv/bin/pip install WTForms==2.3.3 Werkzeug==2.0.1 ``` Scan the virtual environment folder by passing the folder path to Grype as an argument: ```bash grype venv ``` We can do the same with node modules in a `node_modules` folder. First, create an empty project folder and change the working directory to that folder: ```bash mkdir node_project && cd node_project ``` Next, initialize npm with a default configuration: ```bash npm init -y ``` Install a package with known vulnerabilities. The 6.5.2 version of the `qs` query string parser has a known vulnerability [allowing for prototype poisoning](https://security.snyk.io/package/npm/qs/6.5.2). ```bash npm install qs@6.5.2 ``` Finally, use Grype to scan the current working directory: ```bash grype . ``` ### Scan from an SBOM Grype can read vulnerabilities from SBOMs generated by [Syft](https://github.com/anchore/syft). SBOMs can be piped into Grype using stdin: ```bash syft -o syft-json python > sbom.json cat sbom.json | grype ``` You should see Grype results based on the packages itemized in the SBOM. ## Comprehending Grype output By default, Grype output is divided into two sections: a summary of information on the scanned artifact and an itemized list of CVEs. In this section, we'll use an Alpine version of the official Python image as an example. Since we're specifying an older version, you may encounter more CVEs when following the examples than are shown here, as CVEs will accumulate on an image over time. Scan the image with the following command: ```bash grype python:3.10.14-alpine3.20 ``` You will receive output similar to the following: ``` ✔ Vulnerability DB [no update av ✔ Parsed image sha256:f48490 ✔ Cataloged contents 09feb83998b9d ├── ✔ Packages [47 pac │ └── ⠹ Linux kernel cataloger ├── ✔ File digests [659 fi ├── ✔ File metadata [659 lo └── ✔ Executables [144 ex ✔ Scanned for vulnerabilities [10 vulnerabi ├── by severity: 0 critical, 1 high, 8 medium └── by status: 7 fixed, 3 not-fixed, 0 igno NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY busybox 1.36.1-r28 1.36.1-r29 apk CVE-2023-42365 Medium busybox 1.36.1-r28 1.36.1-r29 apk CVE-2023-42364 Medium busybox-binsh 1.36.1-r28 1.36.1-r29 apk CVE-2023-42365 Medium busybox-binsh 1.36.1-r28 1.36.1-r29 apk CVE-2023-42364 Medium pip 23.0.1 23.3 python GHSA-mq26-g339-26xf Medium python 3.10.14 binary CVE-2023-36632 High python 3.10.14 binary CVE-2023-27043 Medium python 3.10.14 binary CVE-2024-4030 Unknown ssl_client 1.36.1-r28 1.36.1-r29 apk CVE-2023-42365 Medium ssl_client 1.36.1-r28 1.36.1-r29 apk CVE-2023-42364 Medium ``` ### Interpreting the summary In the initial portion of its results output, Grype summarizes information on the scanned artifact and gives an overview of known vulnerabilities. In the case of a scanned image, the output includes the image digest, a unique hash of the image that can be used as an identifier. Overview output includes the number of packages, files, and executables found in the artifact. Generally speaking, CVEs are detected against packages, but the number of executables detected can also give you an idea of the attack surface of the scanned image or filesystem. Finally, this portion gives a count of the number of CVEs detected by severity and fixed status. Severity categorization sorts CVEs into four categories based on the Common Vulnerability Scoring System (CVSS).
What is CVSS?

CVEs are assigned numerical scores according to the Common Vulnerability Scoring System (CVSS), an open framework for communicating the severity of software vulnerabilities.

CVSS scores correspond to four categories

  • Critical (9.0-10.0)
  • High (7.0-8.9)
  • Medium (4.0-6.9)
  • Low (0.1-3.9)

In general, the higher the score, the more serious the vulnerability. Higher scores should be prioritized during CVE remediation.

In our output, we can see that we have 0 critical, 1 high, and 8 medium CVEs: ``` ├── by severity: 0 critical, 1 high, 8 medium, 0 low, 0 negligible ``` Grype also counts the number of CVEs by fixed status. If a CVE is marked as fixed, it can be resolved by updating to a newer version of the package. Our output suggests that 7 packages have been fixed and can be remediated with updates: ``` └── by status: 7 fixed, 3 not-fixed, 0 ignored ``` ### Itemized CVEs In addition to the summary, Grype provides an itemized list of CVEs. By default, these are in table format, and list the package name, current version, severity, and package type (such as `apt`, `apk`, or `binary`). If the package is fixed, Grype will also indicate the package version where the fix was introduced. Grype writes itemized CVEs to stdout, so you can redirect the report of itemized CVEs to a file: ```bash grype python:3.10.14-alpine3.20 > report.txt ``` Alternatively, you can use the `--file` flag to write to a file: ```bash grype --file report.txt python:3.10.14-alpine3.20 ``` Redirecting output can also be useful to suppress a long list of CVEs, making the summary more immediately accessible. ## Output formats ### Standard formats You can use Grype to write itemized CVEs to a number of formats, including the XML- or JSON-based [cyclonedx](https://cyclonedx.org/) SBOM standard and the [SARIF](https://sarifweb.azurewebsites.net/) static analysis format. To maximize the information provided by Grype, use the JSON output type: ```bash grype -o json python:3.10.14-alpine3.20 > report.json ``` When using these more detailed formats, Grype provides additional useful fields, such as the data source of the CVE, URLs to information on the CVE, advisories, related vulnerabilities, and details on how the vulnerability was detected. ### Output templates Additional output formats are available as Hugo templates. These include output templates for HTML and CSV, and a [full list](https://github.com/anchore/grype/tree/main/templates) can be found at the Grype GitHub repository. To generate a HTML file of the itemized CVEs, first clone the Grype repository from GitHub. Then provide the path to the template file in your `grype` command: ```bash git clone git@github.com:anchore/grype.git ~/.grype grype -o template -t ~/.grype/templates/html.tmpl python:3.10.14-alpine3.20 > report.html ``` ![Screenshot of rendered HTML generated by the included Grype HTML template](grype_html_output.png) To generate a CSV: ```bash git clone git@github.com:anchore/grype.git ~/.grype grype -o template -t ~/.grype/templates/csv.tmpl python:3.10.14-alpine3.20 > report.csv ``` ## `grype explain` Grype provides an explain subcommand that gives information on the nature of a specific CVE, how it was matched, and the locations of files associated with the vulnerability. The output of this command suggests a useful starting point for remediation. To use `grype explain`, generate JSON output from Grype and pipe it into the `grype explain` subcommand. Indicate the CVE you'd like information on using the `--id` flag. The `-q` flag in the following example suppresses the summary output. ```bash grype -q python:3.10.14-alpine3.20 -o json | grype explain --id CVE-2023-36632 ``` Grype uses information from the JSON output to generate a human-readable report on the specific CVE that includes match information, file locations, and links to information on the vulnerability. ## Additional Resources The following resources may also be useful while working with Grype: ### Tools - [Syft](https://github.com/anchore/syft) - A Grype-compatible tool for generating SBOMs from images and filesystems. - [Grype-DB](https://github.com/anchore/grype-db) - A tool to build Grype databases from specific upstream vulnerability database providers - [Vunnel](https://github.com/anchore/vunnel) - A tool for collating vulnerability provider data - [Grype Chainguard Container](https://images.chainguard.dev/directory/image/grype/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-scanners-grype-tutorial) — A low-to-no CVE container image maintained by Chainguard ### More on Grype - [Chainguard Deep Dive 🤿: Where Does Grype Data Come From?](https://dev.to/chainguard/deep-dive-where-does-grype-data-come-from-n9e) - [Grype on the Anchore blog](https://anchore.com/search/?search=grype) - Blog posts from Anchore related to Grype - [Why Chainguard uses Grype](https://www.chainguard.dev/unchained/why-chainguard-uses-grype-as-its-first-line-of-defense-for-cves) - Why Chainguard contributes to and recommends Grype for vulnerability scanning in container images --- ### Getting started with the PyTorch Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/ai-and-data/pytorch.md Last Modified: September 28, 2026 Tags: Chainguard Containers, AI Chainguard's [PyTorch container image](https://images.chainguard.dev/directory/image/pytorch/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-pytorch) provides a security-hardened foundation for deep learning workloads. Built with [PyTorch](https://pytorch.org/) and [CUDA](https://developer.nvidia.com/about-cuda) support for GPU acceleration, this minimal image maintains full deep learning capabilities while reducing attack surface. This guide demonstrates fine-tuning models and secure inference deployment.
What is Deep Learning?

Deep learning is a subset of machine learning that leverages a flexible computational architecture, the neural network, to address a wide variety of tasks. Neural networks emulate the structure of the brain and consist of interconnected nodes (neurons) that each contain an associated weight and threshold. In concert with an activation function, these values determine whether data is propagated within the network, producing an output layer corresponding to a classification, regression, or other result.

By technical convention, a deep neural network (DNN) has at least three layers: an input layer, an output layer, and one or more hidden layers. In practice, DNNs often have many layers.

Deep neural networks underpin many common computational tasks in modern applications, such as speech to text and generative AI.

Setting up CUDA

Compute Unified Device Architecture (CUDA) is a parallel computing platform developed by NVIDIA. To take advantage of connected GPUs, you’ll need to follow the setup instructions for your local machine or create a CUDA-enabled instance on a cloud provider.

To set up CUDA on your local machine, follow the installation instructions for Linux or Windows. CUDA is not currently supported on Mac OS.

Google Cloud Platform provides CUDA-ready deep learning instances, including PyTorch-specific instructions. Amazon Web Services also provides CUDA-ready deep learning instances.

This guide is designed for use in an environment with access to one or more NVIDIA GPUs. However, the code below is written to also run in a CPU-only environment. Please note that tuning the model will take significantly longer in a CPU-only environment. ## Testing access to GPUs Our first step is to check whether our PyTorch-CUDA environment has access to connected GPUs. If you don't already have Docker Engine installed, follow the [instructions for installing Docker Engine on your host machine](https://docs.docker.com/engine/install/). Run the below command to pull the container image, run it with GPU access, and start a Python interpreter inside the running container. ```bash docker run --rm -it \ --gpus all \ cgr.dev/chainguard/pytorch:latest ``` Running the above for the first time may take a few minutes to pull the `pytorch` Chainguard Container, currently 3.3GB. Once the image runs, you will be interacting with a Python interpreter in the running container. Enter the following commands at the prompt to check the availability of your GPU. ``` Python 3.11.9 (main, Apr 2 2024, 15:40:32) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import torch >>> torch.cuda.is_available() True >>> torch.cuda.device_count() 1 >>> torch.cuda.get_device_name(0) 'Tesla V100-SXM2-16GB' ``` If the CUDA computing environment is accessible to PyTorch, `torch.cuda.is_available()` will return `True`. You should also check that at least one GPU is connected. If your environment only has access to CPU, you can complete the rest of this tutorial, but the step of fine-tuning the pretrained model will take significantly longer. Once you've determined that your environment has access to CUDA and connected GPUs, exit the container by typing `Control-d` or by typing `exit()` and pressing `Enter`. You should be returned to the prompt of your host machine. ## Training and inference overview A common workflow in deep learning is to collect labeled data, train a model using that data, and store the model. Later, this model can be loaded and used for inference, or making predictions based on novel inputs not in the training set. For example, we might train a model to recognize animals, then store the model as a serialized and compressed file. Later, and possibly in a new environment, we can load the model and use it to perform a classification task on novel data, such as an image of a whale provided by a user. It is common for model training to be performed in a development environment, and for inference to be performed in a production environment. We will follow this assumption in this tutorial, but be mindful not to use privileged access or root users in production. In this tutorial, we'll fine-tune a pretrained model for an image classification task: classifying whether a provided image is an octopus 🐙, a whale 🐳, or a penguin 🐧. We've chosen these animals in appreciation of Wolfi and Chainguard, Docker, and Linux, respectively. Rather than train a model from scratch, a process that requires a large set of input data, we'll start with a ResNet model with 18 layers ([resnet18](https://pytorch.org/vision/main/models/generated/torchvision.models.resnet18.html)). Using a fine-tuning approach with a pretrained model with relatively few layers is appropriate when using a limited amount of input data. In our case, we'll be using 60 images for each class, further divided into 40 training and 20 validation images. For the training step, we'll be accessing the container image as root. This allows us to save the model to a volume and preserve it on the host system. In our inference step, we'll access the container as the non-root user, an approach that will be more secure for a production use case. ## Fine-tuning the model In this section, we'll download prepared data to your environment, download a model training script, and run the script to train and save the model. These tasks will all be performed by running the below command. Further details on the Docker command, input data, and script are provided later in the section. First check that `curl` and `tar` are available on your system, and install them if necessary. [Docker Engine](https://docs.docker.com/engine/install/) will also need to be installed. Run the following to download necessary files and train the model. If there is an issue with the command, refer to the [manual step-by-step instructions](#manual-steps-to-fine-tune-the-model) below. Note: if you're following this tutorial in an environment without access to GPU, remove the `--gpus all \` line below before running. ```bash mkdir image_classification && cd image_classification curl https://codeload.github.com/chainguard-dev/pytorch-getting-started/tar.gz/main | \ tar -xz --strip=1 pytorch-getting-started-main/ && \ docker run --user root --rm -it \ --gpus all \ -v "$PWD/:/home/nonroot/octopus-detector" \ cgr.dev/chainguard/pytorch:latest \ "/home/nonroot/octopus-detector/image_classification.py" ``` The above command creates a new folder, `image_classification`, and changes the working directory to that folder. It then uses `curl` to download the training script and training and validation images from GitHub as a tar file and extracts the files. A container based on the `pytorch` container image is then created and the script and data are shared between host and container in a volume. The model is trained using the provided script and data, and the resulting model is saved to the volume. Training should take 1-3 minutes in a GPU-equipped environment, and 20-30 minutes in a CPU-only environment. Once the command completes, you can check your current working directory for a trained model as a `.pt` file: ```bash $ ls README.md image_classification.py data octopus_whale_penguin_model.pt ``` ## Manual steps to fine-tune the model Below are manual steps to perform the above download and training procedure interactively. You may wish to follow these steps if you need to modify the above for your own use case, if you'd like to better understand the steps involved, or if you have difficulty running the above command in your environment. These steps use `git clone` rather than `curl`. Also note that this manual process uses the `:latest-dev` version of the container image, since the `:latest` container image does not include shells such as bash for increased security. In the below steps, the prompt of your host machine will be denoted as `(host) $`, while the prompt of the container machine will be denoted as `(container) $` 1. Check that you have Git and Docker installed: ```bash (host) $ git --version git version 2.30.2 (host) $ docker --version Docker version 20.10.17, build 100c701 ``` 2. Clone the [repository with the training and validation data and the training script](https://github.com/chainguard-dev/pytorch-getting-started) and `cd` into the cloned repository: ```bash (host) $ git clone https://github.com/chainguard-dev/pytorch-getting-started.git (host) $ cd pytorch-getting-started ``` 3. Run the below command to start an interactive session in a running `pytorch` Chainguard Container with root access. If your environment doesn't have access to GPU, remove the `--gpus all \` line before running. Note the volume option, which creates a volume on the container based on the current working directory, allowing access to our training script and data inside the container. Remember that this guide assumes you are training the model in a controlled development environment—do not use root access in any production scenario. ```bash (host) $ docker run --user root --rm -it \ --gpus all \ --entrypoint bash \ -v "$PWD/:/home/nonroot/octopus-detector" \ cgr.dev/chainguard/pytorch:latest-dev ``` 4. You should now have access to an interactive shell inside the container. Navigate to the created volume: ```bash (container) $ cd /home/nonroot/octopus-detector/ (container) $ pwd /home/nonroot/octopus-detector ``` 5. Run the model-training script: ```bash (container) $ python image_classification.py Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /root/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth 100.0% 🐙 Epoch 0/24 🐳 train Loss: 0.9276 Acc: 0.5583 🐧 val Loss: 0.2275 Acc: 0.9500 [...] 🐙 Epoch 24/24 🐳 train Loss: 0.1940 Acc: 0.9167 🐧 val Loss: 0.0248 Acc: 1.0000 Training complete in 1m 39s Best val Acc: 1.000000 ``` 6. If the script ran successfully, you should have a saved model serialized as a `.pt` file in the current working directory: ```bash (container) $ ls octopus_whale_penguin_model.pt data image_classification.py ``` 7. At this point, you've trained your model. Shut down the container by pressing `Control-d` or by typing `exit` and pressing `Enter`. Since we used a volume, the model should also be present on the host machine: ```bash (host) $ ls octopus_whale_penguin_model.pt image_classification.py data ``` ## Running inference You have now downloaded the resnet18 pretrained model and fine-tuned it to detect three classes of images: octopuses, whales, and penguins. Now that the model is trained, we can load it, pass in a new image, and receive the model's prediction. Using an existing model for prediction is called inference, and in many common scenarios inference is run in a production environment. For this reason, we'll access our existing model with the nonroot user in this section. The script ([image_classification.py](https://github.com/chainguard-dev/pytorch-getting-started/blob/main/image_classification.py)) run in the above commands has been written to check if a model exists in the same folder and, if present, load it. It will also perform inference if a path to an image is passed as an argument when the script is run. Since we should now have a model file present on our host machine, let's go ahead and run inference on a new image of an octopus. Feel free to find your own image of an octopus on the web, or run the below command to download [an image](https://raw.githubusercontent.com/chainguard-dev/pytorch-getting-started/main/inference-images/octopus.jpg) not in the training data. The training data used realistic images, so you may not wish to choose, for example, a stylized or cartoon image of an octopus. ```bash curl https://raw.githubusercontent.com/chainguard-dev/pytorch-getting-started/main/inference-images/octopus.jpg > octopus.jpg ``` Now that we have a novel input, let's run inference to classify the image: ```bash docker run --rm -it \ --gpus all \ -v "$PWD/:/home/nonroot/octopus-detector" \ cgr.dev/chainguard/pytorch:latest \ "/home/nonroot/octopus-detector/image_classification.py" \ "/home/nonroot/octopus-detector/octopus.jpg" ``` After running this, you should see the model's classification of the image as output: ```bash octopus ``` Feel free to try the above inference on other images of octopuses, whales, and penguins. The model should have high accuracy for images similar to those in the training set, which consists of photorealistic images. ## Notes on the script In this section, we'll review the script provided in the above steps, highlighting some common options and approaches and a few ways the script might be adapted to other use cases. Deep learning is a complex and emerging field, so this section can only provide a high-level overview and a few recommendations for moving forward. To fine-tune a model for image classification as we did here, you can replace the provided training and validation data with your own. The script examines the number of folders in the training set to determine the targeted number of classes. The folder names are used as class labels. We used 40 training and 20 validation images for each class, but a ratio of 5:1 training to validation may also produce good results. By fine-tuning a pretrained model, we took advantage of transfer learning, meaning that the pretrained model (resnet18) was already trained on inputs with relevance to our classification task. Because we used transfer learning, the relatively small amount of input data was still sufficient for good accuracy in our fine-tuned model. If you're working with a large amount of input data, you might consider using a larger pretrained model, such as resnet34. In addition, if training using significantly more data or training using limited computation relative to the task, you may consider the more efficient convolutional neural network as fixed feature extractor approach, which trains only one attached layer rather than updates the original model. PyTorch maintains a [set of guides](https://pytorch.org/tutorials/) that are frequently updated. These provide a good starting point when undertaking a new project in PyTorch. If you're new to the field of deep learning, the book [Deep Learning for Coders with Fastai and PyTorch](https://course.fast.ai/Resources/book.html) hosts [freely available materials on GitHub](https://github.com/fastai/fastbook). --- ### Migrate a Java application to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-guides/java.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Migration Chainguard's Java containers fill the same roles as the Java base images found on Docker Hub, such as `eclipse-temurin` and `maven`, but are built on [Wolfi](/open-source/wolfi/) with a much smaller package set. Chainguard builds its own JDK from source in Wolfi, and rebuilds these containers nightly so security patches land without manual intervention. Porting a Dockerfile takes a handful of changes, mostly around the non-root user and the absent shell, which [Differences from the Java images on Docker Hub](#differences-from-the-java-images-on-docker-hub) covers in full. For current CVE data on a specific container and tag, refer to the [JRE entry in the Chainguard Containers directory](https://images.chainguard.dev/directory/image/jre/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-java).
What is Distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi OS? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

This guide is intended to help you port an existing Java Dockerfile to a Chainguard Containers base. ## Java Chainguard Containers Chainguard publishes four containers for Java, split by the job they do: | Container | Contents | Use it for | | --- | --- | --- | | [`maven`](https://images.chainguard.dev/directory/image/maven/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-java) | JDK plus Apache Maven | The build stage of a Maven project | | [`gradle`](https://images.chainguard.dev/directory/image/gradle/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-java) | JDK plus Gradle | The build stage of a Gradle project | | [`jdk`](https://images.chainguard.dev/directory/image/jdk/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-java) | Full Java Development Kit | Compiling inside the container without Maven or Gradle | | [`jre`](https://images.chainguard.dev/directory/image/jre/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-java) | Java Runtime Environment only | Running a compiled JAR in production | The `jre` container is the production target. It has no compiler, no build tooling, no shell, and no package manager, which keeps it small but also means you cannot extend it in place. The build containers include a shell and a package manager and can be extended freely. Each of these also comes in a [development variant](/chainguard/containers/concepts/container-variants/), distinguished by the tag suffix (for example, `jre:latest-dev`). The development variants add a shell and package manager to a runtime container, which makes them useful for debugging and for the rare application that needs system tooling at runtime. The recommended approach for migration is to use a multi-stage build: compile in `maven` or `gradle`, then copy the JAR into `jre`. This guide's [migration example](#migration-example) builds exactly that. ## Migrating from other distributions Dockerfiles often contain commands specific to the Linux distribution they are based on. Most commonly this relates to package installation (`apt` versus `yum` versus `apk`), but it also covers the default shell (`bash` versus `ash`) and default utilities (`groupadd` versus `addgroup`). The high-level guide on [Migrating to Chainguard Containers](/chainguard/containers/migration/migrating-to-chainguard-images/) covers distro-based migration and package compatibility for Debian, Alpine, Ubuntu, and Red Hat UBI base images. ## Installing further dependencies Java applications sometimes need native libraries at build time, runtime, or both. Wolfi has a large package repository, though package names may differ from other distributions. The easiest way to search is with apk tools in a `wolfi-base` container: ```shell docker run -it --rm cgr.dev/chainguard/wolfi-base ``` ```shell apk update apk search freetype ``` Add the packages you find to the build stage of your Dockerfile. Note that `apk add` needs write access, so a Dockerfile that installs packages has to switch to the root user first with `USER root`. For more searching tips, check the [Searching for Packages](/chainguard/containers/migration/migrating-to-chainguard-images/#searching-for-packages) section of the base migration guide. ## Differences from the Java images on Docker Hub If you are migrating from Docker Hub's `maven` image or from `eclipse-temurin`, a few differences matter: - **The containers run as a non-root user.** UID `65532` is the default, where the Docker Hub images run as root. If a build step needs elevated privileges, add `USER root` before it, and switch back for the production stage. - **`WORKDIR` differs by container.** It is `/app` in `jre`, and `/home/build` in `maven`, `gradle`, and `jdk`. - **The entrypoint is the tool, not a shell.** `jre` sets `/usr/bin/java` and `maven` sets `/usr/bin/mvn`, so arguments you pass to `docker run` go to that program. For example, `docker run cgr.dev/chainguard/jre:latest -version` prints the container's Java version. To get a shell you need a `-dev` variant and an explicit entrypoint override: `docker run --entrypoint /bin/sh -it cgr.dev/chainguard/jre:latest-dev`. - **`JAVA_HOME` is `/usr/lib/jvm/default-jvm`.** Scripts that hardcode a Temurin path need updating. - **There are far fewer libraries and utilities present.** An application may turn out to have a dependency the container doesn't carry, which you need to add explicitly. ## Migration example This example ports a Spring Boot application from Docker Hub's `maven` image to Chainguard's Maven and JRE containers, in two steps. The application listens on port `8080` and answers `/hello`. Its source is in the [`learning-labs-java` repository](https://github.com/chainguard-dev/learning-labs-java): ```shell git clone https://github.com/chainguard-dev/learning-labs-java.git cd learning-labs-java ``` Each step writes a new Dockerfile rather than editing one in place, so you can build all three and compare them side by side. The repository ships its own `Dockerfile` variants from when the accompanying video was recorded, but those pin container digests from 2024; the files you create here track current tags instead, which is what makes the size and CVE comparison meaningful. ### The starting point Save the following as `Dockerfile.classic`. This Dockerfile is a single-stage build on Docker Hub's `maven` image, which is itself built on Eclipse Temurin: ```Dockerfile FROM maven:latest WORKDIR /work COPY src/ src/ COPY pom.xml pom.xml RUN mvn clean package WORKDIR /app RUN cp /work/target/java-demo-app-1.0.0.jar . ENTRYPOINT ["java", "-jar", "java-demo-app-1.0.0.jar"] ``` Build and run it: ```shell docker build -f Dockerfile.classic -t java-maven . docker run --rm -d --name java-demo -p 8080:8080 java-maven curl localhost:8080/hello docker stop java-demo ``` The result is a working application in a large container. Everything Maven needed in order to build the JAR is still sitting in the image that runs it. ### Step 1: change the base container Copy `Dockerfile.classic` to `Dockerfile.cg` and change a single line, the `FROM` instruction: ```Dockerfile FROM cgr.dev/chainguard/maven:latest ``` The remainder of the file is unchanged. Build it under a new tag to allow a direct comparison: ```shell docker build -f Dockerfile.cg -t java-maven-cg . ``` That single line cuts the image size substantially, and clears out the operating-system package findings a scanner reports, because there are far fewer packages left to report on. Compare the two directly: ```shell docker images | grep java-maven grype java-maven grype java-maven-cg ``` This base swap replaces the operating system underneath your application, so findings against distribution packages largely go away. It does not touch your application's own dependencies: the JARs Maven resolved from `pom.xml` are identical in both images, so any CVEs in those survive the change. Fixing those means updating the dependencies themselves, which is what [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) addresses. If you prefer Docker Hub to `cgr.dev`, Chainguard's Free containers are mirrored there under the `chainguard` organization, so `FROM chainguard/maven:latest` also works. ### Step 2: split the build into two stages The container still carries Maven and a full JDK into production. A multi-stage build compiles in the Maven container and copies only the JAR into the JRE container. Save the following as `Dockerfile.cg-multi`: ```Dockerfile FROM cgr.dev/chainguard/maven:latest AS builder WORKDIR /work COPY src/ src/ COPY pom.xml pom.xml RUN mvn clean package FROM cgr.dev/chainguard/jre:latest AS runner WORKDIR /app COPY --from=builder /work/target/java-demo-app-1.0.0.jar . ENTRYPOINT ["java", "-jar", "java-demo-app-1.0.0.jar"] ``` ```shell docker build -f Dockerfile.cg-multi -t java-maven-multi-cg . docker run --rm -d --name java-demo -p 8080:8080 java-maven-multi-cg curl localhost:8080/hello docker stop java-demo ``` The application behaves the same, in a container roughly 250 MB smaller than the single-stage Chainguard build, because the build tooling never reaches the final stage. The `jre` container also has no shell, so there is nothing for an attacker who reaches the container to run. This step cuts scanner findings a second time, and for a different reason than the base swap did. A single-stage build ships Maven's own bundled JARs and everything it downloaded into the local repository, and a scanner reports on all of them. Only the application JAR survives the copy into the final stage: ```shell grype java-maven-multi-cg ``` There are two important things to note on the `COPY` line. The JAR filename comes from the project's `pom.xml`, so it differs in your application; a wildcard such as `COPY --from=builder /work/target/*.jar app.jar` avoids restating the version. Additionally, because `jre` already sets `WORKDIR` to `/app`, the `WORKDIR` line in the runner stage is explicit rather than required. ### Video demonstration The following video walks through the same migration outlined in the previous steps: [Watch the video on YouTube](https://www.youtube.com/watch?v=FYOVcSv1-oY) ## Additional resources - The [JRE container documentation](https://images.chainguard.dev/directory/image/jre/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-java) has full details on the Java containers, including usage, provenance, and security advisories. - [Build Java containers with Jib](/chainguard/containers/building-and-modifying/build-tools/building-java-containers-with-jib/) covers building Java containers without writing a Dockerfile at all, using Jib's Maven and Gradle plugins. - [Building minimal container images](/chainguard/containers/building-and-modifying/building-minimal-containers/) explains the multi-stage pattern this guide uses, and when a runtime container is the right final base. - [Fully bootstrapping Java from source in Wolfi](https://www.chainguard.dev/unchained/fully-bootstrapping-java-from-source-in-wolfi) describes how Chainguard builds its JDK. - [Debugging distroless container images](/chainguard/containers/troubleshooting/debugging-distroless-images/) covers working with a production container that has no shell. - [How to port a sample application to Chainguard Containers](/chainguard/containers/migration/porting-apps-to-chainguard/) works through porting a legacy application. --- ### How Chainguard tests its container images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/how-we-build-and-test/images-testing.md Last Modified: October 8, 2026 Tags: Overview, Chainguard Containers Chainguard Containers is a collection of minimal, distroless container images that you can use to build and run secure applications. Given the importance of secure, highly performant images, Chainguard performs testing to ensure our container images match the functionality of upstream and other external counterparts. This article provides a high-level overview of Chainguard's approach to testing when building new container images to ensure their security and consistency with comparable container images. ## Build requirements for new container images Chainguard has a set of requirements in place that new container images must meet in order to be included in our [Containers Directory](https://images.chainguard.dev?utm=docs). These requirements fall into two categories: * Container image standards * Comprehensive testing ### Container image standards When building a new container image, Chainguard will take steps to ensure it meets the following standards: | **Requirement** | **Explanation** | | --- | --- | | **Size** | Any new Chainguard Containers should be smaller than their external counterparts, though exceptions may occur. | | **CVEs** | When scanned with a CVE scanning tool like Grype, new container images should return zero CVEs. If a container image does return CVEs in its scan results it should include an explanation, though some reported CVEs may be false positives. Refer to our [Security Advisories](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-images-testing) for more information. | | **Kubernetes accessibility** | Containers used for Kubernetes-based deployments must be able to run inside of a Kubernetes cluster. | | **Architecture** | Chainguard Containers must be built for both the `x86_64` and `aarch64` architectures. | Chainguard also performs the following checks on new container images to ensure that the applications contained within them meet the needs of most use cases: | **Requirement** | **Explanation** | | --- | --- | | **Functionality** | The application is tested to comply with its upstream counterpart's core feature set. | | **Builder Containers** | Chainguard's builder containers can in fact build new, functional container images. | ### Comprehensive testing Chainguard builds a wide variety of different container images, with differing core functionalities and expected installation methods. This means we must vary our testing across container images to test the right things. It can be helpful to think about the various layers in a container image build process and what would be useful to test for each layer: | **Layer** | **Test** | **Applies to** | | --- | --- | --- | | Deployment | Upstream accepted, documented deployment methods | All services and controllers | | Runtime security | Non-root, no extra capacities, read-only rootfs | All | | TLS | Valid chain, hostname, min TLS | Any listener, FIPS | | Persistence | Correct file and directory permissions, read/write, restarts cleanly | Stateful, such as databases | | UI | Smoke test and auth | UI apps, such as Argo | | Metrics and logging | Metrics emitted or exposed if the app exposes them, logs are written to expected locations if they are emitted | Almost all containers intended for production environments | In addition, Chainguard performs a number of automatic checks for new container images as part of our CI/CD process. Depending on the container image, Chainguard performs representative tests, such as functional and integration tests. For example, for applications primarily deployed with a Helm chart, the container image is deployed to an ephemeral Kubernetes cluster using the accepted Helm chart, which is validated in various ways. When applicable, Chainguard will develop functional tests for container images. These tests vary by application, but can generally be thought of as integration tests that run after a container image is built but before it gets tagged. Our goal for these tests is that they fully evaluate the container image's deployment in a representative environment; for example, container images running Kubernetes applications are tested in a Kubernetes cluster and builder or toolchain applications are tested with a `docker run` command or part of a `docker build` process. This means that our container images work with the existing upstream deployment methods, such as Helm charts or Kustomize manifests, helping us to ensure that a container image is as close to a drop-in replacement as possible. Additionally, Chainguard performs automated tests on every *package* included in our container images. These tests run on every new package build within an ephemeral container environment before the package build is published. This allows us to validate the representative functionality of each package well before packages are assembled into container images. ## Learn more Chainguard's rigorous container image testing standards and frequent updates ensure that they will work as expected with few (and often zero) vulnerabilities. If you're having trouble working with a specific Chainguard Container, we encourage you to check out its relevant Overview page in our [Chainguard Containers Directory](https://images.chainguard.dev/directory?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-images-testing). For general help with using Chainguard Containers, you can refer to our [Debugging distroless container images](/chainguard/containers/troubleshooting/debugging-distroless-images/) guide or our [Chainguard Containers FAQs](/chainguard/containers/faq/). For help with specific issues or questions not covered in these resources, please [contact our support team](https://support.chainguard.dev?utm=docs). Refer to [Get support](/get-started/get-support/) for the portal's prerequisites. --- ### How to set up pull-through from Chainguard's container registry to Artifactory URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/artifactory-containers-pull-through.md Last Modified: October 8, 2026 Tags: Chainguard Containers Organizations can route container image pulls through Artifactory to centralize artifact management, enforce policy, and integrate Chainguard Containers into existing CI/CD workflows. You can configure Artifactory as a pull-through cache by setting up a remote repository pointed at [Chainguard's container registry](/chainguard/containers/registry/overview/). This tutorial outlines how to set up remote repositories with [JFrog Artifactory](https://jfrog.com/artifactory/). Specifically, it goes over how to set up one repository you can use as a pull-through cache for Chainguard's public [Free containers](/chainguard/containers/concepts/container-categories/#free-containers) and another you can use for [Production containers](/chainguard/containers/concepts/container-categories/#production-containers) originating from a private Chainguard repository. It also outlines how you can use one of Artifactory's [virtual repositories](https://jfrog.com/help/r/jfrog-artifactory-documentation/virtual-repositories) as a pull-through cache to access resources from multiple remote repositories in a single location. ## Prerequisites To complete this tutorial, you need the following: * Administrative privileges over an Artifactory instance. If you're interested in testing out this configuration, you can set up a trial instance on the [JFrog Artifactory landing page](https://jfrog.com/artifactory/). * Docker installed on your local machine. Refer to [the official documentation](https://docs.docker.com/engine/install/) to set this up. Part of this guide assumes you have access to a private registry provided by Chainguard with one or more Production container images. If you don't already have access to these, you can [contact our sales team](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). To complete this portion, you will also need the following: * Permissions to pull container images from your organization's private Chainguard registry. At minimum, you must be granted the `registry.pull` role, but other built-in roles like `owner`, `editor`, or `viewer` will also work. Refer to our guide on [Built-in roles and capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/#pull-token-creator-roles) for more details. * `chainctl`, Chainguard's command line interface tool, installed on your local machine. To set this up, follow our [installation guide for `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/). ## Setting up Artifactory as a pull-through cache for free containers Chainguard's Free Containers are free to use, publicly available, and always represent versions tagged as `:latest`. To set up a remote repository in Artifactory from which you can pull Chainguard Free Containers: 1. Log in to the JFrog platform. 2. Select the **Administration** tab near the top of the screen. 3. Select **Repositories** from the left-hand navigation menu. 4. On the Repositories page, click **Create a Repository**. 5. Select the **Remote** option. 6. In the **Select Package Type** window, select **Docker**. This takes you to a **Basic** configuration tab where you can enter the following details for your new remote repository: * **Repository Key** — This is the name used to refer to your repository. You can choose whatever name you like here, but this guide's examples use the name `cgr-public`. * **URL** — This must be set to `https://cgr.dev/`. This field **must not** include additional path components — setting it to something like `https://cgr.dev/chainguard/` will cause pulls to fail with a `manifest unknown` error. To shorten the pull path, use the **Project ID** field described below instead. * **Project ID** — Optional. If you set this to `chainguard`, Artifactory prepends that path segment to upstream requests and your users can omit it from their pull commands. Leave it empty to keep the `chainguard/` prefix in the pull path, as this guide's examples do. * **Include Patterns** — Ensure that you use the default value (`**/*`) in this field. * **Enable Token Authentication** — Ensure this setting (under **Docker Settings**) is enabled. This is required, as you must authenticate to the remote repository in order to pull Chainguard Containers through it. * **Block Mismatching Mime Types** — In the **Advanced** configuration tab, ensure that this option is checked. Following that, click **Create Remote Repository**. Next, you can test that you're able to pull a Chainguard Free Container through the remote repository. ### Testing pull-through of a Chainguard free container Before testing whether you're able to pull Chainguard's Free Containers through the remote Artifactory repository, you must retrieve a token generated by Artifactory for your remote repository. Expand the following section to retrieve a token and authenticate:
JFrog Artifactory Token Retrieval
  1. On the Repositories page, find your repository.
  2. Hover your cursor over the repository's row and click the ellipsis (⋯) all the way to the right.
  3. Select Set me up.
  4. Click the Generate Token & Create Instructions button.

The resulting window contains two tabs labeled Configure and Pull, each with certain commands and details you can copy.

In the Configure tab, copy the 64-character token, and then run a docker login command like the following on your local machine:

docker login -u <linky@chainguard.dev> <my-project>.jfrog.io

Be sure to replace the example username and Artifactory instance name in this command. The username following the -u flag must be the email or username associated with your JFrog Artifactory account, while you must replace <my-project> with the name of the Artifactory project where you created the remote repository. If you aren't sure of the name of your project, you can find it in the docker login command below the token you just copied.

At the Password: prompt, paste the token you copied previously and press ENTER.

After running the `docker login` command, you will be able to pull a Chainguard Free Container through Artifactory. The following example pulls the `go` container image: ```sh docker pull .jfrog.io/cgr-public/chainguard/go ``` If you set **Project ID** to `chainguard` when creating the repository, omit that segment: ```sh docker pull .jfrog.io/cgr-public/go ``` Be sure the `docker pull` command you run includes the name of your project as well as your own repository key in place of `cgr-public`, if different. ## Setting up Artifactory as a pull-through cache for production containers Chainguard's Production images are enterprise-ready container images that come with patch Service Level Agreements (SLAs) and features such as [Federal Information Processing Standard](/platform/fips/fips-images/) (FIPS) readiness. The process for setting up an Artifactory repository that you can use as a pull-through cache for Chainguard Production Containers is similar to the one outlined previously for Free Containers, but with a few extra steps. To get started, create [a pull token](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) for your organization's registry. Pull tokens are longer-lived tokens that can be used to pull Chainguard Containers from other environments that don't support OIDC, such as some CI environments, Kubernetes clusters, or registry mirroring tools like Artifactory. To create a pull token with `chainctl`, run the following command: ```sh chainctl auth configure-docker --pull-token ``` `chainctl` uses your organization automatically when you belong to only one. If you have access to more than one, add `--parent `, replacing `` with the name or ID you want to use. > **Note**: You can find your Chainguard organization's name or ID by running `chainctl iam organizations list -o table`. This command returns a `docker login` command like the following: ```output . . . docker login "cgr.dev" --username "" --password "" ``` Record the values for `` and `` as you'll need these credentials when you configure a new remote Artifactory repository for pulling through Production Containers. After noting your credentials, you can begin setting up an Artifactory repository from which you can pull Chainguard Production Containers. This process is similar to the one outlined previously: 1. Log in to the JFrog platform. 2. Select the **Administration** tab near the top of the screen. 3. Select **Repositories** from the left-hand navigation menu. 4. On the Repositories page, click **Create a Repository**. 5. Select the **Remote** option. 6. In the **Select Package Type** window, select **Docker**. Next, enter the following details for your new remote repository in the **Basic** configuration tab: * **Repository Key** — Choose whatever name you like here, but this guide's examples use the name `cgr-private`. * **URL** — This must be set to `https://cgr.dev/`. This field **must not** include additional path components — setting it to something like `https://cgr.dev//` will cause pulls to fail with a `manifest unknown` error. To remove the organization from the pull path, use the **Project ID** field described below instead. * **Project ID** — Optional. Set this to your organization's name (for example, `example.com`) and Artifactory prepends it to upstream requests, letting your users omit it from their pull commands. Because this field is set per repository, you need one remote repository per Chainguard organization if you pull from more than one. * **User Name** — Enter the `` value you noted from the `docker login` command. * **Password / Access Token** — Enter the `` value you noted from the `docker login` command. * **Include Patterns** — Ensure that you use the default value (`**/*`) in this field. * **Enable Token Authentication** — Ensure this setting (under **Docker Settings**) is enabled. This is required, as you must authenticate to the remote repository in order to pull Chainguard Containers through it. * **Block Mismatching Mime Types** — In the **Advanced** configuration tab, ensure that this option is checked. Finally, click **Create Remote Repository**. You can then move on to testing that you're able to pull Chainguard Production container images through this remote repository. ### Testing pull-through of a Chainguard production container As with the `cgr-public` repository example, you must retrieve a token generated by Artifactory for your repository before testing whether you're able to pull Chainguard Production container images through the remote repository you just created. Expand the following section to retrieve a token and authenticate:
JFrog Artifactory Token Retrieval
  1. On the Repositories page, find your repository.
  2. Hover your cursor over the repository's row and click the ellipsis (⋯) all the way to the right.
  3. Select Set me up.
  4. Click the Generate Token & Create Instructions button.

The resulting window contains two tabs labeled Configure and Pull, each with certain commands and details you can copy.

In the Configure tab, copy the 64-character token, and then run a docker login command like the following on your local machine:

docker login -u <linky@chainguard.dev> <my-project>.jfrog.io

Be sure to replace the example username and Artifactory instance name in this command. The username following the -u flag must be the email or username associated with your JFrog Artifactory account, while you must replace <my-project> with the name of the Artifactory project where you created the remote repository. If you aren't sure of the name of your project, you can find it in the docker login command below the token you just copied.

At the Password: prompt, paste the token you copied previously and press ENTER.

After running the `docker login` command, you will be able to pull Chainguard Production Containers through Artifactory. The following example pulls the `chainguard-base` image if your organization has access to it: ```sh docker pull .jfrog.io/cgr-private//chainguard-base:latest ``` If you set **Project ID** to your organization's name when creating the repository, omit the organization from the pull path: ```sh docker pull .jfrog.io/cgr-private/chainguard-base:latest ``` Be sure the `docker pull` command you run includes the name of your Artifactory project and the name of your organization's registry. Additionally, if you entered a different repository key in the setup section, use it in place of `cgr-private`. ## Setting up an Artifactory virtual repository as a pull-through cache Artifactory allows you to create what it refers to as [*virtual repositories*](https://jfrog.com/help/r/jfrog-artifactory-documentation/virtual-repositories). A virtual repository is a collection of one or more repositories (such as local, remote, or other virtual repositories) that have the same package type. The benefit of this is that you can access resources from multiple locations using a single logical URL. You can also use a virtual repository as a pull-through cache. To set this up, create a new virtual repository: 1. Navigate to the **Repositories** tab. 2. Click **Create a Repository**. 3. Select the **Virtual** option. 4. In the **Select Package Type** window, select **Docker**. When you reach the **New Virtual Repository** page, enter a key of your choosing into the **Repository Key** field. This guide's examples refer to this repository as `cgr-virt`. Next, you must select existing repositories to include within this virtual repository. To keep things simple, this guide uses the `cgr-public` and `cgr-private` repositories created previously: 1. Locate the **Repositories** section in the **Basic** configuration tab. 2. Select the repositories you want to add from the **Available Repositories** by clicking their respective checkboxes. 3. Click the right-pointing chevron to move them to **Selected Repositories**. 4. Click **Create Virtual Repository**. With that, you can retrieve the token and `docker login`. ### Testing pull-through with a virtual repository As with the previous examples, you must retrieve a token generated by Artifactory before testing whether you're able to pull Chainguard Containers through the virtual repository. Expand the following section to retrieve a token and authenticate:
JFrog Artifactory Token Retrieval
  1. On the Repositories page, find your repository.
  2. Hover your cursor over the repository's row and click the ellipsis (⋯) all the way to the right.
  3. Select Set me up.
  4. Click the Generate Token & Create Instructions button.

The resulting window contains two tabs labeled Configure and Pull, each with certain commands and details you can copy.

In the Configure tab, copy the 64-character token, and then run a docker login command like the following on your local machine:

docker login -u <linky@chainguard.dev> <my-project>.jfrog.io

Be sure to replace the example username and Artifactory instance name in this command. The username following the -u flag must be the email or username associated with your JFrog Artifactory account, while you must replace <my-project> with the name of the Artifactory project where you created the remote repository. If you aren't sure of the name of your project, you can find it in the docker login command below the token you just copied.

At the Password: prompt, paste the token you copied previously and press ENTER.

After retrieving your token and logging into `docker`, you will be able to pull Chainguard Containers through the Artifactory virtual repository. To pull a public image, you would run a command like the following, which pulls the public `mariadb` image: ```sh docker pull .jfrog.io/cgr-virt/chainguard/mariadb:latest ``` To pull a Production Container, replace `chainguard` with the name of your organization's registry. The following example pulls the `chainguard-base` image if your organization has access to it: ```sh docker pull .jfrog.io/cgr-virt//chainguard-base:latest ``` For both of these commands, be sure the `docker pull` command you run includes the name of your Artifactory project and the name of your organization's registry. Also, if you used a different repository key, substitute it for `cgr-virt` in the previous commands. > **Note**: Take care when aggregating remote repositories that each set a **Project ID**. If both the free and private remotes strip their path prefixes, image names from different sources collapse into the same namespace within the virtual repository — for example, both `chainguard/go` and `/go` would resolve as `go`, and the virtual repository's resolution order determines which one you get. ## Debugging pull-through from Chainguard’s registry to Artifactory If you run into issues when trying to pull images from Chainguard's container registry to Artifactory, ensure the following requirements are met: * Ensure that all Containers [network requirements](/chainguard/containers/registry/network-requirements/) are met. * If you attempt to pull a nonexistent image via pull-through, Artifactory will also make calls to `chainguard.dev` and `www.chainguard.dev`. Calls to these domains should not occur when pulling a valid image. * When configuring a remote Artifactory repository, ensure that the **URL** field is set to `https://cgr.dev/`. This field **must not** contain additional components. Adding a path such as `https://cgr.dev//` produces a `manifest unknown: The named manifest is not known to the registry` error, because Artifactory ignores the path portion of a Docker remote's URL. If your goal is to shorten the pull path, set the repository's **Project ID** to your organization's name instead. * You can troubleshoot by running `docker login` from another node (using the Artifactory pull token credentials) and then trying to pull a Container from `cgr.dev/chainguard/` or `cgr.dev//`. * It may help to [clear the Artifactory cache](https://jfrog.com/help/r/artifactory-cleanup-best-practices/clearing-an-oversized-cache). * Your Artifactory repository may be misconfigured. In this case, create and configure a new remote Artifactory repository to test with. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about the Chainguard container registry. You can also learn more about Chainguard Containers by referring to our [Containers documentation](/chainguard/containers/overview/). If you'd like to learn more about JFrog Artifactory, refer to the [official Artifactory documentation](https://jfrog.com/help/r/jfrog-artifactory-documentation). --- ### Debian compatibility URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/compatibility/debian-compatibility.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Reference Chainguard Containers and Debian base images have different binaries and scripts included in their respective `busybox` and `coreutils` packages. The following table lists common tools and their corresponding package(s) in both Wolfi and Debian distributions. Note that `$PATH` locations like `/usr/bin` or `/sbin` are not included here. If you have compatibility issues with tools that are included in both `busybox` and `coreutils`, be sure to check `$PATH` order and confirm which version of a tool is being run. Generally, if a tool exists in `busybox` but does not have a `coreutils` counterpart, there will be a specific package that includes it. For example the `zcat` utility is included in the `gzip` package in both Wolfi and Debian. You can use the `apk search` command in Wolfi, and the `apt-cache search` command in Debian to find out which package includes a tool. If your workflow depends on tools or packages from the upstream Debian image, note that Chainguard offers *full* variants (tagged `-full`) for a number of our most popular Containers.These variants map to their upstream equivalent (often the Debian image on Docker Hub) and can ease migration before you move to a more minimal image. Refer to [Full container variants](/chainguard/containers/concepts/container-variants/#full-container-variants). | Utility | Wolfi busybox | Debian busybox | Wolfi coreutils | Debian coreutils | | ------------------- | :-----------: | :------------: | :-------------: | :--------------: | | `[` | ✅ | ✅ | ✅ | ✅ | | `[[` | ✅ | ✅ | | | | `acpid` | | ✅ | | | | `add-shell` | ✅ | | | | | `addgroup` | ✅ | | | | | `adduser` | ✅ | | | | | `adjtimex` | ✅ | ✅ | | | | `ar` | | ✅ | | | | `arch` | ✅ | ✅ | | ✅ | | `arp` | | ✅ | | | | `arping` | ✅ | ✅ | | | | `ascii` | | ✅ | | | | `ash` | ✅ | ✅ | | | | `awk` | ✅ | ✅ | | | | `b2sum` | | | ✅ | ✅ | | `base32` | | | ✅ | ✅ | | `base64` | ✅ | ✅ | ✅ | ✅ | | `basename` | ✅ | ✅ | ✅ | ✅ | | `basenc` | | | ✅ | ✅ | | `bbconfig` | ✅ | | | | | `bc` | ✅ | ✅ | | | | `beep` | ✅ | | | | | `bin` | | | | ✅ | | `blkdiscard` | | ✅ | | | | `blkid` | | ✅ | | | | `blockdev` | | ✅ | | | | `brctl` | | ✅ | | | | `bunzip2` | ✅ | ✅ | | | | `bzcat` | ✅ | ✅ | | | | `bzip2` | ✅ | ✅ | | | | `cal` | ✅ | ✅ | | | | `cat` | ✅ | ✅ | ✅ | ✅ | | `chattr` | ✅ | | | | | `chcon` | | | ✅ | ✅ | | `chgrp` | ✅ | ✅ | ✅ | ✅ | | `chmod` | ✅ | ✅ | ✅ | ✅ | | `chown` | ✅ | ✅ | ✅ | ✅ | | `chpasswd` | ✅ | | | | | `chroot` | ✅ | ✅ | ✅ | ✅ | | `chrt` | ✅ | | | | | `chvt` | | ✅ | | | | `cksum` | ✅ | | ✅ | ✅ | | `clear` | ✅ | ✅ | | | | `cmp` | ✅ | ✅ | | | | `comm` | ✅ | | ✅ | ✅ | | `coreutils` | | | ✅ | | | `cp` | ✅ | ✅ | ✅ | ✅ | | `cpio` | ✅ | ✅ | | | | `crc32` | | ✅ | | | | `cryptpw` | ✅ | | | | | `csplit` | | | ✅ | ✅ | | `cttyhack` | | ✅ | | | | `cut` | ✅ | ✅ | ✅ | ✅ | | `date` | ✅ | ✅ | ✅ | ✅ | | `dc` | ✅ | ✅ | | | | `dd` | ✅ | ✅ | ✅ | ✅ | | `deallocvt` | | ✅ | | | | `delgroup` | ✅ | | | | | `deluser` | ✅ | | | | | `depmod` | | ✅ | | | | `devmem` | | ✅ | | | | `df` | ✅ | ✅ | ✅ | ✅ | | `diff` | ✅ | ✅ | | | | `dir` | | | ✅ | ✅ | | `dircolors` | | | ✅ | ✅ | | `dirname` | ✅ | ✅ | ✅ | ✅ | | `dmesg` | ✅ | ✅ | | | | `dnsdomainname` | ✅ | ✅ | | | | `dos2unix` | ✅ | ✅ | | | | `du` | ✅ | ✅ | ✅ | ✅ | | `dumpkmap` | | ✅ | | | | `dumpleases` | | ✅ | | | | `echo` | ✅ | ✅ | ✅ | ✅ | | `ed` | ✅ | | | | | `egrep` | ✅ | ✅ | | | | `env` | ✅ | ✅ | ✅ | ✅ | | `expand` | ✅ | ✅ | ✅ | ✅ | | `expr` | ✅ | ✅ | ✅ | ✅ | | `factor` | ✅ | ✅ | ✅ | ✅ | | `fallocate` | ✅ | ✅ | | | | `false` | ✅ | ✅ | ✅ | ✅ | | `fatattr` | | ✅ | | | | `fdisk` | | ✅ | | | | `fgrep` | ✅ | ✅ | | | | `find` | ✅ | ✅ | | | | `findfs` | ✅ | ✅ | | | | `flock` | ✅ | | | | | `fmt` | | | ✅ | ✅ | | `fold` | ✅ | ✅ | ✅ | ✅ | | `free` | ✅ | ✅ | | | | `freeramdisk` | | ✅ | | | | `fsfreeze` | | ✅ | | | | `fstrim` | | ✅ | | | | `fsync` | ✅ | | | | | `ftpget` | | ✅ | | | | `ftpput` | | ✅ | | | | `fuser` | ✅ | | | | | `getopt` | ✅ | ✅ | | | | `getty` | ✅ | ✅ | | | | `grep` | ✅ | ✅ | | | | `groups` | ✅ | ✅ | | ✅ | | `gunzip` | ✅ | ✅ | | | | `gzip` | ✅ | ✅ | | | | `halt` | | ✅ | | | | `hd` | ✅ | | | | | `head` | ✅ | ✅ | ✅ | ✅ | | `hexdump` | ✅ | ✅ | | | | `hostid` | ✅ | ✅ | ✅ | ✅ | | `hostname` | ✅ | ✅ | | | | `httpd` | | ✅ | | | | `hwclock` | | ✅ | | | | `i2cdetect` | | ✅ | | | | `i2cdump` | | ✅ | | | | `i2cget` | | ✅ | | | | `i2cset` | | ✅ | | | | `i2ctransfer` | | ✅ | | | | `id` | ✅ | ✅ | ✅ | ✅ | | `ifconfig` | | ✅ | | | | `ifdown` | | ✅ | | | | `ifup` | | ✅ | | | | `init` | | ✅ | | | | `inotifyd` | ✅ | | | | | `insmod` | | ✅ | | | | `install` | ✅ | | ✅ | ✅ | | `ionice` | ✅ | ✅ | | | | `iostat` | ✅ | | | | | `ip` | | ✅ | | | | `ipcalc` | | ✅ | | | | `ipcrm` | ✅ | | | | | `ipcs` | ✅ | | | | | `ipneigh` | | ✅ | | | | `join` | | | ✅ | ✅ | | `kill` | ✅ | ✅ | | | | `killall` | ✅ | ✅ | | | | `killall5` | ✅ | | | | | `klogd` | | ✅ | | | | `last` | | ✅ | | | | `less` | ✅ | ✅ | | | | `link` | ✅ | ✅ | ✅ | ✅ | | `linux32` | ✅ | ✅ | | | | `linux64` | ✅ | ✅ | | | | `linuxrc` | | ✅ | | | | `ln` | ✅ | ✅ | ✅ | ✅ | | `loadfont` | | ✅ | | | | `loadkmap` | | ✅ | | | | `logger` | ✅ | ✅ | | | | `login` | ✅ | ✅ | | | | `logname` | | ✅ | ✅ | ✅ | | `logread` | | ✅ | | | | `losetup` | | ✅ | | | | `ls` | ✅ | ✅ | ✅ | ✅ | | `lsattr` | ✅ | | | | | `lsmod` | | ✅ | | | | `lsof` | ✅ | | | | | `lsscsi` | | ✅ | | | | `lzcat` | ✅ | ✅ | | | | `lzma` | ✅ | ✅ | | | | `lzop` | ✅ | ✅ | | | | `lzopcat` | ✅ | | | | | `md5sum` | ✅ | ✅ | ✅ | ✅ | | `md5sum.textutils` | | | | ✅ | | `mdev` | | ✅ | | | | `microcom` | ✅ | ✅ | | | | `mim` | | ✅ | | | | `mkdir` | ✅ | ✅ | ✅ | ✅ | | `mkdosfs` | | ✅ | | | | `mke2fs` | | ✅ | | | | `mkfifo` | ✅ | ✅ | ✅ | ✅ | | `mknod` | ✅ | ✅ | ✅ | ✅ | | `mkpasswd` | ✅ | ✅ | | | | `mkswap` | | ✅ | | | | `mktemp` | ✅ | ✅ | ✅ | ✅ | | `modinfo` | | ✅ | | | | `modprobe` | | ✅ | | | | `more` | ✅ | ✅ | | | | `mount` | | ✅ | | | | `mountpoint` | ✅ | | | | | `mpstat` | ✅ | | | | | `mt` | | ✅ | | | | `mv` | ✅ | ✅ | ✅ | ✅ | | `nameif` | | ✅ | | | | `nc` | | ✅ | | | | `netstat` | ✅ | ✅ | | | | `nice` | ✅ | | ✅ | ✅ | | `nl` | ✅ | ✅ | ✅ | ✅ | | `nmeter` | ✅ | | | | | `nohup` | ✅ | | ✅ | ✅ | | `nologin` | ✅ | ✅ | | | | `nproc` | ✅ | ✅ | ✅ | ✅ | | `nsenter` | ✅ | ✅ | | | | `nslookup` | | ✅ | | | | `nuke` | | ✅ | | | | `numfmt` | | | ✅ | ✅ | | `od` | ✅ | ✅ | ✅ | ✅ | | `openvt` | | ✅ | | | | `partprobe` | | ✅ | | | | `passwd` | ✅ | | | | | `paste` | ✅ | ✅ | ✅ | ✅ | | `patch` | | ✅ | | | | `pathchk` | | | ✅ | ✅ | | `pgrep` | ✅ | | | | | `pidof` | ✅ | ✅ | | | | `ping` | ✅ | ✅ | | | | `ping6` | ✅ | ✅ | | | | `pinky` | | | ✅ | ✅ | | `pipe_progress` | ✅ | | | | | `pivot_root` | ✅ | ✅ | | | | `pkill` | ✅ | | | | | `pmap` | ✅ | | | | | `poweroff` | | ✅ | | | | `pr` | | | ✅ | ✅ | | `printenv` | ✅ | | ✅ | ✅ | | `printf` | ✅ | ✅ | ✅ | ✅ | | `ps` | ✅ | ✅ | | | | `pstree` | ✅ | | | | | `ptx` | | | ✅ | ✅ | | `pwd` | ✅ | ✅ | ✅ | ✅ | | `pwdx` | ✅ | | | | | `rdate` | | ✅ | | | | `rdev` | ✅ | | | | | `readahead` | ✅ | | | | | `readlink` | ✅ | ✅ | ✅ | ✅ | | `realpath` | ✅ | ✅ | ✅ | ✅ | | `reboot` | | ✅ | | | | `remove-shell` | ✅ | | | | | `renice` | ✅ | ✅ | | | | `reset` | ✅ | ✅ | | | | `resize` | ✅ | | | | | `resume` | | ✅ | | | | `rev` | ✅ | ✅ | | | | `rm` | ✅ | ✅ | ✅ | ✅ | | `rmdir` | ✅ | ✅ | ✅ | ✅ | | `rmmod` | | ✅ | | | | `route` | | ✅ | | | | `rpm` | | ✅ | | | | `rpm2cpio` | | ✅ | | | | `run-init` | | ✅ | | | | `run-parts` | ✅ | ✅ | | | | `runcon` | | | ✅ | ✅ | | `sbin` | | | | ✅ | | `sed` | ✅ | ✅ | | | | `seq` | ✅ | ✅ | ✅ | ✅ | | `setkeycodes` | | ✅ | | | | `setpriv` | ✅ | ✅ | | | | `setserial` | ✅ | | | | | `setsid` | ✅ | ✅ | | | | `sh` | ✅ | ✅ | | | | `sha1sum` | ✅ | ✅ | ✅ | ✅ | | `sha224sum` | | | ✅ | ✅ | | `sha256sum` | ✅ | ✅ | ✅ | ✅ | | `sha384sum` | | | ✅ | ✅ | | `sha3sum` | ✅ | ✅ | | | | `sha512sum` | ✅ | ✅ | ✅ | ✅ | | `shred` | ✅ | ✅ | ✅ | ✅ | | `shuf` | ✅ | ✅ | ✅ | ✅ | | `sleep` | ✅ | ✅ | ✅ | ✅ | | `sort` | ✅ | ✅ | ✅ | ✅ | | `split` | ✅ | | ✅ | ✅ | | `ssl_client` | | ✅ | | | | `start-stop-daemon` | | ✅ | | | | `stat` | ✅ | ✅ | ✅ | ✅ | | `stdbuf` | | | ✅ | ✅ | | `strings` | ✅ | ✅ | | | | `stty` | ✅ | ✅ | ✅ | ✅ | | `su` | ✅ | | | | | `sum` | ✅ | | ✅ | ✅ | | `svc` | | ✅ | | | | `svok` | | ✅ | | | | `swapoff` | | ✅ | | | | `swapon` | | ✅ | | | | `switch_root` | | ✅ | | | | `sync` | ✅ | ✅ | ✅ | ✅ | | `sysctl` | ✅ | ✅ | | | | `syslogd` | | ✅ | | | | `tac` | ✅ | ✅ | ✅ | ✅ | | `tail` | ✅ | ✅ | ✅ | ✅ | | `tar` | ✅ | ✅ | | | | `taskset` | | ✅ | | | | `tee` | ✅ | ✅ | ✅ | ✅ | | `telnet` | | ✅ | | | | `test` | ✅ | ✅ | ✅ | ✅ | | `tftp` | | ✅ | | | | `time` | ✅ | ✅ | | | | `timeout` | ✅ | ✅ | ✅ | ✅ | | `top` | ✅ | ✅ | | | | `touch` | ✅ | ✅ | ✅ | ✅ | | `tr` | ✅ | ✅ | ✅ | ✅ | | `traceroute` | ✅ | ✅ | | | | `traceroute6` | ✅ | ✅ | | | | `tree` | ✅ | | | | | `true` | ✅ | ✅ | ✅ | ✅ | | `truncate` | ✅ | ✅ | ✅ | ✅ | | `ts` | | ✅ | | | | `tsort` | ✅ | | ✅ | ✅ | | `tty` | ✅ | ✅ | ✅ | ✅ | | `ttysize` | ✅ | | | | | `tunctl` | ✅ | | | | | `ubirename` | | ✅ | | | | `udhcpc` | | ✅ | | | | `udhcpd` | | ✅ | | | | `uevent` | | ✅ | | | | `umount` | | ✅ | | | | `uname` | ✅ | ✅ | ✅ | ✅ | | `uncompress` | | ✅ | | | | `unexpand` | ✅ | ✅ | ✅ | ✅ | | `uniq` | ✅ | ✅ | ✅ | ✅ | | `unix2dos` | ✅ | ✅ | | | | `unlink` | ✅ | ✅ | ✅ | ✅ | | `unlzma` | ✅ | ✅ | | | | `unlzop` | ✅ | | | | | `unshare` | | ✅ | | | | `unxz` | ✅ | ✅ | | | | `unzip` | ✅ | ✅ | | | | `uptime` | ✅ | ✅ | | | | `users` | | | ✅ | ✅ | | `usleep` | ✅ | ✅ | | | | `usr` | | | | ✅ | | `uudecode` | ✅ | ✅ | | | | `uuencode` | ✅ | ✅ | | | | `vconfig` | ✅ | ✅ | | | | `vdir` | | | ✅ | ✅ | | `vi` | ✅ | ✅ | | | | `vlock` | ✅ | | | | | `w` | | ✅ | | | | `watch` | ✅ | ✅ | | | | `watchdog` | | ✅ | | | | `wc` | ✅ | ✅ | ✅ | ✅ | | `wget` | | ✅ | | | | `which` | ✅ | ✅ | | | | `who` | ✅ | ✅ | ✅ | ✅ | | `whoami` | ✅ | ✅ | ✅ | ✅ | | `xargs` | ✅ | ✅ | | | | `xxd` | ✅ | ✅ | | | | `xz` | | ✅ | | | | `xzcat` | ✅ | ✅ | | | | `yes` | ✅ | ✅ | ✅ | ✅ | | `zcat` | ✅ | ✅ | | | --- ### How to use Chainguard Security Advisories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/security-advisories/how-to-use.md Last Modified: October 1, 2026 Tags: Chainguard Containers, CVE When using scanners such as [Grype](https://github.com/anchore/grype) or [Docker Scout](https://docs.docker.com/scout/) to scan for vulnerabilities in Chainguard Containers, you'll often find that there are few or no CVEs present. However, CVEs can sometimes be found in Chainguard Containers, and you may also encounter CVEs if you're using older tags. In these cases, you will likely wish to check Chainguard's security advisories for information on which CVEs will cause security issues in your deployment. To help demystify the nature of CVEs within Chainguard Containers, we've created a self-service [Security Advisories page](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-security-advisories-how-to-use) that lists every security advisory published for Chainguard Containers. Having this information available allows you to view whether Chainguard is aware of a specific vulnerability reported to exist within a Chainguard Container and whether we've mitigated or are planning to mitigate the CVE. In alignment with the [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/#examples-of-what-chainguard-supports-and-maintains-for-chainguard-containers), our vulnerability management strategy focuses on the latest versions of any given release track, as these are the versions we actively maintain and secure. Accordingly, we only publish new CVE advisories for packages that fall within our defined support scope. We do not actively monitor non-supported versions of a package or image. Our efforts are centered on keeping the latest versions up-to-date and as close to zero CVEs as we can, while encouraging customers to upgrade and stay on supported versions. This guide outlines how you can use Chainguard's Security Advisories to learn more about the status of a CVE within a given package. It will walk through a practical example of discovering a vulnerability in a Chainguard Container, searching for Security Advisories associated with this vulnerability, and then comparing the original container image with a later version. ## Advisory database scope Chainguard Security Advisories cover vulnerabilities in the APK packages that Chainguard ships, including the Go modules, Java archives, and other language components inside those packages. Chainguard records a vulnerability in one of those components against the APK package that contains it. The advisories don't cover software that your own build adds to an image. For an APK finding in a Chainguard image, use [Check whether a reported CVE affects your container](/chainguard/containers/security-and-compliance/vulnerability-management/cve-status/) to compare the scanner result with the advisories for the packages in the image. Use the image digest and the platform that the scanner analyzed. For a Go-module or Java-dependency finding, first find out whether a Chainguard package ships the component or your build added it. For a component that a Chainguard package ships, check the advisories for that package. For a dependency that your build added, the absence of a Chainguard advisory doesn't make the finding a false positive; validate it against the scanner's language-package evidence and the dependency's upstream advisory data. Both cases are described in [When the finding is a Go module or Java dependency](/chainguard/containers/security-and-compliance/vulnerability-management/cve-status/#when-the-finding-is-a-go-module-or-java-dependency). ## Prerequisites You don't need any special access or software to explore Chainguard's Security Advisories. However, this guide includes a few examples that use specific software tools in order to outline a practical example of how one might navigate and use these Security Advisories. To follow along with these examples, you'll need the following tools installed. * A security scanner like [Trivy](https://trivy.dev/docs/latest/getting-started/installation/), [Grype](https://github.com/anchore/grype#installation), or [Docker Scout](https://docs.docker.com/get-docker/) — This guide's examples use Grype to scan container images and identify vulnerabilities. However, you should be able to follow along with any container vulnerability scanning tool. * [`chainctl`](/platform/chainctl/) — Chainguard's command-line interface tool. To install `chainctl`, follow our [installation guide](/platform/chainctl-usage/how-to-install-chainctl/). * [`jq`](https://jqlang.github.io/jq/) — `jq` is a command-line JSON processor that allows you to filter and manipulate streaming JSON data. Although it isn’t strictly necessary for the purposes of this guide, this tutorial includes commands that use `jq` to filter command output that would otherwise be difficult to read. You can install `jq` by following the instructions on [the project’s Download jq page](https://jqlang.github.io/jq/download/). Lastly, note that this guide includes examples involving a sample organization with a private registry provided by Chainguard and named `example.com`. If you would like to follow along with your own private Chainguard Containers, be sure to change this where relevant to reflect your own setup. If you don't have access to a private registry, you can also follow along using Chainguard's public [Free containers](/chainguard/containers/concepts/container-categories/#free-containers), but be aware that these are limited to only the `latest` or `latest-dev` tags. You can download Free Containers from the `cgr.dev/chainguard` registry, as in `cgr.dev/chainguard/go:latest`. ## So you've encountered a CVE in a Chainguard Container Say you use a vulnerability scanner like Grype or Docker Scout to inspect a certain Chainguard Container. This example uses Grype to scan a Production container image, specifically one tagged with `1.21.2`. As of this writing, the `go:1.21.2` image points to the image digest `sha256:04ab6905552b54a6977bed40a4105e9c95f78033e1cde67806259efc4beb959d`. Be aware that this tag will be withdrawn in the future, but the digest will remain available. ```sh grype cgr.dev/example.com/go:1.21.2 ``` Because this is the digest for an older version of Chainguard's Go container image, this command's output will show a number of vulnerabilities that have been found to exist within this specific version of the container image. ``` . . . ├── by severity: 28 critical, 230 high, 185 medium, 4 low, 0 ... ``` This output shows that this particular image has many critical and high vulnerabilities. The Grype output also lists each of the packages affected by CVEs as well as the specific vulnerabilities it found for each. > Note: All of these vulnerabilities have been addressed in newer versions of the Go Chainguard Container. Within this output, we find that the package `nghttp2` is referenced. ```sh grype cgr.dev/example.com/go:1.21.2| grep nghttp2 ``` ``` ... libnghttp2-14 1.56.0-r0 1.57.0-r0 apk CVE-2023-44487 High libnghttp2-14 1.56.0-r0 1.61.0-r0 apk CVE-2024-28182 Medium libnghttp2-14 1.56.0-r0 1.57.0-r0 apk GHSA-qppj-fm5r-hxr3 Unknown ``` We'll use the `HIGH` severity vulnerability listed here as an example when we explore Chainguard's Security Advisories in the next section. Copy or note down the CVE identifier (`2023-44487` in this case). Additionally, note down the name of the affected package (`nghttp2`). You'll use these details to retrieve more information about the CVE shortly. ## Searching the Security Advisories After finding a vulnerability in a Chainguard Container, you can navigate to [Chainguard's **Security Advisories** page](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-security-advisories-how-to-use). This is a helpful resource you can use to determine the status for any CVE found within a Chainguard Container. The Security Advisories page is self-service, allowing you to check whether Chainguard is aware of a specific vulnerability and whether it has been mitigated in a certain package version. You can search the Security Advisories page by entering any CVE identifier to find what packages are affected by that CVE. You can also enter the names of individual packages to find what CVEs have been reported within them. Enter the CVE identifier you copied previously (`2023-44487`) into the search box at the top of the page. This will immediately filter the list of security advisories to only show packages where that CVE has been reported. It will also show the **Status** of each. If you click on any row in the filtered list, it will take you to the CVE's specific page. There, you'll find a list of every package where this CVE has been reported. As with the Security Advisories landing page, you can filter by package name here on the CVE's landing page as well. Enter the name of the package we highlighted previously (`nghttp2`) and the index will immediately filter out any packages that do not mention that string in their metadata. For CVE-2023-44487, the `nghttp2` package's **Status** is marked as **Fixed** in version `1.57.0-r0` as of October 11, 2023. ## Comparing containers Chainguard's Security Advisories have told us that the CVE-2023-44487 was fixed and removed from `nghttp2` with a more recent version than the one available in Chainguard's `go:1.21.2` image. However, we don't have to take that report at face value; we can inspect a later version of the same container image and compare it with version `1.21.2` to determine whether the vulnerability is still present in the later version. If you inspect a later version of the image with Grype, you'll find that this time it does not report the high CVE we noted earlier. This example inspects version `1.21.5` of the image. ```shell grype cgr.dev/example.com/go:1.21.5 | grep nghttp2 ``` You should find that the high CVE fixed in this specific version no longer appears in the output. (You may still see other CVEs fixed in later versions.) You can go a step further by comparing these two container images directly with the `chainctl images diff` command, as in this example. ```sh chainctl images diff \ cgr.dev/example.com/go:1.21.2 \ cgr.dev/example.com/go:1.21.5 | jq . ``` This example will return a lot of output, as there are significant differences from version `1.21.2` to `1.21.5` of the Go container image. If you scroll down to the `vulnerabilities` section of this output, you'll find a list of vulnerabilities that are present in version `1.21.2` but have been removed by version `1.21.5`. ``` "vulnerabilities": { . . . { "id": "CVE-2023-44487", "reference": "chainguard:distro:chainguard:rolling", "severity": "High" }, . . . ``` As this output indicates, `CVE-2023-44487` is no longer present in later versions of the Go Chainguard Container. If you were using version `1.21.2`, you should seriously consider upgrading to a later version. ## Learn more The Security Advisories page serves as a helpful resource for anyone who wants to learn more about CVEs reported within Chainguard Containers. You can search the database of advisories to learn more about any CVEs you encounter as you work with Chainguard Containers. Additionally, we encourage you to explore the [Chainguard Containers Directory](https://images.chainguard.dev/), the parent site of the Security Advisories page. The Directory allows users to explore the complete inventory of Chainguard Containers. Finally, we encourage you to learn more about [noisy scan results](/chainguard/containers/security-and-compliance/working-with-scanners/false-results/) when scanning Chainguard Containers. If a Chainguard advisory or the Chainguard Console says a CVE is fixed but your scanner still reports it, see [Resolve a scanner finding for a CVE Chainguard has fixed](/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve/). For more about why scan results can differ between your scanner and the Chainguard Console, refer to [the support article "Understanding Vulnerability Scanner Discrepancies with Chainguard Images."](https://support.chainguard.dev/hc/en-us/articles/49564106705819-Understanding-Vulnerability-Scanner-Discrepancies-with-Chainguard-Images) --- ### How to retrieve SBOMs and attestations for Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/retrieve-image-sboms.md Last Modified: October 8, 2026 Tags: Chainguard Containers, SBOM Chainguard provides a Software Bill of Materials (SBOM) with every container image, enabling complete transparency about package contents and dependencies for security and compliance requirements. These SBOMs are cryptographically signed and attached as attestations, making them retrievable and verifiable. By including only the minimum packages needed, Chainguard's container images reduce attack surface while the SBOM ensures you can verify exactly what's in each image. Even though they contain the minimum number of packages, there may come a time when you want to know exactly what's running inside of a certain Chainguard Container. For this reason, Chainguard includes a signed SBOM with each image in the form of a [software attestation](https://slsa.dev/attestation-model), allowing you to verify the contents and meet compliance requirements. Chainguard publishes several different types of attestations; refer to the options under the [Available attestation types](#available-attestation-types) section on this page. ## Retrieve a container image's attestation You can retrieve a container image's attestation in two ways: - [Using Cosign](#retrieve-a-container-image-attestation-using-cosign) - [Cosign](/open-source/sigstore/cosign/an-introduction-to-cosign/) — a part of the Sigstore project — supports software artifact signing, verification, and storage in an [OCI (Open Container Initiative)](https://www.chainguard.dev/supply-chain-security-101/what-is-the-open-container-initiative-oci) registry, as well as the retrieval of said artifacts. - [In the Chainguard Console](#retrieve-a-container-image-attestation-in-the-chainguard-console) ### Prerequisites To retrieve an attestation via Cosign, you'll need the following installed on your local machine: - **Cosign**: Follow [our guide on installing Cosign](/open-source/sigstore/cosign/how-to-install-cosign/) to configure it. - **jq**: Follow instructions on the [jq downloads page](https://jqlang.github.io/jq/download/) to set it up. ### Retrieve a container image attestation using Cosign Cosign includes a `download attestation` command that allows you to retrieve a Chainguard Container's attestation over the command line. Different types of attestations are referenced by their **predicate type**. To authenticate these statements and verify the authenticity of the software producer, you can use [`cosign verify-attestation`](/open-source/sigstore/cosign/how-to-verify-file-signatures-with-cosign/). This example command downloads the SPDX attestation for Chainguard's [php image](https://images.chainguard.dev/directory/image/php/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-retrieve-image-sboms): ```shell cosign download attestation \ --platform linux/amd64 \ --predicate-type https://spdx.dev/Document \ cgr.dev/chainguard/php | jq -r '.payload' | base64 -d | jq -r '.predicate' ``` Cosign returns the attestation in a signed envelope, with the SBOM stored as a base64-encoded payload. The command pipes the output through jq to extract the payload, decodes it with base64, and then uses jq again to print the attestation’s predicate, which contains the SBOM. You can include the following flags when retrieving attestations: - The `--platform` flag, which selects the target platform for the image, such as `linux/amd64` or `linux/arm64`. - This flag requires Cosign version 2.2.1 or newer. - The `--predicate-type` flag, required to specify which type of attestation to retrieve. You can use the full URI or the shorthand version as the value of the flag. Refer to the [Available attestation types](#available-attestation-types) section for a list of options. ### Retrieve a container image attestation in the Chainguard Console You can also find container image SBOMs in the [Chainguard Console](https://console.chainguard.dev). After signing in to the Console and clicking either the **Public images** or, if available, **Organization images** you'll be presented with a list of images. Click on any of these to navigate that image's landing page. From there, navigate to the [**SBOM** tab](/platform/console/images-directory/#sbom) to find and download the SBOM for the given image. You can use the drop-down menus above the table to select which version and architecture of the image you want to view. You can also use the search box to find specific packages in the SBOM or use the button to the right of the search box to download the SBOM to your machine. Clicking **Download** reveals a drop-down menu where you can choose to download the image's SBOM in either the SPDX or CycloneDX SBOM formats. Check out our guide on [using the Chainguard Containers Directory](/platform/console/images-directory/) for more details. ## Available attestation types Chainguard publishes several different types of attestations. Not every image will have every predicate type; availability depends on the image and its build process. Available predicate types include: - **SLSA**: `https://slsa.dev/provenance/v1` (`slsaprovenance1`) - The [SLSA 1.0](https://slsa.dev/spec/v1.0/provenance) provenance attestation contains information about the image build environment. - Available on all images. - **apko**: `https://apko.dev/image-configuration` - Contains the configuration used by that particular image build, including direct dependencies, user accounts, and entry point. - Available on all images. - **SPDX**: `https://spdx.dev/Document` (`spdx`,`spdxjson`) - Contains the image SBOM in SPDX format. - Available on all images. - **Chainguard EOL**: `https://chainguard.dev/end-of-life` - End-of-life status. - Only available on EOL images in grace period. - **CycloneDX**: `https://cyclonedx.org/bom` (`cyclonedx`) - Contains the image SBOM in CycloneDX format. - Only available to customers, on new builds or rebuilds after January 29, 2026. - **Chainguard Helm values**: `https://chainguard.dev/helm-values/v1` - Contains Helm values for images with vetted upstream Helm charts. - Only images that are tested with Helm and have a corresponding upstream Helm chart have this attestation. - **Chainguard Helm chart-lock**: `https://chainguard.dev/attestation/chart-lock/v1` - Contains Helm chart-lock data for relevant images. - Only present for images where Helm chart locking is relevant. - **Syft**: `https://chainguard.dev/attestation/syft/v1` - Contains Syft-based SBOM attestation. - Not available on all images; this predicate is less common. ## License information and source code references The SBOM downloaded using either Cosign or Console methods described previously contain identical information. It lists binary packages present in the image, their licensing information using [SPDX license](https://spdx.org/licenses/) and [exceptions lists](https://spdx.org/licenses/exceptions-index.html), and external source code references. These source code references are encoded in the [external references](https://spdx.github.io/spdx-spec/v2.3/package-information/#721-external-reference-field) field, using [external repository identifiers](https://spdx.github.io/spdx-spec/v2.3/external-repository-identifiers/#f35-purl) in the package URL (purl) format. The [purl specification](https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst) allows for various different schemes and types. The following purls are used in Chainguard SPDX SBOM: - `pkg:apk` denotes binary package origin, name, full version number with epoch, and architecture: ``` pkg:apk/wolfi/ca-certificates-bundle@20240315-r4?arch=x86_64 ``` - `pkg:github` is used for upstream source code reference for packages built from GitHub repositories. These purls always include a fixed commit hash and, when available, also include a tag-version: ``` pkg:github/openssl/openssl.git@openssl-3.3.1 pkg:github/openssl/openssl.git@db2ac4f6ebd8f3d7b2a60882992fbea1269114e2 ``` - Note that `pkg:github` is also used to reference melange packaging files from Wolfi and Chainguard. These are provided with a subpath component and a fixed commit hash: ``` pkg:github/wolfi-dev/os@f18ff825f94b9177cf603c6e3d72936683a504d2#glibc.yaml ``` - `pkg:generic` is used to reference any other upstream download locations, most commonly tarballs: ``` pkg:generic/gcc@13.2.0? checksum=sha256%3A8cb4be3796651976f94b9356fa08d833524f62420d6292c5033a9a26af315078& download_url=https%3A%2F%2Fftp.gnu.org%2Fgnu%2Fgcc%2Fgcc-13.2.0%2Fgcc-13.2.0.tar.gz ``` - Also note that `pkg:generic` is used to reference upstream git repositories, outside of GitHub: ``` pkg:generic/ca-certificates@20240315? vcs_url=git%2Bhttps%3A%2F%2Fgitlab.alpinelinux.org%2Falpine%2Fca-certificates%4009e5e43336e532ec8217ae3bfc912bcb7048f65a ``` purls are human-readable, but various programming and scripting languages have [implementations](https://github.com/package-url/purl-spec?tab=readme-ov-file#known-implementations) that can parse them. Because SPDX SBOMs are distributed within the Chainguard Containers repository alongside each image hash, you can achieve source code access compliance by ensuring all attestations are mirrored together with each image. As an example, a snippet of a binary package SPDX stanza with license, version, and source code references is shown here: ```json { "SPDXID": "SPDXRef-Package-glibc-locale-posix-2.39-r6", "externalRefs": [ { "referenceCategory": "PACKAGE_MANAGER", "referenceLocator": "pkg:apk/wolfi/glibc-locale-posix@2.39-r6?arch=x86_64", "referenceType": "purl" }, { "referenceCategory": "PACKAGE_MANAGER", "referenceLocator": "pkg:generic/glibc@2.39?checksum=sha256%3Af77bd47cf8170c57365ae7bf86696c118adb3b120d3259c64c502d3dc1e2d926&download_url=http%3A%2F%2Fftp.gnu.org%2Fgnu%2Flibc%2Fglibc-2.39.tar.xz", "referenceType": "purl" }, { "referenceCategory": "PACKAGE_MANAGER", "referenceLocator": "pkg:github/wolfi-dev/os@f18ff825f94b9177cf603c6e3d72936683a504d2#glibc.yaml", "referenceType": "purl" } ], "licenseDeclared": "LGPL-2.1-or-later", "name": "glibc-locale-posix", "originator": "Organization: Wolfi", "supplier": "Organization: Wolfi", "versionInfo": "2.39-r6" } ``` This snippet shows that the `glibc-locale-posix` binary package is distributed under LGPL license, built from the `glibc-2.39.tar.xz` upstream tarball, using the `glibc.yaml` file from the `wolfi-dev/os` repository. ### Source code references in CycloneDX SBOMs CycloneDX SBOMs contain the same package, license, and source code information as their SPDX counterparts, arranged to follow the [CycloneDX specification](https://cyclonedx.org/specification/overview/). The structure differs from SPDX in a few ways: - Installed packages appear as top-level `library` components instead of being nested inside container or directory wrapper components. - A dependency tree links the image to each installed package, and links each package to any modules bundled within it, such as Go modules. - Build and source provenance appears in the top-level `externalReferences` field on the BOM rather than as components. The melange build definitions use the `build-meta` reference type, and upstream source code references use `source-distribution`. SPDX records this same provenance in each package's `externalRefs` field. Because packages appear as standard components and provenance is kept in external references, these CycloneDX SBOMs import cleanly into software composition analysis (SCA) tools such as Mend. ## Learn more We provide provenance information for every Chainguard Container in their respective [details pages](https://images.chainguard.dev/directory?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-retrieve-image-sboms). After reaching the **Overview** for the image of your choice, navigate to the **Provenance** tab for information on how to retrieve the image's attestations, as well as how to verify the image's attestations and signatures. For example, if you're looking for the provenance information of the Python image, you can navigate to the [Python provenance information page](https://images.chainguard.dev/directory/image/python/provenance?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-retrieve-image-sboms). --- ### Authenticate to Chainguard's Registry URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/authenticating.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Registry ## Public container images Chainguard offers a collection of images that are publicly available, don't require authentication, and are free to use by anyone. However, logging in with a Chainguard account and authenticating when pulling from the registry gives you access to the Chainguard Console, and provides a mechanism for Chainguard to contact you if there are any issues with images you are pulling. This may enable Chainguard to notify you of upcoming deprecations, changes in behavior, critical vulnerabilities and remediations for images you have recently pulled. ## Quickstart: pull your first container These steps take you from nothing to a container image on your machine. Everything after this section covers the cases that need more than a local pull — CI systems, Kubernetes clusters, and registry mirrors. Before you start, [sign up for a Chainguard account](#signing-up) and [install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/), Chainguard's command-line tool. 1. Configure the credential helper: ```sh chainctl auth configure-docker ``` This points your Docker configuration at `chainctl` for `cgr.dev` credentials, then opens a browser window to authenticate you. You don't need to run `chainctl auth login` first; `configure-docker` authenticates you when no valid token is available. For headless machines and other login flows, refer to [Authentication options for `chainctl`](/platform/chainctl-usage/authentication-options/). 2. Find the name of your organization: ```sh chainctl iam organizations list ``` 3. Pull an image, replacing `$ORGANIZATION` with the name from the previous step: ```sh docker pull cgr.dev/$ORGANIZATION/python:latest ``` To see which repositories your organization can pull, run `chainctl images repos list`. To browse the full catalog, visit the [Chainguard Containers Directory](https://images.chainguard.dev/). Your organization's registry holds a subset of what the Directory lists, so browsing a container there doesn't mean you can pull it; refer to [Onboard your teams](/get-started/onboard-your-teams/#what-your-organization-can-pull) for how your subscription determines what's available. If the pull fails, refer to [Troubleshoot registry authentication errors](/chainguard/containers/troubleshooting/registry-errors/), which maps each error `cgr.dev` returns to its cause. ## Signing up You can register a Chainguard account through our [sign up form](https://console.chainguard.dev/auth/login?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). This creates your account and a [Chainguard IAM organization](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/). If you already have an account, you can log in through the [login page](https://console.chainguard.dev/auth/login?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). For more details on signing in, you can review our [sign in guidance](/platform/administration/iam-organizations/how-to-manage-iam-organizations-in-chainguard/#logging-in). If your organization is interested in (or already using) custom identity providers like Okta, you can read [how to authenticate to Chainguard with custom identity providers](/platform/administration/custom-idps/custom-idps/). ## Authenticating with the `chainctl` credential helper You can configure authentication by using the credential helper included with `chainctl`. This is the workflow recommended by Chainguard. First [install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) and configure the credential helper: ```sh chainctl auth configure-docker ``` This updates your Docker config file to call `chainctl` when an auth token is needed. A browser window opens when the token needs to be refreshed. Pulls authenticated in this way are associated with your user. ## Authenticating with a pull token You can also create a "pull token" using `chainctl`. This generates a longer-lived token that can be used to pull images from other environments that don't support OIDC, such as some CI environments, Kubernetes clusters, or with registry mirroring tools like Artifactory. First [install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/), then log in and configure a pull token: ```sh chainctl auth configure-docker --pull-token ``` With the latest release of `chainctl`, this prints a `docker login` command that you can run in the CI environment to log in with a pull token. You can also pass the `--save` flag, which updates your Docker config file with the pull token directly. This token expires in 30 days by default, which can be modified using the `--ttl` flag. It sets the duration for the validity of the token. The maximum valid value is `8760h` (equivalent to 365 days), Valid unit strings range from nanoseconds to hours and are `ns`, `us`, `ms`, `s`, `m`, and `h`, for example `--ttl=24h`. Pulls authenticated in this way are associated with a Chainguard identity, which is associated with the organization selected when the pull token was created. You can also export the pull token details into environment variables for [authentication in automated systems](/chainguard/containers/building-and-modifying/packages/private-apk-repos/#pull-token-automation). Running `chainctl auth pull-token create --output=env` sets `CHAINGUARD_IDENTITY_ID` to the username and `CHAINGUARD_TOKEN` to the password. Refer to [pull token output formats and credential names](/platform/chainctl-usage/pull-token-output/) for the other formats and for the variable names each library ecosystem uses. ### Using a pull token with Podman, Helm, and other tools The `docker login` command that `chainctl auth configure-docker --pull-token` prints contains the credentials for the token. The username is the Chainguard identity associated with the token, and the password is the token itself: ```sh docker login "cgr.dev" \ --username "" \ --password "" ``` Save that pair and pass it to any tool that logs in to an OCI registry. For example, Podman: ```sh podman login cgr.dev --username "$CHAINGUARD_IDENTITY_ID" --password "$CHAINGUARD_TOKEN" ``` Or Helm: ```sh helm registry login cgr.dev --username "$CHAINGUARD_IDENTITY_ID" --password "$CHAINGUARD_TOKEN" ``` Helm needs this login even if your cluster already has a working `imagePullSecret`. That secret covers the image pulls your pods make. It has no effect on the Helm client, which authenticates to the registry on its own to fetch the chart when you run `helm install` against an `oci://` URL. The same username and password work with registry mirroring tools such as Artifactory. Refer to the [pull-through guides](/chainguard/containers/registry/pull-through-guides/) for tool-specific instructions. ### Note on multiple pull tokens Running the `chainctl auth configure-docker --pull-token` command multiple times creates multiple pull tokens. However, with `--save`, each new token overwrites the old one in your Docker config. Tokens cannot be retrieved once they have been overwritten so they must be extracted from the local Docker config and saved elsewhere if multiple are required. ### Revoking a pull token Pull tokens are associated with Chainguard identities so they can be viewed with: ```sh chainctl iam identities list ``` To revoke a token, delete the associated identity. ```sh chainctl iam identity delete ```

Note: On Chainguard Catalog Starter, this command fails, and the Delete action on the Console’s Access Tokens page is unavailable. The limited_owner role assigned to Catalog Starter users can’t delete identities. Delete the token’s role binding instead: run chainctl iam role-bindings list to find the binding, then chainctl iam role-bindings delete <role-binding-id>. This removes the token’s access, although the identity itself remains in the list.

### Managing pull tokens in the Chainguard Console You can also create and view pull tokens in the [Chainguard Console](https://console.chainguard.dev/). In the Console, click **Settings** in the left-hand navigation menu, then click **Access Tokens** in the settings menu. You can also reach this page from the **Manage pull tokens** link on the **Overview** page. The **Access Tokens** page lists your selected organization's pull tokens, with each token's **Name**, **Description**, **Access**, **Created** date, and **Expiration** date. Expired tokens show an **Expired** label, and the **Delete expired tokens** button removes all of them at once. To create a pull token, click **Create token**. In the **Create access token** panel, enter a name and an optional description, then set these options: * **Access** sets what the token can pull: **Container Images**, **Packages**, **Java Libraries**, **JavaScript Libraries**, or **Python Libraries**. It defaults to **Container Images**. * **Expiration** sets when the token expires: **30 days**, **60 days**, **90 days**, or **Custom**. Choosing **Custom** adds a **Custom expiration** date field, where you pick the expiration date. Click **Create token**. The panel shows the new token's **Username** and **Password**, along with instructions to **Configure Docker CLI** or **Configure Kubernetes Pull Secret**. Copy the credentials before you close the panel. The token then appears in the list with the rest of your organization's tokens. If the Console won't let you create a pull token, your role is missing a capability rather than the registry rejecting you. Creating a pull token creates a Chainguard identity and a role-binding for it, so it needs a role with the `identity (create)` and `role_bindings (create)` capabilities. `registry.pull_token_creator` is the least privileged built-in role with both. Refer to [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/) to bind it, and to the [capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/) for what each built-in role carries. ## Authenticating with GitHub Actions You can configure authentication with OIDC-aware CI platforms like GitHub Actions.

Note: This approach requires an assumable identity, which Chainguard Catalog Starter organizations can’t create. On Catalog Starter, authenticate your pipelines with a pull token instead. Refer to Authenticate CI pipelines with a pull token.

First create an identity using `chainctl`, which can be limited to only allow OIDC federation from certain GitHub workflow runs: ```sh chainctl iam identity create github [GITHUB-IDENTITY] \ --github-repo=${GITHUB_ORG}@${GITHUB_OWNER_ID}/${GITHUB_REPO}@${GITHUB_REPO_ID} \ --github-ref=refs/heads/main \ --role=registry.pull ```

Note: The value passed to --github-repo must equal the repository portion of the subject field in the token GitHub issues. GitHub embeds immutable numeric owner and repository IDs in that subject (for example, my-org@123456/repo-name@654321), so --github-repo must include them. Populate GITHUB_OWNER_ID and GITHUB_REPO_ID with your repository’s numeric IDs; for how to retrieve them and when the format applies, refer to Finding your repository’s numeric identifiers. If you need to further scope or change the subject, refer to the “Example subject claims” section of GitHub’s OIDC documentation, then update the identity with chainctl iam identities update.

This creates a Chainguard identity that can be assumed by a GitHub Actions workflow only for the specified GitHub repository, triggered on pushes to the specified branch (such as `refs/heads/main`), with permissions only to pull from Chainguard's registry. When you create this identity, `chainctl` displays its ID. Using this ID, you can configure your GitHub Actions workflow to install `chainctl` and assume this identity when the workflow runs: ```yaml name: Registry Example on: push: branches: ['main'] permissions: contents: read id-token: write # This is needed for OIDC federation. jobs: example: runs-on: ubuntu-latest steps: - uses: chainguard-dev/setup-chainctl@main with: identity: [[ The Chainguard Identity ID you created above ]] - run: docker pull cgr.dev/chainguard/node ``` Pulls authenticated in this way are associated with the Chainguard identity you created, which is associated with the organization selected when the identity was created. If the identity is configured to only work with GitHub Actions workflow runs from a given repo and branch, that identity can't pull from other repos or branches, including pull requests targeting the specified branch. ## Authenticating with CircleCI OIDC token You can configure authentication with OIDC-aware CircleCI platform. First, use `chainctl` to create an [assumed identity](/platform/administration/assumable-ids/assumable-ids/#managing-identities-with-chainctl). This example uses a CircleCI ID of `1234` and works for all projects in that organization. Replace `1234` with your identity issuer org. Modify the subject pattern regex to reduce the scope to specific repos in the organization. ```sh chainctl iam identities create circleci-identity --identity-issuer="https://oidc.circleci.com/org/1234" --subject-pattern="org/1234/project/.+$" --role=registry.pull ``` Use the identity created in the above command, shown here in the third `run` section as `5678`, to configure your workflow to install `chainctl` and assume this identity when the workflow runs: ```yaml version: 2.1 jobs: install-and-authenticate: machine: true environment: CHAINCTL_TOKEN_FILE: "/tmp/oidc_token" steps: - checkout - run: name: Download chainctl command: | curl -o chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/')" - run: name: Install chainctl command: | sudo install -o $UID -g $(id -g) -m 0755 chainctl /usr/local/bin/ - run: name: Configure Docker auth command: | sudo chainctl auth configure-docker --identity-token="$CIRCLE_OIDC_TOKEN" --identity "5678" - run: name: Pull Docker image command: | sudo docker pull cgr.dev/cgr-demo.com/python:latest workflows: version: 2 chainctl-workflow: jobs: - install-and-authenticate ``` Refer to the [CircleCI documentation](https://circleci.com/docs/openid-connect-tokens/#format-of-the-openid-connect-id-token) to learn more about using OpenID Connect tokens in CircleCI jobs. ## Authenticating with Microsoft Entra ID OIDC token You can configure authentication with OIDC using Microsoft Entra ID (formerly Azure Active Directory). To acquire an OIDC ID token in Entra, you must complete an OAuth 2.0/OIDC flow. Entra issues access tokens (for authorization) and ID tokens (for authentication) as separate but related JWTs or [JSON Web Tokens](https://www.rfc-editor.org/rfc/rfc7519). Access tokens grant API access, while ID tokens prove user identity. If you use the [implicit or hybrid flows](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-implicit-grant-flow) in Entra ID, enable **ID tokens (used for implicit and hybrid flows)** for your application. This can be found under **Authentication → Implicit grant and hybrid flows**. Then, configure a redirect URI as described in [Enable ID tokens](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#enable-id-tokens). If you use the authorization code flow (recommended), request the `openid` scope to receive an ID token; you don't need to select any of the portal checkboxes. Then, [authenticate a user and request an ID token](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow#request-an-authorization-code).

Note: For CI workloads, Microsoft’s workload identity federation (federated identity credentials) exchanges your CI’s OIDC token for an access token to a resource; it does not issue ID tokens. If you need a non-interactive OIDC ID token for Chainguard, the simpler option is to use your CI provider’s native OIDC issuer directly with Chainguard. Otherwise, run an interactive user flow (for example, device code) to obtain an ID token.

Retrieve and save an ID token as a local environment variable. The following examples use `MS_ENTRA_ID_OIDC_TOKEN`. Next, use `chainctl` to create an [assumed identity](/platform/administration/assumable-ids/assumable-ids/#managing-identities-with-chainctl). Replace `{tenant}` with your Entra ID tenant ID (GUID). Modify the subject pattern regular expression to reduce access from all users from that issuer to a more appropriate scope for your needs. ```sh chainctl iam identities create entraid-identity \ --identity-issuer="https://login.microsoftonline.com/{tenant}/v2.0" \ --subject-pattern="^.+$" \ # matches all users from this issuer, adjust to restrict access --role=registry.pull ``` Use the identity created in the above command, shown here in the third `run` section as `entraid-identity`, to configure your workflow to install `chainctl` and assume this identity when the workflow runs: ```yaml version: 2.1 jobs: install-and-authenticate: machine: true steps: - checkout - run: name: Download chainctl command: | curl -o chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/')" - run: name: Install chainctl command: | sudo install -o $UID -g $(id -g) -m 0755 chainctl /usr/local/bin/ - run: name: Configure Docker auth command: | sudo chainctl auth configure-docker --identity-token="$MS_ENTRA_ID_OIDC_TOKEN" --identity "entraid-identity" - run: name: Pull Docker image command: | sudo docker pull cgr.dev/cgr-demo.com/python:latest workflows: version: 2 chainctl-workflow: jobs: - install-and-authenticate ``` Refer to the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc) to learn more about using OpenID Connect tokens on the Microsoft Entra ID platform. Of special note is their warning about reading and validating their tokens: > Don't attempt to validate or read tokens for any API you don't own, including the tokens in this example, in your code. Tokens for Microsoft services can use a special format that will not validate as a JWT, and may also be encrypted for consumer (Microsoft account) users. While reading tokens is a useful debugging and learning tool, do not take dependencies on this in your code or assume specifics about tokens that aren't for an API you control. Chainguard does not require you to parse or validate the Microsoft-issued token yourself. Instead, just pass the token to `chainctl` as shown above. ## Authenticating with Kubernetes You can also configure a Kubernetes cluster to use a pull token, as described above. When you create a pull token with `--save`, your Docker config file is updated to include that token and configure it to be used when pulling images from `cgr.dev`. Create the pull token with `--save` before you build the secret. If you set up `chainctl auth configure-docker` without a pull token, your Docker config has no credentials for `cgr.dev`, only an entry naming the `chainctl` credential helper. The kubelet can't run a credential helper, so a secret built from that file pulls without credentials. `--save` writes the token into the file and turns off the credential helper for `cgr.dev`. After that, you can create a Kubernetes secret based on those credentials, following [these instructions](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials): ```sh kubectl create secret generic regcred \ --from-file=.dockerconfigjson= \ --type=kubernetes.io/dockerconfigjson ``` The `--type=kubernetes.io/dockerconfigjson` flag is required. Kubernetes reads pull credentials only from a secret of that type, and without the flag `kubectl create secret generic` creates an `Opaque` secret instead. The kubelet ignores an `Opaque` secret, so the pull runs unauthenticated and fails even though the credentials it holds are correct. If you adapt this command or move it into a manifest, keep the type — or use `kubectl create secret docker-registry`, which sets it for you.

Note: This also makes any other credentials in your Docker config available in the secret. Make sure your Docker config includes only the credentials you need.

Then you can create a Pod that uses that secret, following [these instructions](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#create-a-pod-that-uses-your-secret): ```yaml apiVersion: v1 kind: Pod metadata: name: cgr-example spec: containers: - name: nginx image: cgr.dev/chainguard/nginx:latest imagePullSecrets: - name: regcred ``` For this example, save the file as `cgr-example.yaml`. Then you can create and get the Pod: ```sh kubectl apply -f cgr-example.yaml kubectl get pod cgr-example ``` If the pod stays in `ImagePullBackOff`, refer to [Pods stuck in ImagePullBackOff](/chainguard/containers/troubleshooting/registry-errors/#pods-stuck-in-imagepullbackoff). Learn more in our [sign in guidance](/platform/administration/iam-organizations/how-to-manage-iam-organizations-in-chainguard/#logging-in). --- ### Getting started with the Go Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/go.md Last Modified: October 2, 2026 Tags: Chainguard Containers Chainguard's [Go container image](https://images.chainguard.dev/directory/image/go/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-go) provides a secure foundation for building Go applications. The distroless `latest` variant contains only the Go compiler and runtime, while the `latest-dev` variant includes additional build tools and package management capabilities for development workflows. In this guide, we'll demonstrate how to build and execute Go applications using Chainguard Containers, using three examples from our [demos repository](https://github.com/chainguard-dev/edu-images-demos). In the first example, we'll build a CLI application using a Docker multi-stage build. In the second example, we'll build an application that's accessible by HTTP server, also using a Docker multi-stage build to obtain an optimized runtime. The third example shows how to build an image using [ko](https://ko.build/), a tool that enables you to build container images from Go programs and push them to container registries without requiring a Dockerfile. The examples in this guide recommend executing Go binaries from one of our runtime Chainguard Containers, such as the [`glibc-dynamic`](https://images.chainguard.dev/directory/image/glibc-dynamic/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-go) or [`static`](https://images.chainguard.dev/directory/image/static/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-go) Chainguard Containers. That is possible because Go applications are compiled and the toolchain is not typically required in a runtime container image.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Preparation This tutorial requires Docker to be installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/). The third and optional example requires the installation of [ko](https://ko.build/), which you can install by following the instructions on the official site. ### Cloning the demos repository Start by cloning the demos repository to your local machine: ```shell git clone git@github.com:chainguard-dev/edu-images-demos.git ``` Access the `go` folder in the repository: ```shell cd edu-images-demos/go ``` Here you will find three folders, each with a different demo that we'll cover in this guide. ## Example 1: CLI application in multi-stage build The following example demonstrates a command line application with support for flags and positional arguments. The application prints a modifiable greeting message and provides usage information if the wrong number of arguments are passed by a user or the user passes an unrecognized flag. Start by accessing the `go-greeter` folder in the demos repository: ```shell cd go-greeter ``` For reference, here is the content of the `Dockerfile` for this demo: ```Dockerfile FROM cgr.dev/chainguard/go AS builder COPY . /app RUN cd /app && go build -o go-greeter . FROM cgr.dev/chainguard/static COPY --from=builder /app/go-greeter /usr/bin/ ENTRYPOINT ["/usr/bin/go-greeter"] ``` This Dockerfile will: 1. Start a build stage based on the `go:latest` container image and name it `builder`; 2. Copy the application files to the `/app` directory in the image; 3. Build the application in the `/app` directory; 4. Start a new build stage based on the `static:latest` image; 5. Copy the built application from the `builder` stage to the `/usr/bin` directory in the new image; 6. Set the entrypoint to the built application. Run the following command to build the container image, tagging it `go-greeter`: ```shell docker build . --pull -t go-greeter ``` You can now run the container image with: ```shell docker run go-greeter ``` You should get output similar to the following: ``` Hello, Linky 🐙! ``` You can also pass in arguments that will be parsed by the Go CLI application: ```shell docker run go-greeter -g Greetings "Chainguard user" ``` This will produce the following output: ``` Greetings, Chainguard user! ``` The application will also share usage instructions when prompted with the `--help` flag or when invalid flags are passed. Because we used the `static` Chainguard Container as our runtime, the final container image only requires a few megabytes on disk: ```shell docker inspect go-greeter | jq -c 'first' | jq .Size | numfmt --to iec --format "%8.4f" ``` ``` 3.3009M ``` The final size, `3.309M`, is orders of magnitude smaller than it would be running the application using a Go image. However, if your application is dynamically linked to shared objects, consider using the `glibc-dynamic` Chainguard Container for your runtime or take extra steps to build your Go binary statically. In the next example, we'll build a web application and use the `glibc-dynamic` Chainguard Container as runtime. ## Example 2: Web application The second example demonstrates an application that's accessible by HTTP server. The application renders a simple message that changes based on the URI. Start by accessing the `greet-server` folder in the Go demos repository: ```shell cd greet-server ``` For reference, here is the content of the `Dockerfile` for this demo: ```Dockerfile FROM cgr.dev/chainguard/go AS builder COPY . /app RUN cd /app && go build FROM cgr.dev/chainguard/glibc-dynamic COPY --from=builder /app/greet-server /usr/bin/ EXPOSE 8080 ENTRYPOINT ["/usr/bin/greet-server"] ``` Use the following command to build the container image, tagging it `greet-server`: ```shell docker build . --pull -t greet-server ``` Now you can run the container image with the following command: ```shell docker run -p 8080:8080 greet-server ``` Visit `http://0.0.0.0:8080/` using a web browser on your host machine. You should get a greeting message: ``` Hello, Linky 🐙! ``` Changes to the URI will be routed to the application. Try visiting [http://0.0.0.0:8080/Chainguard%20Customer](http://0.0.0.0:8080/Chainguard%20Customer). You should see the following output: ``` Hello, Chainguard Customer! ``` The application will also share version information at [http://0.0.0.0:8080/version](http://0.0.0.0:8080/version). ## Example 3: Minimal Go Chainguard Container built with ko In this example, we'll build a distroless Go Chainguard Container with [ko](https://ko.build/). ko offers fast container image builds for Go applications without requiring a Dockerfile. Additionally, ko produces [SBOMs](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom) by default, supporting a holistic approach to software security. Start by accessing the `go-digester` folder in the Go demos repository: ```shell cd go-digester ``` The `go-digester` demo uses the `go-containerregistry` library to print out the digest of the latest build of a Chainguard Container, using `go` as the default image to pull the digest from, and with an optional parameter to specify a different container image name. If you have Go installed locally, you can run the application with: ```shell go run main.go ``` You should obtain output similar to this: ``` The latest digest of the go Chainguard Container is sha256:86178b42db2e32763304e37f4cf3c6ec25b7bb83660dcb985ab603e3726a65a6 ``` We'll now use ko to build an image that is suitable to run the application defined in `main.go`. By default, ko uses the `cgr.dev/chainguard/static` image as the base image for the build. You can override this by setting the `KO_DEFAULTBASEIMAGE` environment variable to a different base image. Before building the container image, you'll need to set up the environment variable `KO_DOCKER_REPO`. This environment variable identifies where ko should push images that it builds. This is usually a remote registry like the GitHub Container registry or Docker Hub, but you can publish to your local machine for testing and demonstration purposes. Run the following command to set the `KO_DOCKER_REPO` environment variable to your local machine: ```shell export KO_DOCKER_REPO=ko.local ``` Next, ensuring that you are in the same directory as your `main.go` file, run the following command to build the container with ko: ```shell ko build . ``` Once you run this command, you'll receive output similar to the following. ``` 2024/12/06 13:03:14 Using base cgr.dev/chainguard/static:latest@sha256:5ff428f8a48241b93a4174dbbc135a4ffb2381a9e10bdbbc5b9db145645886d5 for go-digester 2024/12/06 13:03:15 git doesn't contain any tags. Tag info will not be available 2024/12/06 13:03:15 Building go-digester for linux/amd64 2024/12/06 13:03:20 Loading ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336 2024/12/06 13:03:20 Loaded ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336 2024/12/06 13:03:20 Adding tag latest 2024/12/06 13:03:20 Added tag latest ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336 ``` At this point, your container is built. Because the output of `ko build` is an image reference, you can pass it to other tools like Docker. You can learn more about [deployment with ko](https://ko.build/deployment/) and [Kubernetes integration](https://ko.build/features/k8s/) by reading the respective documentation on the official site. We'll demonstrate running the above built container with Docker. > **Note**: To follow along, be sure that you copy and paste the last line of output from your last command that begins `ko.local/go-digester-...` ```shell docker run --rm ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336 ``` Here, you'll expect to receive the same output as before that shows the digest of the Go container. ``` The latest digest of the go Chainguard Container is sha256:86178b42db2e32763304e37f4cf3c6ec25b7bb83660dcb985ab603e3726a65a6 ``` You can also pass in an optional argument to specify which Chainguard Container to pull the latest digest from: ```shell docker run --rm ko.local/go-digester-edc0ed689c7fb820a565f76425bed013:0914a85d803988ab10964323c0cd7b4bf89aed2603f6e8e276f798491c731336 mariadb ``` ``` The latest digest of the mariadb Chainguard Container is sha256:6ba5d792d463b69f93e8d99541384d11b0f9b274e93efdeb91497f8f0aae03d1 ``` ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose Go Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Resolve a scanner finding for a CVE Chainguard has fixed URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/vulnerability-management/scanner-flags-fixed-cve.md Last Modified: October 1, 2026 Tags: Procedural, Chainguard Containers, CVE Sometimes the Chainguard Console or a Chainguard security advisory reports a CVE as fixed, but your vulnerability scanner still reports it in the same image. When this blocks a CI security gate, the cause is usually a mismatch between the image you scanned, the data your scanner uses, and Chainguard's advisory data. Work through the checks on this page in order; they're arranged so that the most common causes come first. This page covers findings in packages that Chainguard ships. That includes Go modules, Java archives, and other language components inside a Chainguard APK package, because Chainguard records advisories against the APK package that contains them. For a dependency that your own build adds to the image, see [Dependencies that your build adds](/chainguard/containers/security-and-compliance/vulnerability-management/cve-status/#dependencies-that-your-build-adds). ## Work through the causes in order Each of the following checks rules out one cause. After each one, scan again; stop when the finding clears. ### Confirm that you scanned the current image A tag such as `latest` moves to a new digest each time Chainguard rebuilds the image. If your pipeline cached an older image or pinned an older digest, the scanner reports CVEs that later builds fixed. Scanning by tag can also pick up an old copy of the image. Some scanners, including Grype, scan an image from the local Docker daemon when one with that tag exists, rather than pulling the current image from the registry. 1. Get the digest that the tag points to now: ```shell crane digest cgr.dev//: ``` The `crane` command uses the same registry credentials as `docker`. If it can't authenticate, run `chainctl auth configure-docker` first. 2. Compare that digest with the image's repository digest in your scanner's output. Without the `--platform` flag, `crane digest` returns the digest of the multi-architecture image, which is the value that Docker and most scanners report as the repository digest. A digest for a single architecture, such as the one that `crane digest --platform` returns, is a different value even for the same image. 3. If the digests differ, scan the current image from the registry by digest, and specify the platform that you deploy: ```shell grype registry:cgr.dev//@sha256: --platform linux/amd64 ``` ### Compare the installed package with Chainguard's fixed version A Chainguard advisory records the version of each package that contains the fix. Chainguard records this separately for each package, including each subpackage built from the same source, and separately for each architecture. A parent package and its subpackages can have different statuses for the same CVE. For example, in one advisory the x86_64 `bind-libs` subpackage is fixed, while the parent `bind` package waits on an upstream fix. 1. In your scanner's output, find the exact name of the flagged package, its installed version, and the architecture of the image that you scanned. 2. Find Chainguard's fixed version for that package and architecture, in any of the following places: - The advisory on the [Security Advisories page](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-containers-security-and-compliance-vulnerability-management-scanner-flags-fixed-cve) of the Chainguard Containers Directory. - The advisory record in the OSV feed, as described in [Look up the fixed version in the advisory feed](#look-up-the-fixed-version-in-the-advisory-feed). - The output of `chainctl images advisories list`, as described in [Check whether a reported CVE affects your container](/chainguard/containers/security-and-compliance/vulnerability-management/cve-status/). If no fixed version is recorded for that package and architecture, Chainguard hasn't fixed the CVE in that package, and the finding is valid. 3. Compare the two versions using APK version ordering, which doesn't always match the order you might expect. For example, a version with a pre-release suffix, such as `1.3.2.1_rc20260601-r0`, sorts before `1.3.2.1-r0`. To compare two versions, run `apk version -t` in a Wolfi container: ```shell docker run --rm cgr.dev/chainguard/wolfi-base apk version -t ``` The command prints `<` if the installed version is earlier than the fixed version, `=` if they're the same, and `>` if the installed version is later. If the installed version is earlier than the fixed version, the finding is valid for that image. Update to an image build that contains the fixed version. If the installed version is the fixed version or later, that package is fixed for this CVE. Before you treat the finding as resolved, confirm that the scanner isn't also reporting the CVE against another package or architecture in the image. The remaining checks explain why a scanner can still report a fixed package. ### Confirm that your scanner reads Chainguard's advisory data A scanner can apply Chainguard's fixed versions and "not affected" determinations only if it reads Chainguard's advisory data. A scanner that doesn't read it relies on upstream or third-party vulnerability data, which describes upstream release versions rather than Chainguard's package builds. Such a scanner keeps reporting CVEs that Chainguard has fixed, and updating its database or waiting doesn't change the result. Check whether your scanner appears in the list of [scanners that support Chainguard](https://www.chainguard.dev/scanners?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-containers-security-and-compliance-vulnerability-management-scanner-flags-fixed-cve), and whether your scanner's documentation requires any configuration to scan Wolfi or Chainguard packages. If your scanner doesn't support Chainguard, ask your scanner vendor to add support. How the scanner analyzes the image also matters. A scanner identifies the packages in a Chainguard image by reading the APK database at `/usr/lib/apk/db/installed`, which only the complete image filesystem contains. A scanner that analyzes image layers individually, or a custom image that removes the APK database, leaves the scanner to identify software by other means, such as file signatures or CPE matching. Chainguard's advisories can't correct those matches. ### Update your scanner's vulnerability database Scanners match packages against a local copy of a vulnerability database. A copy that predates the advisory doesn't contain the fix. Check when your database was built, and update it if it's out of date. For example, with Grype: ```shell grype db status grype db update ``` The `Built` field in the output of `grype db status` shows when the database was built. For other scanners, see the scanner's documentation for how to update its database. In CI, also confirm that the pipeline doesn't restore an old database from a cache. ### Allow time for new advisory data to reach your scanner Chainguard publishes advisory feed updates several times a day. Your scanner's vendor then ingests that data and publishes its own database update on its own schedule. A fix that Chainguard published recently can take some time to appear in your scanner's results, even when your database is up to date. If the advisory is recent, scan again later. ## If the scanner's fixed version doesn't exist in Chainguard repositories A scanner can report a "fixed in" version that you can't find in any Chainguard package repository. This happens when the scanner takes its fixed version from somewhere other than Chainguard's advisory, such as an upstream release range, another distribution's packaging, or its own inference about the next release. Chainguard's fixed version doesn't have to match an upstream release. When an upstream project fixes a vulnerability without publishing a release, Chainguard can ship the fix as a backported patch or as a build of an unreleased upstream revision. The version string of that build reflects how Chainguard packaged it, not the next upstream release number. The fixed version in Chainguard's advisory is the one that applies to Chainguard packages. Compare your installed version with that value, as described in [Compare the installed package with Chainguard's fixed version](#compare-the-installed-package-with-chainguards-fixed-version). If your scanner reads Chainguard's data and still reports a fixed version that Chainguard's advisory doesn't contain, contact support. ## Look up the fixed version in the advisory feed Chainguard publishes its security advisories in the [OSV format](https://ossf.github.io/osv-schema/). Scanners read this feed, so you can use it to see exactly what Chainguard has recorded for a CVE. For more information about the available feeds, see [Programmatic access](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/#programmatic-access). The following steps use `curl` and [`jq`](https://jqlang.org/). 1. Find the Chainguard advisory IDs for the CVE. The feed index lists each advisory's ID along with the upstream identifiers, such as CVE and GHSA IDs, that it covers: ```shell curl -s https://advisories.cgr.dev/chainguard/v2/osv/all.json \ | jq -r --arg cve "CVE--" '.[] | select(.upstream | index($cve)) | .id' ``` The command prints one or more advisory IDs that start with `CGA-`. If it prints more than one, check each record in the next step. If the command prints nothing, Chainguard has no advisory for that ID. That doesn't mean the package is unaffected. Check that the ID is in uppercase, try the vulnerability's GHSA ID instead, or contact support. If you already have output from `chainctl images advisories list`, note that its `ADVISORY` column shows a per-package advisory ID that this feed version doesn't use. The ID that works in the next step is the `CGA-` ID in the `ALIASES` column. 2. Retrieve the advisory record, and show the fixed version and status of the package that your scanner flagged. Use an advisory ID from the previous step: ```shell curl -s https://advisories.cgr.dev/chainguard/v2/osv/.json \ | jq -r --arg pkg "" '.affected[] | select(.package.name == $pkg) | [ .package.purl, ([.ranges[].events[].fixed // empty] | first // "none"), ([.ecosystem_specific.components[].latest_event_status] | unique | join(",")) ] | @tsv' \ | sort -u ``` Each line shows the package URL, the fixed version, and the advisory status. The `arch` value in the package URL shows the architecture. Wolfi packages appear twice for each architecture, once with `wolfi` and once with `chainguard` in the package URL, with the same fixed version. The fixed version is one of the following: - A version string: the package is fixed in that version. - `0`: Chainguard determined that the package isn't affected. - `none`: the package is still affected. The status shows why, such as `detection` while the advisory is under investigation, or `pending_upstream_fix` while Chainguard waits for an upstream fix. ## Contact support If the finding remains after you work through these checks, [contact support](/get-started/get-support/). Include the following information: - The image reference with its digest, and the platform that you scanned. - The scanner's name and version, and when its database was built. - The CVE ID and the Chainguard advisory ID. - The flagged package's name and installed version, and the fixed version that the scanner reports. - The scanner output for the finding. --- ### Chainguard Checks configuration reference URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/configuration.md Last Modified: October 13, 2026 Tags: Chainguard Checks, Configuration, Reference You define your checks in YAML configuration files in your repository's `.chainguard/` directory. This page describes the file format: where the files go, the packages and commands each check gets, and the options that control when and how checks run. For variations, inputs and outputs, services, and caches, refer to [Variations, inputs, services, and caches](/chainguard/checks/variations-services-caches/). The same files also set up [Chainguard Workspaces](/chainguard/workspaces/overview/) sessions, and `run-ci` in a session runs the checks they define. ## File layouts {#file-layouts} Chainguard Checks reads its configuration from one of two layouts. A repository uses one or the other, not both. | Layout | Use it when | | ------ | ----------- | | A single `.chainguard/ci.yaml` file | You're getting started, or all your checks share one set of packages. | | A `.chainguard/ci/` directory | You want to split checks into several files, or give checks different environments. | In the directory layout, each `.yaml` file in `.chainguard/ci/` holds a group of checks, and the file's name becomes part of each check's name on GitHub. For example, a `test` check in `.chainguard/ci/go.yaml` appears as `presubmit / go / test`. Checks in the single-file layout appear as `presubmit / ci / `. The directory layout can also hold the following: - `env/.yaml` files, each defining a named environment that checks can share, and their `.lock` files. A check that names no environment uses `env/default.yaml`. - `resources.yaml`, which replaces the built-in resource classes with your own. Refer to [Define your own resource classes](#define-your-own-resource-classes). - `.md` files that explain checks in more detail. Chainguard Checks never reads them as configuration. Refer to [Describe a check](#describe-a-check). - `services//.yaml` files, each defining a service, with any files the service needs beside it. Refer to [Services](/chainguard/checks/variations-services-caches/#services). Either layout can declare workload identities and cache pools in `.chainguard/ci/identities.yaml`. Refer to [Workload identities and trust](/chainguard/checks/identity-and-trust/) and [Set up cache pools](/chainguard/checks/variations-services-caches/#set-up-cache-pools). Chainguard Checks reads configuration only from files with the `.yaml` extension, so a `.chainguard/ci.yml` file doesn't turn it on. The following table shows how each layout treats the files in `.chainguard/`: | File | Single-file layout | Directory layout | | ---- | ------------------ | ---------------- | | `.chainguard/ci.yaml` | Required | Makes the configuration invalid | | `.chainguard/ci/identities.yaml` | Allowed | Allowed | | Other files in `.chainguard/ci/` | Make the configuration invalid | Allowed if they're among the files listed earlier. Any other file, such as a `.yml` file, makes the configuration invalid. | | `.chainguard/develop.yaml` | Allowed, unless it has a `checks` key | Allowed | | `.chainguard/actions.yaml`, `.chainguard/images.yaml`, and `.chainguard/source.yaml` | Ignored | Ignored | | `.chainguard/README.md`, a `.yml` file, or another file that isn't a `.yaml` or `.lock` file, directly in `.chainguard/` | Makes the configuration invalid | Ignored | In the directory layout, a `.yaml` file directly in `.chainguard/` with single-file layout keys, such as `version` or `checks`, also makes the configuration invalid. A `.chainguard/develop.yaml` file sets up [Chainguard Workspaces](/chainguard/workspaces/session-environment/) sessions. Chainguard Checks runs nothing from it, so changing it doesn't change your checks. Put checks in `.chainguard/ci.yaml` or `.chainguard/ci/` instead. Sessions don't read `.chainguard/ci/env/develop.yaml`, which is an environment like any other that a check can name with `environment: develop`. A pull request that changes `.chainguard/develop.yaml` also gets a **Chainguard Checks / develop-environment** check, as described in [How results appear](/chainguard/checks/github/#how-results-appear). Most examples on this page use the single-file layout. The keys inside each check are the same in both. ## A complete example The following `.chainguard/ci.yaml` file uses the most common options: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [bash, git, go, make] checks: vet: cmd: go vet ./... network: egress: [proxy.golang.org, sum.golang.org] test: cmd: go test ./... timeout: 15m network: egress: [proxy.golang.org, sum.golang.org] lint: script: | gofmt -l . > /tmp/unformatted cat /tmp/unformatted test ! -s /tmp/unformatted advisory: true build: steps: - name: compile cmd: [go, build, ./...] - name: package cmd: make dist needs: [test] network: egress: [proxy.golang.org, sum.golang.org] docs: cmd: make docs when: changed: ["docs/**"] ``` This file installs four packages in every check's VM and defines five checks: - `vet` runs `go vet`, and can reach the Go module proxy and checksum database to download modules. - `test` runs `go test`, can reach the same hosts, and fails if it runs longer than 15 minutes. - `lint` runs a shell script. Because it's advisory, it reports its result but never blocks merging. - `build` runs two steps in order, can reach the Go module hosts, and runs only after `test` passes. - `docs` runs only when a pull request changes a file under `docs/`. The rest of this page describes each option. ## Top-level keys {#top-level-keys} A configuration file has the following top-level keys: | Key | Required | Description | | --- | -------- | ----------- | | `version` | Yes | The format version. Must be `1`. | | `environment` | Yes, in the single-file layout | The packages and variables every check in the file gets. Refer to [Environment](#environment). | | `resources` | No | The default resource class for the file's checks. Refer to [Resources](#resources). | | `checks` | Yes | A map from each check's name to its definition. | | `plans` | No | Named commands that compute a check's `foreach` variations during the run. Refer to [Compute variations during a run](/chainguard/checks/variations-services-caches/#compute-variations-during-a-run). | | `requested_checks_agent` | No | `off` (default), `comment`, or `agent`. Turns on an agent that requests checks for each pull request. Refer to [Let an agent request checks](/chainguard/checks/github/#let-an-agent-request-checks). | Chainguard Checks refuses any other top-level key with an error that names the key. That includes GitHub Actions keys such as `jobs`, `on`, and `name`. ## Environment {#environment} The `environment` key sets up the VM each check runs in: | Key | Description | | --- | ----------- | | `packages` | A list of packages to install, such as `go` or `nodejs-22`. | | `vars` | A map of environment variables to set for every check. | | `services` | Services, such as a database, to start in each check's VM before the check's commands run. Refer to [Services](/chainguard/checks/variations-services-caches/#services). | | `caches` | Caches that carry build output from earlier runs into each check's VM. Refer to [Caches](/chainguard/checks/variations-services-caches/#caches). | Every check's VM starts from a minimal base with BusyBox, GNU tar, and nothing else. Anything a check needs, including `git`, `bash`, `make`, `curl`, or a language toolchain, must be in `packages`. Package names often include a version, such as `go-1.24`, `nodejs-22`, or `python-3.13`. To pin a more specific version, add a version constraint after a tilde, such as `go-1.24~1.24.3`. To find a package's name, use the [`cg-apk`](/platform/mcp-servers/cg-apk/) MCP server, which looks up APK packages. In the directory layout, a check can name a different environment with its own `environment` key, either by name, such as `environment: node` for `env/node.yaml`, or as an inline mapping. ## Check keys Each entry under `checks` defines one check. The key is the check's name. A check must have exactly one of `cmd`, `script`, or `steps`, and can have any of the other keys: | Key | Description | | --- | ----------- | | `cmd` | A command to run without a shell. Refer to [Commands](#commands). | | `script` | A script to run with a shell. Refer to [Commands](#commands). | | `shell` | The shell for `script`. Defaults to `sh -e`. A check with `cmd` or `steps` can't set it, but each step can set its own. Refer to [The script key](#the-script-key). | | `steps` | A list of commands to run in order. Refer to [Commands](#commands). | | `network` | The hosts the check can reach. Refer to [Network access](#network-access). Defaults to no network access. | | `timeout` | How long the check can run, as a duration such as `10m` or `1h30m`. Refer to [Timeouts](#timeouts). | | `needs` | A list of checks in the same file that must pass before this one runs. Refer to [Advisory checks](#advisory-checks) and [Run on request](#run-on-request) for limits. | | `when` | Conditions under which the check runs. Refer to [Conditions](#conditions). | | `trigger` | `presubmit`, `postsubmit`, or a list of both. Refer to [Triggers](#triggers). Defaults to `presubmit`. | | `advisory` | If `true`, the check reports its result but never blocks merging. Defaults to `false`. | | `description` | What the check covers and when it's worth running. Refer to [Describe a check](#describe-a-check). | | `docs` | The name of a Markdown file beside the check file that explains the check in more detail. Directory layout only. Refer to [Describe a check](#describe-a-check). | | `vars` | A map of environment variables for this check. | | `workdir` | The directory to run in, relative to the repository's root. Defaults to the root, which is `/work` in the VM. | | `inputs` | Files to add to the check's VM from another check's output or from another repository. Refer to [Inputs and outputs](/chainguard/checks/variations-services-caches/#inputs-and-outputs). | | `outputs` | Directories that the check produces for later checks in the same run. Refer to [Inputs and outputs](/chainguard/checks/variations-services-caches/#inputs-and-outputs). | | `resources` | The check's resource class. Refer to [Resources](#resources). | | `foreach` | A list of variations to run the check once for each, such as one per package directory, or `plans.` to compute the variations during the run. Refer to [Run a check once per variation](/chainguard/checks/variations-services-caches/#run-a-check-once-per-variation). | | `parallelism` | How many `foreach` variations can run at once. Refer to [Run a check once per variation](/chainguard/checks/variations-services-caches/#run-a-check-once-per-variation). | | `user` | The name of an account to run the check's commands as, such as `build`. Defaults to `root`. Refer to [Run as another user](/chainguard/checks/variations-services-caches/#run-as-another-user). | | `identity` | A workload identity for the check. Refer to [Credentials](#credentials). | Chainguard Checks refuses keys it doesn't recognize, including GitHub Actions keys such as `runs-on`, `env`, `if`, and `timeout-minutes`. For some of them, such as `if` and `on`, the error names the key to use instead. Refer to [Migrate from GitHub Actions](/chainguard/checks/migrate-from-github-actions/) for a full mapping. ### Check names {#check-names} A check's name can use letters, digits, spaces, `_`, and `-`, and must start and end with a letter or digit. It can't contain `.` or `/`. Two checks in the same file can't have names that differ only in case, because [request labels](#run-on-request) ignore case. A check's full name joins its file's name and its own, such as `ci/test` or `go/test`. Each `foreach` variation's full name adds the variation's name, such as `go/test/api`. Every full name can be up to 128 characters. If a name has a character that isn't allowed, the error says only that the name must fit the 128-character limit, so check its characters too. ## Commands A check runs a single command, a script, or a list of steps. ### The cmd key {#the-cmd-key} The `cmd` key runs one command without a shell. You can write it as a string or as a list of arguments: ```yaml checks: test: cmd: go test ./... test-short: cmd: [go, test, -short, ./...] ``` Chainguard Checks splits a string `cmd` into arguments on whitespace and runs it without a shell. Quotes and backslashes work the way they do in a shell, so `cmd: find . -name '*.go'` passes `*.go` to `find` as it is. Chainguard Checks refuses a string `cmd` that contains any of the following: - An unquoted `|`, `&`, `;`, `<`, `>`, `(`, `)`, `$`, `` ` ``, `*`, `?`, `[`, `]`, `{`, or `}`. - An argument that starts with an unquoted `#` or `~`. - A `$` or a backtick inside double quotes. For pipes, redirection, variables, or globs, use `script`, or run a shell yourself, such as `cmd: [sh, -c, "go vet ./... && go test ./..."]`. ### The script key {#the-script-key} The `script` key runs a script with a shell. By default, the shell is `sh -e`, so the script stops at the first command that fails: ```yaml checks: fmt: script: | gofmt -l . > /tmp/unformatted if [ -s /tmp/unformatted ]; then cat /tmp/unformatted exit 1 fi ``` To use a different interpreter, set `shell` to the interpreter's package name, such as `bash`, `python-3.13`, or `nodejs-22`, and list that same package in the environment. The BusyBox `sh` is always available. Chainguard Checks passes the whole script to the interpreter as one argument: | Interpreter | Runs the script with | | ----------- | -------------------- | | BusyBox `sh` (default) | `sh -e -c` | | Bash | `bash -e -c` | | Python | `python3 -c` | | Node.js | `node -e` | | Ruby | `ruby -e` | | Perl | `perl -e` | Neither `sh -e` nor `bash -e` sets `pipefail`, so a pipeline succeeds whenever its last command does. For example, `go test ./... | tee test.log` passes even when tests fail. To make a pipeline fail when any of its commands fails, set `shell: bash` and start the script with `set -o pipefail`, or use a `cmd` such as `[bash, -eo, pipefail, -c, "go test ./... | tee test.log"]`. Either way, list `bash` in the environment's packages. ### The steps key The `steps` key runs a list of commands in order, in the same VM, and stops at the first one that fails. Each step has a `cmd` or a `script`, and can also have the following keys: | Key | Description | | --- | ----------- | | `name` | A name that appears in the check's results. | | `shell` | The shell for the step's `script`, the same as a check's `shell`. | | `id` | An identifier for the step in the check's logs. | For example: ```yaml checks: build: steps: - name: compile cmd: go build ./... - name: test cmd: go test ./... - name: smoke test script: ./bin/app --version | grep -q "app" ``` All steps share the check's network access and timeout. ## Network access {#network-access} A check has no network access unless it has a `network` key. To let a check reach specific hosts, list them under `network.egress`: ```yaml checks: test: cmd: npm test network: egress: - registry.npmjs.org - "*.githubusercontent.com" policy: deny ``` Each entry in `egress` can be one of the following: - A hostname, such as `registry.npmjs.org`, in lowercase and without a scheme or port. - A wildcard for a domain's subdomains, such as `*.githubusercontent.com`. A wildcard doesn't match the domain itself, so list `githubusercontent.com` too if the check needs it. A wildcard can't cover a whole top-level domain, such as `*.com`. - An IPv4 address or CIDR range. IPv6 isn't supported. - `*`, which allows any host. An entry with a scheme or a port, such as `https://registry.npmjs.org` or `registry.npmjs.org:443`, passes validation but matches nothing. If you include a `network` key, `egress` must list at least one entry. To keep a check off the network, leave out `network` entirely. The `policy` key controls what happens when the check tries to reach a host that isn't listed: | Policy | What happens | | ------ | ------------ | | `deny` (default) | Chainguard Checks refuses the connection, and the check keeps running. | | `destroy` | Chainguard Checks stops the check's VM, and the check fails. | Whatever a check allows, it can never reach cloud metadata endpoints, loopback addresses, or private IP ranges. To see which hosts a check tried to reach and whether Chainguard Checks allowed each one, open the check's run page from its **Details** link. Refer to [A check can't reach a host](/chainguard/checks/troubleshooting/#a-check-cant-reach-a-host). ## Conditions {#conditions} The `when` key limits when a check runs. A check without `when` runs on every pull request. An empty `when` makes the configuration invalid, so to run a check every time, leave out `when`. ### Run when files change {#run-when-files-change} To run a check only when a pull request changes certain files, list glob patterns under `when.changed`: ```yaml checks: docs: cmd: make docs when: changed: ["docs/**", "mkdocs.yml"] ``` `changed` must list at least one pattern. Patterns match file paths as follows: - A pattern is relative to the repository's root, such as `docs/**` or `cmd/*/main.go`. A pattern that starts with `/` or `./` never matches. - `*` matches any characters except `/`, and `?` matches any one character except `/`. A set in brackets, such as `[abc]` or `[a-z]`, matches one character from the set. - `**` matches any number of directories, including none, but only as a whole segment between slashes. `docs/**` matches every file under `docs/`, and `**/*.go` matches every Go file in the repository. `**.go` works like `*.go`, so it matches only Go files at the root. - `!`, braces such as `{a,b}`, and `+` have no special meaning, and there's no `when.unchanged`, so you can't exclude files. List the paths that should run the check instead. `chainctl checks validate` refuses a malformed pattern, such as one with an unclosed `[`, but not a pattern that can never match, such as `/docs/**`. For a pull request, Chainguard Checks compares the pull request's changes against its base branch. For a run after merging, it compares the new commit against its first parent, as described in [Run checks after merging](/chainguard/checks/github/#run-checks-after-merging). ### Run when an expression is true {#run-when-an-expression-is-true} To run a check based on the pull request, write a [Common Expression Language (CEL)](https://cel.dev/) expression under `when.expr`. The expression can read the following fields: | Field | Description | | ----- | ----------- | | `context.trigger` | The run's trigger. Compare it to the unquoted constants `presubmit` and `postsubmit`, such as `context.trigger == postsubmit`. | | `context.github.pull_request.number` | The pull request's number. | | `context.github.pull_request.title` | The pull request's title. | | `context.github.pull_request.body` | The pull request's description as it was when the run started, or an empty string if it has none. | | `context.github.pull_request.html_url` | The pull request's URL on GitHub. | | `context.github.pull_request.draft` | Whether the pull request is a draft. | | `context.github.pull_request.labels` | The pull request's labels. | | `context.github.pull_request.head_ref` | The pull request's branch name. | | `context.github.pull_request.head_repository` | The repository the pull request's branch is in, which differs from yours for a pull request from a fork. | | `context.github.pull_request.author.login` | The GitHub login of the pull request's author. | | `context.github.pull_request.author.id` | The numeric GitHub account ID of the pull request's author. | | `context.github.pull_request.author.type` | The type of the author's GitHub account: `User`, `Bot`, or `Organization`. | For example, this check skips draft pull requests: ```yaml checks: e2e: cmd: make e2e when: expr: "!context.github.pull_request.draft" ``` A run after merging has no pull request, and reading a `context.github.pull_request` field in that run is an error that fails the run. For a check that also runs after merging, guard the field with `has()`, such as `expr: "!has(context.github.pull_request) || !context.github.pull_request.draft"`. If you set both `changed` and `expr`, the check runs only when both are true. To combine changed files with other conditions in other ways, call `changed()` in the expression with a list of patterns, which match files the same way as `when.changed`. For example, this check runs when a pull request changes a file under `docs/` or has the `docs` label: ```yaml checks: docs: cmd: make docs when: expr: 'changed(["docs/**"]) || "docs" in context.github.pull_request.labels' ``` An expression can be up to 2,048 characters, and Chainguard Checks refuses one whose estimated cost to evaluate is over its limit. Chainguard Checks evaluates conditions when it creates a run, against the pull request as it is then. If the run passes while a condition skipped a check, and you then change the pull request, such as its title or labels, the commit runs again. A passed run of a pull request that changes your Chainguard Checks configuration also runs again after such a change, whether or not a condition skipped a check. Refer to [Reruns when a pull request changes](/chainguard/checks/github/#reruns-when-a-pull-request-changes). A pull request's author sets its `draft`, `title`, `head_ref`, and `body` fields, and anyone who can label it sets its `labels`. If a condition on one of these fields skips a blocking check, those people decide whether the check runs, and the summary check can pass without it. `chainctl checks explain` adds a note to each blocking check whose condition reads one of these fields. ### Run on request {#run-on-request} A check with `when: {requested: true}` runs only when someone asks for it. Use this for slow or expensive checks that most pull requests don't need: ```yaml checks: e2e-full: cmd: make e2e-full description: The full end-to-end suite against every supported database. Run it for changes to the storage layer or to database migrations. when: requested: true ``` A check that runs on request follows these rules: - It must have a [`description`](#describe-a-check) that says what it covers and when it's worth running. - `requested` can only be `true`, and the same `when` can't also set `changed` or `expr`. - Its name can be at most 43 characters, so that its label fits GitHub's 50-character limit. When two files in the directory layout have a check with the same name, the label includes the file's name, such as `ci/run:go/e2e-full`, and the limit counts the file's name and the `/` after it too. - A check that doesn't run on request can't need one that does, because it would then run only when the other check is requested. To run the check on a pull request, add the label `ci/run:`, such as `ci/run:e2e-full`. Refer to [Run a check on request](/chainguard/checks/github/#run-a-check-on-request) for details. A run after merging never runs a check on request. ## Describe a check {#describe-a-check} A check's `description` says what the check covers and when it's worth running, in up to 1,024 characters. In the directory layout, a check can also point to a longer explanation with `docs`, which names a Markdown file beside the check file: ```yaml {title=".chainguard/ci/e2e.yaml"} version: 1 checks: e2e-full: cmd: make e2e-full description: The full end-to-end suite against every supported database. Run it for changes to the storage layer or to database migrations. docs: e2e-full.md when: requested: true ``` The file, `.chainguard/ci/e2e-full.md` in this example, must be directly in `.chainguard/ci/` and can be up to 1 MiB. Its name must end in `.md`, can use letters, digits, `.`, `_`, and `-`, and can be up to 128 bytes. If the file is missing or larger than 1 MiB, the configuration is invalid. Set `docs` on the check, not on a `foreach` entry. The single-file layout doesn't support `docs`. If you turn on `requested_checks_agent`, the agent reads each check's `description` and `docs` to decide which checks to request. Refer to [Let an agent request checks](/chainguard/checks/github/#let-an-agent-request-checks). ## Advisory checks {#advisory-checks} A check with `advisory: true` reports its real result on the pull request, with `(advisory)` in its title, but never makes the **Chainguard Checks (presubmit)** summary check fail. Use it for a check you want to see but not enforce yet, such as a new linter. A blocking check can't need an advisory check, either with `needs` or by taking an [input](/chainguard/checks/variations-services-caches/#pass-files-between-checks) from it. If the advisory check failed, the blocking check wouldn't run, and the summary check would fail. Make both checks advisory, or both blocking. ## Triggers The `trigger` key sets which events run a check: | Trigger | When the check runs | | ------- | ------------------- | | `presubmit` (default) | On each pull request, against the commit GitHub would create by merging it. | | `postsubmit` | On each new commit on your default branch, after a change merges. | To run a check at both times, list both: ```yaml checks: test: cmd: go test ./... trigger: [presubmit, postsubmit] ``` Postsubmit results appear on the commit on your default branch, in a summary check named **Chainguard Checks (postsubmit)**. ## Timeouts {#timeouts} By default, a check has no time limit of its own. A whole run can take up to 50 minutes, and a check still running at that point fails. To fail a check sooner, set `timeout` to a duration, such as `10m` or `1h`. ## Resources {#resources} By default, each check's VM has 2 vCPUs, 8 GB of memory, and a 14 GB disk. To give a check more, set `resources` to one of the following classes: | Class | vCPUs | Memory | Disk | | ----- | ----- | ------ | ---- | | `medium` | 4 | 16 GB | 40 GB | | `large` | 8 | 32 GB | 40 GB | | `xlarge` | 16 | 64 GB | 40 GB | On pull requests, a class without a suffix can run on either CPU architecture, and usually runs on arm64. Each built-in class also has `-amd64` and `-arm64` variants, such as `large-amd64`, that pin the architecture. With the built-in classes, use an `-amd64` class for a check that needs x86-64, such as one that downloads an x86-64 binary. Chainguard Checks takes each check's class from the first of these that sets `resources`: the `foreach` entry, the check, the check's file, and `env/default.yaml` in the directory layout. If none of them sets `resources`, the check gets the default class. Your organization's allowance sets the largest class you can use. With the Starter allowance, the largest is `medium`. If any check in a run asks for `large` or `xlarge`, the whole run fails before any check starts, and the summary check shows **Run failed**. In each check's VM, `/tmp` is held in memory, and can hold up to a quarter of the VM's memory: about 2 GB with the built-in default, and 4 GB with `medium`. Files in `/tmp` use memory that the check's processes would otherwise have. Put large files, such as a virtual environment or build output, under `/var/tmp` or `/work`, which are on the VM's disk. ### Define your own resource classes {#define-your-own-resource-classes} In the directory layout, a `.chainguard/ci/resources.yaml` file replaces the built-in classes with your own: ```yaml {title=".chainguard/ci/resources.yaml"} version: 1 resource_classes: default: cpus: 2 memory: 8Gi disk: 20Gi big: cpus: 4 memory: 16Gi disk: 40Gi big-amd64: cpus: 4 memory: 16Gi disk: 40Gi platform: os: linux architecture: amd64 ``` A check selects one of these classes by name, such as `resources: big`. The file follows these rules: - It must define `default`, which a check gets when nothing sets its `resources`. - It replaces the built-in classes, so `medium`, `large`, `xlarge`, and their `-amd64` and `-arm64` variants aren't available unless you define classes with those names. - Each class needs `cpus`. `memory` and `disk` are whole numbers of `Mi` or `Gi`, such as `512Mi` or `16Gi`. A class without `memory` gets 4 GiB, and a class without `disk` gets 5 GiB. - Only `platform` pins a class to an architecture, with `os: linux` and `architecture` set to `amd64` or `arm64`. A class without `platform` can run on either architecture, whatever its name. - Class names use lowercase letters, digits, and `-`, start with a letter, and can be up to 63 characters. - The single-file layout doesn't support `resources.yaml`. A `.chainguard/ci/resources.yaml` file beside `.chainguard/ci.yaml` makes the configuration invalid. Your organization's allowance limits your own classes too. If any check in a run asks for more vCPUs, memory, or disk than the allowance allows, the whole run fails before any check starts. ## Environment variables {#environment-variables} Set environment variables with `vars`, either in `environment` for every check or on a single check: ```yaml environment: packages: [go] vars: CGO_ENABLED: "0" checks: test: cmd: go test ./... vars: GOFLAGS: -tags=integration ``` In a check's `vars` or a `foreach` entry's `vars`, but not in `environment.vars`, a variable's value can also come from the pull request, with a binding such as `"${{ context.github.pull_request.number }}"`. The binding must be the variable's whole value, and it reads the same fields as [`when.expr`](#run-when-an-expression-is-true). Chainguard Checks fills in bindings when it creates the run: - A list, such as `labels`, becomes a JSON array, such as `["bug","docs"]`. - `context.trigger` becomes `presubmit` or `postsubmit`. - A run after merging has no pull request, so a binding to a `context.github.pull_request` field leaves the variable unset. To tell an unset variable from an empty one in a script, test it with `${VAR+set}`. A binding to `context.github.pull_request.body` works as follows: - It sets the variable to the pull request's description as it was when the run started, line breaks included. - Only the check's commands get the variable, not its [services](/chainguard/checks/variations-services-caches/#services). - To pick up an edited description, use **Re-run** or push a new commit. - The variable can't be `CHAINGUARD_BASE_REVISION`, `GOOGLE_APPLICATION_CREDENTIALS`, `GITHUB_TOKEN`, or `GOFLAGS`, which Chainguard Checks can set itself. - The description can be up to 65,536 bytes, and can't contain a NUL character. A character outside ASCII takes more than one byte. If a binding breaks these rules, the run fails, as described in [The summary check says the run couldn't be created or failed](/chainguard/checks/troubleshooting/#the-summary-check-says-the-run-couldnt-be-created-or-failed). Chainguard Checks doesn't set `CI` or any `GITHUB_` variables. When a run has a base commit, Chainguard Checks sets `CHAINGUARD_BASE_REVISION` to that commit's SHA. For a pull request, that's the base branch commit that GitHub merged the pull request into. For a run after merging, it's the new commit's first parent. Your repository's history is in the VM, so with `git` in your packages, a check can compare against it, such as with `git diff "$CHAINGUARD_BASE_REVISION"`. ## Report errors and annotations {#report-errors-and-annotations} A check can print special lines to its output that Chainguard Checks turns into annotations and summaries on the pull request, or uses to protect its log: | Line | What it does | | ---- | ------------ | | `::error file=main.go,line=12::message` | Reports an error as an annotation on that file and line. | | `::warning file=main.go,line=12::message` | Reports a warning as an annotation on that file and line. | | `::notice file=main.go,line=12::message` | Reports a notice as an annotation on that file and line. | | `::sandbox-summary::`, then `::end-sandbox-summary::` | Adds the Markdown between the two lines to the check's output on GitHub. Refer to [Add a Markdown summary](#add-a-markdown-summary). | | `::add-mask::value` | Hides `value` wherever it appears later in the check's log. | An `::error`, `::warning`, or `::notice` line with a `file` becomes an annotation at that level. Without a `file`, the line appears in the check's output only. The `file` path must be relative to the repository's root, not to the check's `workdir`. A line can also set `endLine`, `col`, `endColumn`, and `title`. Without `line`, the annotation goes on line 1 of the file. Columns apply only to an annotation on a single line. Chainguard Checks reads these lines only when `::` starts the line, and only from the last 1 MiB of a check's log, so a check with a long log should print them near the end. Chainguard Checks shows up to 50 annotations for each check, and annotations don't change whether a check passes. The annotation lines use the same format as GitHub Actions workflow commands, so many linters that support GitHub Actions output work without changes. ### Add a Markdown summary {#add-a-markdown-summary} In place of the `GITHUB_STEP_SUMMARY` file in GitHub Actions, a check can add a summary in Markdown to its output on GitHub, such as a list of the problems it found. Print the summary between a `::sandbox-summary::` line and a `::end-sandbox-summary::` line: ```yaml checks: fmt: script: | gofmt -l . > /tmp/unformatted if [ -s /tmp/unformatted ]; then echo '::sandbox-summary::' echo 'These files need `gofmt`:' echo sed 's/^/- /' /tmp/unformatted echo '::end-sandbox-summary::' exit 1 fi ``` A summary works as follows: - Each marker must be on a line of its own. The summary appears in the check's output, above the end of the check's log. - If a check prints several summaries, they appear together, in the order the check printed them. - A summary ends with the step that prints it. If the step ends before `::end-sandbox-summary::`, the summary appears as far as it went, with a note. - HTML appears as text, except the `
`, ``, and `
` tags. - Links and images stay only if they point into your repository on GitHub, under `https://github.com///`, or to the run pages that a check's **Details** link opens. Other links become plain text. - An `@` mention in a summary doesn't notify anyone. - GitHub limits the size of a check's output, so Chainguard Checks cuts a summary longer than about 53 KiB and links to the full log. In a check with `foreach`, each variation's summary can be up to 8 KiB. ## Credentials {#credentials} A check runs without credentials by default. Its `/work` directory holds your repository's tracked files and history at the commit it tests, so most checks need none. Submodule directories are empty, and files that Git LFS tracks hold their pointer files instead of their content. During the beta, a check gets no GitHub credential, so it can't fetch from private GitHub repositories, such as private Go modules or submodules, or private Git LFS content. To add files from a public repository, use a [`git` input](/chainguard/checks/variations-services-caches/#check-out-another-repository). A check that must authenticate to Google Cloud or to Chainguard, such as to pull private images from `cgr.dev`, can use a *workload identity*. You declare workload identities in `.chainguard/ci/identities.yaml`, and a check requests one by name with `identity`. Workload identities need setup on the provider's side as well. During the beta, contact your Chainguard account team to set them up, or [contact Chainguard](https://www.chainguard.dev/contact?utm=docs) if you signed up yourself. Unless `identities.yaml` on your default branch has a `trust` section, any open pull request can get an identity, including one from a fork. Refer to [Workload identities and trust](/chainguard/checks/identity-and-trust/) for the file's format, which pull requests get an identity, and how to limit them. ## Validate your configuration To find mistakes in your configuration without running anything, run `chainctl checks validate .` from your repository's directory. To see how Chainguard Checks interprets your configuration, run `chainctl checks explain`. Refer to [Validate your configuration offline](/chainguard/checks/run-checks/#validate-your-configuration-offline) for details. ## More examples ### Node.js This file installs Node.js 22 and npm, installs dependencies, and runs the tests: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [nodejs-22, npm] checks: test: steps: - name: install cmd: npm ci - name: test cmd: npm test network: egress: [registry.npmjs.org] ``` The `install` and `test` steps run in the same VM, so the packages that `npm ci` installs are available to `npm test`. If your project uses packages from another registry, add that registry's host to `egress`. ### Python This file installs Python 3.13 and pip, installs the project's requirements, and runs `pytest`: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [python-3.13, py3.13-pip] checks: test: script: | python3 -m venv /var/tmp/venv . /var/tmp/venv/bin/activate pip install -r requirements.txt pytest network: egress: [pypi.org, files.pythonhosted.org] ``` The script creates a virtual environment so that `pip` installs packages for the check alone. It puts the environment in `/var/tmp`, on the VM's disk, because [`/tmp` is held in memory](#resources). Both `pypi.org` and `files.pythonhosted.org` are in `egress`, because pip reads the package index from the first and downloads files from the second. --- ### Manage Chainguard Workspaces sessions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/manage-sessions.md Last Modified: October 13, 2026 Tags: Chainguard Workspaces, chainctl, Procedural A Chainguard Workspaces session lasts beyond a single terminal. You can detach from it and return later, keep it running while an agent works, run more than one terminal in it, or open several sessions on the same repository. This page explains how to do each of these with `chainctl develop`. The examples on this page use `$ORGANIZATION` for the name of your Chainguard organization. If you belong to only one organization, you can omit `--parent`. ## How chainctl develop chooses a session {#how-chainctl-develop-chooses-a-session} The `chainctl develop` command takes one optional argument, which tells it what to open: | Argument | What `chainctl develop` does | | -------- | ---------------------------- | | None, or a directory | Reattaches to the directory's session if it has exactly one, and creates one if it has none. If the directory has several sessions, it asks which one you want. | | A subdirectory of a checkout | Opens the whole repository with its history, and starts the shell in the subdirectory. | | A repository URL, such as `github.com/acme/app` | Opens the repository at its default branch, without syncing a local directory. | | A session name, such as `app/quiet-harbor`, or a short ID, such as `3f9a2c1e` | Returns to that session. | Sessions you open from a directory and sessions you open from a repository URL are separate. Running `chainctl develop github.com/acme/app` doesn't return to a session you opened from your local clone of that repository. ## Choose the session's repository {#choose-the-sessions-repository} A session that you open on a directory is on one GitHub repository, and the session's `github` credential is scoped to it. When the session can start from one of the repository's commits, `chainctl` uploads only what differs from that commit. By default, the session is on the repository that your checkout's `origin` remote points to. If the checkout's remotes point to more than one GitHub repository, such as a fork and its upstream, `chainctl` lists them, starting with the one `origin` points to, and asks which one to use when it creates the session. Without a terminal, or with `--non-interactive`, it uses the repository that `origin` points to. To choose ahead of time, pass `--git-remote` with the name of a remote. This example opens a session on the repository that the `upstream` remote points to: ```shell chainctl develop --git-remote upstream --parent $ORGANIZATION ``` If the directory's session is on a different repository than the remote you name, `chainctl develop` fails and tells you to add `--new`, as described in [Open more than one session](#open-more-than-one-session). To open a session on no repository, pass `--git-remote -`, or choose **Upload this directory on no repository** when `chainctl` asks. The session then has no `github` service, so it gets no GitHub credential, and `chainctl` uploads the whole checkout with its history, instead of only what differs from a commit. Files that `.gitignore` excludes still stay on your laptop. Whatever the session is on, `chainctl` doesn't upload your checkout's `.git/config` file or its `.git/hooks` directory, because they can contain credentials and commands that git runs. Chainguard adds an `origin` remote that points to the session's repository, so `origin` is the session's only remote, even if you chose another remote. A session on no repository has no remotes. Your other remotes aren't in the session, and neither are hooks that you or a hook manager installed. To fetch from or push to another of your remotes in the session, add it with its HTTPS URL: ```shell git remote add upstream https://github.com/acme/app ``` The session's `github` credential already covers your checkout's other github.com remotes where the Chainguard App is installed, as described in [What a session can reach by default](/chainguard/workspaces/credentials/#what-a-session-can-reach-by-default). To run your hooks in the session, install them again there, for example with `pre-commit install`. ## Detach from a session {#detach-from-a-session} While you're attached to a session, press `Ctrl+]` to open the session menu. You can also type `~.` at the session's shell prompt, or as the first thing you type after you attach or press `Enter`. In a program that draws its own input line, such as Claude Code, `~.` works only right after `Enter`, so use `Ctrl+]` there. The menu has the following options: | Option | Key | What it does | | ------ | --- | ------------ | | **Detach — let it park** | `d` or `Enter` | Returns you to your laptop's shell. The session keeps running for about 10 minutes, then parks. | | **Detach — keep running 4h** | | Returns you to your laptop's shell and keeps the session running for 4 hours. | | **Detach — keep running for…** | | Asks for a duration, such as `90m`, or a time, such as `17:30`, up to 24 hours from now. | | **Park now** | | Returns you to your laptop's shell and parks the session right away, even one you're keeping running. To resume it, run `chainctl develop`, which waits for the park to finish. | | **Restart the host** | | Parks the session and resumes it on a new host while you stay attached. Running processes continue unless the snapshot can't be restored. | | **Rename…** | `n` | Changes the session's name. | | **Credentials** | | Shows and changes what the session can reach. Refer to [Manage session credentials](/chainguard/workspaces/credentials/). | | **Status** | | Shows the session's name and state, its repository and commit or its directory, its branch, its credential endpoints, what serves its laptop credentials, and the last clipboard copy. | | **Logs** | `l` | Shows every message `chainctl` has shown since you attached, newest last. | | **Help** | `h` or `?` | Lists every key. | | **Back** | `Esc` | Closes the menu and returns to the session. | The menu ignores what you type for a moment after it opens, so pause before you press a key. The first option is what `d` or `Enter` does, and the detach options depend on the session. For a session you're already keeping running, the first option is **Detach — keeps running until** followed by a time. A session created with `--keep always` has one detach option, which shows how long it keeps running. The `--keep` flag also changes the first option, as described in [Keep a session running while you're away](#keep-a-session-running-while-youre-away). To send `Ctrl+]` to a program in the session, press it twice. To see every key, choose **Help**, or type `~?` wherever `~.` works. If you exit the session's shell, for example with `exit`, any other terminals you opened in the session close too. If the session isn't already being kept running, `chainctl` asks whether to keep it running while you're away. Press `Esc` to let it park. ## Return to a session {#return-to-a-session} To return to a session, run `chainctl develop` from the directory you opened it on, or pass the session's name or short ID from anywhere: ```shell chainctl develop app/quiet-harbor --parent $ORGANIZATION ``` If the session is parked, `chainctl` resumes it first, which usually takes a few seconds. The session's memory is restored along with its disk, so running processes, such as a build or an agent, continue where they stopped. Returning to a session doesn't sync your directory again. Your directory syncs only the first time you attach, so move later changes between your laptop and the session with git. Only one `chainctl` can be attached to a session at a time. If you attach from another terminal or laptop, the new one takes over, and the first one prints `Detached from acme/app/quiet-harbor: another client attached.` followed by the command that returns to the session. ## Keep a session running while you're away {#keep-a-session-running-while-youre-away} A session that keeps running uses compute while you're away, but an agent in it can keep working. It counts against your organization's monthly allowance the whole time it runs, as described in [Availability and limits](/chainguard/workspaces/overview/#availability-and-limits). To keep a session running, choose one of the **Detach — keep running** options in the session menu. To decide ahead of time that a session keeps running when you detach, pass `--keep` with a duration of up to 24 hours when you open or return to it: ```shell chainctl develop --keep 8h --parent $ORGANIZATION ``` With `--keep 8h`, the menu's first option becomes **Detach — keep running 8h**, and exiting the shell keeps the session running without asking. For a long-running task, create a new session with `--keep always`. That session keeps running instead of parking when you detach, until 7 days after you last detach from it: ```shell chainctl develop --keep always --name migration --parent $ORGANIZATION ``` Because it doesn't park, a `--keep always` session can use up the monthly allowance in a few days. To stop it sooner, choose **Park now** in the session menu, or delete the session. Claude Code can keep working after you close the terminal, for as long as its credential lasts. Refer to [Keep Claude Code working while you're away](/chainguard/workspaces/coding-agents/#keep-claude-code-working-while-youre-away). For a credential that your laptop serves, such as a `cgr.dev` login you switched to your laptop, the terminal you detached from keeps serving the session until you press `Ctrl+C` or close the terminal. After that, requests that need your laptop fail until you return to the session. Refer to [How credentials reach a session](/chainguard/workspaces/credentials/#how-credentials-reach-a-session). ## Use several terminals in a session {#use-several-terminals-in-a-session} When you run `chainctl develop` in an interactive terminal, it keeps a status row at the bottom of the window. The row lists the session's terminals and shows each message from `chainctl` for a few seconds. It ends with `^] menu`, or with a count of messages you might have missed, such as `^] menu · 2 new`. To read them, choose **Logs** in the session menu. A session can run up to nine terminals, for example Claude Code in one and your tests in another. `chainctl` also calls them PTYs. To manage them, press `Ctrl+]` to open the session menu, which lists the session's terminals, and then press one of the following keys: | Key | What it does | | --- | ------------ | | `c` | Starts a terminal and switches to it. The new terminal opens in terminal 1's current directory. | | `1` to `9` | Switches to that terminal. | | `r` | Renames the terminal shown. Until you rename it, a terminal shows the title of the program running in it, or `shell`. | | `x` | Asks you to confirm, then closes the terminal shown and stops what runs in it. | On the status row, the name of the terminal shown is bold. A terminal you're not looking at is marked when it has new output, or when its program rings the bell or sends a notification. When a terminal's command exits, for example after you type `exit` in it, the terminal closes and the row shows the command's exit status. Terminal 1 is the session's shell, so you can't close it with `x`. Exiting it closes the other terminals and detaches you, as described in [Detach from a session](#detach-from-a-session). When you detach from the session menu instead, every terminal stays open, and when you return from the same laptop, `chainctl` opens the terminal you were in when you left. All the terminals share the session's `/work` directory. To run several coding agents in one session, refer to [Run several agents in parallel](/chainguard/workspaces/coding-agents/#run-several-agents-in-parallel). If the session menu doesn't list the session's terminals, the session was created before Workspaces supported more than one, and it has only its shell. ## Open more than one session With the free monthly allowance, each user can have one session at a time, including a parked one. To open another, delete the one you have first. To work on more than one thing at a time in a single session, open more terminals in it, as described in [Use several terminals in a session](#use-several-terminals-in-a-session). If your organization's allowance includes more sessions, such as one arranged with your Chainguard account team, you can run several sessions on the same repository, for example to give each of several agents its own copy of the code. To open another session on a directory that already has one, pass `--new`, and optionally `--name` to choose the second part of its name: ```shell chainctl develop --new --name scratch --parent $ORGANIZATION ``` This command creates a session named `app/scratch`. Without `--name`, `chainctl` generates a name. Once a directory has several sessions, return to one by its name or short ID. The `--name`, `--env`, `--check`, `--cache`, and `--keep always` flags each open a new session, so you don't need `--new` with them. ## List sessions {#list-sessions} To list your sessions, most recently used first, run `chainctl develop list`: ```shell chainctl develop list --parent $ORGANIZATION ``` The output shows each session's name, the branch checked out in it, its state, and when you last used it: ``` NAME | BRANCH | STATE | LAST USED -----------------------|-----------|------------------------|----------- acme/app/quiet-harbor | main | running, parks in 6m | 4m ago acme/app/scratch | fix-tests | parked, expires Oct 13 | 2d ago ``` The `STATE` column tells you whether a session is running, when it parks or stops running, and when a parked session expires. To add each session's commit, environment, and directory, pass `-o wide`. To include expired sessions, pass `--expired`. To list every user's sessions in the organization, with an `OWNER` column, pass `--all`. Only organization owners can use `--all`, and you can return to, rename, or delete only your own sessions. ## Rename a session To rename a session, pass its current name or short ID and the new second part of its name. The repository part stays the same: ```shell chainctl develop rename app/quiet-harbor tests --parent $ORGANIZATION ``` This command renames the session to `app/tests`. ## Delete a session A parked session expires 5 days after it parks. Chainguard then discards its VM and snapshot, but the session stays in `chainctl develop list --expired`, and its name stays in use until you delete it. To delete a session, run `chainctl develop delete` with its name or short ID: ```shell chainctl develop delete app/scratch --parent $ORGANIZATION ``` The command asks you to confirm, then destroys the session's VM and its snapshot. Anything in the session that you didn't push is lost. To skip the confirmation, for example in a script, pass `--yes`. ## Use sessions from scripts {#use-sessions-from-scripts} To use `chainctl develop` without a terminal, for example from a script or a coding agent on your laptop, pass `--non-interactive`. With it, `chainctl` never prompts or guesses. An ambiguous session reference is an error, and so is a repository with neither a `.chainguard/develop.yaml` nor a `.chainguard/ci.yaml` file, which would otherwise open on the default environment. To use the default environment, pass `--env default`. To create a session and print its details as JSON without attaching to it, pass `--output json`: ```shell chainctl develop --new --non-interactive --output json --parent $ORGANIZATION ``` The session's `/work` directory stays empty until you attach with `chainctl develop` from the same directory, which uploads it. Without a terminal, a session can't ask you to approve requests beyond its defaults, so it refuses them. To allow them ahead of time, pass `--with`, as described in [Manage session credentials](/chainguard/workspaces/credentials/). --- ### Troubleshoot a wrapped container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting.md Last Modified: October 7, 2026 Tags: Chainguard Containers, Custom Assembly, Troubleshooting, Debugging > **Note**: Guarded Entrypoint is in beta. To use it, contact Chainguard customer support to enable it for your organization. This page covers two kinds of problems. A container that is built with [Guarded Entrypoint](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/) can fail to start. A build can also fail because Chainguard refuses to wrap an image. ## First move: set GUARDED_DISABLE When a wrapped container fails to start, set the `GUARDED_DISABLE` environment variable on the container and redeploy. The wrapper then starts the image's original entrypoint and arguments without doing anything else. You don't need to rebuild the image. With Kubernetes, set the variable on the deployment: ```shell kubectl set env deployment/$DEPLOYMENT GUARDED_DISABLE=1 ``` With Docker, pass it to `docker run`: ```shell docker run -e GUARDED_DISABLE=1 cgr.dev/$ORGANIZATION/$REPO:latest ``` `GUARDED_DISABLE` counts as set unless its value, lowercased and trimmed, is empty, `0`, `false`, `no`, or `off`. A value of `1` or `true` turns the wrapper off. A value of `0` or `false` leaves it on. The wrapper checks `GUARDED_DISABLE` before it reads any other setting. This means that a malformed setting in the image doesn't stop it from working. When `GUARDED_DISABLE` is set, the wrapper does the following: * It doesn't resolve secret references. Your application sees the literal `cg+...` values. * It doesn't run preflight checks. * It doesn't apply `command_override`. The image's original ENTRYPOINT and CMD run. * It makes no network connections. If the container then starts, the wrapper or its settings might have caused the failure. Remove the variable after you fix the configuration and Chainguard rebuilds the image. If the container still fails, the cause isn't necessarily the application or the deployment. With `GUARDED_DISABLE` set, your application receives the literal `cg+...` values, and the preflight checks don't run. An application that needs its secrets can fail for that reason. With `GUARDED_DISABLE` set, an image whose only command comes from `command_override` exits with code `124` instead of starting. Anyone who can set environment variables on a container can set `GUARDED_DISABLE`. This is true for a repo with `fail_mode: closed` too. Refer to [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/). ## Find out why the container stopped Start with the container's exit code. For a Kubernetes pod, `kubectl describe pod` shows it under `Last State`. For Docker, run the following command: ```shell docker inspect --format '{{.State.ExitCode}}' $CONTAINER ``` Then read the container's logs. The wrapper writes JSON messages to standard error, one object per line, and each message names the setting or variable that failed. It never logs a secret value: ```shell kubectl logs $POD ``` For a pod in a restart loop, add `--previous` to read the logs of the container that stopped. For more detail, set `GUARDED_ENTRYPOINT_LOG` on the container to `debug`. The levels are `debug`, `info`, `warn`, and `quiet`. ### Exit codes before the application starts The wrapper uses the following exit codes when it stops the container before your application runs. Once the application starts, the container exits with the application's exit code. | Code | Meaning | What to check | | --- | --- | --- | | `120` | A Guarded Entrypoint setting is invalid. | Check the setting that the log message names. | | `121` | Secret resolution failed. This includes a `${VAR}` in the command that names a variable that isn't set. | Check the backend address, credentials, and reference. Check that each `${VAR}` has a value. | | `122` | A preflight check failed. | Check that the target is reachable from the container, and consider a longer `timeout`. | | `123` | There is no command to run. | Check that the image has an entrypoint or CMD, or that `command_override` sets a command. | | `124` | `GUARDED_DISABLE` is set and there is no command to run. | Pass a command, or check that the image has an entrypoint or CMD. | | `125` | The wrapper itself failed, for example when it couldn't fork a process. | Check the container's resource limits. | | `126` | The command exists but can't be run. | Check the execute permission, that the command is not a directory, and that a script's `#!` interpreter exists. | | `127` | The command wasn't found. | Check that the first element of `command` is on the container's `PATH`, or use an absolute path. | Your own application can also exit with any code from `120` to `127`. To tell the two apart, look in the logs for a wrapper error line just before the container exited. With `fail_mode: open`, only the configuration errors described in [Fail mode](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/#fail-mode) stop the container with exit code `121`. ### A container whose entrypoint is an init system An image whose entrypoint is an init system that must run as PID 1, such as systemd or s6-overlay, doesn't work under the wrapper. The container behaves in one of two ways: * It exits with code `1` at start, and systemd prints `Explicit --user argument required to run as user manager.` * It exits with code `129` when you stop it. To fix it, set `GUARDED_DISABLE` on the deployment, or turn off Guarded Entrypoint for the repo. Refer to [Init systems aren't supported](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/#init-systems-arent-supported). ## Entrypoints the wrapper refuses When Chainguard can't wrap an image, the rebuild of that image fails. Chainguard doesn't ship a broken image. Instead, the build records a failure and its reason. To find the failure, list the repo's builds: ```shell chainctl images repos build list --repo $REPO --parent $ORGANIZATION ``` The `Result` column shows the failure, and the `Reason` column shows why. Chainguard fills the `Reason` column only for failures that it records outside a build, such as these refusals and binding conflicts. It is empty for an ordinary build failure. The failed build has no tags in the `Tags` column. For the full text, run `chainctl images repos build logs --repo $REPO --parent $ORGANIZATION` and select the failed build. Without a terminal, for example in a pipeline, pass `--build-id` with the build's ID. The output has this form: ```output guarded entrypoint refused for tags [latest] (digest sha256:...): the environment sets GUARDED_DISABLE; the tags are not rebuilt and are dropped from the repo's active tag list until the refusal is resolved detail: set in the base image environment ``` The optional second line, `detail: ...`, gives more information. If the repo sets `command_override`, the detail also says that `command_override` is not applied. The text after the digest is the reason. It is one of the following: | Reason | Meaning | | --- | --- | | `the environment sets GUARDED_DISABLE` | The image's environment sets `GUARDED_DISABLE`. The detail says where it is set. | | `the listed wrapper version does not read every GUARDED_* setting` | The repo's `contents.packages` pins `guarded-entrypoint` or `guarded-entrypoint-fips` to a release that is too old for the repo's settings. The detail names the first release that reads them all. | The tags named in the message aren't rebuilt until you resolve the refusal. A tag that isn't rebuilt doesn't receive package updates, including CVE fixes, until then. To resolve it, do one of the following: * For the wrapper version reason, remove the `guarded-entrypoint` or `guarded-entrypoint-fips` pin from the repo's `contents.packages` list. * Turn off Guarded Entrypoint for the repo. Refer to [Turn off Guarded Entrypoint](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/#turn-off-guarded-entrypoint). * With Custom Assembly Overlays, bind the overlay that sets `guarded_entrypoint` only to the tags that Chainguard doesn't refuse. To check ahead of time whether an image is supported, refer to [Supported and refused entrypoints](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/#supported-and-refused-entrypoints). ## Learn more * [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/) * [Guarded Entrypoint examples](/chainguard/containers/custom-assembly/guarded-entrypoint/examples/) * [Guarded Entrypoint trust boundary](/chainguard/containers/custom-assembly/guarded-entrypoint/trust-boundary/) --- ### Configuring Guardener URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/guardener/github/configuration.md Last Modified: October 13, 2026 Tags: GitHub, Configuration Guardener is configured entirely through files committed to a `.chainguard/` directory — either in each repository, or once at the organization level in your `.github` repository. This page explains the configuration model that all Guardener features share. For the specific options of each feature, refer to its dedicated page. ## The `.chainguard/` directory Guardener reads its configuration from the `.chainguard/` directory at the root of each repository. Every feature has its own file: ```text .chainguard/ ├── actions.yaml # Hardened Actions ├── images.yaml # Container Image Suggestions and Container Image Migration └── source.yaml # Commit Verification ``` Because these files live in your repository, your configuration is reviewed through pull requests, versioned in git history, and audited like any other code change. ## Features are opt-in per repository Installing the Chainguard App does not change any repository on its own. Each feature stays disabled until you add its configuration file and enable it. This means you can: - Roll a feature out to one repository at a time. - Try a feature in report-only or non-blocking mode before enforcing it. - Keep different repositories on different configurations. A repository with no `.chainguard/` files is unaffected by Guardener even when the app is installed and the organization is linked. ## Available features | Feature | Config file | What it does | | ----------------------------------------------------------------- | -------------------------- | --------------------------------------------------------------------------- | | [Hardened Actions](/chainguard/guardener/github/actions-security/) | `.chainguard/actions.yaml` | Recommends and migrates GitHub Actions to hardened, SHA-pinned equivalents. | | [Container Image Suggestions](/chainguard/guardener/github/image-suggestions/) | `.chainguard/images.yaml` | Recommends Chainguard container images for the images a pull request adds or changes. | | [Container Image Migration](/chainguard/guardener/github/image-migration/) | `.chainguard/images.yaml` | Opens a pull request that replaces the images a repository already uses with Chainguard container images. | | [Commit Verification](/chainguard/guardener/github/commit-verification/) | `.chainguard/source.yaml` | Verifies that commits in a pull request are signed by an authorized signer. | The `.chainguard/` directory also holds configuration for two other products that use the Chainguard App: a `ci.yaml` file or `ci/` directory for [Chainguard Checks](/chainguard/checks/configuration/), and a `develop.yaml` file for [Chainguard Workspaces](/chainguard/workspaces/session-environment/#use-a-chainguarddevelopyaml-file). Additional features will be added over time, each with its own `.chainguard/` file and opt-in configuration. ## Organization-level configuration with the `.github` repository Rather than committing `.chainguard/` files to every repository, you can define configuration once at the organization level. Guardener reads a `.chainguard/` directory from your organization's `.github` repository and applies it as the default for every repository in the organization. The `.github` repository is a special repository that GitHub already uses for organization-wide defaults (such as community health files and default workflows). Guardener follows the same convention: ```text .github/ # your organization's .github repository └── .chainguard/ ├── actions.yaml # org-wide default for Hardened Actions ├── images.yaml # org-wide default for Container Image Suggestions and Container Image Migration └── source.yaml # org-wide default for Commit Verification ``` To use org-level configuration: 1. Create a repository named `.github` in your organization if you don't already have one. 2. Add the Chainguard App to the `.github` repository (or install it on **All repositories**). 3. Commit your `.chainguard/` configuration files to the default branch of the `.github` repository. Once in place, every repository Guardener can access inherits this configuration without needing its own `.chainguard/` files. ### How repository and organization configuration combine Configuration committed directly to a repository takes precedence over the organization-level configuration from the `.github` repository: - A repository with its own `.chainguard/.yaml` file uses that file for the feature, ignoring the org-level default for it. - A repository without a given feature file falls back to the org-level default in the `.github` repository. - A feature that is configured in neither place stays disabled for the repository. This lets you set a baseline for the whole organization and override it only where a specific repository needs different behavior. > **Note:** Guardener reads the org-level configuration from the default branch (that is, `main`) of the `.github` repository, just as it does for per-repository configuration. ## Applying configuration changes To add or update the Guardener configuration: 1. Create or edit the relevant file in `.chainguard/` on a branch. 2. Open a pull request with your change. 3. Merge the pull request. Guardener picks up the new configuration for subsequent events. > **Note:** Guardener uses the repo's default branch (that is, `main`) for its configuration. ## Next steps - **[Hardened Actions](/chainguard/guardener/github/actions-security/)** — Configure `.chainguard/actions.yaml`. - **[Container Image Suggestions](/chainguard/guardener/github/image-suggestions/)** — Configure `.chainguard/images.yaml`. - **[Container Image Migration](/chainguard/guardener/github/image-migration/)** — Add a `migrate` block to `.chainguard/images.yaml`. - **[Commit Verification](/chainguard/guardener/github/commit-verification/)** — Configure `.chainguard/source.yaml`. --- ### Getting started with the Chainguard Agent Skills public registry URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/agent-skills/public-registry.md Last Modified: October 14, 2026 Tags: Agent Skills, Overview Chainguard publishes a curated set of hardened agent skills in a public registry at `skills.cgr.dev/public`. Anyone with `chainctl` can browse and install them — no entitlement and no legal terms required. The Chainguard Agent Skills public registry is pull-only: you can install skills from the registry, but you can't push your own skills to it. This guide walks through the full workflow: listing the available skills, inspecting one, pulling it to audit how Chainguard hardened it, verifying its signature, installing it, and running it with an agent.

Note: Chainguard Agent Skills is in beta.

## Prerequisites To follow this guide, you need `chainctl` **v0.2.376** or later, installed. Refer to our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't have it yet. Unlike a [private Chainguard skills registry](/chainguard/agent-skills/skills-registry/), the public registry requires no entitlement, terms acceptance, or organization membership. You do need a Chainguard account to list and pull skills, but you don't need to be a customer. ## List available skills Sign in, then browse the skills published in Chainguard's public registry with the `list` subcommand. The `--recursive` flag lists skills across every source in the registry. Public skills are namespaced by their upstream source (`public////`), so the recursive listing shows each skill's full path: ```shell chainctl auth login chainctl skills list --group public --recursive ``` ```output SOURCE | NAME | TAGS | UPDATED ----------------|------------------------------------------------------|--------------------------------------------------|-------------- skills.cgr.dev | github.com/github/awesome-copilot/agent-supply-chain | 2201a49dd6f972ac4f685d03361d58c9c9206690, latest | 2 months ago skills.cgr.dev | github.com/github/awesome-copilot/game-engine | cf4347e88c2e40a9aabe5801748ec6bf924c09be, latest | 2 months ago skills.cgr.dev | github.com/github/awesome-copilot/mcp-security-audit | 2201a49dd6f972ac4f685d03361d58c9c9206690, latest | 2 months ago . . . ``` The public registry is large, so a full `--recursive` listing can take a while to return. To browse a single source, scope the `--group` to its path instead: ```shell chainctl skills list --group public/github.com/github/awesome-copilot ``` ```output SOURCE | TYPE | NAME | TAGS | UPDATED ----------------|-------|-----------------------------------|--------------------------------------------------|-------------- skills.cgr.dev | skill | acreadiness-generate-instructions | -- | -- skills.cgr.dev | skill | acreadiness-policy | 2201a49dd6f972ac4f685d03361d58c9c9206690, latest | 2 months ago skills.cgr.dev | skill | add-educational-comments | -- | -- skills.cgr.dev | skill | adobe-illustrator-scripting | -- | -- skills.cgr.dev | skill | agent-owasp-compliance | 2201a49dd6f972ac4f685d03361d58c9c9206690, latest | 3 weeks ago skills.cgr.dev | skill | agent-supply-chain | 2201a49dd6f972ac4f685d03361d58c9c9206690, latest | 2 months ago skills.cgr.dev | skill | agentic-eval | -- | -- skills.cgr.dev | skill | ai-team-orchestration | -- | -- . . . ``` Each hardened skill is tagged with the upstream commit Chainguard hardened it from, and most also carry `latest`. A skill with `--` in the `TAGS` column has no tags, so you can't pull or install it by tag. ## Describe a skill To retrieve a skill's reference, digest, tags, and metadata, use the `describe` subcommand. The output records the upstream source and the exact commit Chainguard hardened from: ```shell chainctl skills describe skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ``` ```output FIELD | VALUE -----------------|-------------------------------------------------------------------------------------------------- Display Name | game-engine Reference | public/github.com/github/awesome-copilot/game-engine Install Name | public-github.com-github-awesome-copilot-game-engine OCI URL | skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest Description | Expert skill for building web-based game engines and games using HTML5, Canvas, WebGL, and ... License | MIT Upstream | github.com/github/awesome-copilot/skills/game-engine Upstream Commit | cf4347e88c2e40a9aabe5801748ec6bf924c09be License Source | LICENSE Tag | cf4347e88c2e40a9aabe5801748ec6bf924c09be Digest | sha256:c8a079466ef2eb8de03847f0a96efe0b5131c628d9164bf7edb887bdd5ed668d Size | 1.2 KB Published | 6 days ago ``` ## Pull a skill to inspect it Where `install` drops a skill straight into your agent's skills directory, `pull` writes the skill's files to a directory you choose so you can inspect them first: ```shell chainctl skills pull skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ./game-engine ``` ```output Skill written to: /home/linky/game-engine ``` Every hardened skill ships with a `HARDENING.md` that records the upstream source, the exact commit Chainguard hardened from, and the outcome of each hardening run: ```shell cat game-engine/HARDENING.md ``` ```output # Hardening report - skill: `github.com/github/awesome-copilot/skills/game-engine` - sha: `cf4347e88c2e40a9aabe5801748ec6bf924c09be` - harden run: 1 (outcome: completed) - SKILL.md modified: true Hardened by the multi-model harden pipeline. The per-model fix plans, cross-model critiques, and the reconciled synthesis are recorded under `notes/harden/run_1/`. The corrected SKILL.md is this version's hardened overlay. ``` The report pins the exact upstream `sha` Chainguard hardened from, the outcome of the run, and whether the hardened overlay changed the skill's `SKILL.md`. Skills are hardened by a multi-model pipeline whose per-model fix plans and reconciled synthesis are recorded for the run, so you can trace exactly what was inspected and changed. ## Verify a skill's signature Chainguard signs every skill in the public registry, and the signature covers the skill's files, including `HARDENING.md`. To check that Chainguard signed a skill and that it hasn't changed since, use the `verify` subcommand: ```shell chainctl skills verify skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ``` ```output FIELD | VALUE -----------|-------------------------------------------------------------------------------------- Status | verified Digest | sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f Identity | https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a Issuer | https://issuer.enforce.dev Log Index | 141100935 ``` To verify a skill as you fetch it, add `--verify` to `pull` or `install`. The command then checks the exact digest it fetches and writes nothing unless the skill is `verified`: ```shell chainctl skills pull --verify skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ./game-engine ``` For what `verify` checks, its other results, and how to verify a skill with Cosign, refer to [Verifying Chainguard Agent Skills signatures](/chainguard/agent-skills/verifying-skills/). ## Install a skill {#install-a-skill} Download and install the skill to make it available to agents on your machine with the `install` subcommand: ```shell chainctl skills install skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ``` This command automatically detects any agents on your machine and places the skill into their relevant directories. The following example output shows the results on a machine where Claude Code is present: ```output Installing github.com/github/awesome-copilot/game-engine AGENT | LOCATION | MODE -------------|--------------------------------------------------------------------|------------------------------------------------------------------------------------ Claude Code | .claude/skills/public-github.com-github-awesome-copilot-game-engine | symlink → ../../.agents/skills/public-github.com-github-awesome-copilot-game-engine ``` The `install` command writes to project-local directories unless you add `--global`. To use the skill in [Chainguard Workspaces](/chainguard/workspaces/overview/) sessions, run it from your repository's top-level directory and commit what it writes: the `.agents/skills/` and `.claude/skills/` entries and the `skills-lock.json` file, as described in [Configure Claude Code in a session](/chainguard/workspaces/coding-agents/#configure-claude-code-in-a-session). ## Run the skill from an agent Load the skill into Claude Code or any MCP-compatible agent. In Claude Code, invoke it by name: ```Agent /game-engine ``` The agent loads the skill and runs it, confirming it installed and loaded correctly end to end. ## Uninstall the skill To remove a skill from your machine, pass its name to the `uninstall` subcommand. Use the skill's install name, which `describe` reports as the `Install Name` field — for this skill, `public-github.com-github-awesome-copilot-game-engine`: ```shell chainctl skills uninstall public-github.com-github-awesome-copilot-game-engine ``` The command prompts for confirmation before removing any files: ```output This will remove skill "public-github.com-github-awesome-copilot-game-engine" from local agent directories. Proceed? Do you want to continue? [y,N]: Uninstalled skill "public-github.com-github-awesome-copilot-game-engine". ``` By default, `uninstall` removes the skill from every agent directory where it's installed. Use the `--agent` flag to remove it from specific agents only, or the `--global` flag to remove it from global directories instead of the current project. Add the `-y` flag to skip the confirmation prompt. ## Command reference | Action | Command | | ----- | ----- | | List skills | `chainctl skills list --group public --recursive` | | Describe a skill | `chainctl skills describe skills.cgr.dev/public////:` | | Verify a skill's signature | `chainctl skills verify skills.cgr.dev/public////:` | | Pull a skill | `chainctl skills pull skills.cgr.dev/public////: ` | | Pull or install only if the signature verifies | Add `--verify` to `pull` or `install` | | Install a skill | `chainctl skills install skills.cgr.dev/public////:` | | Uninstall a skill | `chainctl skills uninstall ` | ## Next steps To publish, install, and run skills scoped to your own organization, refer to [Getting started with the Chainguard Skills Registry](/chainguard/agent-skills/skills-registry/). --- ### Getting started with the Chainguard Spark FIPS container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/ai-and-data/spark-fips.md Last Modified: September 28, 2026 Tags: Chainguard Containers, FIPS Apache Spark is a distributed computing engine for batch processing, stream processing, and machine learning at scale. Organizations subject to federal compliance requirements—including FedRAMP, FISMA, and Department of Defense frameworks—must use FIPS 140-3 validated cryptography for all cryptographic operations in Spark. Chainguard's Spark FIPS container packages Apache Spark with the Bouncy Castle FIPS cryptographic provider, replacing the standard JVM cryptographic modules with NIST-validated equivalents. In FIPS mode, TLS connections require BCFKS-format keystores rather than the standard PKCS12 or JKS formats, and only FIPS-approved cipher suites are permitted. This guide covers two deployment patterns. The first demonstrates how to generate BCFKS keystores and verify that Spark runs correctly with FIPS TLS configured. The second shows how to deploy Spark in Kubernetes cluster mode using the Spark Operator FIPS container, which is the recommended approach for production workloads.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Prerequisites Both examples require Docker and access to the Chainguard private registry. Example 2 also requires `kubectl`, `helm`, and `kind`. - [Docker](https://docs.docker.com/get-docker/) installed and running - Chainguard customer credentials for `cgr.dev/chainguard-private` - For Example 2: [`kubectl`](https://kubernetes.io/docs/tasks/tools/), [`helm`](https://helm.sh/docs/intro/install/), and [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start/) Authenticate your Docker client to the Chainguard registry: ```shell chainctl auth configure-docker ``` ## Understanding BCFKS keystores Standard Java applications use JKS or PKCS12 keystores. Neither format is available in FIPS mode. Bouncy Castle FIPS requires BCFKS (Bouncy Castle Keystore), a proprietary keystore format that complies with FIPS 140-3 storage requirements. The Spark FIPS container pre-installs the Bouncy Castle FIPS libraries at `/usr/share/java/bouncycastle-fips/` and pre-configures the JVM to load the BCFIPS provider. Any `keytool` operation that creates or reads a BCFKS keystore must explicitly specify the BCFIPS provider: ``` -storetype BCFKS -providername BCFIPS -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider -providerpath /usr/share/java/bouncycastle-fips/bc-fips.jar ``` Before running `keytool`, clear `JAVA_TOOL_OPTIONS` and `JDK_JAVA_OPTIONS`, then set them explicitly with the Bouncy Castle module path. The image's default values for those variables include global JVM options that conflict with the keytool BCFIPS provider arguments. ## Example 1: Generate BCFKS keystores and run a local smoke test This example generates BCFKS keystores inside the Spark FIPS container and then runs the built-in SparkPi example to verify that Spark starts correctly with FIPS TLS configured. ### Generate a BCFKS keystore and truststore Create a working directory, then run the keystore generation inside the container as root so it can write to the mounted output volume. The script creates a local certificate authority, a server keystore signed by that CA, and a truststore containing the CA certificate. Set your keystore passwords as environment variables. Use the same values throughout both examples: ```shell export KEY_PASSWORD=$KEY_PASSWORD export TRUSTSTORE_PASSWORD=$TRUSTSTORE_PASSWORD ``` ```shell mkdir -p spark-ssl ``` ```shell docker run --rm --user 0:0 \ -v "$(pwd)/spark-ssl":/out \ -e KEY_PASSWORD=$KEY_PASSWORD \ -e TRUSTSTORE_PASSWORD=$TRUSTSTORE_PASSWORD \ --entrypoint /bin/sh \ cgr.dev/chainguard-private/spark-fips:latest -c ' set -e BC_JAR=/usr/share/java/bouncycastle-fips/bc-fips.jar BC_DIR=/usr/share/java/bouncycastle-fips run_kt() { env -u JAVA_TOOL_OPTIONS -u JDK_JAVA_OPTIONS \ JAVA_TOOL_OPTIONS="--module-path=${BC_DIR} --add-modules=jdk.crypto.ec,jdk.crypto.cryptoki" \ keytool "$@" \ -providername BCFIPS \ -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ -providerpath "${BC_JAR}" } # Create a local CA keypair run_kt -genkeypair \ -alias root-ca \ -dname "CN=Spark CA, OU=Test, O=Test, C=US" \ -keyalg RSA -keysize 2048 -validity 3650 \ -ext bc=ca:true \ -keystore /out/ca-keystore.bcfks -storetype BCFKS \ -storepass "${TRUSTSTORE_PASSWORD}" -keypass "${TRUSTSTORE_PASSWORD}" # Export the CA certificate run_kt -exportcert -rfc \ -alias root-ca \ -keystore /out/ca-keystore.bcfks -storetype BCFKS \ -storepass "${TRUSTSTORE_PASSWORD}" \ -file /out/ca.crt # Create the server keystore run_kt -genkeypair \ -alias spark-server \ -dname "CN=localhost, OU=Test, O=Test, C=US" \ -keyalg RSA -keysize 2048 -validity 825 \ -ext "SAN=dns:localhost,ip:127.0.0.1" \ -keystore /out/keystore.bcfks -storetype BCFKS \ -storepass "${KEY_PASSWORD}" -keypass "${KEY_PASSWORD}" # Create the truststore and import the CA certificate run_kt -importcert -noprompt \ -alias root-ca -file /out/ca.crt \ -keystore /out/truststore.bcfks -storetype BCFKS \ -storepass "${TRUSTSTORE_PASSWORD}" echo "Keystores written to /out:" ls -lh /out/*.bcfks ' ``` After the script completes, the `spark-ssl/` directory contains three files: | File | Purpose | | ------ | --------- | | `keystore.bcfks` | Server certificate and private key | | `truststore.bcfks` | CA certificate used to verify peers | | `ca-keystore.bcfks` | CA keypair used to sign the server certificate (not needed at runtime) | For production deployments, replace the self-signed CA with certificates from your organization's PKI, and store passwords in your secrets management system rather than inline in shell commands. ### Verify the keystore Confirm the keystore was created correctly: ```shell docker run --rm --user 0:0 \ -v "$(pwd)/spark-ssl":/out \ -e KEY_PASSWORD=$KEY_PASSWORD \ --entrypoint /bin/sh \ cgr.dev/chainguard-private/spark-fips:latest -c ' BC_JAR=/usr/share/java/bouncycastle-fips/bc-fips.jar BC_DIR=/usr/share/java/bouncycastle-fips env -u JAVA_TOOL_OPTIONS -u JDK_JAVA_OPTIONS \ JAVA_TOOL_OPTIONS="--module-path=${BC_DIR} --add-modules=jdk.crypto.ec,jdk.crypto.cryptoki" \ keytool -list \ -keystore /out/keystore.bcfks \ -storetype BCFKS \ -providername BCFIPS \ -providerclass org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider \ -providerpath "${BC_JAR}" \ -storepass "${KEY_PASSWORD}" ' ``` The output lists the alias `spark-server` as a `PrivateKeyEntry`, confirming that the keystore is accessible by the BCFIPS provider. ### Run SparkPi with FIPS TLS Run the built-in SparkPi example in local mode with FIPS TLS configured. The command sets both the Spark SSL layer (`spark.ssl.*`) and the JVM JSSE layer (`javax.net.ssl.*`). In local mode both sets of settings apply to the same JVM, but you need both in Kubernetes cluster mode where executor JVMs are separate processes. ```shell docker run --rm --user 0:0 \ -v "$(pwd)/spark-ssl":/ssl \ -e KEY_PASSWORD=$KEY_PASSWORD \ -e TRUSTSTORE_PASSWORD=$TRUSTSTORE_PASSWORD \ cgr.dev/chainguard-private/spark-fips:latest \ spark-submit \ --master local[1] \ --class org.apache.spark.examples.SparkPi \ --conf spark.ssl.enabled=true \ --conf spark.ssl.keyStore=/ssl/keystore.bcfks \ --conf spark.ssl.keyStoreType=BCFKS \ --conf spark.ssl.keyStoreProvider=BCFIPS \ --conf spark.ssl.keyStorePassword=$KEY_PASSWORD \ --conf spark.ssl.trustStore=/ssl/truststore.bcfks \ --conf spark.ssl.trustStoreType=BCFKS \ --conf spark.ssl.trustStoreProvider=BCFIPS \ --conf spark.ssl.trustStorePassword=$TRUSTSTORE_PASSWORD \ --conf "spark.driver.extraJavaOptions=-Dorg.bouncycastle.fips.approved_only=true -Djavax.net.ssl.keyStore=/ssl/keystore.bcfks -Djavax.net.ssl.keyStoreType=BCFKS -Djavax.net.ssl.keyStoreProvider=BCFIPS -Djavax.net.ssl.keyStorePassword=$KEY_PASSWORD -Djavax.net.ssl.trustStore=/ssl/truststore.bcfks -Djavax.net.ssl.trustStoreType=BCFKS -Djavax.net.ssl.trustStoreProvider=BCFIPS -Djavax.net.ssl.trustStorePassword=$TRUSTSTORE_PASSWORD" \ /usr/lib/spark/examples/jars/spark-examples.jar 10 ``` A successful run prints output similar to: ``` Pi is roughly 3.14... ``` ## Example 2: Deploy on Kubernetes with the Spark Operator In Kubernetes cluster mode, Spark creates a driver pod and one or more executor pods. At driver pod startup, the Spark Operator mounts a runtime ConfigMap at `/usr/lib/spark/conf`, which overwrites any files baked into the image at that path. This means you cannot rely on keystores or `spark-defaults.conf` files placed there at image build time. The solution is to store keystores in a Kubernetes Secret and mount it at a path outside the Spark configuration directory. Spark properties, including the TLS configuration, go into the `sparkConf` section of the SparkApplication resource. The operator writes those values directly into the driver and executor configurations at submission time, bypassing the mounted ConfigMap entirely. ### Set up a local Kubernetes cluster Create a kind cluster for testing: ```shell kind create cluster --name spark-fips-test ``` Pull the Chainguard images and load them into the kind cluster. Kind nodes do not share the host Docker image cache, so this step is required for local testing: ```shell docker pull cgr.dev/chainguard-private/spark-fips:latest docker pull cgr.dev/chainguard-private/spark-operator-fips:latest kind load docker-image cgr.dev/chainguard-private/spark-fips:latest \ --name spark-fips-test kind load docker-image cgr.dev/chainguard-private/spark-operator-fips:latest \ --name spark-fips-test ``` For production clusters, configure an image pull secret for `cgr.dev` rather than pre-loading images. Refer to the [Chainguard registry authentication documentation](/chainguard/containers/registry/authenticating/) for details. ### Create namespaces and RBAC Create separate namespaces for the operator and the Spark jobs, and grant the Spark service account the permissions it needs to create driver and executor pods: ```shell kubectl create namespace spark-operator kubectl create namespace spark-jobs kubectl create serviceaccount spark --namespace spark-jobs kubectl create rolebinding spark-role \ --clusterrole=edit \ --serviceaccount=spark-jobs:spark \ --namespace spark-jobs ``` ### Create the keystore secret If you completed Example 1, the `spark-ssl/` directory already contains the generated files. Otherwise, run the keystore generation step from that example first. Store the keystores in a Kubernetes Secret: ```shell kubectl create secret generic spark-ssl-stores \ --from-file=keystore.bcfks=./spark-ssl/keystore.bcfks \ --from-file=truststore.bcfks=./spark-ssl/truststore.bcfks \ --namespace spark-jobs ``` ### Install the Spark Operator Install the Spark Operator using the Chainguard Spark Operator FIPS image. The `spark.jobNamespaces` setting tells the operator to watch the `spark-jobs` namespace for SparkApplication resources: ```shell helm repo add spark-operator https://kubeflow.github.io/spark-operator helm repo update helm install spark-operator spark-operator/spark-operator \ --namespace spark-operator \ --set image.registry=cgr.dev \ --set image.repository=chainguard-private/spark-operator-fips \ --set image.tag=latest \ --set image.pullPolicy=Never \ --set "spark.jobNamespaces[0]=spark-jobs" ``` Wait for the operator to become ready: ```shell kubectl wait deployment/spark-operator-controller \ --namespace spark-operator \ --for=condition=Available \ --timeout=120s ``` ### Run a SparkApplication with FIPS TLS Save the following manifest as `spark-pi-fips.yaml`. It runs the built-in SparkPi example in cluster mode with FIPS TLS configured. The keystores are mounted from the Secret at `/keystores`, and the FIPS TLS settings are passed through `sparkConf` so they apply to both the driver and executor JVMs. ```yaml apiVersion: sparkoperator.k8s.io/v1beta2 kind: SparkApplication metadata: name: spark-pi-fips namespace: spark-jobs spec: type: Scala mode: cluster image: cgr.dev/chainguard-private/spark-fips:latest imagePullPolicy: Never mainClass: org.apache.spark.examples.SparkPi mainApplicationFile: local:///usr/lib/spark/examples/jars/spark-examples.jar arguments: - "10" sparkVersion: "4.1.2" restartPolicy: type: Never volumes: - name: keystores secret: secretName: spark-ssl-stores sparkConf: spark.ssl.enabled: "true" spark.ssl.keyStore: "/keystores/keystore.bcfks" spark.ssl.keyStoreType: "BCFKS" spark.ssl.keyStoreProvider: "BCFIPS" spark.ssl.keyStorePassword: "$KEY_PASSWORD" spark.ssl.trustStore: "/keystores/truststore.bcfks" spark.ssl.trustStoreType: "BCFKS" spark.ssl.trustStoreProvider: "BCFIPS" spark.ssl.trustStorePassword: "$TRUSTSTORE_PASSWORD" spark.kubernetes.driverEnv.JAVA_TOOL_OPTIONS: "--module-path=/usr/share/java/bouncycastle-fips --add-modules=jdk.crypto.ec,jdk.crypto.cryptoki" spark.kubernetes.executorEnv.JAVA_TOOL_OPTIONS: "--module-path=/usr/share/java/bouncycastle-fips --add-modules=jdk.crypto.ec,jdk.crypto.cryptoki" driver: serviceAccount: spark volumeMounts: - name: keystores mountPath: /keystores readOnly: true env: - name: JDK_JAVA_OPTIONS value: >- -Dorg.bouncycastle.fips.approved_only=true -Djavax.net.ssl.keyStore=/keystores/keystore.bcfks -Djavax.net.ssl.keyStoreType=BCFKS -Djavax.net.ssl.keyStoreProvider=BCFIPS -Djavax.net.ssl.keyStorePassword=$KEY_PASSWORD -Djavax.net.ssl.trustStore=/keystores/truststore.bcfks -Djavax.net.ssl.trustStoreType=BCFKS -Djavax.net.ssl.trustStoreProvider=BCFIPS -Djavax.net.ssl.trustStorePassword=$TRUSTSTORE_PASSWORD executor: instances: 1 volumeMounts: - name: keystores mountPath: /keystores readOnly: true env: - name: JDK_JAVA_OPTIONS value: >- -Dorg.bouncycastle.fips.approved_only=true -Djavax.net.ssl.keyStore=/keystores/keystore.bcfks -Djavax.net.ssl.keyStoreType=BCFKS -Djavax.net.ssl.keyStoreProvider=BCFIPS -Djavax.net.ssl.keyStorePassword=$KEY_PASSWORD -Djavax.net.ssl.trustStore=/keystores/truststore.bcfks -Djavax.net.ssl.trustStoreType=BCFKS -Djavax.net.ssl.trustStoreProvider=BCFIPS -Djavax.net.ssl.trustStorePassword=$TRUSTSTORE_PASSWORD ``` The `JDK_JAVA_OPTIONS` value in the driver and executor sections overrides the image's default, which sets only the trust store type. The full value here adds FIPS approved-only mode and JSSE settings for both the keystore and truststore. The `JAVA_TOOL_OPTIONS` override in `sparkConf` extends the image's default module path with the EC and CRYPTOKI modules that Bouncy Castle FIPS requires at runtime. For production deployments, do not hardcode passwords in the manifest. Store them in a Kubernetes Secret and reference them via `valueFrom.secretKeyRef` in the `env` sections. `sparkConf` values like `spark.ssl.keyStorePassword` cannot reference environment variables directly. The production pattern is to use an init container or a wrapper script that generates `spark-defaults.conf` from environment variables before the Spark entrypoint runs. Apply the manifest: ```shell kubectl apply -f spark-pi-fips.yaml ``` ### Verify the job Watch the `spark-jobs` namespace until the driver pod completes: ```shell kubectl get pods --namespace spark-jobs --watch ``` Once the driver pod shows `Completed`, retrieve the Pi estimate from its logs: ```shell POD=$(kubectl get pods --namespace spark-jobs \ -l spark-role=driver \ -o jsonpath='{.items[-1:].metadata.name}') kubectl logs "$POD" --namespace spark-jobs | grep "Pi is" ``` A successful run prints output similar to: ``` Pi is roughly 3.14... ``` You can also confirm that FIPS approved-only mode was active by checking the `JDK_JAVA_OPTIONS` line near the top of the driver logs: ```shell kubectl logs "$POD" --namespace spark-jobs | grep "JDK_JAVA_OPTIONS" ``` The output includes `-Dorg.bouncycastle.fips.approved_only=true`. ### Clean up Delete the SparkApplication and the kind cluster when you're done: ```shell kubectl delete -f spark-pi-fips.yaml kind delete cluster --name spark-fips-test ``` --- ### Find a matching Chainguard image using the API URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-tools/find-match.md Last Modified: September 28, 2026 Tags: Chainguard Images, Migration, SBOM, API This guide walks through calling the [Chainguard Image Matcher API](/platform/api/spec-api-v1/#tag/imagematcher) to find the best Chainguard equivalent for an existing container image. It assumes you already have an SBOM for the image you want to migrate. For background on how the matcher works and how it scores recommendations, refer to [Image Matcher overview](/chainguard/containers/migration/migration-tools/image-matcher/). ## Prerequisites Before getting started, you will need: - An SBOM for your source image in CycloneDX JSON format, with `purl` values on each component. - SBOMs produced by [Syft](https://github.com/anchore/syft), Trivy, `docker sbom`, or cdxgen all work. - `chainctl` [installed and authenticated](/platform/api/authentication/). - `jq` installed. - Your Chainguard organization UID. - Retrieve it from **Settings > General** in the [Chainguard Console](https://console.chainguard.dev/org/-/settings/general), or run `chainctl iam groups list`. ## Step 1: Reshape your SBOM for the API The Image Matcher API does not accept a raw CycloneDX document. It expects an `sbomComponents` array containing only distribution packages, with a small number of fields per component. Save the following as `build-request.jq`: ```jq { parent_id: $org_id, dist_name: $dist, arch: $arch, sbom: { sbomComponents: [ .components[] | select(.purl != null and (.purl | startswith($purl_prefix))) | { type: .type, purl: .purl, name: .name, cpe: .cpe, bomref: ."bom-ref", version: .version } ] } } ``` This filter does three things: it selects only distribution packages by PURL prefix, drops irrelevant CycloneDX fields (licenses, properties, publisher, etc.) that the API ignores, and renames `bom-ref` to `bomref` to match the API's expected field name. Run it with values for your source distribution: ```bash export ORG_ID="" jq \ --arg org_id "$ORG_ID" \ --arg dist "debian" \ --arg arch "x86_64" \ --arg purl_prefix "pkg:deb/" \ -f build-request.jq \ your-image.cdx.json \ > request.json ``` Use the following table to match `dist`, `arch`, and `purl_prefix` to your source image: | Distribution | `dist` | `purl_prefix` | |-------------------|----------------|---------------| | Debian | `debian` | `pkg:deb/` | | Ubuntu | `ubuntu` | `pkg:deb/` | | Alpine | `alpine` | `pkg:apk/` | | Red Hat / RHEL | `redhat` | `pkg:rpm/` | | SUSE | `suse` | `pkg:rpm/` | | Amazon Linux | `amazon-linux` | `pkg:rpm/` | Pass `dist_version` as an additional `--arg` if you know the exact version (for example, `12` for Debian 12, `24.04` for Ubuntu 24.04). This is optional, but it narrows the candidate set and improves results. ### Example request body The reshaped request body for a Debian nginx image looks like the following: ```json { "parent_id": "", "dist_name": "debian", "arch": "x86_64", "sbom": { "sbomComponents": [ { "type": "library", "purl": "pkg:deb/debian/nginx@1.28.0-1~bookworm?arch=amd64&distro=debian-12", "name": "nginx", "cpe": "cpe:2.3:a:nginx:nginx:1.28.0-1~bookworm:*:*:*:*:*:*:*", "bomref": "pkg:deb/debian/nginx@1.28.0-1~bookworm?arch=amd64&distro=debian-12&package-id=...", "version": "1.28.0-1~bookworm" }, { "type": "library", "purl": "pkg:deb/debian/libssl3@3.0.17-1~deb12u3?arch=amd64&distro=debian-12&upstream=openssl", "name": "libssl3", "cpe": "cpe:2.3:a:libssl3:libssl3:3.0.17-1~deb12u3:*:*:*:*:*:*:*", "bomref": "pkg:deb/debian/libssl3@3.0.17-1~deb12u3?arch=amd64&distro=debian-12&package-id=...", "version": "3.0.17-1~deb12u3" } ] } } ``` ## Step 2: Call the API Run the following: ```bash curl -sS \ -H "Authorization: Bearer $(chainctl auth token)" \ -H "Content-Type: application/json" \ -X POST "https://console-api.enforce.dev/image-recommendation/v1/match" \ -d @request.json ``` ## Step 3: Interpret the results The response contains a ranked `images` array, ordered from highest to lowest `probabilityScore`. For example: ```json { "images": [ { "imageRef": "cgr.dev//nginx:latest", "probabilityScore": 98.57, "satisfiedCount": 6, "totalRequired": 86, "coverage": 0.07, "satisfiedPackages": [...], "missingPackages": [...], "extraPackages": 14 } ], "totalExternalPackages": 143, "requiredApks": ["nginx-mainline", "libgcc", "glibc", ...], "unmatchedExternalPkgs": ["adduser", "apt", "base-files", ...] } ``` Key fields to check: - **`probabilityScore`**: the matcher's confidence estimate (0–100). A score of 90 or above is a strong match. Scores in the 50–70 range warrant human review. - **`imageRef`**: the full OCI reference for the recommended image. Use the final path segment and tag (for example, `nginx:latest`) when displaying this to users or comparing against the [Chainguard image catalog](https://images.chainguard.dev/). Keep the full reference if you intend to pull the image. - **`missingPackages`**: packages from your SBOM that are not present in the recommended image. Some missing packages are expected: Chainguard images are intentionally minimal. Review this list to identify any packages your application requires at runtime. - **`unmatchedExternalPkgs`**: source packages the matcher could not map to any Chainguard APK. A long list is normal for general-purpose distributions like Debian, where many entries are build-time tools or base-image scaffolding. ### Low `coverage` with a high `probabilityScore` You may notice that `coverage` (the fraction of required APKs present in the image) can be low even when `probabilityScore` is high. This is expected behavior. Package weights are not uniform: the package that defines an image's purpose carries a weight orders of magnitude larger than common shared dependencies. A `probabilityScore` of 98 with `coverage` of 0.07 means the matcher is highly confident it has found the right image, even though many low-level library names differ between your source distribution and Chainguard's APK catalog. Learn more about the probability score in the [Image Matcher overview](/chainguard/containers/migration/migration-tools/image-matcher/#understanding-the-probability-score). ### Reviewing multiple candidates The API returns up to 10 candidates by default. For common stacks the top result is typically correct, but it is good practice to review the second and third results, particularly when: - The top score is below 90. - The top two scores are within a few points of each other. - The source image is not a well-known public image. To request more or fewer candidates, add a `count` field to your request body. To suppress weak matches, add a `threshold` field (default: `50.0`). --- ### Package and image name mappings URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/reference/package-name-mappings.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Packages When migrating to Chainguard Containers, you may notice that some package and image names differ from their upstream counterparts. This guide explains why these mappings exist and provides a comprehensive reference of how Chainguard maps image and package names to our container ecosystem. ## Why Chainguard remaps package names Different Linux distributions often use different names for the same software. For example, Debian calls its C compiler package `build-essential`, while Alpine calls the equivalent package `build-base` and Fedora uses `gcc` and related packages. Chainguard Containers standardizes these names to provide consistency regardless of which distribution you're migrating from. In some cases, upstream package names can be ambiguous or misleading. To create more clarity, Chainguard maps `netcat-traditional` to `netcat-openbsd` to specify the implementation, and `google-chrome-stable` to `chromium` to reflect the open source base. Some distributions split a single piece of software into many sub-packages, while others bundle functionality together. Chainguard's package naming reflects a more streamlined approach that reduces the number of packages you need to install, minimizing the attack surface by avoiding unnecessary package splits. ### Container image name conventions For container images, Chainguard follows naming conventions that prioritize: - **Specificity**: Instead of generic names, we use descriptive names (for example, `argocd-repo-server` instead of just `argocd`) - **Consistency**: All our images follow similar naming patterns - **Discoverability**: Names that clearly indicate the software's purpose ### Using package mappings When you're using Chainguard's [Dockerfile Converter (dfc)](/chainguard/containers/migration/migration-tools/dockerfile-conversion/), these mappings are applied automatically. The tool recognizes upstream package and image names and translates them to their Chainguard equivalents. For manual migrations, you can reference the following tables to find the correct package or image name you need. ## Package name mappings ### Debian/Ubuntu packages The following table shows how Debian and Ubuntu package names (used with `apt`, `apt-get`) map to Chainguard package names (used with `apk`).

Debian Package Name Chainguard/Wolfi Package Name(s)
awscli aws-cli
build-essential build-base
default-mysql-client mysql-client
fonts-liberation font-liberation
fonts-open-sans font-opensans
fuse fuse2 , fuse-common
g++ gcc
gettext-base gettext
git-delta delta
gnupg2 gnupg
google-chrome-stable chromium
libbz2-dev bzip2-dev
libc-client-dev glibc-dev
libc6-dev glibc-dev
libcairo2 cairo
libcups2 cups-libs
libcurl4-openssl-dev curl-dev
libdbd-mysql-perl perl-dbd-mysql
libdbi-perl perl-dbi
libfreetype6-dev freetype-dev
libgssapi-krb5-2 krb5-libs
libheif1 libheif
libicu-dev icu-dev
libjpeg-dev libjpeg-turbo-dev
libjpeg62-turbo libjpeg-turbo
libkrb5-dev krb5-dev
liblzma-dev xz-dev
libmagic1 libmagic , libmagic-dev
libmariadb-dev mariadb-dev
libmariadb-dev-compat mariadb-connector-c-dev
libncurses5-dev ncurses
libncursesw5-dev ncurses-dev
libp11-kit0 p11-kit
libpq-dev postgresql-dev
libpq5 libpq
librdkafka1 librdkafka
libreadline-dev readline
librtmp1 librtmp
libsqlite3-dev sqlite-libs
libssl-dev libssl3
libvshadow-utils shadow
libxi6 libxi
libxmlsec1 xmlsec
libxmlsec1-dev xmlsec-dev
libxmlsec1-openssl xmlsec-openssl
libzip5 libzip
locales glibc-locales
lsb-release lsb-release-minimal
netbase wolfi-baselayout
netcat-traditional netcat-openbsd
pcre2-utils pcre2
pkg-config pkgconf
postgresql-client-14 postgresql-14-client
postgresql-contrib postgresql-14-contrib
protobuf-compiler protobuf-c-compiler
python3 python-3
python3-openssl py3-pyopenssl
python3-pip py3-pip
python3-virtualenv py3-virtualenv
python3-wheel py3-wheel
s3fs s3fs-fuse
software-properties-common (not needed - functionality included in base image)
sqlite3 sqlite
ssh openssh-client , openssh-server
uuid-runtime util-linux-misc
watch procps
xfonts-utils font-util , mkfontscale , bdftopcf
xz-utils xz
zlib1g-dev zlib-dev
### Fedora/RedHat/UBI packages The following table shows how Fedora, RedHat, and UBI package names (used with `yum`, `dnf`, `microdnf`) map to Chainguard package names.

Fedora Package Name Chainguard/Wolfi Package Name(s)
apr apr-util
apr-devel apr-util-dev
apr-util-devel apr-util-dev
aspnetcore-runtime-10.0 aspnet-10-runtime
aspnetcore-runtime-8.0 aspnet-8-runtime
aspnetcore-runtime-9.0 aspnet-9-runtime
aspnetcore-targeting-pack-10.0 aspnet-10-targeting-pack
aspnetcore-targeting-pack-8.0 aspnet-8-targeting-pack
aspnetcore-targeting-pack-9.0 aspnet-9-targeting-pack
audit-libs audit
augeas-libs augeas
avahi-libs avahi
babel py3-babel
bind-license bind
bind-utils bind-tools
brotli-devel brotli-dev
bsdtar libarchive-tools
bzip2-devel bzip2-dev
bzip2-libs bzip2
capstone capstone-5
cargo cargo-c
cargo-doc cargo-c
clang-devel clang-17-dev
clang-libs clang
compat-openssl11 openssl
composefs-libs composefs
container-tools containerd
containernetworking-plugins cni-plugins
containers-common-extra containers-common
coreutils-common coreutils
cpp gcc
criu crac-criu
criu-libs crac-criu
cronie-anacron cronie
cryptsetup-libs cryptsetup
cyrus-sasl-devel cyrus-sasl-dev
cyrus-sasl-gssapi cyrus-sasl
cyrus-sasl-lib cyrus-sasl
cyrus-sasl-plain cyrus-sasl
dbus-daemon dbus
dbus-tools dbus
device-mapper-devel device-mapper
device-mapper-event device-mapper-event-libs
dotnet-runtime-10.0 dotnet-10-runtime
dotnet-runtime-8.0 dotnet-8-runtime
dotnet-runtime-9.0 dotnet-9-runtime
dotnet-sdk-10.0 dotnet-10-sdk
dotnet-sdk-8.0 dotnet-8-sdk
dotnet-sdk-9.0 dotnet-9-sdk
dotnet-sdk-aot-10.0 dotnet-10-aot
dotnet-sdk-aot-9.0 dotnet-9-aot
dotnet-targeting-pack-10.0 dotnet-10-targeting-pack
dotnet-targeting-pack-8.0 dotnet-8-targeting-pack
dotnet-targeting-pack-9.0 dotnet-9-targeting-pack
dracut dracut-ng
elfutils-debuginfod-client elfutils
elfutils-libelf elfutils
elfutils-libs elfutils
enchant enchant2
expat-devel expat-dev
expect py3-pexpect
fftw-devel fftw-dev
fftw-libs fftw
fftw-libs-double fftw-double-libs
fftw-libs-single fftw-single-libs
file-libs file
flac-libs flac
freetype-devel freetype-dev
fribidi-devel fribidi-dev
fuse fuse3
fuse-devel fuse2
fuse-libs fuse2
fuse3-devel fuse3-dev
gcc-c++ gcc
gcc-gfortran gfortran
gcc-toolset-12 gcc-12
gcc-toolset-12-annobin-annocheck gcc-12
gcc-toolset-12-annobin-docs gcc-12
gcc-toolset-12-annobin-plugin-gcc gcc-12
gcc-toolset-12-binutils binutils
gcc-toolset-12-binutils-devel gcc-12
gcc-toolset-12-binutils-gold gcc-12
gcc-toolset-12-build gcc-12
gcc-toolset-12-dwz gcc-12
gcc-toolset-12-gcc gcc-12
gcc-toolset-12-gcc-c++ gcc-12
gcc-toolset-12-gcc-gfortran gcc-12
gcc-toolset-12-gcc-plugin-annobin gcc-12
gcc-toolset-12-gcc-plugin-devel gcc-12
gcc-toolset-12-gdb gdb
gcc-toolset-12-libasan-devel gcc-12
gcc-toolset-12-libatomic-devel gcc-12
gcc-toolset-12-libgccjit gcc-12
gcc-toolset-12-libgccjit-devel gcc-12
gcc-toolset-12-libgccjit-docs gcc-12
gcc-toolset-12-libitm-devel gcc-12
gcc-toolset-12-liblsan-devel gcc-12
gcc-toolset-12-libquadmath-devel gcc-12
gcc-toolset-12-libstdc++-devel gcc-12
gcc-toolset-12-libstdc++-docs gcc-12
gcc-toolset-12-libtsan-devel gcc-12
gcc-toolset-12-libubsan-devel gcc-12
gcc-toolset-12-offload-nvptx gcc-12
gcc-toolset-12-runtime gcc-12
gcc-toolset-13 gcc-13
gcc-toolset-13-annobin-annocheck gcc-13
gcc-toolset-13-annobin-docs gcc-13
gcc-toolset-13-annobin-plugin-gcc gcc-13
gcc-toolset-13-binutils binutils
gcc-toolset-13-binutils-devel gcc-13
gcc-toolset-13-binutils-gold gcc-13
gcc-toolset-13-dwz gcc-13
gcc-toolset-13-gcc gcc-13
gcc-toolset-13-gcc-c++ gcc-13
gcc-toolset-13-gcc-gfortran gcc-13
gcc-toolset-13-gcc-plugin-annobin gcc-13
gcc-toolset-13-gcc-plugin-devel gcc-13
gcc-toolset-13-gdb gdb
gcc-toolset-13-libasan-devel gcc-13
gcc-toolset-13-libatomic-devel gcc-13
gcc-toolset-13-libgccjit gcc-13
gcc-toolset-13-libgccjit-devel gcc-13
gcc-toolset-13-libitm-devel gcc-13
gcc-toolset-13-liblsan-devel gcc-13
gcc-toolset-13-libquadmath-devel gcc-13
gcc-toolset-13-libstdc++-devel gcc-13
gcc-toolset-13-libstdc++-docs gcc-13
gcc-toolset-13-libtsan-devel gcc-13
gcc-toolset-13-libubsan-devel gcc-13
gcc-toolset-13-offload-nvptx gcc-13
gcc-toolset-13-runtime gcc-13
gcc-toolset-14 gcc-14
gcc-toolset-14-annobin-annocheck gcc-14
gcc-toolset-14-annobin-docs gcc-14
gcc-toolset-14-annobin-plugin-gcc gcc-14
gcc-toolset-14-binutils binutils
gcc-toolset-14-binutils-devel gcc-14
gcc-toolset-14-binutils-gold gcc-14
gcc-toolset-14-dwz gcc-14
gcc-toolset-14-gcc gcc-14
gcc-toolset-14-gcc-c++ gcc-14
gcc-toolset-14-gcc-gfortran gfortran-14
gcc-toolset-14-gcc-plugin-annobin gcc-14
gcc-toolset-14-gcc-plugin-devel gcc-14
gcc-toolset-14-libasan-devel gcc-14
gcc-toolset-14-libatomic-devel gcc-14
gcc-toolset-14-libgccjit gcc-14
gcc-toolset-14-libgccjit-devel gcc-14
gcc-toolset-14-libitm-devel gcc-14
gcc-toolset-14-liblsan-devel gcc-14
gcc-toolset-14-libquadmath-devel gcc-14
gcc-toolset-14-libstdc++-devel gcc-14
gcc-toolset-14-libstdc++-docs gcc-14
gcc-toolset-14-libtsan-devel gcc-14
gcc-toolset-14-libubsan-devel gcc-14
gcc-toolset-14-offload-nvptx gcc-14
gcc-toolset-14-runtime gcc-14
gcc-toolset-15 gcc
gcc-toolset-15-binutils binutils
gcc-toolset-15-gcc gcc
gcc-toolset-15-libgccjit libgccjit
gd-devel gd-dev
gdb-headless gdb
gdbm-libs gdbm
gettext-libs gettext
git-core git
glib2 glib
glib2-devel glib-dev
glibc-devel glibc-dev
glibc-headers glibc-dev
gmp-devel gmp-dev
go-toolset go-1.25
golang golangci-lint
golang-docs golangci-lint
gpgme-devel gpgme-dev
graphite2-devel graphite2-dev
haproxy haproxy-3.0
harfbuzz-devel harfbuzz-dev
httpd apache2
httpd-core apache2-config
httpd-devel apache2-dev
httpd-tools apache2-utils
hunspell-en hunspell-dictionary-en
hunspell-en-US hunspell-dictionary-en
info texinfo
ipset-libs ipset
iptables-libs iptables
jasper-libs jasper
java-1.8.0-openjdk openjdk-8
java-1.8.0-openjdk-devel openjdk-8
java-1.8.0-openjdk-headless openjdk-8
java-11-openjdk openjdk-11
java-11-openjdk-devel openjdk-11-default-jdk
java-11-openjdk-headless openjdk-11
java-11-openjdk-jmods openjdk-11-jmods
java-17-openjdk openjdk-17
java-17-openjdk-devel openjdk-17-default-jdk
java-17-openjdk-headless openjdk-17
java-17-openjdk-jmods openjdk-17-jmods
java-21-openjdk openjdk-21
java-21-openjdk-devel openjdk-21-default-jdk
java-21-openjdk-headless openjdk-21
java-21-openjdk-jmods openjdk-21-jmods
jbig2dec-libs jbig2dec
kbd-misc kbd
kernel-headers linux-headers
keyutils-libs-devel keyutils-libs
krb5-devel krb5-dev
lcms2-devel lcms2-dev
libICE libice
libSM libsm
libX11 libx11
libX11-common libxt
libX11-devel libx11-dev
libXScrnSaver libxscrnsaver
libXau libxau
libXau-devel libxau-dev
libXaw libxaw
libXcomposite libxcomposite
libXcursor libxcursor
libXdamage libxdamage
libXext libxext
libXfixes libxfixes
libXft libxft
libXft-devel libxft-dev
libXi libxi
libXinerama libxinerama
libXmu libxmu
libXpm libxpm
libXpm-devel libxpm-dev
libXrandr libxrandr
libXrender libxrender
libXrender-devel libxrender-dev
libXt libxt
libXtst libxtst
libXv libxv
libXxf86vm libxxf86vm
libacl acl
libassuan-devel libassuan-dev
libattr attr
libblkid-devel libblkid
libbrotli brotli
libcap-devel libcap-dev
libcap-ng-devel libcap-ng-dev
libcom_err-devel libcom_err
libcurl curl
libcurl-devel curl-dev
libdb db
libdb-devel db-dev
libdb-utils db-utils
libedit-devel libedit-dev
libevent-devel libevent-dev
libexif-devel libexif-dev
libffi-devel libffi-dev
libgcrypt-devel libgcrypt-dev
libgpg-error-devel libgpg-error-dev
libgs libgsf
libicu icu
libicu-devel icu-dev
libjpeg-turbo-devel libjpeg-turbo-dev
libmnl-devel libmnl-dev
libmount-devel libmount
libmpc mpc
libmpc-devel mpc-dev
libnghttp2 nghttp2
libnghttp2-devel nghttp2-dev
libnsl2 libnsl
libpcap-devel libpcap-dev
libpciaccess-devel libpciaccess-dev
libpkgconf pkgconf
libpng-devel libpng-dev
libpq libpq-18
libpq-devel postgresql-devel
libquadmath-devel libquadmath
librdkafka-devel librdkafka-dev
librsvg2 librsvg
libseccomp-devel libseccomp-dev
libsecret-devel libsecret-dev
libselinux-devel libselinux-dev
libselinux-utils libselinux
libsepol-devel libsepol-dev
libserf serf
libss libssp
libssh-devel libssh-dev
libstdc++-devel libstdc++-dev
libtalloc talloc
libtdb tdb
libtiff tiff
libtiff-devel tiff-dev
libtool-ltdl libtool
liburing-devel liburing-dev
libuuid-devel libuuid
libverto-devel libverto-dev
libwebp-devel libwebp-dev
libxcb-devel libxcb-dev
libxcrypt-devel libxcrypt-dev
libxml2-devel libxml2-dev
libxslt-devel libxslt-dev
libyaml yaml
libyaml-devel yaml-dev
libzip-devel libzip
libzstd zstd
libzstd-devel zstd-dev
lld lld-21
llvm-devel llvm-dev
llvm-doc llvm
llvm-libs llvm
llvm-toolset llvm-21
lmdb-libs lmdb
lua lua5.4
lua-libs lua5.4-libs
lvm2-libs lvm2
lz4-devel lz4-dev
lz4-libs lz4
mariadb-common mariadb
mariadb-connector-c-devel mariadb-connector-c-dev
maven maven-3.8
mpfr-devel mpfr-dev
mysql-common mysql
mysql-libs mariadb-connector-c
ncurses-c++-libs ncurses
ncurses-devel ncurses-dev
ncurses-libs ncurses
net-snmp-utils net-snmp
nginx-core nginx-mainline
nginx-mod-http-image-filter nginx-mainline-mod-http_image_filter
nginx-mod-http-perl nginx-mainline-mod-http_perl
nginx-mod-http-xslt-filter nginx-mainline-mod-http_xslt_filter
nginx-mod-mail nginx-mainline-mod-mail
nginx-mod-stream nginx-mainline-mod-stream
nodejs-docs nodejs
nodejs-libs nodejs
nspr libnspr
nss-tools nss
nss_wrapper-libs nss_wrapper
numactl-libs numactl
openblas-devel openblas-dev
openexr-libs openexr
openjpeg2 openjpeg
openjpeg2-devel openjpeg-dev
openjpeg2-tools openjpeg-tools
openldap-devel openldap-dev
openssh-clients openssh-client
openssl-devel openssl-dev
openssl-libs openssl
ostree-libs ostree
pam linux-pam
pcre-cpp pcre
pcre-devel pcre-dev
pcre-utf16 pcre
pcre-utf32 pcre
pcre2-devel pcre2-dev
pcre2-utf16 pcre2
pcre2-utf32 pcre2
perl-Algorithm-Diff perl-algorithm-diff
perl-App-cpanminus perl-app-cpanminus
perl-B perl-b-hooks-endofscope
perl-Benchmark benchmark
perl-CPAN perl-cpan-distnameinfo
perl-CPAN-DistnameInfo perl-cpan-distnameinfo
perl-CPAN-Meta perl-cpan-meta-requirements
perl-CPAN-Meta-Requirements perl-cpan-meta-requirements
perl-Clone perl-clone
perl-DBD-MySQL perl-dbd-mysql
perl-DBI perl-dbi
perl-Devel-PPPort perl-devel-ppport
perl-Digest perl-digest-md5
perl-Digest-MD5 perl-digest-md5
perl-Encode perl-encode-locale
perl-Encode-Locale perl-encode-locale
perl-Encode-devel perl-encode-locale
perl-ExtUtils-Install perl-extutils-installpaths
perl-ExtUtils-MakeMaker perl-extutils-makemaker-cpanfile
perl-File-Copy perl-file-copy-recursive
perl-File-Listing perl-file-listing
perl-File-Which perl-file-which
perl-File-pushd perl-file-pushd
perl-Git git
perl-HTML-Parser perl-html-parser
perl-HTML-Tagset perl-html-tagset
perl-HTTP-Cookies perl-http-cookies
perl-HTTP-Date perl-http-date
perl-HTTP-Message perl-http-message
perl-HTTP-Negotiate perl-http-negotiate
perl-HTTP-Tiny perl-http-tinyish
perl-IO perl-io-gzip
perl-IO-HTML perl-io-html
perl-IO-Socket-SSL perl-io-socket-ssl
perl-Importer perl-importer
perl-LWP-MediaTypes perl-lwp-mediatypes
perl-LWP-Protocol-https perl-lwp-protocol-https
perl-MRO-Compat perl-mro-compat
perl-Module-Build perl-module-build
perl-Module-CPANfile perl-module-cpanfile
perl-Mozilla-CA perl-mozilla-ca
perl-Net perl-net-snmp
perl-Net-HTTP perl-net-http
perl-Net-SSLeay perl-net-ssleay
perl-Parse-PMFile perl-parse-pmfile
perl-String-ShellQuote perl-string-shellquote
perl-Sub-Exporter perl-sub-exporter-progressive
perl-Term-Table perl-term-table
perl-Test perl-test-pod
perl-Test-Simple perl-test-simple
perl-Text-Diff perl-text-diff
perl-Time perl-time-hires
perl-Time-HiRes perl-time-hires
perl-Try-Tiny perl-try-tiny
perl-URI perl-uri
perl-WWW-RobotRules perl-www-robotrules
perl-YAML yaml
perl-devel perl-dev
perl-interpreter perl
perl-less less
perl-lib perl-libwww
perl-libnet libnet
perl-libs perl
perl-macros perl
perl-mro perl-mro-compat
php-pecl-apcu php-apcu
php-pecl-zip php-zip
podman-docker podman
podman-remote podman
postgresql postgresql-18
procps-ng procps
pybind11-devel py3-pybind11
python3 python-3.9
python3-Cython py3-cython
python3-PyMySQL py3-pymysql
python3-appdirs py3-appdirs
python3-argcomplete py3-argcomplete
python3-attrs py3-attrs
python3-audit py3-audit
python3-augeas py3-augeas
python3-babel py3-babel
python3-bind bind
python3-cairo py3-cairo
python3-cffi py3-cffi
python3-clang py3-clang
python3-configobj py3-configobj
python3-cryptography py3-cryptography
python3-debug py3-debugpy
python3-devel python-3.9
python3-distro py3-distro
python3-dmidecode dmidecode
python3-docutils py3-docutils
python3-ethtool ethtool
python3-gssapi py3-gssapi
python3-imagesize py3-imagesize
python3-imath py3-imath
python3-iniconfig py3-iniconfig
python3-jinja2 py3-jinja2
python3-jmespath py3-jmespath
python3-jsonpatch py3-jsonpatch
python3-jsonpointer py3-jsonpointer
python3-jsonschema py3-jsonschema
python3-jwcrypto py3-jwcrypto
python3-kmod kmod
python3-ldap py3-ldap
python3-lesscpy py3-lesscpy
python3-libmount libmount
python3-libs python-3.9
python3-libselinux libselinux
python3-libsemanage libsemanage
python3-libxml2 libxml2
python3-lxml py3-lxml
python3-mako py3-mako
python3-markdown py3-markdown
python3-markupsafe py3-markupsafe
python3-net-snmp net-snmp
python3-netaddr py3-netaddr
python3-netifaces py3-netifaces
python3-nftables nftables
python3-oauthlib py3-oauthlib
python3-packaging py3-packaging
python3-pexpect py3-pexpect
python3-pip py3-pip
python3-pluggy py3-pluggy
python3-ply py3-ply
python3-policycoreutils policycoreutils
python3-prettytable py3-prettytable
python3-protobuf py3-protobuf
python3-psutil py3-psutil
python3-psycopg2 py3-psycopg2
python3-ptyprocess py3-ptyprocess
python3-pyasn1 py3-pyasn1
python3-pyasn1-modules py3-pyasn1-modules
python3-pybind11 py3-pybind11
python3-pycparser py3-pycparser
python3-pycurl py3-pycurl
python3-pyelftools py3-pyelftools
python3-pygments py3-pygments
python3-pyparsing py3-pyparsing
python3-pyrsistent py3-pyrsistent
python3-pyserial py3-pyserial
python3-pytest py3-pytest
python3-pytest-timeout py3-pytest-timeout
python3-pytz py3-pytz
python3-pyyaml py3-pyyaml
python3-requests-oauthlib py3-requests-oauthlib
python3-resolvelib py3-resolvelib
python3-rrdtool rrdtool
python3-ruamel-yaml py3-ruamel-yaml
python3-ruamel-yaml-clib py3-ruamel-yaml-clib
python3-scipy py3-scipy
python3-snowballstemmer py3-snowballstemmer
python3-talloc py3-talloc
python3-toml py3-toml
python3-tomli py3-tomli
python3-unbound unbound
python3-wcwidth py3-wcwidth
python3-wheel py3-wheel
python3.11 python-3.11
python3.11-Cython py3.11-cython
python3.11-PyMySQL py3.11-pymysql
python3.11-attrs py3.11-attrs
python3.11-cffi py3.11-cffi
python3.11-charset-normalizer py3.11-charset-normalizer
python3.11-cryptography py3.11-cryptography
python3.11-devel python-3.9
python3.11-idna py3.11-idna
python3.11-iniconfig py3.11-iniconfig
python3.11-libs python-3.9
python3.11-lxml py3.11-lxml
python3.11-packaging py3.11-packaging
python3.11-pip py3.11-pip
python3.11-pip-wheel py3-pip-wheel
python3.11-pluggy py3.11-pluggy
python3.11-ply py3.11-ply
python3.11-psycopg2 py3.11-psycopg2
python3.11-pybind11 py3.11-pybind11
python3.11-pycparser py3.11-pycparser
python3.11-pyparsing py3.11-pyparsing
python3.11-pytest py3.11-pytest
python3.11-pyyaml py3.11-pyyaml
python3.11-requests py3.11-requests
python3.11-scipy py3.11-scipy
python3.11-setuptools py3.11-setuptools
python3.11-setuptools-rust py3.11-setuptools-rust
python3.11-setuptools-wheel py3-setuptools-wheel
python3.11-six py3.11-six
python3.11-urllib3 py3.11-urllib3
python3.11-wheel py3.11-wheel
python3.12 python-3.12
python3.12-Cython py3.12-cython
python3.12-PyMySQL py3.12-pymysql
python3.12-cffi py3.12-cffi
python3.12-charset-normalizer py3.12-charset-normalizer
python3.12-cryptography py3.12-cryptography
python3.12-devel python-3.9
python3.12-flit-core py3.12-flit-core
python3.12-idna py3.12-idna
python3.12-iniconfig py3.12-iniconfig
python3.12-libs python-3.9
python3.12-lxml py3.12-lxml
python3.12-packaging py3.12-packaging
python3.12-pip py3.12-pip
python3.12-pip-wheel py3-pip-wheel
python3.12-pluggy py3.12-pluggy
python3.12-ply py3.12-ply
python3.12-psycopg2 py3.12-psycopg2
python3.12-pybind11 py3.12-pybind11
python3.12-pycparser py3.12-pycparser
python3.12-pytest py3.12-pytest
python3.12-pyyaml py3.12-pyyaml
python3.12-requests py3.12-requests
python3.12-scipy py3.12-scipy
python3.12-setuptools py3.12-setuptools
python3.12-setuptools-rust py3.12-setuptools-rust
python3.12-setuptools-wheel py3-setuptools-wheel
python3.12-urllib3 py3.12-urllib3
python3.12-wheel py3.12-wheel
qpdf-libs qpdf
rdma-core-devel rdma-core-dev
rest restic
ruby ruby-3.3
ruby-devel ruby-3.3
ruby-libs ruby-3.3
rubygem-bundler ruby3.4-bundler
rubygem-io-console ruby3.4-io-console
rubygem-json ruby3.4-json
rubygem-pg ruby3.4-pg
rubygem-psych ruby3.4-psych
rubygem-rexml ruby3.4-rexml
rubygems ruby-3.4
rubygems-devel ruby-3.4-dev
rust rustup
rust-doc rustup
rust-toolset rust-1.91
shadow-utils shadow
shadow-utils-subid shadow
sisu basisu
spirv-tools-libs spirv-tools
sqlite-devel sqlite-dev
subversion-devel subversion-dev
subversion-tools subversion
systemd-devel systemd-dev
systemd-libs systemd
tcl-devel tcl-dev
time timescaledb-tune
tk-devel tk-dev
toolbox gitlab-toolbox-ce-18.2
unixODBC unixodbc
unixODBC-devel unixodbc-dev
util-linux-user util-linux
uuid uuidgen
valgrind-devel valgrind-dev
varnish-docs varnish
vim-common vim
xmlsec1 xmlsec
xmlsec1-openssl xmlsec-openssl
xz-devel xz-dev
xz-libs xz
zlib-devel zlib-dev
### Alpine packages Alpine Linux package names generally align with Chainguard's package names, as both use `apk` and share similar package management philosophies. In most cases, no mapping is necessary when migrating from Alpine to Chainguard Containers. ## Image name mappings The following table shows how upstream container image names map to Chainguard Containers. Note that wildcard patterns (indicated by `*`) match multiple variants of an image name.

Upstream Image Name Chainguard Container
alpine chainguard-base:latest
altinity/clickhouse-server altinity-clickhouse-server
amazon/cloudwatch-agent amazon-cloudwatch-agent
amazoncorretto amazon-corretto-jdk
apache/airflow airflow-core
apache/apisix apache-apisix
apache/beam_python3.11_sdk apache-beam-python-sdk
apache/beam_python3.7_sdk apache-beam-python-sdk
apache/hop apache-hop
apache/kvrocks apache-kvrocks
apache/nifi apache-nifi
apache/nifi-registry apache-nifi-registry
apache/tika apache-tika
apache/yunikorn yunikorn-scheduler
argoproj/argo-rollouts kubectl-argo-rollouts
atmoz/sftp atmoz-sftp
authentik/server authentik
banzaicloud/logging-operator kube-logging-operator
bitnami/mongodb-exporter prometheus-mongodb-exporter
bitnami/pgpool pgpool2
boky/postfix boky-postfix
calico/node calico-node
camunda/keycloak camunda-keycloak
camunda/zeebe camunda-zeebe
chartmuseum/chartmuseum helm-chartmuseum
cilium/cilium cilium-agent
cincan/virustotal vt
clickhouse/clickhouse-server clickhouse
confluentinc/cp-kafka confluent-kafka
continuumio/miniconda3 conda
cr.l5d.io/linkerd/cni-plugin linkerd-cni-plugin
cr.l5d.io/linkerd/controller linkerd-controller
cr.l5d.io/linkerd/debug linkerd-debug
cr.l5d.io/linkerd/extension-init linkerd-extension-init
cr.l5d.io/linkerd/proxy linkerd-proxy
cr.l5d.io/linkerd/proxy-init linkerd-proxy-init
cr.l5d.io/linkerd/tap linkerd-tap
cr.l5d.io/linkerd/web linkerd-web
crossplane/provider-aws crossplane-aws
crossplane/provider-azure crossplane-azure
crossplane/provider-sql crossplane-sql
custompodautoscaler/operator custom-pod-autoscaler-operator
cyberark/secrets-provider-for-k8s cyberark-secrets-provider-for-k8s
cybertecpostgresql/pg_timetable pg-timetable
cypress/base cypress-base
daprio/daprd dapr-daprd
daprio/injector dapr-injector
daprio/operator dapr-operator
daprio/placement dapr-placement
daprio/scheduler dapr-scheduler
daprio/sentry dapr-sentry
dart dart-runtime
daskgateway/dask-gateway dask-gateway-server
datadog/agent datadog-agent
debezium/connect debezium-connect
debian chainguard-base:latest
dependencytrack/apiserver dependency-track-apiserver
dependencytrack/bundled dependency-track
dependencytrack/frontend dependency-track-frontend
docker docker-dind
docker.dragonflydb.io/dragonflydb/operator dragonfly-operator
docker/compose docker-compose
dopplerhq/kubernetes-operator doppler-kubernetes-operator
dragonflyoss/dfdaemon dragonfly
eclipse-temurin jdk
envoyproxy/gateway envoy-gateway
envoyproxy/ratelimit envoy-ratelimit
fedora chainguard-base:latest
gcc gcc-glibc
gcr.io/datadoghq/operator datadog-operator
gcr.io/k8s-staging-sig-storage/csi-external-health-monitor-controller kubernetes-csi-external-health-monitor
gcr.io/k8s-staging-sig-storage/csi-provisioner kubernetes-csi-external-provisioner
gcr.io/k8s-staging-sig-storage/csi-resizer kubernetes-csi-external-resizer
gcr.io/kaniko-project/executor kaniko
gcr.io/kaniko-project/warmer kaniko-warmer
gcr.io/knative-releases/knative.dev/eventing/cmd/broker/filter knative-eventing-filter
gcr.io/knative-releases/knative.dev/eventing/cmd/broker/ingress knative-eventing-ingress
gcr.io/knative-releases/knative.dev/eventing/cmd/controller knative-eventing-controller
gcr.io/knative-releases/knative.dev/eventing/cmd/in_memory/channel_controller knative-eventing-channel-controller
gcr.io/knative-releases/knative.dev/eventing/cmd/in_memory/channel_dispatcher knative-eventing-channel-dispatcher
gcr.io/knative-releases/knative.dev/eventing/cmd/jobsink knative-eventing-jobsink
gcr.io/knative-releases/knative.dev/eventing/cmd/mtchannel_broker knative-eventing-mtchannel-broker
gcr.io/knative-releases/knative.dev/eventing/cmd/webhook knative-eventing-webhook
gcr.io/knative-releases/knative.dev/net-istio/cmd/webhook knative-net-istio-webhook
gcr.io/knative-releases/knative.dev/net-kourier/cmd/kourier net-kourier
gcr.io/knative-releases/knative.dev/operator/cmd/operator knative-operator-webhook
gcr.io/knative-releases/knative.dev/serving/cmd/activator knative-serving-activator
gcr.io/knative-releases/knative.dev/serving/cmd/autoscaler knative-serving-autoscaler
gcr.io/knative-releases/knative.dev/serving/cmd/controller knative-serving-controller
gcr.io/knative-releases/knative.dev/serving/cmd/queue knative-serving-queue
gcr.io/knative-releases/knative.dev/serving/cmd/webhook knative-serving-webhook
gcr.io/ml-pipeline/api-server kubeflow-pipelines-api-server
gcr.io/ml-pipeline/cache-deployer kubeflow-pipelines-cache-deployer
gcr.io/ml-pipeline/cache-server kubeflow-pipelines-cache-server
gcr.io/ml-pipeline/frontend kubeflow-pipelines-frontend
gcr.io/ml-pipeline/metadata-envoy kubeflow-pipelines-metadata-envoy
gcr.io/ml-pipeline/metadata-writer kubeflow-pipelines-metadata-writer
gcr.io/ml-pipeline/persistenceagent kubeflow-pipelines-persistenceagent
gcr.io/ml-pipeline/scheduledworkflow kubeflow-pipelines-scheduledworkflow
gcr.io/ml-pipeline/viewer-crd-controller kubeflow-pipelines-viewer-crd-controller
gcr.io/ml-pipeline/visualization-server kubeflow-pipelines-visualization-server
ghcr.io/apache/camel-karavan-devmode apache-camel-karavan-devmode
ghcr.io/azure/azure-workload-identity/webhook azure-workload-identity-webhook
ghcr.io/cloudnative-pg/postgresql postgres-cloudnative-pg
ghcr.io/crossplane-contrib/function-auto-ready crossplane-function-auto-ready
ghcr.io/crossplane-contrib/function-environment-configs crossplane-function-environment-configs
ghcr.io/crossplane-contrib/function-go-templating crossplane-function-go-templating
ghcr.io/crossplane-contrib/provider-aws crossplane-aws-provider
ghcr.io/crossplane-contrib/provider-aws-cloudformation crossplane-aws-cloudformation
ghcr.io/crossplane-contrib/provider-aws-cloudfront crossplane-aws-cloudfront
ghcr.io/crossplane-contrib/provider-aws-cloudwatchlogs crossplane-aws-cloudwatchlogs
ghcr.io/crossplane-contrib/provider-aws-dynamodb crossplane-aws-dynamodb
ghcr.io/crossplane-contrib/provider-aws-ec2 crossplane-aws-ec2
ghcr.io/crossplane-contrib/provider-aws-eks crossplane-aws-eks
ghcr.io/crossplane-contrib/provider-aws-elasticache crossplane-aws-elasticache
ghcr.io/crossplane-contrib/provider-aws-firehose crossplane-aws-firehose
ghcr.io/crossplane-contrib/provider-aws-iam crossplane-aws-iam
ghcr.io/crossplane-contrib/provider-aws-kinesis crossplane-aws-kinesis
ghcr.io/crossplane-contrib/provider-aws-kms crossplane-aws-kms
ghcr.io/crossplane-contrib/provider-aws-lambda crossplane-aws-lambda
ghcr.io/crossplane-contrib/provider-aws-memorydb crossplane-aws-memorydb
ghcr.io/crossplane-contrib/provider-aws-rds crossplane-aws-rds
ghcr.io/crossplane-contrib/provider-aws-route53 crossplane-aws-route53
ghcr.io/crossplane-contrib/provider-aws-s3 crossplane-aws-s3
ghcr.io/crossplane-contrib/provider-aws-sns crossplane-aws-sns
ghcr.io/crossplane-contrib/provider-aws-sqs crossplane-aws-sqs
ghcr.io/crossplane-contrib/provider-family-aws crossplane-aws
ghcr.io/crossplane-contrib/provider-family-gcp crossplane-gcp-provider-family
ghcr.io/crossplane-contrib/provider-gcp-compute crossplane-gcp-compute
ghcr.io/crossplane-contrib/provider-gcp-container crossplane-gcp-container
ghcr.io/crossplane-contrib/provider-gcp-dns crossplane-gcp-dns
ghcr.io/crossplane-contrib/provider-gcp-kms crossplane-gcp-kms
ghcr.io/crossplane-contrib/provider-gcp-pubsub crossplane-gcp-pubsub
ghcr.io/crossplane-contrib/provider-gcp-storage crossplane-gcp-storage
ghcr.io/crossplane-contrib/provider-gitlab crossplane-provider-gitlab
ghcr.io/crossplane-contrib/provider-kubernetes crossplane-provider-kubernetes
ghcr.io/crossplane-contrib/provider-terraform crossplane-provider-terraform
ghcr.io/dask/dask-kubernetes-operator dask-kubernetes
ghcr.io/flux-iac/tf-runner tofu-controller-runner
ghcr.io/fluxcd/flux-cli flux
ghcr.io/fluxcd/helm-controller flux-helm-controller
ghcr.io/fluxcd/image-automation-controller flux-image-automation-controller
ghcr.io/fluxcd/image-reflector-controller flux-image-reflector-controller
ghcr.io/fluxcd/kustomize-controller flux-kustomize-controller
ghcr.io/fluxcd/notification-controller flux-notification-controller
ghcr.io/fluxcd/source-controller flux-source-controller
ghcr.io/fluxcd/source-watcher flux-source-watcher
ghcr.io/graalvm/graalvm-community graalvm
ghcr.io/graalvm/jdk-community graalvm
ghcr.io/graalvm/nodejs-community graalvm
ghcr.io/grafana/alloy-operator grafana-alloy-operator
ghcr.io/kube-logging/logging-operator/node-exporter kube-logging-operator-node-exporter
ghcr.io/kubeflow/katib/earlystopping-medianstop kubeflow-katib-earlystopping-medianstop
ghcr.io/kubeflow/katib/katib-controller kubeflow-katib-controller
ghcr.io/kubeflow/katib/katib-db-manager kubeflow-katib-db-manager
ghcr.io/kubeflow/kubeflow/central-dashboard kubeflow-centraldashboard
ghcr.io/kubeflow/kubeflow/jupyter-web-app kubeflow-jupyter-web-app
ghcr.io/kubeflow/kubeflow/kfam kubeflow-access-management
ghcr.io/kubeflow/kubeflow/notebook-controller kubeflow-notebook-controller
ghcr.io/kubeflow/kubeflow/poddefaults-webhook kubeflow-admission-webhook
ghcr.io/kubeflow/kubeflow/profile-controller kubeflow-profile-controller
ghcr.io/kubeflow/kubeflow/pvcviewer-controller kubeflow-pvcviewer-controller
ghcr.io/kyverno/background-controller kyverno-background-controller
ghcr.io/kyverno/cleanup-controller kyverno-cleanup-controller
ghcr.io/kyverno/policy-reporter kyverno-policy-reporter-plugin
ghcr.io/kyverno/policy-reporter/kyverno-plugin kyverno-policy-reporter-plugin-kyverno
ghcr.io/kyverno/policy-reporter/trivy-plugin kyverno-policy-reporter-plugin-trivy
ghcr.io/kyverno/reports-controller kyverno-reports-controller
ghcr.io/kyverno/reports-server kyverno-reports-server
ghcr.io/mesosphere/cloud-provider-vsphere/driver mesosphere-vsphere-csi-driver
ghcr.io/mesosphere/cloud-provider-vsphere/syncer mesosphere-vsphere-csi-syncer
ghcr.io/nginx/nginx-s3-gateway/nginx-oss-s3-gateway nginx-s3-gateway
ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java opentelemetry-java-instrumentation
ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator opentelemetry-operator-target-allocator
ghcr.io/opencost/opencost opencost-ui
ghcr.io/opencost/opencost-ui opencost
ghcr.io/shadowsocks/sslocal-rust shadowsocks-rust-sslocal
ghcr.io/shadowsocks/ssserver-rust shadowsocks-rust-ssserver
ghcr.io/sigstore/scaffolding/createctconfig sigstore-scaffolding-ctlog-createctconfig
ghcr.io/sigstore/scaffolding/createdb sigstore-scaffolding-trillian-createdb
ghcr.io/sigstore/scaffolding/createtree sigstore-scaffolding-trillian-createtree
ghcr.io/sigstore/scaffolding/getoidctoken sigstore-scaffolding-getoidctoken
ghcr.io/sigstore/scaffolding/managectroots sigstore-scaffolding-ctlog-managectroots
ghcr.io/sigstore/scaffolding/rekor-createsecret sigstore-scaffolding-rekor-createsecret
ghcr.io/sigstore/scaffolding/updatetree sigstore-scaffolding-trillian-updatetree
ghcr.io/sigstore/timestamp-server timestamp-authority-server
ghcr.io/spiffe/oidc-discovery-provider spire-oidc-discovery-provider
ghcr.io/tektoncd/github.com/tektoncd/chains/cmd/controller tekton-chains
golang* go
google/cloud-sdk google-cloud-sdk
grafana/agent-operator grafana-agent-operator
grafana/alloy grafana-alloy
grafana/beyla grafana-beyla
grafana/mimir grafana-mimir
grafana/oncall grafana-oncall
grafana/pyroscope grafana-pyroscope
grafana/rollout-operator grafana-rollout-operator
guacamole/guacamole guacamole-server
guacamole/guacd guacamole-server
infinispan/operator infinispan-operator
istio/install-cni istio-install-cni
istio/operator istio-operator
istio/pilot istio-pilot
istio/proxyv2 istio-proxy
jaegertracing/all-in-one jaeger-all-in-one
jenkins/inbound-agent jenkins-inbound-agent
jitesoft/tesseract-ocr tesseract
jitsucom/bulker jitsucom-bulker
jitsucom/jitsu jitsucom-console
jupyterhub/k8s-hub jupyterhub-k8s-hub
jupyterhub/k8s-image-awaiter jupyterhub-k8s-image-awaiter
jupyterhub/k8s-network-tools jupyterhub-k8s-network-tools
justwatch/elasticsearch_exporter prometheus-elasticsearch-exporter
k8ssandra/medusa cassandra-medusa
kafbat/kafka-ui kafbat-ui
kserve/agent kserve-agent
kserve/modelmesh kserve-modelmesh
kserve/modelmesh-controller kserve-modelmesh-controller
kserve/modelmesh-runtime-adapter kserve-modelmesh-runtime-adapter
kserve/rest-proxy kserve-rest-proxy
kserve/router kserve-router
kserve/storage-initializer kserve-storage-initializer
kubeflowkatib/file-metrics-collector kubeflow-katib-file-metrics-collector
kubeflowkatib/suggestion-goptuna kubeflow-katib-suggestion-goptuna
kubeflowkatib/suggestion-hyperband kubeflow-katib-suggestion-hyperband
kubeflowkatib/suggestion-hyperopt kubeflow-katib-suggestion-hyperopt
kubeflowkatib/suggestion-optuna kubeflow-katib-suggestion-optuna
kubeflowkatib/suggestion-pbt kubeflow-katib-suggestion-pbt
kubeflowkatib/suggestion-skopt kubeflow-katib-suggestion-skopt
kubernetesui/dashboard kubernetes-dashboard
kubernetesui/dashboard-api kubernetes-dashboard-api
kubernetesui/dashboard-auth kubernetes-dashboard-auth
kubernetesui/dashboard-metrics-scraper kubernetes-dashboard-metrics-scraper
kubernetesui/dashboard-web kubernetes-dashboard-web
letsencrypt/trillian_log_server trillian-logserver
library/docker docker-dind
longhornio/support-bundle-kit longhorn-support-bundle-kit
mailcow/unbound unbound-mailcow
martenseemann/quic-go-interop quic-go
mattermost/mattermost-team-edition mattermost
mcr.microsoft.com/azure-cli az
mcr.microsoft.com/azure-functions/node azure-functions-node
mcr.microsoft.com/azure-functions/python azure-functions-python
mcr.microsoft.com/dotnet/aspnet aspnet-runtime
mcr.microsoft.com/dotnet/runtime dotnet-runtime
mcr.microsoft.com/dotnet/sdk dotnet-sdk
mcr.microsoft.com/k8s/azureserviceoperator azure-service-operator
mcr.microsoft.com/oss/kubernetes/azure-cloud-controller-manager cloud-provider-azure-controller-manager
mcr.microsoft.com/oss/kubernetes/azure-cloud-node-manager cloud-provider-azure-node-manager
mcr.microsoft.com/oss/v2/azure/secrets-store/provider-azure secrets-store-csi-driver-provider-azure
minio/minio minio-client
minio/operator minio-operator
mongo mongodb
nacos/nacos-server nacos
ncabatoff/process-exporter prometheus-process-exporter
neuvector/controller neuvector-controller
newrelic/infrastructure-bundle newrelic-infrastructure-bundle
newrelic/infrastructure-k8s newrelic-infrastructure-k8s
newrelic/k8s-events-forwarder newrelic-k8s-events-forwarder
newrelic/newrelic-fluentbit-output newrelic-fluent-bit-output
newrelic/nri-kube-events newrelic-kube-events
newrelic/nri-kubernetes newrelic-kubernetes
newrelic/nri-prometheus newrelic-prometheus
newrelic/nri-statsd newrelic-nri-statsd
nextcloud nextcloud-server
nodejs* node
nvcr.io/nvidia/driver nvidia-gpu-driver
nvidia/container-toolkit nvidia-container-toolkit
nvidia/k8s-device-plugin nvidia-device-plugin
oliver006/redis_exporter prometheus-redis-exporter
openbao/openbao openbao-k8s
openebs/provisioner-localpv dynamic-localpv-provisioner
openjdk jdk
opensearchproject/opensearch-operator opensearch-k8s-operator
pgpool/pgpool pgpool2
powerdns/dnsdist-master dnsdist
powerdns/pdns-auth-master pdns-auth
powerdns/pdns-recursor-54 pdns-recursor
prom/alertmanager prometheus-alertmanager
prom/blackbox-exporter prometheus-blackbox-exporter
prom/cloudwatch-exporter prometheus-cloudwatch-exporter
prom/mysqld-exporter prometheus-mysqld-exporter
prom/node-exporter prometheus-node-exporter
prom/pushgateway prometheus-pushgateway
prom/statsd-exporter prometheus-statsd-exporter
prometheuscommunity/pgbouncer-exporter prometheus-pgbouncer-exporter
public.ecr.aws/ebs-csi-driver/volume-modifier-for-k8s aws-volume-modifier-for-k8s
public.ecr.aws/eks-distro/kubernetes-csi/external-attacher kubernetes-csi-external-attacher
public.ecr.aws/eks-distro/kubernetes-csi/external-provisioner kubernetes-csi-external-provisioner
public.ecr.aws/eks-distro/kubernetes-csi/external-resizer kubernetes-csi-external-resizer
public.ecr.aws/eks-distro/kubernetes-csi/external-snapshotter/csi-snapshotter kubernetes-csi-external-snapshotter
public.ecr.aws/eks-distro/kubernetes-csi/livenessprobe kubernetes-csi-livenessprobe
public.ecr.aws/eks-distro/kubernetes-csi/node-driver-registrar kubernetes-csi-node-driver-registrar
public.ecr.aws/gravitational/teleport-distroless teleport
public.ecr.aws/k1n1h4h4/cert-manager-aws-privateca-issuer aws-privateca-issuer
public.ecr.aws/karpenter/controller karpenter
public.ecr.aws/mountpoint-s3-csi-driver/aws-mountpoint-s3-csi-driver mountpoint-s3-csi-driver
quay.io/argoproj/argocli argo-cli
quay.io/argoproj/argoexec argo-exec
quay.io/argoproj/workflow-controller argo-workflowcontroller
quay.io/cilium/certgen cilium-certgen
quay.io/debezium/connect debezium-connect
quay.io/infinispan/server infinispan
quay.io/jetstack/cmctl cert-manager-cmctl
quay.io/jupyter/base-notebook jupyter-base-notebook
quay.io/kubescape/operator kubescape-operator
quay.io/metallb/controller metallb-controller
quay.io/metallb/speaker metallb-speaker
quay.io/minio/mc minio-client
quay.io/minio/operator minio-operator
quay.io/minio/operator-sidecar minio-operator-sidecar
quay.io/mongodb/mongodb-kubernetes-readinessprobe mongodb-kubernetes-operator-readinessprobe
quay.io/prometheus-operator/admission-webhook prometheus-admission-webhook
quay.io/prometheus/cloudwatch-exporter prometheus-cloudwatch-exporter
quay.io/prometheus/node-exporter prometheus-node-exporter
quay.io/prometheus/snmp-exporter prometheus-snmp-exporter
quay.io/prometheuscommunity/pgbouncer-exporter prometheus-pgbouncer-exporter
quay.io/prometheuscommunity/yet-another-cloudwatch-exporter prometheus-yet-another-cloudwatch-exporter
quay.io/skopeo/stable skopeo
quay.io/superq/chrony-exporter chrony_exporter
quay.io/tigera/operator tigera-operator
rabbitmqoperator/messaging-topology-operator rabbitmq-messaging-topology-operator
rancher/agent rancher-agent
rancher/fleet rancher-fleet-agent
rancher/fleet-agent rancher-fleet-agent
rancher/k3s k3s-static
rancher/rke2-runtime rke2-runtime-airgap
rancher/security-scan rancher-security-scan
rancher/shell rancher-shell
rancher/system-upgrade-controller rancher-system-upgrade-controller
redis redis-sentinel
regclient/regbot regclient-regbot
regclient/regctl regclient-regctl
regclient/regsync regclient-regsync
registry.gitlab.com/gitlab-org/build/cng/certificates gitlab-certificates
registry.k8s.io/autoscaling/addon-resizer kubernetes-autoscaler-addon-resizer
registry.k8s.io/autoscaling/vpa-admission-controller vertical-pod-autoscaler-admission-controller
registry.k8s.io/autoscaling/vpa-recommender vertical-pod-autoscaler-recommender
registry.k8s.io/autoscaling/vpa-updater vertical-pod-autoscaler-updater
registry.k8s.io/capi-ipam-ic/cluster-api-ipam-in-cluster-controller cluster-api-ipam-provider-in-cluster
registry.k8s.io/cloud-provider-gcp/cloud-controller-manager cloud-provider-gcp-cloud-controller-manager
registry.k8s.io/conformance kube-conformance
registry.k8s.io/csi-secrets-store/driver secrets-store-csi-driver
registry.k8s.io/csi-secrets-store/driver-crds secrets-store-csi-driver-crds
registry.k8s.io/defaultbackend kubernetes-ingress-defaultbackend
registry.k8s.io/dns/k8s-dns-node-cache kubernetes-dns-node-cache
registry.k8s.io/ingress-nginx/controller ingress-nginx-controller
registry.k8s.io/kube-apiserver kubernetes-kube-apiserver
registry.k8s.io/kube-controller-manager kubernetes-kube-controller-manager
registry.k8s.io/kube-proxy kubernetes-kube-proxy
registry.k8s.io/kube-scheduler kubernetes-kube-scheduler
registry.k8s.io/pause kubernetes-pause
registry.k8s.io/provider-aws/cloud-controller-manager cloud-provider-aws
registry.k8s.io/sig-storage/csi-attacher kubernetes-csi-external-attacher
registry.k8s.io/sig-storage/csi-node-driver-registrar kubernetes-csi-node-driver-registrar
registry.k8s.io/sig-storage/livenessprobe kubernetes-csi-livenessprobe
registry.k8s.io/sig-storage/nfsplugin kubernetes-csi-driver-nfs
registry.k8s.io/sig-storage/snapshot-controller kubernetes-csi-external-snapshot-controller
registry.k8s.io/sig-storage/snapshotter kubernetes-csi-external-snapshotter
rook/ceph rook-ceph
s3-controller aws-s3-controller
sbtscala/scala-sbt sbt
selenium/base selenium-base
selenium/distributor selenium-distributor
selenium/event-bus selenium-event-bus
selenium/hub selenium-hub
selenium/node-base selenium-node-base
selenium/node-chromium selenium-node-chromium
selenium/node-docker selenium-node-docker
selenium/node-firefox selenium-node-firefox
selenium/router selenium-router
selenium/session-queue selenium-session-queue
selenium/sessions selenium-sessions
selenium/standalone-chromium selenium-standalone-chromium
selenium/standalone-docker selenium-standalone-docker
selenium/standalone-firefox selenium-standalone-firefox
sonobuoy/systemd-logs sonobuoy-systemd-logs
stakater/reloader stakater-reloader
static* static:latest
strimzi/kafka strimzi-kafka
strimzi/operator strimzi-kafka-operator
temporalio/admin-tools temporal-admin-tools
temporalio/server temporal-server
temporalio/ui temporal-ui-server
thingsboard/tb thingsboard-tb-js-executor
timescale/timescaledb timescaledb-compat
ubuntu chainguard-base:latest
ubuntu/squid squid-proxy
us-docker.pkg.dev/secretmanager-csi/secrets-store-csi-driver-provider-gcp/plugin secrets-store-csi-driver-provider-gcp
victoriametrics/operator victoriametrics-operator
victoriametrics/victoria-metrics victoriametrics-vmstorage
vitess/lite vitess-lite
vmware/kube-fluentd-operator kube-logging-operator-fluentd
wavefronthq/proxy wavefront-proxy
wavefronthq/wavefront-kubernetes-collector wavefront-collector-for-kubernetes
wrouesnel/postgres_exporter prometheus-postgres-exporter
xpkg.upbound.io/crossplane-contrib/provider-keycloak crossplane-keycloak
## Learn more For more information about working with Chainguard Containers and package management, you can check out our overview of [Chainguard's package model](/chainguard/containers/building-and-modifying/packages/package-model/). Additionally, you may find our doc on [Using the Dockerfile Converter](/chainguard/containers/migration/migration-tools/dockerfile-conversion/) to be useful. --- ### Chainguard VMs FAQ URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/vms/faq.md Last Modified: October 8, 2026 Tags: Chainguard VMs, FAQ ## Which platforms and hypervisors does Chainguard VMs support? {#which-platforms-and-hypervisors-are-chainguard-vms-available-for} Chainguard VMs is available for AWS ([EC2](https://aws.amazon.com/ec2/) and [ECS](https://aws.amazon.com/ecs/)/[EKS](https://aws.amazon.com/eks/)), [GCP](https://cloud.google.com/?hl=en) (Compute Engine), and [Azure Compute](https://azure.microsoft.com/en-us/products/category/compute) cloud environments, and also for on-prem solutions based on KVM such as [QEMU](https://www.qemu.org/), [VMware](https://www.vmware.com/products/cloud-infrastructure/vsphere), [Nutanix](https://www.nutanix.com/), among others. ## What kinds of VMs are currently available? As part of our initial offering, we’re providing Container Host VMs, Base VMs, and Application VMs. This list should expand as we fine tune the product based on customer feedback. ## What are Container Host VMs and which versions are available? Container Host VMs allow you to run containerized workloads on a hardened VM runtime. We currently offer container host VMs for AWS Container Services ECS and EKS, and also for native compute instances on AWS EC2, Google Compute Engine, and Azure Compute. ## What are Base VMs and which versions are available? Base VMs are general purpose VMs that can be customized to suit your application needs. Current offerings include Chainguard Base, Java Base, and Python Base VM images available for native compute instances on AWS EC2, Google Compute Engine, and Azure Compute. ## What are Application VMs and which versions are available? Application VMs come pre-packaged with popular backend applications running as systemd services. We currently offer Nginx, Jenkins, and Squid Proxy Application VMs available for native compute instances on AWS EC2, Google Compute Engine, and Azure Compute. ## Which operating system is used by Chainguard VMs? Chainguard's virtual-machine images are based on [Chainguard OS](https://get.chainguard.dev/chainguard-your-os-whitepaper-0), our minimal Linux distribution initially designed to run on containers and now extended to include a kernel and other components. ## Which Linux kernel is used in Chainguard VMs? The [Chainguard Factory](/platform/factory/overview/) tracks both the stable upstream and the latest LTS (for FIPS) versions of the kernel, building from source to provide the most up-to-date and patched versions. ## Does Chainguard VMs support in-place upgrades? {#do-chainguard-vms-support-in-place-upgrades} No, Chainguard VMs does not support in-place upgrades (e.g. via package upgrade). The upgrade strategy is based on node replacement. ## How does FIPS work on VMs? In Virtual Machines, FIPS is traditionally dependent on the Linux kernel, which requires engineers to provision dedicated hardware and virtual machines (VMs) with the host kernel configured in FIPS mode in order to be compliant. Using a FIPS validated Linux kernel allows VMs to provide FIPS graded cryptography for use cases like Disk Encryption, IPSec, KMSV, dm-verity, dm-integrity, among others. This design, which requires maintenance of FIPS cryptographic boundaries at the kernel-level, drives significant friction for vendors delivering FIPS compliant workloads for modern cloud-native applications, since it forces a dependence on a limited set of FIPS-enabled kernels. With kernel FIPS you often need separate, kernel-pinned images (and careful reboots) to keep the validated stack intact. Making the cryptographic module user-space or [kernel independent](https://www.chainguard.dev/unchained/kernel-independent-fips-images) breaks that coupling, so the same validated module can serve many VMs and kernels with less toil and fewer surprises. ## Does Chainguard VMs support FIPS? {#do-chainguard-vms-support-fips} Yes, Chainguard VMs supports **kernel independent FIPS**. This means that application workloads use a FIPS validated entropy source independent of the kernel. The advantage to this approach is that the certification of the entropy source does not need to be performed against a specific kernel, so customers can take advantage of new kernel features while remaining FIPS compliant. It also means that VMs no longer need to be booted in FIPS mode. Note that with kernel independent FIPS, some low level operating system functions such as disk encryption, IPSEC, KMSV, among others do not use FIPS validated entropy. This is less relevant on cloud platforms, since disk volumes are encrypted with FIPS validated entropy, as is network and filesystem encryption. On the cloud, kernel independent FIPS is a more efficient way of servicing FIPS workloads in VMs. --- ### CVE remediation for Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/policies-and-security/cve-remediation.md Last Modified: October 2, 2026 Tags: Chainguard Libraries, CVE CVE remediation for Chainguard Libraries provides protection against critical and high CVEs. Applications often rely on older versions of libraries, but upstream maintainers may not apply and release patches for those versions. Chainguard addresses this gap by backporting vulnerability fixes from newer releases to older releases, particularly in cases where maintainers are no longer able to support and provide fixes. CVE remediation helps reduce risk for organizations that cannot always upgrade quickly, especially when moving to a newer version would introduce disruptive changes. Remediated artifacts are published as incremental patch versions, allowing teams to take a targeted fix for a CVE without taking on a broader upgrade at the same time. CVE remediation is available for a subset of [Chainguard Libraries for Python](/chainguard/libraries/python/overview/), [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) (available in private preview), and [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/overview/). If you want to request CVE remediation for additional libraries, reach out to your account team. ## About CVE remediation CVE remediation focuses on critical and high vulnerabilities. Chainguard backports fixes that are already available in the new versions of the upstream project to older versions that may no longer receive updates. Before publishing a remediated version, Chainguard validates that the remediated version does not introduce regressions. All upstream test suites are run before and after applying the fix to confirm functional consistency. Chainguard also develops additional regression tests to validate the effectiveness of the CVE fix. Remediated libraries are distributed through a dedicated repository. This provides the option to make remediated versions available for your development or opt out of using these versions completely and continue to use upstream versions only. ### Remediated version naming Chainguard publishes remediated versions using ecosystem-specific version suffixes. #### Python version naming For Python, remediated packages use a `+cgr.N` local version suffix. For example, if `flask==1.1.2` has a remediated build, Chainguard publishes it as `flask==1.1.2+cgr.1`. Python package managers treat this as a higher-precedence local version of the base release, so remediated versions can be selected automatically during dependency resolution when the remediated index is configured. #### Java version naming For Java, remediated artifacts use a `-0.cgr.N` suffix appended to the base version. For example, if `org.apache.commons:commons-lang3:3.18.0` has a remediated build, that build is published as org.apache.`commons:commons-lang3:3.18.0-0.cgr.1`. If Chainguard publishes another remediated iteration for the same base version, the trailing number increases, such as `-0.cgr.2` or `-0.cgr.3`. #### JavaScript version naming For JavaScript, remediated artifacts use a `-cgr.N` suffix appended to the base version. For example, a remediated build of `example-package v4.17.21` is published as `example-package@4.17.21-cgr.1`. Because `-cgr.N` is an npm prerelease identifier, it must not be treated as a newer ordinary release by npm semver resolution. The JavaScript build integration selects the remediated artifact while preserving the dependency version requested by the application. ### Remediation and transitive dependencies **Python** For Python, installing a `+cgr.N` package doesn't automatically remediate its entire dependency tree. Chainguard publishes a `+cgr.N` version only for a package that has a remediation to deliver, and it leaves that package's own dependency declarations unchanged. This is intentional. Most packages don't pin their dependencies tightly enough to require a rewrite, so the package metadata stays identical to upstream. Remediation propagates across the dependency tree through the index, not through dependency declarations. When you configure the Chainguard index, your package manager resolves every dependency, both direct and transitive, through Chainguard Libraries rather than the upstream index. As described in [Remediated version naming](#remediated-version-naming), a `+cgr.N` build is a higher-precedence local version, so the resolver selects it automatically wherever Chainguard has published one. Dependencies without a remediation resolve to the standard upstream version served through the same index. **Java** Java remediation behavior differs from Python. For Java packages that publish a BOM, Chainguard may update the BOM to roll forward dependencies to remediated versions. This means transitive dependencies can receive CVE remediations without requiring changes to your own dependency declarations. Note that BOM-based remediation is best-effort and does not cover all packages or dependency trees. To ensure full control over dependency versions in your project, use `dependencyManagement` or equivalent tooling to explicitly manage and override dependency versions. **JavaScript** JavaScript remediation operates on the resolved dependency tree in the project lockfile rather than through package metadata or semver precedence. To preview available remediations, run: ```bash chainctl libraries remediate ``` To adopt remediated artifacts, run: ```bash chainctl libraries remediate --apply ``` The command walks the lockfile and updates each eligible package occurrence to the corresponding Chainguard remediated version when one is available. For a direct dependency, it sets the exact `-cgr.N` version in `package.json`. For a transitive dependency, it adds an override or resolution rule supported by your package manager. In both cases, it updates the lockfile. This process can remediate transitive dependencies, but only when a remediated build exists for the exact package version resolved in the lockfile. Dependencies without a published `-cgr.N` build remain unchanged. A single rule can cover more than one occurrence of a package. The package manager applies the rule during resolution wherever a dependency matches the rule's package name and version, then records the substituted versions in the lockfile. Because matching is by name and version rather than by position, one rule can remediate every matching node in the dependency tree — you don't need a separate rule for each occurrence. How widely a rule applies depends on its syntax: a rule scoped to a name and version matches only that version, while broader patterns can match additional ranges or nesting. After applying remediation, review the updated lockfile to confirm that each dependency you intended to remediate resolves to the selected `-cgr.N` version. The lockfile is the authoritative record of what your package manager will install. Re-run `chainctl libraries remediate` after every install or dependency update so that newly resolved entries are evaluated. You can also use a direct package override in `package.json` to select an exact remediated version manually. Unlike Python, JavaScript remediation does not rely on a remediated index selecting a higher-precedence version. Unlike Java, it does not depend on BOM updates to propagate remediation through the dependency tree. ### CVE remediation for vendored dependencies Some Python packages bundle compiled code written in other languages (such as Go, Rust, or C/C++) directly into their wheel. When a CVE exists in a dependency of that vendored code, Chainguard may publish a remediated version even when no CVE has been filed against the Python package itself. In these cases, Chainguard bumps the vulnerable dependency within the vendored code and publishes a new `+cgr.N` version of the package. Because the vulnerability exists at the vendored dependency level rather than the Python package level, no advisory entry is published in the VEX feed for these versions. However, scanners that inspect vendored binaries will reflect the fix in their results. ## Browse libraries with CVE remediation Remediated libraries are published in dedicated repositories: - Python: In a PyPI-compatible index at `https://libraries.cgr.dev/python-remediated/` - the simple index is at `https://libraries.cgr.dev/python-remediated/simple/` - Java: In a repository at `https://libraries.cgr.dev/java-remediated/` - a companion to the standard Chainguard Libraries for Java repository at `https://libraries.cgr.dev/java/` - JavaScript: Consume remediated artifacts through the standard JavaScript endpoint: `https://libraries.cgr.dev/javascript/`. You can: - Browse them in the Chainguard Console - Use the public VEX feed to understand what has been remediated - This feed does not include [vendored dependencies](#cve-remediation-for-vendored-dependencies). - View them in a browser at the simple index URL - Learn more in [Python overview > Manual access](/chainguard/libraries/python/overview/#manual-access) and in [Java overview > Manual access](/chainguard/libraries/java/overview/#manual-access). - Expose them to your developers via a repo manager - Learn more in the global configuration docs for [Python](/chainguard/libraries/python/global-configuration/) and [Java](/chainguard/libraries/java/global-configuration/). ### Browse remediated libraries in the Chainguard Console In the Chainguard Console, navigate to the Python or Java libraries, then click the **Remediated** tab. Click into a library to see which versions have remediated CVEs. Learn more in [Browsing Chainguard Libraries](/chainguard/libraries/introduction/browse/). ### Public VEX feed Advisories for each CVE addressed in our remediated libraries are published via a public VEX feed at [https://libraries.cgr.dev/openvex/v1/all.json](https://libraries.cgr.dev/openvex/v1/all.json). Supported scanners and your own custom tooling can use this feed to identify and recognize remediated versions. To view more details on the CVE and versions of a library that have been remediated, identify the library then navigate to the URL. For example: `https://libraries.cgr.dev/openvex/v1/pypi/urllib3.openvex.json`. ## Scanning remediated libraries Chainguard works closely with scanner partners so that remediated versions are properly recognized in vulnerability reports. This ensures that teams can maintain their existing scanning workflows while benefiting from patched dependencies. Find more general information and specifics for supported scanners in [Vulnerability Scanners and Chainguard Libraries](/chainguard/libraries/policies-and-security/scanners/). --- ### Using init containers with Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/init-containers.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Overview, Procedural Chainguard's container images are designed with minimalism and security in mind. By including fewer packages and tools, they have a smaller attack surface than their counterparts. However, there are cases where the external counterparts have certain desirable features, like useful startup scripts or configuration defaults. There are several ways to customize Chainguard Containers. For example, you can use [Custom Assembly](/chainguard/containers/custom-assembly/overview/) to add packages to an otherwise minimal Chainguard container image. Changing a Chainguard container image's configuration — such as updating its entrypoint or adding startup scripts — requires a different strategy. One method for doing so in Kubernetes deployments is to use *init containers*. This guide provides a brief overview of what init containers are as well as their benefits. It then outlines an example of how you can build an init container and use it to reconfigure Chainguard's nginx container image. ## Prerequisites In order to follow this guide, you will need the following: * Docker, which you will use to create an image to be used by an init container, installed on your local machine. Refer to the [Docker documentation](https://docs.docker.com/engine/install/) to set this up. * A container registry you can push the init container image to. This guide assumes you have access to a [Docker Hub registry](https://hub.docker.com/), though the example can be adjusted for other registries. * Access to a Kubernetes cluster. This could be a cluster that you run locally with a tool like [minikube](https://minikube.sigs.k8s.io/docs/) or [kind](https://kind.sigs.k8s.io/), or a cloud-hosted cluster such as [Amazon EKS](https://aws.amazon.com/eks/) or [Google GKE](https://cloud.google.com/kubernetes-engine). ## Benefits of init containers Init containers are specialized containers that run before application containers within a Kubernetes pod. Init containers are temporary: they run and complete their given tasks before the main application container starts, and then immediately exit. Init containers are useful for preparing the application environment, since they allow you to create configuration files or modify settings before your app starts. They can be used to set up prerequisites for the application, such as database migrations or environment variable configurations. You can also use init containers to create directories, set file permissions, install dependencies, or populate data stores with initial data. In some cases, they can retrieve secrets and place them in a shared volume for the application. Put together, this allows you to separate initialization logic from your app code, making both more manageable. Init containers can also run in sequence, so you can control the order of operations. If one init container fails, Kubernetes will restart the pod and run the init containers again until they succeed. ## Configuring Chainguard's `nginx` container with an init container Say your organization uses an nginx container to host an application. You've typically used the [default nginx container image from Docker Hub](https://hub.docker.com/_/nginx) for this purpose. This image runs startup scripts inside the `/docker-entrypoint.d` directory of the image. Although this allows for flexibility, it introduces various security risks: * **Arbitrary Code Execution**: If anyone (whether through build scripts, other layers, mounted volumes) adds or modifies a script file within `/docker-entrypoint.d`, it will get executed automatically on container startup. * **Unintended Script Execution**: If you accidentally include a shell script (or malicious one gets included through CI/CD or shared build layers), it will be executed, regardless of its purpose or trustworthiness. * **Privilege Escalation**: If the container runs as root (which is common in base images), any `*.sh` script executed at startup runs with full privileges. Malicious scripts can add users, exfiltrate secrets, overwrite configs, etc. Rather than exposing yourself to such risks, you can instead run the default nginx image from Docker Hub as an init container, which you'll then use to reconfigure Chainguard's more minimal and secure nginx container image. This involves running the startup configuration in the init container, mounting the resulting configuration to a shared volume, and thereby decoupling the `docker-entrypoint.sh` script from the core nginx image. This avoids introducing the aforementioned security risks into the minimal Chainguard nginx container image. This section illustrates how to set up an nginx init container based on the default nginx container from Docker Hub and then run it inside a Kubernetes deployment to reconfigure a Chainguard nginx container image. This example involves creating a sample `nginx.conf` file, building an init container with `docker`, and deploying it in a Kubernetes pod. ### Creating a sample `nginx.conf` file To get started, run the following command to create an `nginx.conf` file: ```shell cat > nginx.conf < Dockerfile.init < ``` > **Note**: If you aren't using Docker Hub as your container registry, you will need to include the host name of your registry in this variable. Docker defaults to Docker Hub (`docker.io`) if this is omitted. After creating this environment variable, build the init container image: ```shell docker build -f Dockerfile.init -t $NAMESPACE/nginx-init . ``` Next, push the init container image to your registry so you can reference it from your Kubernetes deployment: ```shell docker push $NAMESPACE/nginx-init ``` Once you have built and pushed this init image to your registry it is now available to be run inside pipelines where you have your `nginx.conf` available for shared mounting and configuration in the init step. ### Setting up a Kubernetes configuration Since you are building the entrypoint logic into the init container image itself, the Kubernetes deployment YAML for your init container would follow a similar structure to this: ```yaml initContainers: - name: run-entrypoint image: $NAMESPACE/nginx-init volumeMounts: - name: nginx-config mountPath: /etc/nginx ``` This instructs the init container to execute the startup scripts for the nginx configuration in the `docker-entrypoint.d` directory. In your main nginx configuration for the deployment, it would follow a structure similar to this: ```yaml containers: - name: nginx image: cgr.dev/chainguard/nginx:latest ports: - containerPort: 80 volumeMounts: - name: nginx-config mountPath: /etc/nginx ``` Because your nginx container would mount the configurations from the shared volumeMount nginx-config in this case, you would now avoid having to run startup steps outside of the init. The following command creates a deployment manifest named `init-deployment.yaml` that performs these steps using the init container image you created earlier: ```shell cat > init-deployment.yaml < 80 Forwarding from [::1]:8080 -> 80 ``` Then, **in a separate terminal window**, use `curl` to reach the pod: ```shell curl http://localhost:8080 ``` ``` Hello from minimal NGINX! ``` This confirms that the workload is indeed running and nginx is working as expected. Note that this example copies over only an `nginx.conf` file, but you can use this strategy to set up other nginx configurations. For example, you could also copy a `mime.types` file over to the Chainguard nginx container image. ## Learn more Init Containers provide a powerful, flexible mechanism to set up application environments and help with migration challenges associated with nginx images. By providing a fresh environment without requiring modifications to existing app containers, init containers can streamline the setup process for migrating to Chainguard images and enhance compatibility with existing workflows. This tutorial is centered around Chainguard's nginx container image, but the concepts outlined here are applicable when migrating to other Chainguard Containers as well. We have a number of resources available on migrating to Chainguard Containers, and we encourage you to get started with our [Overview of migrating to Chainguard Containers](/chainguard/containers/migration/migrations-overview/). For more information on working with Chainguard's nginx container image, check out our guide on [Getting started with nginx](/chainguard/containers/getting-started/web-and-data-services/nginx/). --- ### Proxy and cache Helm charts with Artifactory URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/helm-charts/proxy-and-cache.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Helm charts, Artifactory, iamguarded, Product This page shows you how to set up and use Chainguard Helm Charts with Artifactory via remote Helm OCI repositories. ## Create and configure Helm OCI repository in Artifactory In Artifactory's **Administration** module, select **Repositories**, then **Create a Repository**, and choose **Remote**. Set the package type to **Helm** and give the repository a key (name); this guide uses `iamguarded-charts`. For the full list of settings, refer to JFrog's [remote repositories documentation](https://docs.jfrog.com/artifactory/docs/remote-repositories). To determine values for the `User Name` and `Password / Access Token` fields, run the following command: ```bash chainctl auth configure-docker --pull-token --save ``` Output will look like this: ```bash To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" ``` In the repository's configuration, set the **URL** to `https://cgr.dev`, and set the **User Name** and **Password / Access Token** to the values from the command output above. Once created, the next step is to configure Helm to pull from the mirrored repository. We can then test that it works by pulling the Helm Chart from the repository. ## Configure Helm to pull from the mirrored repository and install > **NOTE:** In the following example, $JFROG_USERNAME and $JFROG_TOKEN refer to the Artifactory username and token used to gain access to the newly created Helm repository. For testing purposes, a quick way to generate this against any repository is by using the "Set Me Up" button in the top right corner of the UI. ```bash JFROG_USERNAME= # Your username, i.e. username@chainguard.dev JFROG_TOKEN= # Your token helm registry login -u $JFROG_USERNAME -p $JFROG_TOKEN chainguard.jfrog.io Login Succeeded helm pull oci://chainguard.jfrog.io/iamguarded-charts/YOUR-ORGANIZATION/iamguarded-charts/kafka Pulled: chainguard.jfrog.io/iamguarded-charts/YOUR-ORGANIZATION/iamguarded-charts/kafka:32.2.18 Digest: sha256:8cc051f049fbd75cfc84306bf11adf0ef4ea0d19bc3a1d3ae46284b3aab5b083 ``` Finally, we'll create a Kubernetes Secret that will be used to pull the `kafka-iamguarded` image from Artifactory and then run the `helm install` process. > **NOTE:** Similar to the Helm example, `$JFROG_USERNAME` and `$JFROG_TOKEN` refer to the Artifactory username and token used to gain access to your existing remote repository used to mirror Chainguard images from your organization. This may be different than the credentials used to gain access to your remote Helm repository. ```bash JFROG_USERNAME= # Your username, i.e. username@chainguard.dev JFROG_TOKEN= # Your token ORGANIZATION= # Your organization, i.e. YOUR-ORGANIZATION kubectl create secret docker-registry chainguard-pull-secret \ --docker-server=chainguard.jfrog.io \ --docker-username=$JFROG_USERNAME \ --docker-password=$JFROG_TOKEN secret/chainguard-pull-secret created helm install kafka oci://chainguard.jfrog.io/iamguarded-charts/YOUR-ORGANIZATION/iamguarded-charts/kafka \ --set image.registry=chainguard.jfrog.io \ --set image.repository=iamguarded-charts/YOUR-ORGANIZATION/iamguarded-charts/kafka \ --set "global.org=$ORGANIZATION" \ --set "global.imagePullSecrets[0].name=chainguard-pull-secret" Pulled: chainguard.jfrog.io/iamguarded-charts/YOUR-ORGANIZATION/iamguarded-charts/kafka:32.2.18 Digest: sha256:8cc051f049fbd75cfc84306bf11adf0ef4ea0d19bc3a1d3ae46284b3aab5b083 NAME: kafka LAST DEPLOYED: Fri Jul 11 15:10:56 2025 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None NOTES: CHART NAME: kafka CHART VERSION: 32.2.18 APP VERSION: 4.0.0 ``` A few things to note regarding the above values: - The `global.org` value is set to the organization name you're pulling Helm Charts from. This is specific to the Chainguard variant of the Helm Chart. - The `global.imagePullSecrets[0].name` value is set to the name of the Kubernetes Secret that will be used to pull the `kafka-iamguarded` image from Artifactory (or whichever registry images are being pulled from, for non-Artifactory setups). --- ### Chainguard OS FAQs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-os/faq.md Last Modified: September 28, 2026 Tags: Chainguard OS This FAQ addresses common questions about [Chainguard OS](/chainguard/chainguard-os/), the security-focused operating system that powers Chainguard's production container images and enterprise features. ## What is Chainguard OS? Chainguard OS is a minimal, hardened Linux-based operating system designed for secure, containerized software delivery. Built in-house by Chainguard, it serves as the foundation for Chainguard’s container products and emphasizes continuous integration, immutable artifacts, and alignment with upstream software. ## What is the relationship between Chainguard OS and Wolfi? Wolfi refers to the OS of Chainguard’s [free tier container images](/chainguard/containers/concepts/container-categories/#free-containers). Chainguard OS refers to the production-grade distribution that powers all other Chainguard products. Please note that mixing and matching content across Wolfi and Chainguard OS is not supported. ## What are the core principles behind Chainguard OS? Chainguard OS is built around four core principles: 1. Continuous Integration and Delivery (CI/CD) 1. Nano Updates and Rebuilds 1. Minimal, Hardened, Immutable Artifacts 1. Delta Minimization Each of these principles ensures that Chainguard OS can provide a more secure and efficient platform for software distribution. ## What makes Chainguard OS different from traditional Linux distributions? Chainguard OS is designed specifically for more secure and containerized application delivery. Our approach differs from traditional distros in several key ways: * No LTS model: instead of fixed major releases, Chainguard OS continuously delivers updates in alignment with upstream changes. * Purpose-built containers: Chainguard OS is focused on “application systems” instead of a general-purpose operating system. * Minimal package footprint: Chainguard OS ships only what is strictly needed, avoiding unnecessary libraries and tools. * Automation-driven: using CI/CD pipelines, Chainguard OS delivers more secure, tested, and verifiable artifacts. * Ephemeral design: Chainguard OS embraces container-native patterns, making updates and rollbacks trivial. ## What are the benefits of using Chainguard OS? * Security — reduced attack surface, hardened builds, and continuous patching. * Compliance — automatically generated SBOMs and provenance metadata for all artifacts. * Operational efficiency — reduces long upgrade cycles and manual patching. * Supply chain integrity — built using the [Chainguard Factory](https://www.youtube.com/watch?v=iU9hmW6hrGs) and adhering to [SLSA](https://slsa.dev/) standards. --- ### Configure JavaScript build tools URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/javascript/build-configuration.md Last Modified: October 7, 2026 Tags: Chainguard Libraries, JavaScript Chainguard Libraries for JavaScript works with your existing build tools — npm, pnpm, Yarn, and Bun — through a registry configuration change. This page is a reference for configuring each supported build tool. It covers registry configuration, authentication, cache clearing, and minimal example projects. Apply these changes on every workstation and build server that builds your applications or downloads libraries, including CI/CD infrastructure such as Jenkins, TeamCity, or GitHub Actions. The `https://libraries.cgr.dev/javascript` endpoint is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for JavaScript. By default, it serves only Chainguard-built artifacts. When [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) is enabled for your organization, the same endpoint can also serve requested versions from upstream, under Chainguard security controls. The same endpoint also serves remediated `-cgr.N` artifacts, so there is no separate remediated repository to configure. The registry configuration on this page does not select remediated versions on its own; to select them, run `chainctl libraries remediate` in your project. Refer to [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/) for details. This guide outlines how to configure your build tool. If you are looking for something else, refer to the following guides depending on your goals: | If you want to... | Use this page | | --- | --- | | Understand what Chainguard Libraries for JavaScript is and how it works | [JavaScript overview](/chainguard/libraries/javascript/overview/) | | Set up organization-wide access through a repository manager | [Global configuration](/chainguard/libraries/javascript/global-configuration/) | | Look up how to configure a specific build tool (npm, pnpm, Yarn, Bun) | This page | | Migrate an existing project step by step | [JavaScript migration guide](/chainguard/libraries/javascript/migration/) | | Install inside a container build without leaking credentials into the image | [Container builds](/chainguard/libraries/policies-and-security/build-containers/) | If a package or version is blocked by a policy or malware scan, your build tool returns an error. Refer to the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/) for more details. ## Access approaches ### Repository manager: JFrog Artifactory Build configuration to retrieve artifacts from Artifactory typically requires you to authenticate and use the identity token in the configuration of your build tool. Follow the steps from the [global configuration](/chainguard/libraries/javascript/global-configuration/#jfrog-artifactory) to determine URL and authentication details. ### Repository manager: Sonatype Nexus Repository Build configuration to retrieve artifacts from Nexus may require authentication. Use your username and password for Nexus in your build tool configuration. Follow the steps from the [global configuration](/chainguard/libraries/javascript/global-configuration/#sonatype-nexus-repository) to determine URL and authentication details. ### Direct access Build configuration to retrieve artifacts **directly** from the Chainguard Libraries for JavaScript repository at `https://libraries.cgr.dev/javascript/` requires authentication with username and password from a pull token as detailed in [access documentation](/chainguard/libraries/introduction/access/#pull-token). Refer to the minimal example projects on this page for demonstrations using direct access for each build tool. > Note: Direct access requires per-project and per-workstation configuration. For organizations with multiple teams, proxying through an artifact manager is recommended. Refer to the [global configuration](/chainguard/libraries/javascript/global-configuration/) for setup guides. ## Updating lockfile hashes When migrating an existing JavaScript project to Chainguard Libraries, your lockfile contains integrity hashes generated against packages previously downloaded from the npm registry or through your repository manager. Because Chainguard rebuilds packages in a secured build environment rather than distributing upstream artifacts directly, the resulting checksums differ even for identical package versions. These hashes must be updated before your package manager will accept packages from Chainguard. Learn more in the [JavaScript migration guide](/chainguard/libraries/javascript/migration/#step-3-update-your-lockfile) and in the [`chainctl libraries update-hashes` command docs](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/). ### Authentication `update-hashes` fetches checksums from Chainguard Libraries, which requires authentication. Where it fetches from depends on your environment: some setups authenticate directly to `libraries.cgr.dev`, but if your build routes through a repository manager configured as a pull-through proxy for Chainguard Libraries, point `update-hashes` there instead with `--registry-url` so it validates against the same source your build used. > **Note**: Running `chainctl libraries update-hashes` requires the `libraries.javascript.pull` permission or the Owner role. Authenticating to `libraries.cgr.dev` directly: - **Logged in locally**: Run the command while authenticated; if you have no other credential it authenticates with a [pull token](/chainguard/libraries/introduction/access/#pull-token) for your organization, prompting for one only if you have access to more than one. Pass `--parent ` to skip that prompt. To avoid it entirely, scope your login to the libraries registry once with `chainctl auth login --audience=libraries.cgr.dev` — that session is then used automatically. (`chainctl auth configure-npm` also sets up this libraries-scoped session as part of configuring npm.) - **CI or non-interactive**: - For a session token, pass `--token ` or set `CHAINCTL_AUTH_TOKEN`. The token must be scoped to the libraries registry; mint one with `chainctl auth token --audience=libraries.cgr.dev`. - For a [pull token](/chainguard/libraries/introduction/access/#pull-token) (an identity and secret), pass it as basic auth: `--username --password `, or set `CHAINCTL_REGISTRY_USERNAME` / `CHAINCTL_REGISTRY_PASSWORD`. - **From `~/.netrc`**: Credentials for the registry host are read from `~/.netrc` (or `$NETRC`); refer to [.netrc for authentication](/chainguard/libraries/introduction/access/#netrc). Pass `--ignore-netrc` to skip an unrelated entry. Authenticating to a repository manager: When you target a repository manager with `--registry-url`, authenticate with **that** registry's credentials — `--username`/`--password`, the `CHAINCTL_REGISTRY_USERNAME` / `CHAINCTL_REGISTRY_PASSWORD` environment variables, or a matching `~/.netrc` entry. Chainguard-scoped tokens are never sent to third-party hosts. Learn more about using a repository manager in the [global configuration documentation](/chainguard/libraries/javascript/global-configuration/). ## Adopting remediated JavaScript artifacts Pointing your build tool at the Chainguard registry does not select remediated `-cgr.N` artifacts on its own. JavaScript remediation operates on the resolved dependency tree in your lockfile, so you adopt it with `chainctl libraries remediate` after your project is configured to use the Chainguard registry and a lockfile exists. > **Note**: Running `chainctl libraries remediate` requires the > `libraries.javascript.pull` permission or the Owner role. First, preview the available remediations: ```bash chainctl libraries remediate ``` The `--apply` step reads credentials from a `.npmrc` file in your project directory. Some registry setup options on this page store those settings elsewhere, such as your user-level `.npmrc` or your package manager's own configuration, so you can finish registry setup without a project `.npmrc`. If you don't have one, create it by running `chainctl auth configure-npm` from the project directory: ```bash chainctl auth configure-npm ``` This writes a project-level `.npmrc` with the registry URL and credentials. Do not commit these credentials to version control; supply them through environment variables or CI secrets at build time. Yarn and pnpm authenticate differently; see [Yarn](#yarn) and [pnpm](#pnpm) for their setup steps. Then adopt them: ```bash chainctl libraries remediate --apply ``` With `--apply`, the command walks your lockfile and updates each eligible package to the corresponding Chainguard remediated version. How it records the change depends on where the package sits in your dependency tree: for a direct dependency, it sets the exact `-cgr.N` version in `package.json`; for a transitive dependency, it adds an override or resolution rule your package manager supports (for example, the `overrides` field for npm or `resolutions` for Yarn). The command also updates the lockfile. Only packages with a `-cgr.N` build for the exact version resolved in your lockfile are changed; every other dependency stays unchanged. Because `--apply` updates the lockfile and verifies the result before it changes your project, adoption is complete once the command succeeds. You don't need to regenerate the lockfile or rewrite hashes afterward. To confirm the change, review the modified files (`package.json` and your lockfile), run an install that preserves the lockfile, and run your application's tests. If you also changed your registry configuration, see the per-tool **Apply registry changes** sections for [npm](#npm-apply), [pnpm](#pnpm-apply), [Yarn](#yarn-apply), or [Bun](#bun-apply). Re-run `chainctl libraries remediate` after every install or dependency update so that newly resolved entries are evaluated. ### Adopt remediated versions manually You can also adopt a remediation manually, without `chainctl libraries remediate`: 1. Identify the exact remediated version. 1. Pin the exact `-cgr.N` version as an override or resolution in the `package.json` or lockfile (the `overrides` field for npm or Bun, `pnpm.overrides` for pnpm, or `resolutions` for Yarn). 1. Regenerate only the relevant lockfile entries. For example: - `npm install --package-lock-only` - `pnpm install --lockfile-only` - `yarn install` - `bun install` Review the lockfile diff and run your normal tests afterward. An override may affect multiple occurrences, and some package managers apply the rule tree-wide. Refreshing the lockfile alone doesn't adopt the remediation: because `-cgr.N` is an npm prerelease identifier, a semver range such as `^4.17.21` never matches it, so the override is what selects the remediated build. For remediated version naming and how remediation propagates through transitive dependencies, see [CVE remediation for Chainguard Libraries](/chainguard/libraries/policies-and-security/cve-remediation/). ## npm [npm](https://www.npmjs.com/) is the default package manager for Node.js, widely used for managing JavaScript dependencies and scripts. It allows developers to install, share, and manage packages for their projects. For more details, see the [npm documentation](https://docs.npmjs.com/). **Declare dependencies in package.json** With npm, you declare JavaScript package dependencies in a `package.json` file and separated into development and runtime dependencies. The following snippet shows a minimal example with a couple of dependencies each: ```json { "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.1.1", "node": "^22.18.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.1.5", }, "devDependencies": { "@eslint/js": "^9.14.0", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5" } } ``` By default, npm retrieves packages from the npm Registry at `https://registry.npmjs.org` and stores them locally in the `node_modules` directory of the project after running `npm install`. This operation also creates the `package-lock.json` file. Note that dependency versions are typically declared with the `^` before the version string. This indicates higher, compatible versions, following the semantic versioning scheme of the package are used automatically. For example, the declaration of version `^22.18.0` for `node`, actually results in the use of version `22.20.0` or even a higher version once available and `npm install` is run. Any dependency or dependency version changes require another install and therefore an update to the lock file. The lock file also encodes the checksum values in the `integrity` field and the download URL in the `resolved` field for each module. **Direct access: Point registry to Chainguard** To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to Chainguard in your user `.npmrc` file: ```bash npm config set registry https://libraries.cgr.dev/javascript/ ``` ### Using a repository manager To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to your repository manager in your user `.npmrc` file: ```shell npm config set registry https://repo.example.com:8443/repository/javascript-all/ ``` The command results in the following line in the `.npmrc` file: ```properties registry=https://repo.example.com:8443/repository/javascript-all/ ``` Refer to the [`npmrc` documentation](https://docs.npmjs.com/cli/v11/configuring-npm/npmrc) for alternative configurations, for example per project or globally, and details for configuring authentication. Example URLs: - JFrog Artifactory: `https://example.jfrog.io/artifactory/javascript-all/` - Sonatype Nexus: `https://repo.example.com:8443/repository/javascript-all/` - Direct access: `https://libraries.cgr.dev/javascript/` ### Apply registry changes To apply the registry changes, remove the `node_modules` directory, [update the artifact hashes](#updating-lockfile-hashes), then run the `npm install` command again. This re-fetches all packages from Chainguard and updates the lockfile in place with updated hashes: ```bash rm -rf node_modules chainctl libraries update-hashes npm install ``` As an alternative, you can remove the `node_modules` directory _and_ the `package-lock.json` file, then reinstall. This regenerates the lockfile and updates the hashes. Regenerating re-resolves your dependencies, so it can change your pinned versions, and any new versions published within your configured cooldown window will return an error. See [Update your lockfile](/chainguard/libraries/javascript/migration/#step-3-update-your-lockfile). **Clear caches** If you encounter stale or corrupted package data, clear the cache with: ```bash npm cache clean --force ``` To verify the cache is empty: ```bash npm cache verify ``` ### Using private npm packages alongside Chainguard Repository If your organization publishes its own packages to the public npm Registry under a scoped prefix (for example, `@your-org/package-name`), you may want those packages to be fetched directly from npm rather than going through the [Chainguard Repository](/chainguard/libraries/javascript/overview/); for example, to bypass the cooldown period for packages you own and trust. npm supports per-scope registry configuration, which lets you route packages with a specific prefix to a different registry: ``` # .npmrc registry=https://libraries.cgr.dev/javascript/ //libraries.cgr.dev/javascript/:_auth=${token} @your-org:registry=https://registry.npmjs.org/ ``` However, npm has a behavior where, when it fetches package metadata from a scoped registry, it may rewrite the resolved tarball URL in the lockfile to use the primary registry host (in this case, Chainguard Repository) instead of the scoped registry. This causes subsequent `npm install` runs to attempt to fetch your scoped packages from Chainguard Repository, resulting in a 404 or authentication error. To prevent this, add the following line to your `.npmrc`: ``` replace-registry-host=never ``` This tells npm never to rewrite the registry host in resolved URLs, so scoped packages remain associated with their correct upstream registry in the lockfile. After adding this line, verify your lockfile reflects the correct resolved URLs: scoped packages should resolve to `registry.npmjs.org` and all other packages should resolve to `libraries.cgr.dev/javascript`. ### Minimal example project For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). **1. Create a JavaScript project** Use the following steps to create a minimal example project for npm with Chainguard Libraries for JavaScript. ```shell mkdir npm-example cd npm-example npm init -y ``` **2. Configure the .npmrc file** Once the environment variables are set, configure the `.npmrc` file. Run the following command to configure registry access and authentication in the `.npmrc` file in the current project directory: ```shell chainctl auth configure-npm ``` [This command](/platform/chainctl/chainctl-docs/chainctl_auth_configure-npm/) writes a project-level `.npmrc` with the registry URL and base64-encoded credentials. It also prints the equivalent `npm config set` commands for use in CI or other environments where you need to configure `.npmrc` manually. If this command returns an error, ensure that you are using the [latest version of `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl). Alternatively, you can configure the `.npmrc` file manually:
Configure .npmrc manually

The following steps configure registry access with authentication in the .npmrc file in the current project directory:

export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)

npm config set registry https://libraries.cgr.dev/javascript/ --location=project
npm config set //libraries.cgr.dev/javascript/:_auth "${token}" --location=project

Note that the trailing slash in the registry URL is required, and that setting username and _password instead of auth with a token does not work with npm. The -w 0 option for base64 is required and supported by the GNU coreutils versions included in most operating systems.

**Example .npmrc file** ``` registry=https://libraries.cgr.dev/javascript/ //libraries.cgr.dev/javascript/:_auth=aWRlbnRpdHktaWQ6dG9rZW4= ``` The value of `auth` is the base64 encoding of `identity-id:token`, including the `/` character in the username. **3. Verify authentication with npm ping** Before installing packages, you can verify that authentication is configured correctly by running: ```bash npm ping --userconfig .npmrc ``` A successful response looks like: ```bash npm notice PING https://libraries.cgr.dev/javascript/ npm notice PONG 1065ms ``` The PONG response confirms that your credentials are valid and the registry is reachable. If the command fails, check that the .npmrc file exists in the current directory and that your token has not expired. **4. Add dependencies and build the project** Add dependencies for your project into the `package.json` file to test retrieval from Chainguard Libraries, build the project, and list the dependencies: ```shell npm add commander@4.1.1 npm install npm list ``` Following this, find the downloaded package in `node_modules/commander`. The commands also result in the creation of the lock file `package-lock.json`, which contains the source URL for each package in the `resolved` field. Adjust the registry configuration to use your repository manager and add any other desired packages for further testing. ## pnpm [pnpm](https://pnpm.io/) is a fast, disk space-efficient package manager for JavaScript, designed as an alternative to npm and Yarn. For more information, refer to the [pnpm documentation](https://pnpm.io/motivation). **Limitations** Before getting started, note the following limitations: - The Chainguard Repository [upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) has been tested with pnpm v11. We recommend using pnpm v11 or newer. - pnpm v11 re-verifies lockfile entries during install, including when you run `pnpm install --frozen-lockfile`. With Chainguard Repository for JavaScript, this can cause errors even when the lockfile is up to date. If this issue occurs, set `trustLockfile: true` in the `pnpm-workspace.yaml` to configure pnpm to trust the existing lockfile. - Setting `lockfileIncludeTarballUrl: true` in the `pnpm-workspace.yaml` ensures pnpm continues to fetch the upstream tarball URL recorded in the lockfile. Without this setting, you can encounter integrity errors when Chainguard builds a package version that had previously been mirrored from upstream. This helps avoid integrity errors until the lockfile is updated. - If you use Chainguard Libraries with pnpm `trustPolicy: no-downgrade`, pnpm may fail installation. Because Chainguard Libraries serves rebuilt packages, pnpm may treat those packages as a trust downgrade. To work around this, disable it in your pnpm configuration: `trustPolicy: off` **Declare dependencies in package.json** With pnpm, you declare JavaScript package dependencies in a `package.json` file and separated into development and runtime dependencies. The following snippet shows a minimal example with a couple of dependencies each: ```json { "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.1.1", "node": "^22.18.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.1.5", }, "devDependencies": { "@eslint/js": "^9.14.0", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5" } } ``` By default, pnpm retrieves the packages the npm Registry at `https://registry.npmjs.org` and stores them locally in the `node_modules` directory of the project after running `pnpm install`. This operation also creates the `pnpm-lock.yaml` file. Note that dependency versions are typically declared with the `^` before the version string. This indicates higher, compatible versions, following the semantic versioning scheme of the package, are used automatically. For example, the declaration of version `^22.18.0` for `node`, actually results in the use of version `22.20.0` or even a higher version once available and pnpm install is run. Any dependency or dependency version changes require another install and therefore an update to the lock file. The lock file also encodes the checksum values in the `integrity` field and other information for each module. **Direct access: Point registry to Chainguard** To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to Chainguard and set auth credentials. The following command writes to the project-level configuration: ```bash export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0) pnpm config set registry https://libraries.cgr.dev/javascript/ --location=project pnpm config set //libraries.cgr.dev/:_auth "${token}" --location=project ``` To set the registry at the user level instead of project-level, omit the `--location=project` flag. The configuration should look like the following: ```properties registry=https://libraries.cgr.dev/javascript/ //libraries.cgr.dev/:_auth= ``` The registry URL always points to the `/javascript/` repository; you do not point it at `/javascript-upstream/`. The authentication entry, however, is keyed to the whole `libraries.cgr.dev` host (`//libraries.cgr.dev/`) rather than to the `/javascript/` path alone. This matters because packages that Chainguard has not yet rebuilt are served through the [upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) at `https://libraries.cgr.dev/javascript-upstream/`, and pnpm authenticates against whatever tarball URL the registry returns for each package. A credential keyed to the host covers both the `/javascript/` and `/javascript-upstream/` paths. If you prefer to scope credentials per path, you must configure **both** paths. Otherwise, installs that resolve upstream-fallback packages fail with an HTTP 401 error: ```properties registry=https://libraries.cgr.dev/javascript/ //libraries.cgr.dev/javascript/:_auth= //libraries.cgr.dev/javascript-upstream/:_auth= ``` ### Using a repository manager To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to your repository manager in your user `.npmrc` file: ```shell pnpm config set registry https://repo.example.com:8443/repository/javascript-all/ ``` The command results in the following line in the `.npmrc` file: ```properties registry=https://repo.example.com:8443/repository/javascript-all/ ``` Refer to the [pnpm registry documentation](https://pnpm.io/settings#registry--authentication-settings) for alternative configurations, for example per project or globally, and details for configuring authentication. Example URLs: - JFrog Artifactory: `https://example.jfrog.io/artifactory/javascript-all/` - Sonatype Nexus: `https://repo.example.com:8443/repository/javascript-all/` - Direct access: `https://libraries.cgr.dev/javascript/` ### Apply registry changes To apply the registry changes, remove the `node_modules` directory, [update the artifact hashes](#updating-lockfile-hashes), then run the `pnpm install` command again. This re-fetches all packages from Chainguard and updates the lockfile in place with updated hashes: ```bash rm -rf node_modules chainctl libraries update-hashes pnpm install ``` As an alternative, you can remove the `node_modules` directory _and_ the `pnpm-lock.yaml` file, then reinstall. This regenerates the lockfile and updates the hashes. Regenerating re-resolves your dependencies, so it can change your pinned versions, and any new versions published within your configured cooldown window will return an error. See [Update your lockfile](/chainguard/libraries/javascript/migration/#step-3-update-your-lockfile). **Clear pnpm caches** pnpm has three separate layers of cached data. If you encounter stale or corrupted package data, you can clear all of these caches:
Clear pnpm caches

Metadata (packuments)

There is an experimental command to delete the metadata cache:

pnpm cache delete

HTTP cache

pnpm maintains a separate HTTP cache at ~/.cache/pnpm (or $XDG_CACHE_HOME/pnpm if that variable is set). Delete it with:

rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/pnpm"

Content-addressable store (tarballs)

To find and manually remove the full store:

pnpm store path

Then delete the directory that command outputs:

On macOS/Linux -

rm -rf "$(pnpm store path)"

On Windows -

Use the path returned by pnpm store path and delete it via File Explorer or rmdir.

Note: pnpm prune removes unused tarballs but does not remove packument metadata. If you are seeing 404 errors after switching to or updating the Chainguard registry endpoint, use the commands above rather than pnpm prune.

### Update lockfile hashes If you are migrating an existing project and want to preserve your current lockfile, use [`chainctl libraries update-hashes`](#updating-lockfile-hashes) to update only the integrity hashes in place instead. Now you can proceed with your development and testing. ### Minimal example project Use the following steps to create a minimal example project for pnpm with Chainguard Libraries for JavaScript. ```shell mkdir pnpm-example cd pnpm-example pnpm init ``` For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). To create pull token credentials and set them as environment variables, run: ```bash eval $(chainctl auth pull-token --output env --repository=javascript) ``` Once the environment variables are set, the following steps configure registry access and authentication in the `.npmrc` file in the current project directory: ```bash export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0) pnpm config set registry https://libraries.cgr.dev/javascript/ --location=project pnpm config set //libraries.cgr.dev/:_auth "${token}" --location=project ``` The trailing slash in the registry URL is required. The authentication entry is keyed to the whole `libraries.cgr.dev` host (`//libraries.cgr.dev/`) so that it also covers packages served through the [upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) path. Note that the `-w 0` option for `base64` is required and supported by the GNU coreutils versions included in most operating systems. Alternatively, you can set `username` and `_password` instead of `_auth`. Note that the `_password` value must still be base64-encoded; pnpm does not accept a raw token here: ```bash pnpm config set //libraries.cgr.dev/:username "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}" --location=project pnpm config set //libraries.cgr.dev/:_password "$(echo -n "${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)" --location=project ``` Add dependencies for your project into the `package.json` file to test retrieval from Chainguard Libraries, build the project, and list the dependencies: ```shell pnpm add commander@4.1.1 pnpm install pnpm list ``` Following this, find the downloaded package in `node_modules/.pnpm/commander@4.1.1` and `node_modules/commander`. The commands also result in the creation of the lock file `pnpm-lock.yaml`, which contains the source URL for each package in the `tarball` field. Adjust the registry configuration to use your repository manager and add any other desired packages for further testing. ## Yarn [Yarn](https://yarnpkg.com/) is a popular package manager for JavaScript projects, offering fast, reliable, and secure dependency management as an alternative to npm. It is widely used for managing project dependencies, scripts, and workflows in Node.js and other JavaScript development environments. For more details, refer to the [Yarn documentation](https://yarnpkg.com/getting-started). This section applies to modern versions of Yarn, also known as Yarn Berry, with versions 2.x and higher. If you are using Yarn 1.x refer to the [Yarn Classic section](#yarn-classic). **Declare dependencies in package.json** With Yarn, you declare JavaScript package dependencies in a `package.json` file and separated into different scoped dependencies such as development and runtime dependencies. The following block shows a minimal example with `react` and `node` as main runtime dependencies and `eslint` as development dependency: ```json { "name": "yarn-berry-example", "packageManager": "yarn@4.10.3", "dependencies": { "node": "^22.20.0", "react": "^19.1.1" }, "devDependencies": { "eslint": "^9.36.0" } } ``` By default, Yarn retrieves the packages from the registry at `https://registry.yarnpkg.com` and stores them locally in the `.yarn` folder in the user's home directory after running `yarn`. Specific packages are linked into the project. This operation also creates the `yarn.lock` file. Note that dependency versions are typically declared with the `^` before the version string. This indicates higher, compatible versions, following the semantic versioning scheme of the package, are used automatically. For example, the declaration of version `^22.18.0` for `node`, actually results in the use of version `22.20.0` or even a higher version once available and `yarn` is run. Any dependency or dependency version changes require another install and therefore an update to the lock file. The lock file also encodes the checksum values in the `checksum` field. **Direct access: Point registry to Chainguard** To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to Chainguard in your user `.yarnrc` file: ```bash yarn config set npmRegistryServer https://libraries.cgr.dev/javascript/ ``` ### Using a repository manager To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to your repository manager in your project `.yarnrc.yml` file: ```shell yarn config set npmRegistryServer https://repo.example.com:8443/repository/javascript-all ``` The command results in the following line in the `.yarnrc.yml` file: ``` npmRegistryServer: "https://repo.example.com:8443/repository/javascript-all" ``` Refer to the [`config set` documentation](https://yarnpkg.com/cli/config/set) for more details such as authentication support. Example URLs: - JFrog Artifactory: `https://example.jfrog.io/artifactory/javascript-all` - Sonatype Nexus: `https://repo.example.com:8443/repository/javascript-all` - Direct access: `https://libraries.cgr.dev/javascript` ### Apply registry changes To apply the registry change, run the `yarn` command again. This forces an update of all packages from the new registry and regeneration of the lock file. If you encounter stale or corrupted package data, clear the cache with: ```bash yarn cache clean --all ``` If you're seeing checksum mismatch errors rather than stale data, you can control Yarn's behavior via the [`checksumBehavior` setting](https://yarnpkg.com/configuration/yarnrc#checksumBehavior) in `.yarnrc.yml`. Setting it to `reset` causes Yarn to purge and re-fetch any cache entry whose checksum doesn't match, without clearing the entire cache: ```yaml checksumBehavior: reset ``` ### Update lockfile hashes If you are migrating an existing project and want to preserve your current lockfile, use [`chainctl libraries update-hashes`](#updating-lockfile-hashes) to update only the integrity hashes in place instead. #### Using Chainguard Libraries with `yarn install --immutable` Yarn Berry's `--immutable` flag prevents changes to `yarn.lock` during installation. Migrating to Chainguard Libraries requires a lockfile preparation step before your normal `--immutable` workflow can resume. Yarn Berry's lockfile stores both resolved `__archiveUrl` entries and Yarn-specific checksums (used by `--immutable` to detect tampering). Run the following command to update both: ```bash chainctl auth configure-npm TOKEN=$(grep '_authToken=' .npmrc | sed 's/.*_authToken=//') chainctl libraries update-hashes --token "$TOKEN" yarn.lock yarn install --mode=update-lockfile ``` After running these commands, verify that your lockfile is stable under `--immutable`: ```bash yarn install --immutable ``` If this command exits without error and without modifying `yarn.lock`, your lockfile is ready to commit. Expand the following section to learn about recommended workflows.
Workflows when using the --immutable flag

Recommended workflow for dependency updates

When updating dependencies, repeat the lockfile preparation steps before committing:

chainctl auth configure-npm
TOKEN=$(grep '_authToken=' .npmrc | sed 's/.*_authToken=//')
chainctl libraries update-hashes --token "$TOKEN" yarn.lock
yarn install --mode=update-lockfile
yarn install --immutable
yarn build
yarn test

Review the yarn.lock diff before committing. Expected changes are updated __archiveUrl resolutions pointing to Chainguard, updated Yarn checksum entries, and any intentional dependency version changes. Unexpected changes to package versions should be investigated before committing.

Recommended CI workflow

After the updated lockfile is committed, your CI configuration does not need to change. Use your standard immutable install:

yarn install --immutable
yarn build
yarn test

For stricter checksum and cache validation, you can also add --check-cache:

yarn install --immutable --check-cache

Do not commit .npmrc credentials to version control. Use environment variables or CI secrets to supply credentials at build time.

### Minimal example project Use the following steps to create a minimal example project for yarn with Chainguard Libraries for JavaScript. The script sets the policy to use the latest stable release of Yarn. ```shell mkdir yarn-berry-example cd yarn-berry-example yarn policies set-version stable yarn init ``` For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). Once the environment variables are set, the following steps configure registry access with authentication in the `.yarnrc.yml` file in the current project directory: ```shell export authInfo="${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" yarn config set npmRegistryServer https://libraries.cgr.dev/javascript yarn config set 'npmRegistries["//libraries.cgr.dev/javascript"].npmAuthIdent' "${authInfo}" yarn config set 'npmRegistries["//libraries.cgr.dev/javascript"].npmAlwaysAuth' "true" ``` Note the following details: - The `authInfo` token is passed as authentication identity `npmAuthIdent` and only uses the username and password values from the pull token separated by colon without any further encoding. - Setting `npmAlwaysAuth` is required. Add dependencies for your project into the `package.json` file to test retrieval from Chainguard Libraries, build the project, and list the dependencies: ```shell yarn add commander@4.1.1 yarn install yarn info ``` Following this, find the downloaded package in the local shared cache. The commands also result in the creation of the lock file `yarn.lock`, which contains the source URL for each package in the `archiveUrl` parameter of the `resolution` field. Adjust the registry configuration to use your repository manager and add any other desired packages for further testing. ## Yarn Classic [Yarn Classic](https://classic.yarnpkg.com/) is the legacy 1.x release of [Yarn](#yarn). With Yarn, you declare JavaScript package dependencies in a `package.json` file and separated into different scoped dependencies such as development and runtime dependencies. The following block shows a minimal example with `react` and `node` as main runtime dependencies and `eslint` as development dependency: ```json { "name": "yarn-classic-example", "version": "1.0.0", "description": "A minimal example project for using yarn classic", "main": "index.js", "author": "Chainguard", "license": "MIT", "private": false, "dependencies": { "node": "^22.18.0", "react": "^19.1.1" }, "devDependencies": { "eslint": "^9.36.0" } } ``` By default, Yarn retrieves the packages from the registry at `https://registry.yarnpkg.com` and stores them locally in the `node_modules` directory of the project after running `yarn`. This operation also creates the `yarn.lock` file. Note that dependency versions are typically declared with the `^` before the version string. This indicates higher, compatible versions, following the semantic versioning scheme of the package, are used automatically. For example, the declaration of version `^22.18.0` for `node`, actually results in the use of version `22.20.0` or even a higher version once available and `yarn` is run. Any dependency or dependency version changes require another install and therefore an update to the lock file. The lock file also encodes the checksum values in the `integrity` field and the download URL in the `resolved` field for each module. **Direct access: Point registry to Chainguard** To change a project to use Chainguard Libraries for JavaScript, first export your pull token as base64-encoded environment variables: ```bash export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0) ``` Then, set auth and set the registry URL to point to Chainguard in your user `.npmrc` file: ```bash cat > .npmrc << 'EOF' registry=https://libraries.cgr.dev/javascript/ //libraries.cgr.dev/javascript/:_auth="$token" //libraries.cgr.dev/javascript/:always-auth=true //libraries.cgr.dev/javascript-upstream/:_auth="$token" //libraries.cgr.dev/javascript-upstream/:always-auth=true EOF ``` ### Using a repo manager To change a project to use Chainguard Libraries for JavaScript, set the registry URL to point to your repository manager in your `.npmrc` file: ``` cat > .npmrc << EOF registry=https://repo.example.com:8443/repository/javascript-all EOF ``` Example URLs: - JFrog Artifactory: `https://example.jfrog.io/artifactory/javascript-all` - Sonatype Nexus: `https://repo.example.com:8443/repository/javascript-all` - Direct access: `https://libraries.cgr.dev/javascript` ### Apply registry changes Note that you can also use the `yarn config set registry` command to set the registry in the `.yarnrc` file, however this approach does not support authentication as typically required for repository managers as well as for direct access to Chainguard Libraries for JavaScript. Refer to the [`.yarnrc` documentation](https://classic.yarnpkg.com/lang/en/docs/yarnrc/) for more details. To apply the registry changes, remove the `node_modules` directory, [update the artifact hashes](#updating-lockfile-hashes), then run the `yarn install` command again. This re-fetches all packages from Chainguard and updates the lockfile in place with updated hashes: ```bash rm -rf node_modules chainctl libraries update-hashes yarn install ``` As an alternative, you can remove the `node_modules` directory _and_ the `yarn-lock.json` file, then reinstall. This regenerates the lockfile and updates the hashes. Regenerating re-resolves your dependencies, so it can change your pinned versions, and a release still inside your configured cooldown window returns a 404 error. See [Update your lockfile](/chainguard/libraries/javascript/migration/#step-3-update-your-lockfile). Another option, after deleting `node_modules` and `yarn-lock.json`, is to run `yarn upgrade`. This updates all dependencies to their latest allowed versions and regenerates the lock file with updated hashes. If you encounter stale or corrupted package data, clear the cache with: ```bash yarn cache clean ``` ### Minimal example project Use the following steps to create a minimal example project for yarn with Chainguard Libraries for JavaScript. ```shell mkdir yarn-classic-example cd yarn-classic-example yarn init -y ``` For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). Once the environment variables are set, the following steps configure registry access with authentication in the `.npmrc` file directory: ```shell export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0) cat > .npmrc << EOF registry=https://libraries.cgr.dev/javascript/ //libraries.cgr.dev/javascript/:_auth="$token" //libraries.cgr.dev/javascript/:always-auth=true EOF ``` Note the following details: - Using yarn configuration files such as `.yarnrc` and commands like `yarn config set registry` does not work with authentication details, and the proposed approach with `.npmrc` file is preferable. - The `token` token is passed as authentication token `_auth` and uses the username and password values from the pull token separated by colon in `base64` encoding. Note that the `-w 0` option for `base64` is required and supported by the GNU coreutils versions included in most operating systems. - Setting `always-auth` is required. - The trailing slash in the registry URL and authentication references to it is required. Add dependencies for your project into the `package.json` file to test retrieval from Chainguard Libraries, build the project, and list the dependencies: ```shell yarn add commander@4.1.1 yarn install yarn list ``` Following this, find the downloaded package in the `node_modules` directory. The commands also result in the creation of the lock file `yarn.lock`, which contains the source URL for each package in the `resolved` field. Adjust the registry configuration to use your repository manager and add any other desired packages for further testing. ## Bun [Bun](https://bun.com/) is a fast, all-in-one JavaScript runtime, bundler, and package manager designed as an alternative to Node.js tooling. It provides an integrated package manager that is compatible with the npm ecosystem. **Declare dependencies in package.json** With Bun you declare dependencies in a `package.json` file just like [npm](#npm). The following snippet shows a minimal example: ```json { "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.1.1", "node": "^22.18.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.1.5" }, "devDependencies": { "@eslint/js": "^9.14.0", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5" } } ``` By default Bun installs packages from the npm Registry at `https://registry.npmjs.org` and stores them locally in the `node_modules` directory after running `bun install`. Bun creates a binary lockfile named `bun.lock` to record resolved versions and checksums. Note that dependency versions are typically declared with `^` to allow compatible newer releases under semantic versioning. For example, `^22.18.0` for `node` can result in `22.20.0` or higher when `bun install` runs. Any dependency or version changes require running `bun install` again, which updates the lockfile. ### Using a repository manager To switch a project to use Chainguard Libraries for JavaScript, point Bun at your repository manager. Add the [registry configuration](https://bun.com/docs/runtime/bunfig#install-registry) to the `bunfig.toml` file of your project: ` ```toml [install] # set default registry as a string registry = "https://repo.example.com:8443/repository/javascript-all/" ``` Alternatively you can use an [`.npmrc` file](#npm). You can also temporarily override for install: ```shell bun install --registry=https://repo.example.com:8443/repository/javascript-all/ ``` Refer to [Bun documentation](https://bun.com/docs) for additional registry and authentication options. Example registry URLs: - JFrog Artifactory: https://example.jfrog.io/artifactory/javascript-all/ - Sonatype Nexus: https://repo.example.com:8443/repository/javascript-all/ - Direct access: https://libraries.cgr.dev/javascript/ ### Apply registry changes To apply the registry changes, remove the `node_modules` directory, [update the artifact hashes](#updating-lockfile-hashes), then run the `bun install` command again. This re-fetches all packages from Chainguard and updates the lockfile in place with updated hashes: ```bash rm -rf node_modules chainctl libraries update-hashes bun install ``` As an alternative, you can remove the `node_modules` directory _and_ the `bun-lock.json` file, then reinstall. This regenerates the lockfile and updates the hashes. Regenerating re-resolves your dependencies, so it can change your pinned versions, and a release still inside your configured cooldown window returns a 404 error. See [Update your lockfile](/chainguard/libraries/javascript/migration/#step-3-update-your-lockfile). ### Minimal example project Use the following steps to create a minimal example project for bun with Chainguard Libraries for JavaScript. ```shell mkdir bun-example cd bun-example bun init -y ``` For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). Once the environment variables are set, the following steps configure registry access with authentication in the `bunfig.toml` file in the current project directory: ```shell cat > bunfig.toml << EOF [install.registry] url = "https://libraries.cgr.dev/javascript/" username = "$CHAINGUARD_JAVASCRIPT_IDENTITY_ID" password = "$CHAINGUARD_JAVASCRIPT_TOKEN" EOF ``` Note that the trailing slash in the registry URL is required. Add dependencies for your project into the `package.json` file to test retrieval from Chainguard Libraries, build the project, and list the dependencies: ```shell bun add commander@4.1.1 bun install bun pm ls ``` Following this, find the downloaded package in `node_modules/commander`. The commands also result in the creation of the lock file `bun.lock`, which contains the source URL for each package in the `packages` section. Adjust the registry configuration to use your repository manager and add any other desired packages for further testing. --- ### Using chainctl to manage Custom Assembly resources URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/custom-assembly-chainctl.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural, Custom Assembly Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) is a tool that allows customers to create customized containers with extra packages and annotations added. This enables customers to reduce their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs. You can use [`chainctl`, Chainguard's command-line interface tool](/platform/chainctl/), to further customize your Custom Assembly builds and retrieve information about them. This guide provides an overview of the relevant `chainctl` commands and outlines how you can edit the configuration of Custom Assembly containers, as well as retrieve a list of a customized image's builds and its build logs. > **Note**: This tutorial highlights using `chainctl` to interact with Custom Assembly resources. However, you can also interact with Custom Assembly using [the Chainguard Console](/chainguard/containers/custom-assembly/custom-assembly-console/), as well as [the Chainguard API](/chainguard/containers/custom-assembly/custom-assembly-api-demo/). ## Adding packages to a customized container image `chainctl` offers two ways to change the packages in a Custom Assembly container image. The `edit` subcommand opens the build configuration in your text editor, and the `apply` subcommand takes a configuration file you've already written. Both produce the same result, and both start a build once you confirm the change. For a shorter, task-first version of these procedures, along with how to find a package name and how to confirm the package reached the finished image, see [Adding a package to a Chainguard Container](/chainguard/containers/building-and-modifying/adding-packages/). ### Editing packages interactively To edit one of your organization's Custom Assembly container images, you can run the `chainctl images repos build edit` command: ```shell chainctl images repos build edit --repo $CONTAINER ``` This example includes the `--repo` argument, which points to the name of the image you want to customize. If you omit it, `chainctl` prompts you to select a container image interactively. It also prompts for the organization when you have access to more than one; pass `--parent=` to skip that prompt. This command will open up a file with your machine's default text editor. This file will contain a structure like the following: ```yaml contents: packages: - yarn - wget ``` Edit this file by adding or removing any packages you like. Then, save and close the file. Note that you can undo all the customization and return the image to its original state by removing every entry listed under `packages:`. Before applying the change, `chainctl` will outline the changes you made and prompt you to confirm that you want to move forward with the change: ```output /tmp/3352123767.yaml (-deletion / +addition): contents: packages: - yarn - - wget + - bash Applying build config to $CONTAINER Are you sure? Do you want to continue? [y,N]: ``` Enter `y` to apply the changes. Following that, you'll be able to see the updated builds in the Chainguard Console, though it may take a few minutes for these changes to populate. ### Applying packages non-interactively To edit a customized container image without any interactivity, you can use the `apply` subcommand. This method requires you to have a YAML file listing the desired packages, like the example created with this command: ```shell cat > build.yaml < **Note**: This feature only affects which repository URLs are written into the image for runtime use. Build-time package resolution always uses Chainguard's `apk.cgr.dev` repositories. For more details on limitations and compatibility, refer to the [Custom runtime repositories overview](/chainguard/containers/custom-assembly/overview/#custom-runtime-repositories). To add custom runtime repositories, use `chainctl images repos build edit` as with other customizations: ```shell chainctl images repos build edit --repo $CONTAINER ``` In the text editor, add a `runtime_repositories` field under `contents`: ```yaml contents: packages: - curl - jq runtime_repositories: - https://apk-mirror.example.com/chainguard - https://apk-mirror.example.com/extras ``` After saving and confirming the changes, the resulting image's `/etc/apk/repositories` will contain only your custom URLs: ``` https://apk-mirror.example.com/chainguard https://apk-mirror.example.com/extras ``` The default `virtualapk.cgr.dev` entries aren't present in the image. You can also apply custom runtime repositories declaratively using the `apply` subcommand: ```shell cat > build.yaml <`), because `apk` looks up the key by that name during verification. `chainctl` uses filenames verbatim and returns an error if two files share the same basename. You can also define runtime keys in the editor or in a YAML manifest by adding a `runtime_keyring` field under `contents`. Each entry has a `name` and the key's PEM `content`: ```yaml contents: packages: - curl - jq runtime_repositories: - https://apk-mirror.example.com/chainguard runtime_keyring: - name: key-ee8fa0a3.rsa.pub content: | -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- ``` Apply the manifest with the `apply` subcommand as with other customizations. In the confirmation diff, each key appears as the SHA-256 digest of its content rather than the raw PEM text. To confirm that a digest matches a local key file, run `sha256sum `. When you run the `edit` subcommand, the editor buffer includes any existing runtime keys. Keep the `runtime_keyring` section to preserve the keys, edit an entry to replace a key, or delete the field to remove all custom keys from the image. #### Key validation rules Runtime keys are validated when the configuration is applied. The following rules apply: * Each entry must contain exactly one PEM-encoded RSA public key: a single `PUBLIC KEY` block with no other content before or after it. Certificates, OpenSSH keys, and EC keys aren't accepted. * Key names must be unique. * Key names can't be empty, begin with a dot, or contain path separators. * Key names can't begin with `chainguard` or `wolfi`; these prefixes are reserved. * The combined size of all keys can't exceed 512 KB. ## Retrieving information about Custom Assembly containers You can also use the `list` subcommand to retrieve every one of a customized image's builds from the past 24 hours: ```shell chainctl images repos build list --repo $REPO ``` This command is useful for quickly determining which builds were successful or failed: ``` START TIME | COMPLETION TIME | RESULT | TAGS --------------------------------+-------------------------------+---------+--------------------------------------------- Thu, 01 May 2025 10:10:40 PDT | Thu, 01 May 2025 10:10:45 PDT | Success | 20, 20.19, 20.19.1 Thu, 01 May 2025 10:10:34 PDT | Thu, 01 May 2025 10:10:46 PDT | Success | 22-slim, 22.15-slim, 22.15.0-slim Thu, 01 May 2025 10:10:33 PDT | Thu, 01 May 2025 10:10:41 PDT | Success | 23, 23.11, 23.11.0, latest . . . ``` Lastly, you can also retrieve the logs for a given build with the `logs` subcommand: ```shell chainctl images repos build logs --repo $REPO ``` This command will prompt you to select the build report you want to view. These are organized in reverse chronological order by the time of each build: ```output Select a build report to view logs: Wed, 16 Apr 2025 16:36:52 PDT - Wed, 16 Apr 2025 16:37:08 PDT Success (18-dev, 18.20-dev, 18.20.8-dev) > Wed, 16 Apr 2025 16:36:45 PDT - Wed, 16 Apr 2025 16:37:00 PDT Success (20-dev, 20.19-dev, 20.19.0-dev) Wed, 16 Apr 2025 16:36:42 PDT - Wed, 16 Apr 2025 16:36:52 PDT Success (18, 18.20, 18.20.8) Wed, 16 Apr 2025 16:36:41 PDT - Wed, 16 Apr 2025 16:36:51 PDT Success (22-slim, 22.14-slim, 22.14.0-slim) Wed, 16 Apr 2025 16:36:32 PDT - Wed, 16 Apr 2025 16:36:57 PDT Success (22-dev, 22.14-dev, 22.14.0-dev) Wed, 16 Apr 2025 16:36:32 PDT - Wed, 16 Apr 2025 16:36:44 PDT Success (20-slim, 20.19-slim, 20.19.0-slim) Wed, 16 Apr 2025 16:36:29 PDT - Wed, 16 Apr 2025 16:36:41 PDT Success (23-slim, 23.11-slim, 23.11.0-slim) Wed, 16 Apr 2025 16:36:19 PDT - Wed, 16 Apr 2025 16:36:29 PDT Success (23, 23.11, 23.11.0, latest) Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:42 PDT Success (23-dev, 23.11-dev, 23.11.0-dev, latest-dev) Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:31 PDT Success (20, 20.19, 20.19.0) Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:31 PDT Success (22, 22.14, 22.14.0) Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:18 PDT Success (18-slim, 18.20-slim, 18.20.8-slim) Wed, 16 Apr 2025 16:35:35 PDT - Wed, 16 Apr 2025 16:35:47 PDT Success •••••••••••••• ↑/k up • ↓/j down • / filter • q quit • ? more ``` Highlight your chosen build report and select it by pressing `ENTER`. This will open up the build's logs: ```output 2025-04-17T16:00:08-07:00[INFO]Building image with locked configuration: {Contents:{BuildRepositories:[] RuntimeRepositories:[https://apk.cgr.dev/45a0c61eEXAMPLEf050c5fb9ac06a69eed764595] ``` ## Making bulk changes across repos You are not required to make changes to custom assembly repos one at a time. Instead, you can make bulk changes to customize across multiple repos at the same time. For example, if you wanted to apply custom certs at scale across every repo you own with "kubernetes-*" you could use this: ```shell chainctl images repos build apply \ --repo="kubernetes-*" \ --with-certificates=ca.pem ``` You can use `chainctl images repos build apply` to do things like: * Pass `--repo` multiple times: `--repo=python --repo=nginx --repo=node` * Use wildcards: `--repo="python-*"` or `--repo="*"` (all repos under the parent) * Combine with `--dry-run` to see which repos would change (drift detection) without actually updating anything. For more information, refer to [chainctl images repos build apply](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_apply/). ## Customizing specific tags The `chainctl images repos build` commands apply a customization to every tag in a repository. To apply a customization to some tags only, use the `chainctl images overlays` commands. For details, refer to [Manage Custom Assembly Overlays with chainctl](/chainguard/containers/custom-assembly/overlays/chainctl/). ## Learn more You can also use `chainctl` to add custom certificates to your Custom Assembly images. Refer to our guide on [Adding custom certificates with Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/#using-chainctl-to-add-custom-certificates-using-custom-assembly) for more information. Additionally, you can interact with Custom Assembly with the [Chainguard API](/platform/api/spec/). Our tutorial on [Using the Chainguard API to manage Custom Assembly resources](/chainguard/containers/custom-assembly/custom-assembly-api-demo/) outlines how to run a demo application that updates the configuration of a Custom Assembly container through the Chainguard API. --- ### Configure Java build tools URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/java/build-configuration.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Java Chainguard Libraries for Java works with your existing build tools — Maven, Gradle, and Bazel — through a repository configuration change. This page is a reference for configuring each supported build tool. It covers repository access, authentication, cache clearing, and minimal example projects. Apply these changes on every workstation and build server that builds your applications or downloads libraries, including CI/CD infrastructure such as Jenkins, TeamCity, or GitHub Actions. The `https://libraries.cgr.dev/java/` endpoint is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for Java. By default, it serves only Chainguard-built artifacts. When [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) is enabled for your organization, the same endpoint can also serve requested versions from Maven Central under Chainguard security controls. This guide outlines how to configure your build tool. If you are looking for something else, refer to the following guides depending on your goals: | If you want to... | Use this page | | --- | --- | | Understand what Chainguard Libraries for Java is and how it works | [Java overview](/chainguard/libraries/java/overview/) | | Set up organization-wide access through a repository manager | [Global configuration](/chainguard/libraries/java/global-configuration/) | | Migrate an existing project step by step | [Java migration guide](/chainguard/libraries/java/migration/) | | Install inside a container build without leaking credentials into the image | [Container builds](/chainguard/libraries/policies-and-security/build-containers/) | If a package or version is blocked by a policy or malware scan, your build tool returns an error. Refer to the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/) for more details. ## Library access approaches ### Repo manager #### Cloudsmith Build configuration to retrieve artifacts from Cloudsmith requires you to authenticate. Use your username and password for Cloudsmith in your build tool configuration. Follow the steps from the [global configuration](/chainguard/libraries/java/global-configuration/#cloudsmith) to determine URL and authentication details. #### JFrog Artifactory Build configuration to retrieve artifacts from Artifactory typically requires you to authenticate and use the identity token in the configuration of your build tool. Follow the steps from the [global configuration](/chainguard/libraries/java/global-configuration/#jfrog-artifactory) to determine URL and authentication details. #### Sonatype Nexus Repository Build configuration to retrieve artifacts from Nexus may require authentication. Use your username and password for Nexus in your build tool configuration. Follow the steps from the [global configuration](/chainguard/libraries/java/global-configuration/#sonatype-nexus-repository) to determine URL and authentication details. ### Direct access Build configuration to retrieve artifacts **directly** from the Chainguard Libraries for Java repository requires authentication with username and password from a pull token as detailed in [access documentation](/chainguard/libraries/introduction/access/#pull-token). If using Chainguard's [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/) for Java libraries (available in beta), set it as the top repository. The recommended ordering for repositories is: 1. `https://libraries.cgr.dev/java-remediated/` for remediated Spring Boot libraries; this is available in beta as part of the [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/) feature. 1. `https://libraries.cgr.dev/java/` 1. `https://repo1.maven.org/maven2/` or your Maven Central proxy ## Selecting remediated library versions When using the [CVE remediation feature](/chainguard/libraries/policies-and-security/cve-remediation/), available in beta to Chainguard Libraries for Java, your build will not receive a remediated Java artifact automatically with the overlay repository configured. To use the remediated build, you must opt in to the suffixed version directly, or route resolution to it through dependency management, Gradle constraints, or version ranges. ### Update dependency version directly For Maven, you can set the suffixed version as a dependency in your `pom.xml`. For example: ```xml org.apache.commons commons-lang3 3.18.0-0.cgr.1 ``` For Gradle, you can set the suffixed version in the dependencies block in `build.gradle`: ```build.gradle implementation 'org.apache.commons:commons-lang3:3.18.0-0.cgr.1' ``` ### Override the version centrally When you use this option, the remediated version will apply to both direct and transitive dependencies. For Maven, update the `dependencyManagement` in a parent POM or the project POM: ```xml org.apache.commons commons-lang3 3.18.0-0.cgr.1 ``` For Gradle, update the `constraints` block in `build.gradle`: ```build.gradle dependencies { constraints { implementation('org.apache.commons:commons-lang3:3.18.0-0.cgr.1') { because 'CVE remediation via Chainguard Libraries overlay' } } } ``` ## Apache Maven [Apache Maven](https://maven.apache.org/) is the most widely used build tool in the Java ecosystem. Dependencies are declared in a `pom.xml` file and resolved from remote repositories. ### Step 1: Remove Maven caches Maven caches downloaded artifacts in a local repository. When adopting Chainguard Libraries for Java you must delete that local cache so that libraries are re-downloaded from Chainguard. By default the cache is located in a hidden `.m2/repository` directory in your user's home directory. Use the following command to delete it: ```shell rm -rf ~/.m2/repository ``` ### Step 2: Configure access Before running a new build you must configure access to the Chainguard Libraries for Java. There are two options for configuring Maven to use Chainguard Libraries: * Repository manager — routes all artifact requests through your organization's Artifactory, Nexus, or Cloudsmith instance * Direct access — configures Chainguard Libraries directly in `~/.m2/settings.xml`, without a repository manager #### Configure access with a repository manager Before running a new build you must configure access to Chainguard Libraries for Java. If your organization uses a repository manager, configure a global mirror in `~/.m2/settings.xml` that routes all artifact requests through it. A typical setup defines a global mirror (id `repo-example` in the following example) for all artifacts and configures the URL of the repository group or virtual repository from your repository manager `https://repo.example.com/group/`. Since the group or virtual repository combines release and snapshot artifacts you must override the built-in `central` repository and its configuration in an automatically activated profile. ```xml repo-example * https://repo.example.com/repository/group repo-manager repo-manager central http://central true true central http://central true true ``` **Setting credentials for the server** If your repository manager requires authentication, you must specify credentials for the server. The `id` value in the server element must match the `id` value in the mirror configuration - `repo-example` in the example. The username and password values vary depending on the repository manager and the configured authentication; contact the administrator and refer to the [global configuration documentation](/chainguard/libraries/java/global-configuration/). ```xml ... repo-example YOUR_USERNAME_FOR_REPOSITORY_MANAGER YOUR_PASSWORD ``` Note that you can use a secret manager application to populate the credentials for each user on their workstation as well as for service applications in your CI/CD pipelines into environment variables, for example `CHAINGUARD_JAVA_IDENTITY_ID` and `CHAINGUARD_JAVA_TOKEN`. You can then use an identical server configuration, and therefore settings file, for all users: ```xml ... repo-example ${env.CHAINGUARD_JAVA_IDENTITY_ID} ${env.CHAINGUARD_JAVA_TOKEN} ``` Refer to the [official documentation for the Maven settings file](https://maven.apache.org/settings.html) for more details. If the administrator only re-configured the existing repository group or virtual repository, you can trigger a build to initiate use of Chainguard Libraries for Java. #### Configure direct access If you are not using a repository manager at your organization, you can configure access to the Chainguard Libraries for Java repository directly. If [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) is enabled for your organization, the `https://libraries.cgr.dev/java/` repository can serve both Chainguard-built artifacts and eligible upstream Maven Central artifacts through the same endpoint. If upstream fallback is not enabled, continue to configure Maven Central or your Maven Central proxy after the Chainguard repository, as shown in the following example. If you are participating in the beta for CVE remediation, include the `https://libraries.cgr.dev/java-remediated/` repository first. If you are using direct access with the Chainguard Repository and you want Chainguard policy controls to apply consistently, configure Chainguard as a global Maven mirror. Without a global mirror, Maven can fall back to its built-in Maven Central definition when Chainguard reports a dependency as unavailable, bypassing the policy and malware scanning controls provided by Chainguard. The following `~/.m2/settings.xml` configures direct access with Chainguard's remediated Java repository as the primary repository, falling back to the standard Chainguard Libraries repository when a remediated version is not available. If a library is not yet built by Chainguard and you have enabled upstream fallback, then upstream packages will be subject to malware scanning and any cooldown policies you have configured. This settings file uses [environment variables](/chainguard/libraries/introduction/access/#env) for the pull token detailed in [Chainguard Libraries access](/chainguard/libraries/introduction/access/). ```xml chainguard chainguard chainguard-remediated https://libraries.cgr.dev/java-remediated/ true false chainguard-java https://libraries.cgr.dev/java/ true false central invalid true false chainguard-java-remediated https://libraries.cgr.dev/java-remediated/ true false chainguard-java https://libraries.cgr.dev/java/ true false central invalid true false chainguard Chainguard Mirror https://libraries.cgr.dev/java/ * chainguard-remediated ${env.CHAINGUARD_JAVA_IDENTITY_ID} ${env.CHAINGUARD_JAVA_TOKEN} chainguard-java ${env.CHAINGUARD_JAVA_IDENTITY_ID} ${env.CHAINGUARD_JAVA_TOKEN} ``` The preceding settings affects all projects built on the machine where the file is configured. Alternatively you can add the `repositories` and `pluginRepositories` to individual project `pom.xml` files. Authentication details must remain within the settings file. If your `settings.xml` is using credentials set as environment variables, ensure the variables are exported. >Note: Upstream fallback includes a 7-day cooldown by default. If you have a cooldown policy configured, your build will fail if a package falls within the cooldown window. Refer to [the FAQ](#build-fails-with-unknown-host-invalid-or-nodename-nor-servname-provided) on this page for more information. ### Minimal example project Use the following steps to create a minimal example project for Maven with Chainguard Libraries for Java. For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). **1. Remove the Maven cache** Remove the Maven cache as described in the [Remove Maven caches](#step-1-remove-maven-caches) section. **2. Create a new Maven project** This command generates a new Maven project from the `archetype` template, sets the group ID and artifact ID, creates a full project structure, and then moves into the generated project directory: ```bash mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=maven-example \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DarchetypeVersion=1.5 \ -DinteractiveMode=false cd maven-example ``` **3. Add a dependency to pom.xml** Add a dependency on `com.google.guava:guava` to the `` section of `pom.xml`. Open the file and insert the following before the closing `dependencies` tag: ```xml com.google.guava guava 33.4.0-jre ``` **4. Configure credentials** Once the environment variables are set, configure credentials in `~/.m2/settings.xml`: ```bash mkdir -p ~/.m2 cat > ~/.m2/settings.xml << EOF no-repo-manager no-repo-manager chainguard https://libraries.cgr.dev/java/ true false central https://repo1.maven.org/maven2/ true false chainguard https://libraries.cgr.dev/java/ true false central https://repo1.maven.org/maven2/ true false chainguard ${env.CHAINGUARD_JAVA_IDENTITY_ID} ${env.CHAINGUARD_JAVA_TOKEN} EOF ``` If you are using Chainguard's remediated repository for Java libraries, make sure to add `https://libraries.cgr.dev/java-remediated/` first, as shown in the [direct access example](#direct-access) earlier on this page. **5. Build the project** Then build the project: ```bash mvn install ``` During the build, Maven logs each artifact download with the source repository. Lines beginning with `Downloaded from chainguard:` confirm the artifact was served by Chainguard Libraries. Lines beginning with `Downloaded from central:` indicate that a dependency is not present in Chainguard and the download fell back to Maven Central; this is expected behavior. #### Verify the project works as expected Following the build, the dependencies declared in `pom.xml` are downloaded to the local Maven repository at ~/.m2/repository. For example, the `guava` dependency added in the example project can be found at: ``` ~/.m2/repository/com/google/guava/guava/33.4.0-jre/guava-33.4.0-jre.jar ``` To verify the artifact was built by Chainguard, use `chainctl`: ```bash chainctl libraries verify \ ~/.m2/repository/com/google/guava/guava/33.4.0-jre/guava-33.4.0-jre.jar ``` > **Note**: Running `chainctl libraries verify` requires the `libraries.java.pull` permission or the Owner role. A successfully verified artifact produces output similar to the following: ```bash Artifact: /Users/example/.m2/repository/com/google/guava/guava/33.4.0-jre/guava-33.4.0-jre.jar Verification Coverage: 100.00% ``` Adjust the repository URL to use your repository manager and add any other desired packages for further testing. ## Gradle [Gradle](https://gradle.org/) is a commonly used build tool in the Java ecosystem. ### Step 1: Remove Gradle caches Gradle uses a local cache of libraries. When adopting Chainguard Libraries for Java you must delete that local cache so that libraries are downloaded again. By default the cache is located in a hidden `~/.gradle/caches` directory in your user's home directory. Use the following command to delete it: ```bash rm -rf ~/.gradle/caches/ ``` Gradle can also be configured to use a local Maven repository with a repository configuration in the global `init.gradle` or a project specific `build.gradle` file: ```groovy repositories { ... mavenLocal() } ``` If this configuration is used, ensure you [delete the local Maven repository as well](#step-1-remove-maven-caches). ### Step 2: Change Gradle configuration Before running a new build you must configure access to the Chainguard Libraries for Java. There are three options for configuring Gradle to use Chainguard Libraries: * Repository manager — routes all artifact requests through your organization's Artifactory, Nexus, or Cloudsmith instance * Direct access — configures Chainguard Libraries as a repository in your build file, without a repository manager * An [`init` script](https://docs.gradle.org/current/userguide/init_scripts.html#sec:using_an_init_script) — applies the repository configuration globally to all Gradle projects on a machine If the administrator for your organization’s repository manager created a new repository or virtual repository or group repository, you must update your Gradle configuration. Artifact download in Gradle can be configured in an [`init` script](https://docs.gradle.org/current/userguide/init_scripts.html#sec:using_an_init_script) using the repositories definition. Each project can also [declare repositories](https://docs.gradle.org/current/userguide/declaring_repositories_basics.html) separately. #### Using a repository manager A typical setup removes the direct reference to Maven Central `mavenCentral()` and any other repositories, and adds a replacement definition with the URL of the repository group or virtual repository from your repository manager `https://repo.example.com/group/` and any applicable authentication details. Open `app/build.gradle` and update the `repositories` block to include the following repository. Ensure it is located above the `mavenCentral` repository and any other repositories: ```groovy repositories { maven { url = uri("https://repo.example.com/group/") credentials { username = "YOUR_USERNAME_FOR_REPOSITORY_MANAGER" password = "YOUR_PASSWORD" } } } ``` > **Note**: Do not store credentials directly in build files; use environment >variables or local Gradle properties instead. Example URLs for repository managers: * Cloudsmith: `https://dl.cloudsmith.io/basic/exampleorg/java-all/maven/` * JFrog Artifactory: `https://example.jfrog.io/artifactory/java-all/` * Sonatype Nexus: `https://repo.example.com:8443/repository/java-all/` #### Direct access to Chainguard Libraries If your organization does not use a repository manager you can configure the Chainguard Libraries for Java repository with the credentials from [Chainguard Libraries access](/chainguard/libraries/introduction/access/). The following `repositories` block demonstrates the recommended method of using [environment variables](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials) for your pull token credentials. Open `app/build.gradle` and update the `repositories` block to include the Chainguard repository. Ensure it is located above the `mavenCentral` repository and any other repositories. If you are using Chainguard's [remediated library repository](/chainguard/libraries/policies-and-security/cve-remediation/), set it as the top repository: ```groovy repositories { maven { url = uri("https://libraries.cgr.dev/java-remediated/") credentials { username = providers.environmentVariable("CHAINGUARD_JAVA_IDENTITY_ID").orNull password = providers.environmentVariable("CHAINGUARD_JAVA_TOKEN").orNull } } maven { url = uri("https://libraries.cgr.dev/java/") credentials { username = providers.environmentVariable("CHAINGUARD_JAVA_IDENTITY_ID").orNull password = providers.environmentVariable("CHAINGUARD_JAVA_TOKEN").orNull } } mavenCentral() } ``` #### Using an init script The following listing shows a valid `init.gradle` file that applies the repository configuration globally using `~/.gradle/init.gradle`. It wraps the `repositories` element with `allprojects` so that the scope of the file affects all projects built locally with Gradle. It also allows for downloads for plugins and build scripts from the remote URL using `buildscript`. Lastly the example shows use of an internal repository manager that only serves artifacts without authentication using HTTP only. Since this is not advisable unless other networking setups allow a secure use with HTTP, the override with the property `allowInsecureProtocol` is required: ```groovy allprojects { buildscript { repositories { maven { url = "http://repo.example.com:8081/repository/java-all/" allowInsecureProtocol = true } } } repositories { maven { url = "http://repo.example.com:8081/repository/java-all/" allowInsecureProtocol = true } } } ``` ### Minimal example project Use the following steps to create a minimal example project for Gradle with Chainguard Libraries for Java. For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). **1. Clear the cache** Clear the Gradle cache as described in the [Remove Gradle caches](#step-1-remove-gradle-caches) section. **2. Create the example project** Create a project directory and navigate to it: ```bash mkdir gradle-example && cd $_ gradle init --type java-application --dsl groovy \ --project-name gradle-example \ --package com.example \ --no-incubating \ --no-split-project ``` **3. Edit app/build.gradle** Open `app/build.gradle` in a text editor. For example, to open it in `nano`: ```bash nano app/build.gradle ``` Edit the `repositories` block in `app/build.gradle` to point to Chainguard and use the environment variables for your pull token credentials: ```app/build.gradle repositories { maven { url = 'https://libraries.cgr.dev/java/' credentials { username = providers.environmentVariable("CHAINGUARD_JAVA_IDENTITY_ID").orNull password = providers.environmentVariable("CHAINGUARD_JAVA_TOKEN").orNull } } mavenCentral() } ``` If you are using Chainguard's remediated repository for Java libraries, make sure to add `https://libraries.cgr.dev/java-remediated/` first, as shown in the [direct access example](#direct-access-to-chainguard-libraries) earlier in the Gradle section on this page. **4. Build the project** Run the following command: ```bash ./gradlew app:assemble ``` The project generated in this example includes `com.google.guava:guava` as a dependency via the version catalog in `gradle/libs.versions.toml`, so guava is downloaded from Chainguard Libraries as part of the build. Following the build, you can find the guava jar declared in the version catalog at: ``` ~/.gradle/caches/modules-2/files-2.1/com.google.guava ``` #### Verify the project works as expected To verify the artifact was built by Chainguard, use `chainctl`. In this example, we first use `find` to locate the jar: ```bash find ~/.gradle/caches/modules-2/files-2.1/com.google.guava/guava -name "*.jar" | sort ``` Then copy the exact path to the jar and verify it with `chainctl`: ```bash chainctl libraries verify /full/path/to/guava-.jar ``` > **Note**: Running `chainctl libraries verify` requires the `libraries.java.pull` permission or the Owner role. A successfully verified artifact produces output similar to the following: ```bash Artifact: ~/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/33.5.0-jre/.../guava-33.5.0-jre.jar Verification Coverage: 100.00% ``` Adjust the repository URL to use your repository manager and add any other desired packages for further testing. ## Bazel [Bazel](https://bazel.build/) is a fast, scalable, and extensible build tool commonly used in large-scale projects. ### Step 1: Remove Bazel caches Bazel uses a cache to store downloaded artifacts. When adopting Chainguard Libraries for Java, you must delete this cache to ensure that libraries are downloaded again. By default, the cache is located in the `.cache/bazel` on Linux or `/private/var/tmp/_bazel_$USER` on MacOS directory in your user's home directory. Use the following command to delete it: ```shell bazel clean --expunge ``` The [Bazel documentation on output directories](https://bazel.build/remote/output-directories) contains further details. ### Step 2: Change Bazel configuration #### Using a repository manager Before running a new build, you must configure access to Chainguard Libraries for Java. If the administrator for your organization’s repository manager created a new repository or virtual repository, you must update your Bazel configuration to use the repository manager. Bazel uses `MODULE.bazel` files to define external dependencies as `artifacts`. You can configure a Maven repository for artifact retrieval using `repositories` from the [`rules_jvm_external`](https://github.com/bazel-contrib/rules_jvm_external) rule: Following is an example configuration for a repository manager: ```MODULE.bazel bazel_dep(name = "rules_jvm_external", version = "6.3") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( name = "maven", # Example dependencies to retrieve artifacts = [ "com.google.guava:guava:32.0.1-jre", "org.slf4j:slf4j-api:2.0.5", "ch.qos.logback:logback-classic:1.4.7", ], repositories = [ # To use Chainguard Libraries for Java via a repository manager: "https://repo.example.com/repository/java-all/", ], # Uncomment and configure authentication if needed: # auth = { # "https://repo.example.com/repository/java-all/": { # "type": "basic", # "username": "YOUR_USERNAME_FOR_REPOSITORY_MANAGER", # "password": "YOUR_PASSWORD", # }, # }, ) use_repo(maven, "maven") ``` Example URLs for repository managers: * Cloudsmith: https://dl.cloudsmith.io/basic/exampleorg/java-all/maven/ * JFrog Artifactory: https://example.jfrog.io/artifactory/java-all/ * Sonatype Nexus: https://repo.example.com:8443/repository/java-all/ #### Using direct access If your organization does not use a repository manager, you can configure the Chainguard Libraries for Java repository directly, and include the Maven Central repository as fallback. Replace the placeholders `CHAINGUARD_JAVA_IDENTITY_ID` and `CHAINGUARD_JAVA_TOKEN` with the credentials provided by Chainguard: ```MODULE.bazel maven.install( name = "maven", # Example dependencies to retrieve artifacts = [ "com.google.guava:guava:32.0.1-jre", "org.slf4j:slf4j-api:2.0.5", "ch.qos.logback:logback-classic:1.4.7", ], repositories = [ # To use Chainguard Libraries directly (requires credentials): "https://libraries.cgr.dev/java/", # Use Maven Central as fallback "https://repo1.maven.org/maven2/", ], auth = { "https://libraries.cgr.dev/java/": { "type": "basic", "username": "CHAINGUARD_JAVA_IDENTITY_ID", "password": "CHAINGUARD_JAVA_TOKEN", }, }, ) ``` Ensure that the Chainguard repository is listed before any other repositories to prioritize it for artifact retrieval. For more complex Bazel setups, you can use [.netrc for authentication](/chainguard/libraries/introduction/access/#netrc). Refer to the [official Bazel documentation for `rules_jvm_external`](https://github.com/bazel-contrib/rules_jvm_external) for more detailed configuration options. ## Other build tools Other build tools such as [Apache Ant](https://ant.apache.org/) with the [Maven Artifact Resolver Ant Tasks](https://maven.apache.org/resolver-ant-tasks/), [sbt](https://www.scala-sbt.org/), [Leiningen](https://leiningen.org/) and others use Maven or Gradle caches or similar approaches. Refer to the documentation of your specific tool and the preceding sections to determine how to remove any used caches. These tools also include their own mechanisms to configure repositories for binary artifact retrieval. Consult the specific documentation and adjust your configuration to use your repository manager and newly created repository group or virtual repository. Example URLs for repository managers: * Cloudsmith: `https://dl.cloudsmith.io/basic/exampleorg/java-all/maven/` * JFrog Artifactory: `https://example.jfrog.io/artifactory/java-all/` * Sonatype Nexus: `https://repo.example.com:8443/repository/java-all/` ## Troubleshooting ### Build fails with "Unknown host invalid" or "nodename nor servname provided" If your build fails with an error similar to the following, a dependency has been blocked by Chainguard's cooldown policy: ``` Non-resolvable import POM: Could not transfer artifact io.airlift:bom:pom:436 from/to central (https://invalid): invalid: nodename nor servname provided, or not known ``` The `https://invalid` URL in the error indicates that Chainguard blocked the artifact rather than serving it, and the fallback to Central was also blocked. This happens when a recently published package falls within the cooldown window — a security feature that holds newly published artifacts for a configurable period before serving them, to allow time for malware scanning. To avoid build failures, you can [disable the cooldown or decrease its length](/chainguard/chainguard-repository/library-policies/#cooldown-policies). --- ### Configure Python build tools URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/python/build-configuration.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Python Chainguard Libraries for Python works with your existing build tools — pip, Poetry, and uv — through a repository configuration change. This page is a reference for configuring each supported build tool. It covers repository access, authentication, cache clearing, and minimal example projects. Apply these changes on every workstation and build server that builds your applications or downloads libraries, including CI/CD infrastructure such as Jenkins, TeamCity, or GitHub Actions. The `https://libraries.cgr.dev/python/` endpoint is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for Python. By default, it serves only Chainguard-built artifacts. When [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) is enabled for your organization, the same endpoint can also serve requested versions from PyPI under Chainguard security controls. This guide outlines how to configure your build tool. If you are looking for something else, refer to the following guides depending on your goals: | If you want to... | Use this page | | --- | --- | | Understand what Chainguard Libraries for Python is and how it works | [Python overview](/chainguard/libraries/python/overview/) | | Set up organization-wide access through a repository manager | [Global configuration](/chainguard/libraries/python/global-configuration/) | | Look up how to configure a specific build tool (pip, Poetry, uv) | This page | | Migrate an existing project step by step | [Python migration guide](/chainguard/libraries/python/migration/) | | Install inside a container build without leaking credentials into the image | [Container builds](/chainguard/libraries/policies-and-security/build-containers/) | Refer to the [minimal example projects](#minimal-example-projects) on this page for demonstrations using `uv` and `pip`. If a package or version is blocked by a policy or malware scan, your build tool returns an error. Refer to the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/) for more details. ## Step 1: Retrieve authentication credentials To configure any build tool, you must first access credentials from your organization's repository manager or for direct access. ### Cloudsmith The following steps allow you to determine the URL and authentication details for accessing your organization's Cloudsmith repository manager. 1. Log into Cloudsmith. 1. Select the **Repositories** tab and click on the *python-all* repository. 1. Select the **Packages** tab. 1. Select **Push/Pull Packages** on the right. 1. Choose the **Python** format. 1. Select your desired authentication method for **Entitlement tokens** and copy the URL to use in your build tool - for example `https://dl.cloudsmith.io/.../exampleorg/python-all/python/simple/`. In the URL `...` is replaced with a default token or your personal token depending on your selection and `exampleorg` is replaced with the name of your organization. The URL contains both the name of the repository `python-all` as well as `python` as an identifier for the format. 1. Alternatively, use the **API Key** and copy the URL to use in your build tool - for example `https://username:{{apiKey}}@dl.cloudsmith.io/basic/exampleorg/python-all/python/simple/`. Replace `username` and `exampleorg` with your Cloudsmith details and replace `{{apiKey}}` with the API key from the **Personal API Keys** section from the drop down on your username. Note that for use with build tools you must include the `simple/` context so that the package index is used successfully. ### Google Artifact Registry Use the [Google Cloud Artifact Registry documentation](https://docs.cloud.google.com/artifact-registry/docs/python/authentication) to authenticate to your Python Artifact Registry. ### JFrog Artifactory The following steps allow you to determine the identity token and URL for accessing your organization's JFrog Artifactory repository manager. 1. Select **Administration** in the top navigation bar. 1. Select **Repositories** in the left hand navigation. 1. Select the **Virtual** tab in the repositories view. 1. Locate the **python-all** repository row and press the three dots (**...**) in the last column on the right. 1. Select **Set Me Up** in the dialog. 1. Select **Generate Token & Create Instructions** 1. Copy the generated token value to use as the password for authentication. 1. Select **Generate Settings**. 1. Copy the value from one of the *URL* fields. They are all identical. For example, `https://exampleorg.jfrog.io/artifactory/python-all` with `exampleorg`. Note that for use with build tools you must append `simple/` to the URL so that the package index is used successfully - `https://exampleorg.jfrog.io/artifactory/python-all/simple/`. ### Sonatype Nexus Repository The following steps allow you to determine the URL and authentication details for accessing your organization's Sonatype Nexus repository group. 1. Click **Browse** in the **Welcome** view or the browse icon (cube) in the top navigation bar. 1. Locate the **URL** column for the *python-all* repository group and press **copy**. The URL should take the following format: `https://repo.example.com/repository/python-all/`. Note that for use with build tools you must append `simple/` to the URL so that the package index is used successfully - `https://repo.example.com/repository/python-all/simple/`. 1. No further configuration is necessary if your repository manager is configured for anonymous access with **Security** - **Anonymous Access** - **Access** - **Allow anonymous users to access the server** is activated. If authentication is required, you must use the relevant details such as username and password in your build tool configuration. ### Direct access The build configuration to retrieve artifacts **directly** from the Chainguard Libraries for Python repositories requires authentication with username and password from a pull token as detailed in [access documentation](/chainguard/libraries/introduction/access/#pull-token). Note that there are multiple repositories: - `https://libraries.cgr.dev/python/` with the simple index at `https://libraries.cgr.dev/python/simple` - `https://libraries.cgr.dev/python-remediated` with the simple index at `https://libraries.cgr.dev/python-remediated/simple` Configuration for multiple index use and authentication varies for each packaging tool. Typically Python tools include support for [.netrc](/chainguard/libraries/introduction/access/#netrc). Refer to examples using `uv` and `pip` under [Minimal example projects](#minimal-example-projects). ## Step 2: Configure your build tools Once you have credentials and the index URL from your organization's repository manager, you're ready to set up specific build tools for local development or CI/CD. The recommended configuration is to use the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint rather than manually configuring fallback to upstream PyPI. ### Authentication [pip](#pip), [uv](#uv), poetry, and other Python build and packaging tools have dedicated support for configuring authentication to the repository manager or the Chainguard Libraries for Python directly. As an alternative that works across tools and is often preferred, use [.netrc for authentication](/chainguard/libraries/introduction/access/#netrc). ### Updating lockfile hashes If you are migrating an existing Python project to Chainguard Libraries, your lockfile likely contains integrity hashes generated against packages previously downloaded from the PyPI registry or through your repository manager. Because Chainguard rebuilds packages from verified source, the checksums for those packages differ from the ones already recorded in your lockfile. These hashes must be updated before reinstalling. The [`chainctl libraries update-hashes` command](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/) automates lockfile hash updates for all supported Python lockfile formats. Rather than manually regenerating lock files with each tool, you can run the command directly against your existing lockfile to update hashes to Chainguard checksums while preserving your locked dependency versions, without re-resolving your dependency graph. Supported formats include `requirements.txt` (pip-tools `--hash` style), `poetry.lock`, `uv.lock`, `pdm.lock`, `Pipfile.lock`, and `pylock.toml`. Run the command in your project directory to auto-detect the lockfile: ```bash chainctl libraries update-hashes ``` > **Note**: Running `chainctl libraries update-hashes` requires the `libraries.python.pull` permission or the Owner role. Or specify a lockfile path directly: ```bash chainctl libraries update-hashes path/to/requirements.txt ``` If you install through a repository manager rather than pulling from Chainguard directly, pass the full repository URL with `--registry-url` so the hashes are updated against the same source your project actually installs from. For example: ```bash chainctl libraries update-hashes --registry-url https://your-org.jfrog.io/artifactory/api/pypi/chainguard-libraries/simple ``` Learn about using this command with repo managers in the [Global configuration](/chainguard/libraries/python/global-configuration/) page. #### Authentication `update-hashes` fetches checksums from Chainguard Libraries (`libraries.cgr.dev`), which requires authentication. Choose whichever fits your environment: - **Logged in locally**: Run the command while authenticated; if you have no other credential it authenticates with a [pull token](/chainguard/libraries/introduction/access/#pull-token) for your organization, prompting for one only if you have access to more than one. Pass `--parent ` to skip that prompt. To avoid it entirely, scope your login to the libraries registry once with `chainctl auth login --audience=libraries.cgr.dev` — that session is then used automatically. - **CI or non-interactive**: - For a session token, pass `--token ` or set `CHAINCTL_AUTH_TOKEN`. The token must be scoped to the libraries registry; mint one with `chainctl auth token --audience=libraries.cgr.dev`. - For a [pull token](/chainguard/libraries/introduction/access/#pull-token) (an identity and secret), pass it as basic auth: `--username --password `, or set `CHAINCTL_REGISTRY_USERNAME` / `CHAINCTL_REGISTRY_PASSWORD`. - **From `~/.netrc`**: Credentials for the registry host are read from `~/.netrc` (or `$NETRC`); refer to [.netrc for authentication](/chainguard/libraries/introduction/access/#netrc). Pass `--ignore-netrc` to skip an unrelated entry. When you target a repository manager with `--registry-url` (for example Artifactory or JFrog), authenticate with **that** registry's credentials — `--username`/`--password`, the `CHAINCTL_REGISTRY_USERNAME` / `CHAINCTL_REGISTRY_PASSWORD` environment variables, or a matching `~/.netrc` entry. Chainguard-scoped tokens are never sent to third-party hosts. By default, Chainguard hashes are appended alongside existing upstream hashes. After updating the lockfiles, to switch your environment to use Chainguard packages, configure your tool to use the Chainguard index and reinstall. The command will output a "Next steps" section that includes the tool-specific command for reinstalling. ### pip The [pip tool](https://pip.pypa.io/en/stable/) is the most widely used utility for installing Python packages. In this section, we use the credentials from your organization's repository manager to configure `pip` to ingest dependencies from Chainguard Libraries. First, clear your local `pip` cache to ensure that packages are sourced from Chainguard Libraries for Python: ```shell pip cache purge ``` #### Using a repository manager To update `pip` to use our repository manager's URL globally, create or edit your `~/.pip/pip.conf` file. You may need to create the `~/.pip` folder as well. For example: ```sh mkdir -p ~/.pip nano ~/.pip/pip.conf ``` Update this configuration file with the following, replacing `` with the URL provided by your repository manager including the `simple/` context: ```pip.conf [global] index-url = ``` Updating this global configuration affects all projects built on the workstation. Alternately, if your project uses a `requirements.txt` file in projects, you can add the following to it to configure on a project-by-project basis: ``` --index-url package-name==version ``` Note the different syntax for `index-url` in the two files. Refer to the official documentation for [configuring authentication with pip](https://pip.pypa.io/en/stable/topics/authentication/) if you are not using [.netrc for authentication](/chainguard/libraries/introduction/access/#netrc). #### Using direct access When using [direct access](#direct-access) to the Chainguard Libraries for Python repository with `pip`, you must ensure the following are set in your configuration file: - Replace any `/` in the username value `CG_PULLTOKEN_USERNAME` with `_`. - Ensure the `simple` context is used for the URL. - The password value `CG_PULLTOKEN_PASSWORD` remains unchanged. Example for `requirements.txt`: ``` --index-url https://CG_PULLTOKEN_USERNAME:CG_PULLTOKEN_PASSWORD@libraries.cgr.dev/python/simple/ ``` Example for `~/.pip/pip.conf`: ``` [global] index-url = https://CG_PULLTOKEN_USERNAME:CG_PULLTOKEN_PASSWORD@libraries.cgr.dev/python/simple/ ``` Note that `pip` supports installing Python libraries from one main repository URL specified with `index-url` and one or more additional repositories specified with `extra-index-url` without any specific prioritization beyond resolving semantic versions. The following example uses authentication from a local `.netrc` file and places the remediated repository first as the primary source, falling back to the standard Chainguard Libraries repository when a remediated version is not available: ``` --index-url https://libraries.cgr.dev/python-remediated/simple/ --extra-index-url https://libraries.cgr.dev/python/simple/ ``` If you are using `pip` and prefer to pull from multiple repositories while prioritizing Chainguard Libraries for Python, we recommend using a repository manager. Alternatively, other Python package managers, detailed in the following sections, provide support for index priority resolution behavior. Refer to a demonstration using `pip` under [Minimal example projects](#pip-minimal). ### Poetry [Poetry](https://python-poetry.org/) helps you declare, manage, and install dependencies of Python projects, and can be used with Chainguard Libraries for Python." List the Python package caches used by your Poetry project: ```shell poetry cache list ``` The following commands clear the default cache, the cache for a repository named `pypi`, and the cache of packages of the repo `python-all` from your repository manager as configured in [the global configuration](/chainguard/libraries/python/global-configuration/): ```shell poetry cache clear --all _default_cache poetry cache clear --all pypi poetry cache clear --all python-all ``` #### Using a repository manager Set up HTTP authentication to the repository `python-all` on your repository manager with the username `example` and the password `secret` in your project directory: ```shell poetry config http-basic.python-all example secret ``` The authentication is used for the `python-all` repository that you add to the `pyproject.toml` with the following command: ```shell poetry source add python-all https://repo.example.com/../python-all/simple/ ``` Example URLs including the required `simple` context: - JFrog Artifactory: `https://example.jfrog.io/artifactory/api/pypi/python-all/simple/` - Sonatype Nexus: `https://repo.example.com:8443/repository/python-all/simple/` The following configuration is added: ```toml [[tool.poetry.source]] name = "python-all" url = "https://repo.example.com/../python-all/simple/" priority = "primary" ``` Trigger a new download of the dependencies: ```shell poetry install ``` If necessary, you can fix or even regenerate your `poetry.lock` file: ```shell poetry lock poetry lock --regenerate ``` Regenerating re-resolves your dependencies, so it can change your pinned versions, and a release still inside your configured cooldown window returns a 404 error. See [Update your lockfile](/chainguard/libraries/python/migration/#step-2-update-your-lockfile). Proceed to build your project: ```shell poetry build ``` #### Using direct access For [direct access](#direct-access) to Chainguard Libraries for Python with Poetry, use your username `CG_PULLTOKEN_USERNAME` and password `CG_PULLTOKEN_PASSWORD` values from the pull token creation and the URL with the simple context `https://libraries.cgr.dev/python/simple/`: In order to install Python libraries from multiple repositories with Chainguard Libraries for Python as the priority, `poetry` supports setting a [primary package source](https://python-poetry.org/docs/repositories/#project-configuration). You can use this to configure Chainguard Libraries for Python as the first choice for any library access, remediated packages from Chainguard as another choices and a fallback to the PyPI public index for any missing packages: ```shell poetry config http-basic.chainguard CG_PULLTOKEN_USERNAME CG_PULLTOKEN_PASSWORD ``` The authentication is used for the `chainguard` repository that you add to the `pyproject.toml` with the following command: ```shell poetry source add --priority=primary chainguard https://libraries.cgr.dev/python/simple/ ``` Optionally, add the remediated Python libraries as supplemental source: ```shell poetry source add chainguard-remediated https://libraries.cgr.dev/python-remediated/simple/ ``` If you require a fallback to PyPI, you can add it as supplemental source: ```shell poetry source add PyPI ``` Alternatively, edit the `pyproject.toml` file directly: ```toml [[tool.poetry.source]] name = "chainguard-remediated" url = "https://libraries.cgr.dev/python-remediated/simple" priority = "primary" [[tool.poetry.source]] name = "chainguard" url = "https://libraries.cgr.dev/python/simple" [[tool.poetry.source]] name = "PyPI" ``` The [Poetry documentation](https://python-poetry.org/docs/) contains more information about your project build, dependencies, versions, and other aspects. ### uv [uv](https://docs.astral.sh/uv) is a fast Python package and project manager written in Rust. It uses PyPI by default, but also [supports the use of alternative package indexes](https://docs.astral.sh/uv/configuration/indexes/). #### Using a repository manager To update your global configuration to use your organization's repository manager with `uv`, create or edit the `~/.config/uv/uv.toml` configuration file. You may also need to create the `~/.config/uv/` folder first. For example: ```sh mkdir -p ~/.config/uv nano ~/.config/uv/uv.toml ``` Add the following to your `uv` global configuration file: ```toml [[tool.uv.index]] name = "" url = "" ``` Add the name for your repository, such as `corppypi`, within the quotes. Replace the `` with the URL provided by your repository manager including the `simple/` context. Note that updating the global configuration affects all projects built on the workstation. Alternately, you can update each project by adding the same configuration in `pyproject.toml`. Refer to the official documentation for [configuring authentication with uv](https://docs.astral.sh/uv/configuration/authentication/) and [using alternative package indexes](https://docs.astral.sh/uv/guides/integration/alternative-indexes/) if you are not using [.netrc for authentication](/chainguard/libraries/introduction/access/#netrc). #### Using direct access For [direct access](#direct-access) to Chainguard Libraries for Python with uv, use `.netrc`, environment variables, or embed credentials directly in the index URL using your username `CG_PULLTOKEN_USERNAME` and password `CG_PULLTOKEN_PASSWORD` values from the pull token creation and the URL with the simple context `https://libraries.cgr.dev/python/simple/`. The recommended approach for CI/CD and shared configurations is to use uv's native index-scoped environment variables. For a named index, uv reads credentials from `UV_INDEX__USERNAME` and `UV_INDEX__PASSWORD`, where `` is the index name uppercased with hyphens replaced by underscores. This avoids storing credentials in config files while keeping the index configuration shareable. For example, with an index named `chainguard`: ```shell export UV_INDEX_CHAINGUARD_USERNAME=CG_PULLTOKEN_USERNAME export UV_INDEX_CHAINGUARD_PASSWORD=CG_PULLTOKEN_PASSWORD ``` Configure the index without embedded credentials in `pyproject.toml`: ```toml [[tool.uv.index]] name = "chainguard" url = "https://libraries.cgr.dev/python/simple/" default = true authenticate = "always" ``` Or in `uv.toml`: ```toml [[index]] name = "chainguard" url = "https://libraries.cgr.dev/python/simple/" authenticate = "always" ``` Alternatively, embed credentials directly in the URL (avoid in shared or version-controlled files): Example for `pyproject.toml`: ```toml [[tool.uv.index]] name = "chainguard" url = "https://CG_PULLTOKEN_USERNAME:CG_PULLTOKEN_PASSWORD@libraries.cgr.dev/python/simple/" default = true ``` Example for `uv.toml`: ```toml [[index]] url = "https://CG_PULLTOKEN_USERNAME:CG_PULLTOKEN_PASSWORD@libraries.cgr.dev/python/simple/" ``` Refer to a demonstration using `uv` under [Minimal example projects](#uv-minimal). #### Multiple indexes In order to install Python libraries from multiple repositories with Chainguard Libraries for Python as the priority, `uv` supports [searching across multiple indexes](https://docs.astral.sh/uv/concepts/indexes/#searching-across-multiple-indexes). You can use this to configure the remediated Chainguard Libraries for Python index as the first choice for any library access, with a fallback to the Chainguard Libraries for Python index. In addition, if you are consuming from our remediated Python libraries index, we recommend setting the [index-strategy setting](https://docs.astral.sh/uv/reference/settings/#index-strategy) to `unsafe-best-match`. This ensures that index resolution continues to work when remediated libraries have dependencies on non-remediated libraries. Example `pyproject.toml` index setup for direct access to remediated and default packages with netrc-based authentication. Note that the order of the entries in the configuration file is significant and determines the order for resolving dependencies: ```toml [[tool.uv.index]] name = "cgr-pr" url = "https://libraries.cgr.dev/python-remediated/simple" authenticate = "always" [[tool.uv.index]] name = "cgr-p" url = "https://libraries.cgr.dev/python/simple" authenticate = "always" default = true # replaces PyPI as the lowest priority index ``` Setting `default = true` on the Chainguard index is important. Without it, uv appends PyPI as an implicit lowest-priority index, and with `unsafe-best-match` it can resolve packages from PyPI even when Chainguard serves them. This bypasses Chainguard malware blocking and cooldown. If you need packages that Chainguard has not built, use the [built-in upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) instead of adding PyPI to your configuration. Set the index strategy to allow fallback from the remediated package index to the Chainguard index in `pyproject.toml`: ```toml [tool.uv] index-strategy = "unsafe-best-match" ``` Run a build to observe the resolved packages. For example, the declared dependency to `flask` version `2.0.0` results in the use of version `2.0.0+cgr.1`. ## Minimal example projects ### uv Use the following steps to create a minimal example project for uv with Chainguard Libraries for Python. For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). > **Migrating an existing project?** If you have an existing uv lockfile with > upstream hashes, use [`chainctl libraries update-hashes`](#updating-lockfile-hashes) > to update hashes in place rather than starting from scratch. **1. Configure credentials** Once the environment variables are set, configure credentials in `~/.netrc`: ```bash cat >> ~/.netrc << EOF machine libraries.cgr.dev login ${CHAINGUARD_PYTHON_IDENTITY_ID} password ${CHAINGUARD_PYTHON_TOKEN} EOF chmod 600 ~/.netrc ``` > **Note**: The `machine libraries.cgr.dev` entry is shared across ecosystems. > Make sure your entry is using a pull token with Python entitlement. In this example, the global uv index is set to the Chainguard Python repositories without embedded credentials, allowing uv to authenticate automatically using `.netrc`. Create the global index file `~/.config/uv/uv.toml` then open it in a text editor such as `nano`: ```bash mkdir -p ~/.config/uv nano ~/.config/uv/uv.toml ``` Update it to include the remediated and standard Chainguard indexes: ```toml [[index]] url = "https://libraries.cgr.dev/python-remediated/simple/" authenticate = "always" [[index]] url = "https://libraries.cgr.dev/python/simple/" authenticate = "always" ``` **2. Initialize a new project** This command creates a new directory, moves to the new directory, then initializes it with uv: ```bash mkdir uv-example && cd $_ uv init ``` **3. Edit pyproject.toml** Open `pyproject.toml` with a text editor, such as `nano`: ```bash nano pyproject.toml ``` Add `flask==2.0.0` to the `dependencies` list in the `[project]` section, and add the following index configurations for the Python libraries to the end of the file: ```toml [project] name = "uv-example" version = "0.1.0" requires-python = ">=3.9" dependencies = [ "flask==2.0.0", ] [tool.uv] index-strategy = "unsafe-best-match" # This allows uv to search across multiple indexes to find remediated versions of Python libraries [[tool.uv.index]] name = "cgr-pr" url = "https://libraries.cgr.dev/python-remediated/simple" authenticate = "always" [[tool.uv.index]] name = "cgr-p" url = "https://libraries.cgr.dev/python/simple" authenticate = "always" default = true ``` **4. Build the project** Build the project and sync dependencies: ```bash uv sync ``` Following this, confirm the patched Chainguard version of flask was resolved: ```bash uv pip list | grep flask ``` The output should show `flask 2.0.0+cgr.1`, confirming the remediated version was installed. #### Verify the project works as expected To verify the installed packages were built by Chainguard, use `chainctl`: ```bash chainctl libraries verify --detailed .venv/ ``` A successfully verified project produces output similar to the following: ```bash - flask Status: Verified as built from source Details: Version: 2.0.0+cgr.1 (verified via per-package SBOM - built from source by Chainguard) Python package: flask==2.0.0+cgr.1 ``` ### pip Use the following steps to create a minimal example project for pip with Chainguard Libraries for Python. For testing purposes, you can use direct access and environment variables as detailed in the [access documentation](/chainguard/libraries/introduction/access/#use-environment-variables-for-pull-token-credentials). > **Migrating an existing project?** If you have an existing pip lockfile with > upstream hashes, use [`chainctl libraries update-hashes`](#updating-lockfile-hashes) > to update hashes in place rather than starting from scratch. **1. Update your configuration to point to Chainguard** Once the environment variables are set, update `~/.pip/pip.conf` to point to Chainguard Libraries. You may need to create the `~/.pip` directory first: ```bash mkdir -p ~/.pip nano ~/.pip/pip.conf ``` In `~/.pip/pip.conf`, add the following: ```pip.conf [global] index-url = https://CHAINGUARD_PYTHON_IDENTITY_ID:CHAINGUARD_PYTHON_TOKEN@libraries.cgr.dev/python/simple/ ``` > **Note**: Replace any `/` in your `CHAINGUARD_PYTHON_IDENTITY_ID`'s value with `_` in the URL. The password value remains unchanged. **2. Create a virtual environment** Create a new directory, navigate to it, and create and activate a virtual environment: ```bash mkdir pip-example && cd $_ python3 -m venv .venv source .venv/bin/activate ``` **3. Add and install dependencies** Create a `requirements.txt` file: ```bash nano requirements.txt ``` In the `requirements.txt` file, add the following: ```requirements.txt flask==2.0.0 ``` Run the following command to install the dependencies: ```bash pip install -r requirements.txt ``` #### Verify the project works as expected To verify the installed packages were built by Chainguard, use `chainctl`: ```bash chainctl libraries verify --detailed .venv/ ``` A successfully verified project produces output similar to the following: ```output Artifact: .venv/ Verification Coverage: 55.56% Verified packages: 5 of 9 ... - flask Status: Verified as built from source Details: Version: 2.0.0 (verified via per-package SBOM - built from source by Chainguard) Python package: flask==2.0.0 ``` Adjust the index URL to use your repository manager and add any other desired packages for further testing. --- ### Manage and update dependencies URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/java/management.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Java Chainguard Libraries for Java operates transparently after configuring your [repository manager](/chainguard/libraries/java/global-configuration/) or [your build tool](/chainguard/libraries/java/build-configuration/), automatically providing security-enhanced versions of your Maven dependencies. After you configure Chainguard Libraries for Java, use this page for recurring maintenance tasks. Chainguard Libraries serves Chainguard-built artifacts when they are available. When [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls/) is enabled, an artifact that Chainguard has not yet built may first be served through Chainguard’s upstream tier. With [build pinning](/chainguard/libraries/policies-and-security/build-pinning/), the exact package version remains pinned to the artifact tier your organization first received, so a previously downloaded upstream artifact is not immediately replaced when Chainguard publishes a built equivalent. Existing artifacts may already be present in a developer’s local Maven cache or in a repository manager cache, so a previously downloaded upstream artifact is not automatically replaced just because a Chainguard-built equivalent becomes available. The following sections detail optional management, maintenance, and auditing steps on the repository manager and the build tool. ## Verify dependencies Use `chainctl libraries verify` to check whether an artifact comes from Chainguard Libraries: ```bash chainctl libraries verify path/to/artifact.jar ``` For Java, run verification against the individual JAR files in the local Maven repository cache before assembling a fat JAR or other bundled artifact. The verifier identifies artifacts using their checksums and provenance information; it cannot reliably trace merged classes in a fat JAR back to their source JARs. For command options, permissions, and supported artifact types, refer to the [Verification documentation](/chainguard/libraries/policies-and-security/verification/). ### Inspect artifacts in a repository manager If your organization uses a repository manager, you can inspect the Chainguard proxy or remote repository to audit which artifacts were retrieved through Chainguard Libraries. Use the repository manager’s package or browsing view to locate an artifact and compare its coordinates, file name, size, checksum, and available metadata. Refer to the [Verification page](/chainguard/libraries/policies-and-security/verification/) for more information on verifying artifacts in a repository manager. ## Refresh cached artifacts The number of available artifacts in Chainguard Libraries for Java increases over time. If an artifact was already retrieved from the Maven Central Repository and is available in your repository manager or local repository it is not automatically replaced with the equivalent Chainguard Library version. To adopt a newer Chainguard-built artifact, refer to the [build pinning documentation](/chainguard/libraries/policies-and-security/build-pinning/#adopt-a-chainguard-build-after-removing-a-pin) for instructions on removing existing pinned versions. Refreshing cached artifacts may also be necessary to solve other issues, such as stale or corrupted artifacts or metadata, repository configuration changes, and resolution troubleshooting. To refresh the same artifact your organization is already using: 1. Remove the affected artifact from the developer’s local Maven cache. 1. If applicable, remove the affected artifact from the repository manager’s proxy cache, following your organization’s cache-management procedure. 1. Run the build again so Maven requests the artifact from the configured Chainguard Libraries repository. 1. Verify the resulting JARs with `chainctl libraries verify`. If an exact package version is pinned, Chainguard continues to serve the pinned artifact after the cache is refreshed. Prefer removing only the affected artifact or dependency subtree. Avoid broadly deleting production or shared caches unless you understand the operational impact and have a recovery plan. ### Prepare for checksum changes A checksum identifies the exact bytes of a library artifact. Chainguard-built artifacts have different checksums from upstream artifacts with the same Maven coordinates and version because they are rebuilt in a secured environment. During initial migration, if your project records checksums or integrity values, update those values as part of the migration or cache refresh, then run your normal tests and verification checks. For a full migration sequence, including cache and project-configuration handling, refer to the [migration guide for Chainguard Libraries for Java](/chainguard/libraries/java/migration/). For organizations that use Chainguard's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls/), build pinning keeps the exact artifact previously served for that package version. This prevents a later Chainguard rebuild from unexpectedly changing the checksum. You must remove the pin to adopt a newer Chainguard build. Refer to [Build pinning](/chainguard/libraries/policies-and-security/build-pinning/) for more information. ## Security and policy guidance Refer to the following pages for topics broader than routine dependency maintenance: - [CVE remediation for Chainguard Libraries](/chainguard/libraries/policies-and-security/cve-remediation/) for remediated library versions and upgrade guidance. - [Chainguard Libraries policies](/chainguard/chainguard-repository/library-policies/) for upstream fallback, cooldown, and package-serving policy. - [Error messages](/chainguard/libraries/troubleshooting/errors/) and the [Chainguard Libraries FAQ](/chainguard/libraries/troubleshooting/faq/) for troubleshooting and edge cases. --- ### Manage and update dependencies URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/python/management.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Python Chainguard Libraries for Python operates transparently after completing the [repository manager configuration](/chainguard/libraries/python/global-configuration/) or [build tool configuration](/chainguard/libraries/python/build-configuration/), automatically providing security-enhanced versions of your PyPI dependencies. After setup, most package retrieval happens through your configured package index or repository manager. Use this page for recurring maintenance tasks and for deciding where to troubleshoot when a dependency changes. Chainguard Libraries serves Chainguard-built artifacts when they are available. When [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls/) is enabled, an artifact that Chainguard has not yet built may first be served through Chainguard’s upstream tier. With [build pinning](/chainguard/libraries/policies-and-security/build-pinning/), the exact package version remains pinned to the artifact tier your organization first received, so a previously downloaded wheel or source distribution is not immediately replaced when Chainguard publishes a built equivalent. A package may already be present in a developer’s `pip`, `uv`, or Poetry cache, in a repository manager cache, or a container layer. A cached package is not automatically replaced just because a Chainguard-built equivalent becomes available. For Python, a single package version can resolve to different wheels depending on the Python version and platform. The pinned file, selected artifact, and resulting hash may therefore vary by environment; test and update hashes for each supported environment when applicable. ## Verify dependencies Use `chainctl libraries verify` to check whether an artifact comes from Chainguard Libraries. To verify an installed virtual environment: ```bash chainctl libraries verify --detailed .venv/ ``` For additional verification commands, command options, permissions, and supported artifact types, refer to the [Verification documentation](/chainguard/libraries/policies-and-security/verification/). ### Inspect artifacts in a repository manager If your organization uses a repository manager, you can inspect the Chainguard proxy or remote repository to audit which artifacts were retrieved through Chainguard Libraries. Use the repository manager’s package or browsing view to locate an artifact and compare its coordinates, file name, size, checksum, and available metadata. Refer to the [Verification page](/chainguard/libraries/policies-and-security/verification/) for more information on verifying artifacts in a repository manager. ## Refresh cached artifacts The number of available artifacts in Chainguard Libraries for Python increases over time. If an artifact was already retrieved from the PyPI Repository and is available in your repository manager or local repository it is not automatically replaced with the equivalent Chainguard Library version. To adopt new Chainguard-built artifacts, refer to the [build pinning documentation](/chainguard/libraries/policies-and-security/build-pinning/#adopt-a-chainguard-build-after-removing-a-pin) for instructions on removing existing pinned versions. Refreshing cached artifacts may also be necessary to solve other issues, such as stale or corrupted artifacts or metadata, repository configuration changes, and resolution troubleshooting. To refresh the same artifact your organization is already using: 1. Confirm that the project and CI/CD environment use the intended Chainguard index or repository manager endpoint. 1. Update the dependency or dependency constraint with your package manager. 1. Run the project’s tests and security checks. 1. Review and commit the resulting lockfile or hash-pinned requirements changes. If an exact package version is pinned, Chainguard continues to serve the pinned artifact after the cache is refreshed. For a more fine-grained approach you can also delete subsections of local repositories and the proxy repositories. ### Prepare for package hash changes A hash identifies the exact bytes of a downloaded Python artifact. Chainguard-built artifacts have different hashes from the equivalent upstream artifacts because they are rebuilt in a secure environment. During initial migration, if your project uses hash-pinned lockfiles, update those values with [the `chainctl libraries update-hashes` command](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/), then run your normal tests and verification checks. For a full migration sequence, including cache and project-configuration handling, refer to the [migration guide for Chainguard Libraries for Python](/chainguard/libraries/python/migration/). For organizations that use Chainguard's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls/), build pinning keeps the exact artifact previously served for that package version. This prevents a later Chainguard rebuild from unexpectedly changing the hash. You must remove the pin to adopt a newer Chainguard build. Refer to [Build pinning](/chainguard/libraries/policies-and-security/build-pinning/) for more information. --- ### Chainguard Libraries access URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/introduction/access.md Last Modified: October 14, 2026 Tags: Chainguard Libraries Chainguard Libraries provide controlled access to security-enhanced Java and Python dependencies through the unified Chainguard platform authentication system. This guide explains how to access (download) Chainguard library artifacts for your organization. ## Getting started ### Prerequisites - Ensure you have access to Chainguard Libraries. - If you are not a Chainguard user yet, a new Chainguard account must be created and you must [add an entitlement to Chainguard Libraries](/chainguard/libraries/introduction/access/#manage-library-entitlements). - If you have access to more than one Chainguard organization, confirm the name of the one you want to use. You can then pass it to `chainctl` commands with the `--parent` parameter. ### Direct access vs. artifact manager There are two approaches to access: Using an artifact manager or direct access. #### Artifact manager If your organization uses an artifact manager such as Cloudsmith, JFrog Artifactory, or Sonatype Nexus, you can set up and configure credentials once per language ecosystem. Then, all projects and developers automatically inherit the configuration. This option is recommended for organizations with multiple teams, and provides centralized access controls and consistent uptime. #### Direct access Set up authentication directly in each project's build configuration. This option allows for faster initial setup, but it does not allow for global configuration. It requires configuration per project and workstation, which creates more overhead as you scale across teams and projects. Both approaches require pull tokens for authentication; refer to [Pull token characteristics and use](#pull-token-characteristics-and-use) for more information. A check in [Chainguard Checks](/chainguard/checks/overview/) doesn't need a pull token. It can download JavaScript and Python libraries through a [workload identity](/chainguard/checks/identity-and-trust/#chainguard-hosts) that holds the `libraries.javascript.pull` or `libraries.python.pull` role. A [Chainguard Workspaces](/chainguard/workspaces/overview/) session has no credential for Chainguard Libraries, as described in [Credentials a session can't supply](/chainguard/workspaces/credentials/#credentials-a-session-cant-supply).

Note: For Python users, the Chainguard keyring provider uses short-lived credentials and is the preferred method where your environment supports it.

### Initial authentication Once your user account is created and access is confirmed, [install the Chainguard Control `chainctl` command line tool](/platform/chainctl-usage/how-to-install-chainctl/) and log in to your account: ```shell chainctl auth login ```

Note: chainctl auth login supports several login flows, including device-code login for machines without a browser and assumable identities for CI. If the default browser flow doesn't work in your environment, see Authentication options.

After authentication in a browser window, a successful login displays a message and a token: ```output Successfully exchanged token. Valid! Id: 8a4141a........7d9904d98c ``` ## Creating pull tokens for libraries Pull tokens authenticate requests to download library artifacts from Chainguard. You can create the pull tokens: - With [the chainctl command](#creating-pull-tokens-with-chainctl), or - [Using the Chainguard console](#creating-pull-tokens-with-the-chainguard-console). For environments where short-lived credentials are not suitable, such as some CI/CD platforms, you can generate a pull token, which provides longer-lived access to Chainguard Libraries. To create a pull token you must have the relevant [entitlement](#entitlement) for the ecosystem and the `libraries.java.pull_token_creator`, `libraries.javascript.pull_token_creator`, or `libraries.python.pull_token_creator` role. ### Creating pull tokens with chainctl Create a new pull token for the Chainguard Libraries for Java with the [chainctl auth pull-token](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/) command: ```shell chainctl auth pull-token --repository=java --ttl=8670h ``` - `--repository=java`: retrieve the token for use with [Chainguard Libraries for Java](/chainguard/libraries/java/overview/). Use `python` for a token to use [Chainguard Libraries for Python](/chainguard/libraries/python/overview/) and `javascript` for a token to use [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/overview/). - `--ttl=8670h`: set the duration for the validity of the token, defaults to `720h` (equivalent to 30 days), maximum valid value is `8760h` (equivalent to 365 days), valid unit strings range from nanoseconds to hours and are `ns`, `us`, `ms`, `s`, `m`, and `h`. Use the optional `--name` flag to supply a meaningful and short name for the token, to be able to locate it easier at a later stage. If you belong to a single organization, `chainctl` selects it automatically. If you have access to more than one, it asks “With which location is the pull token associated?” Use the arrow keys to select the organization that has the entitlement to access Chainguard Libraries for Java, and press `/` to filter the list. Pass `--parent=` to skip the prompt. `chainctl` returns a username and password suitable for basic authentication in the response: ```output Username: Password: ``` ### Creating pull tokens with the Chainguard Console Follow these steps to create a pull token for Chainguard Libraries in the Chainguard console: 1. Use your authentication details to access the console at [https://console.chainguard.dev/](https://console.chainguard.dev/). 2. In the left-hand navigation, click **Overview**. 3. Click the **Manage pull tokens** link, then click **Create token**. - Alternatively, select **Access Tokens** from the menu on the **Settings** page. 4. Configure the access token: - **Name**: Provide a name. The name can later be used to locate the token in the list. - **Description**: Optionally provide a description of the token. - **Access**: Choose the library that this token should access. - **Expiration**: Set an expiration date for the token. The default is 30 days. 5. Click **Create token**. 6. When the username and password values are displayed, save them in a secure location. You need them to use the pull token, and the console doesn't display them again. ## Pull token characteristics and use The username is the identity ID of the pull token identity that `chainctl` just created, and the password is the token itself. `chainctl` labels the same two values `identity_id` and `token` with `--output=json`, and `CHAINGUARD_JAVA_IDENTITY_ID` and `CHAINGUARD_JAVA_TOKEN` with `--output=env`. Refer to [pull token output formats and credential names](/platform/chainctl-usage/pull-token-output/) for the full mapping. The returned username and password combination is a new credential set in the organization that is independent of the account used to create and retrieve the credential set. It is therefore suitable for use in any service application, such as a repository manager or a build tool that is not tied to a specific user. You can also use the token as an individual for your development with direct access to Chainguard Libraries. To use the pull token in another environment, supply the username and password for basic authentication. Note that the actual returned values are much longer.

Note: Chainguard does not offer an SLA for uptime availability of the Chainguard Libraries repositories at libraries.cgr.dev. To reduce production risk and ensure reliability, we recommend proxying the repositories through your own artifact repository whenever possible.

For artifact manager setup, see the global configuration guides: - [Java](/chainguard/libraries/java/global-configuration/) - [JavaScript](/chainguard/libraries/javascript/global-configuration/) - [Python](/chainguard/libraries/python/global-configuration/) For direct access, see the build configuration guides: - [Java](/chainguard/libraries/java/build-configuration/) - [JavaScript](/chainguard/libraries/javascript/build-configuration/) - [Python](/chainguard/libraries/python/build-configuration/) ### Use environment variables for pull token credentials Using environment variables for username and password is more secure than hard coding the values in configuration files. In addition, you can use the same configuration and files for all users to simplify setup and reduce errors. Use the `env` environment output option to create a snippet for a new token suitable for integration in a script. ```shell $ chainctl auth pull-token --output env --repository=java export CHAINGUARD_JAVA_IDENTITY_ID= export CHAINGUARD_JAVA_TOKEN= ``` Combine the call with `eval` to populate the environment variables directly by calling `chainctl`: ```shell eval $(chainctl auth pull-token --output env --repository=java) ``` Equivalent commands for Python and JavaScript are supported and result in values for the `CHAINGUARD_PYTHON_IDENTITY_ID`/`CHAINGUARD_PYTHON_TOKEN` and `CHAINGUARD_JAVASCRIPT_IDENTITY_ID`/`CHAINGUARD_JAVASCRIPT_TOKEN` variables. Use the export commands in a `~/.env` or similar file or use your preferred secrets management application to reuse the token in multiple sessions. Source the file or load the secrets from the app to have access to the tokens in your shell session, including build configurations file that can load from environment variables, for example a Maven `settings.xml` file. Running this command as part of a login script or some other automation allows your organization to replace actual username and password values in your build tool configuration with environment variable placeholders: - [Java build tool configuration](/chainguard/libraries/java/build-configuration/) - [JavaScript build tool configuration](/chainguard/libraries/javascript/build-configuration/) - [Python build tool configuration](/chainguard/libraries/python/build-configuration/) #### uv index-scoped environment variables When using `uv` for Python, you can supply credentials through index-scoped environment variables instead of a shared secrets file or `.netrc`. For a named index, uv reads `UV_INDEX__USERNAME` and `UV_INDEX__PASSWORD`, where `` is the index name uppercased with hyphens replaced by underscores. For an index named `chainguard`, set: ```shell export UV_INDEX_CHAINGUARD_USERNAME="${CHAINGUARD_PYTHON_IDENTITY_ID}" export UV_INDEX_CHAINGUARD_PASSWORD="${CHAINGUARD_PYTHON_TOKEN}" ``` This pairs well with the `eval` approach above: generate the `CHAINGUARD_PYTHON_IDENTITY_ID` and `CHAINGUARD_PYTHON_TOKEN` variables, then map them to uv's index-scoped names. Refer to the [uv build configuration](/chainguard/libraries/python/build-configuration/#uv) for index setup details. ### .netrc for authentication [curl](https://curl.se/) and a number of other tools support configuration of username and password authentication details for a specific domain in the [`.netrc` file](https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html), typically located in the user's home directory. Use this approach for authentication to a repository manager in your organization or to Chainguard Libraries directly, for example with [pip and others for Chainguard Libraries for Python](/chainguard/libraries/python/build-configuration/#pip), with [bazel for Chainguard Libraries for Java](/chainguard/libraries/java/build-configuration/#bazel) or for manual testing with curl. The following example shows a suitable setup for a repo manager available at `repo.example.com`: ```bash machine repo.example.com login YOUR_USERNAME_FOR_REPOSITORY_MANAGER password YOUR_PASSWORD ``` For a direct connection to Chainguard Libraries, for example for testing with curl, use the following example with the username `CHAINGUARD_PYTHON_IDENTITY_ID` and password `CHAINGUARD_PYTHON_TOKEN` value for the pull token for the desired language ecosystem: ```bash machine libraries.cgr.dev login CHAINGUARD_PYTHON_IDENTITY_ID password CHAINGUARD_PYTHON_TOKEN ``` Note that the long string for the password value must use only one line. When you use this `.netrc` file in a container build, mount it as a build secret rather than copying it into an image layer. See [Container builds](/chainguard/libraries/policies-and-security/build-containers/). ### Verification Use the credentials for manual testing in a browser or with a script and curl if you know the URL for a specific library artifact. Refer to the following sections for more details: - [Technical details and manual testing for Java libraries](/chainguard/libraries/java/overview/#technical-details) - [Technical details and manual testing for JavaScript libraries](/chainguard/libraries/javascript/overview/#technical-details) - [Technical details and manual testing for Python libraries](/chainguard/libraries/python/overview/#technical-details) - [Use environment variables](#env) - [.netrc for authentication](#netrc) ## Python keyring provider Python users can leverage an alternative to pull tokens. The [Chainguard keyring implementation](https://github.com/chainguard-dev/keyrings-chainguard-libraries) provides short-lived credentials from supported environments, such as local development and CI/CD platforms that can use [assumable identities](/platform/administration/assumable-ids/assumable-ids/). Where possible, Chainguard recommends using short-lived credentials to access Chainguard Libraries. To set up the keyring, install the `keyrings-chainguard-libraries` package: ```shell pip install keyrings-chainguard-libraries ``` *Note:* If you haven't set up access to Chainguard Libraries for Python, the above command installs the package from PyPI. After installing and configuring Chainguard Libraries for Python, you can get the private package again, to get the package built by Chainguard. To re-install the package: ```bash pip install keyrings-chainguard-libraries --ignore-installed --no-cache-dir ``` Once the keyring package is installed, when you request to install packages from Chainguard Libraries for Python, the keyring automatically retrieves short-lived credentials for you, using `chainctl`. To use the keyring with a project `uv`, install the keyring: ```shell uv pip install keyrings-chainguard-libraries ``` *Note:* If you haven't set up access to Chainguard Libraries for Python, the above command installs the package from PyPI. After installing and configuring Chainguard Libraries for Python, you can get the private package again, to get the package built by Chainguard. To re-install the package: ```shell uv pip install keyrings-chainguard-libraries --reinstall --no-cache ``` By default, [uv disables keyring auth](https://docs.astral.sh/uv/reference/settings/#keyring-provider). To enable it in the global uv.toml: ```toml keyring-provider = "subprocess" ``` To enable it in a project-specific pyproject.toml: ```toml [tool.uv] keyring-provider = "subprocess" ``` ## Pull token management Pull tokens are separate identities with username and password that are used for access to Chainguard Libraries. The tokens have a limited Time to Live (TTL) with a default of 30 days and a maximum TTL of 365 days. As a result, pull tokens become invalid after the TTL and are flagged as expired. For your use of Chainguard Libraries you must replace the token with a new one. Expired tokens can no longer be used for access to Chainguard Libraries, but otherwise do not cause any issues and continue to exist until you delete them. Inspect all pull tokens for your organization in the Chainguard console: - Use your authentication details to access the console at [https://console.chainguard.dev/](https://console.chainguard.dev/). - Select **Overview** in the left-hand navigation. - Select the **Manage pull tokens** link. - Alternatively, select **Settings** in the left-hand navigation, and select **Access Tokens** in the menu on the settings page. The list includes the following columns: - **Name** - the name of the pull token. Expired pull tokens show an **Expired** warning. - **Description** - the description of the pull token. - **Access** - what the pull token can pull, such as **Java Libraries**. - **Created** - the date when the pull token was created. - **Expiration** - string description of the expiration status of the token, such as *in 8 months* or *4 days ago*. - **Actions** - menu button to perform actions on the pull token: **Delete** and **Edit**. Use the action to remove a pull token: - Locate the row of the desired pull token, typically an expired token. - Use the menu button in the **Actions** column of the same row and select **Delete**. - Confirm the deletion in the dialog by pressing **Delete pull token**. Alternatively use chainctl with the [auth pull-token](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/) and [iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) commands for various inspection and management tasks. List all pull tokens with the `list` command: ```shell chainctl auth pull-token list ``` The displayed list includes the following columns: - **ID** - the identifying username of the pull token - **NAME** - the name of the pull token - **DESCRIPTION** - the description of the pull token - **ROLES** - the assigned organization and role for the pull token. The value shows the name of the organization separate from the role with a colon. Valid roles are all pull authorization from for apk packages `apk.pull`, container images `registry.pull`, Python libraries `libraries.python.pull`, Java libraries `libraries.java.pull`, and JavaScript libraries `libraries.javascript.pull` . - **EXPIRES** - the number of days until the end of the TTL period. Negative values indicate expired tokens. List all pull tokens for Chainguard Libraries for Java that are not yet expired: ```shell chainctl auth pull-token list --repository=java ``` List all expired pull tokens for Chainguard Libraries for Python: ```shell chainctl auth pull-token list --repository=python --expired=true ``` Use the [delete command for IAM identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities_delete/) to delete a specific pull token using its ID `45a0c61ea6fd97...`: ```shell chainctl iam identities delete 45a0c61ea6fd97... ``` Use the identifier or name of your organization `example` and the `--expired` flag to remove all expired pull tokens: ```shell chainctl iam ids rm --expired ``` ## Manage library entitlements You can create, list, and remove entitlements using [`chainctl libraries entitlements`](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements/). ### Create entitlements As administrator you can use [`chainctl libraries entitlements create`](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/) for one or more ecosystems: ```shell chainctl libraries entitlements create --ecosystems=JAVASCRIPT,JAVA,PYTHON ``` To enable upstream fallback for JavaScript, use the `--policy` flag: ```bash chainctl libraries entitlements create --ecosystems=JAVASCRIPT --policy=CHAINGUARD_AND_UPSTREAM ``` To update the upstream fallback policy on an existing entitlement, rerun the `create` command with the new `--policy` value. ### Remove entitlements You can delete an ecosystem library entitlement for a specific ecosystem from your organization with [`chainctl libraries entitlements delete`](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/): ```shell chainctl libraries entitlements delete --ecosystem=JAVASCRIPT ``` ### List entitlements You can verify entitlements for your organization `example.com` to verify which ecosystems are enabled: ```shell chainctl libraries entitlements list ``` The output includes the ecosystem and configured policy in the table: ```output Ecosystem Library Entitlements for example (45a0...p7q) ID | ECOSYSTEM | POLICY -----------------------------------------------------------|------------|-------------------------------- 45a0c61a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q/12345abc67890 | JAVASCRIPT | POLICY_CHAINGUARD_AND_UPSTREAM 45a0c61a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q/45678abc67890 | JAVA | POLICY_CHAINGUARD 45a0c61a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q/56789abc67890 | PYTHON | POLICY_CHAINGUARD ``` ## Manage library policies Users with the Owner role can create, enable, disable, and list library policies using [`chainctl libraries policy`](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) commands. These policies apply to all packages pulled through Chainguard Repository. The [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) must be enabled for an ecosystem in order to use policies. Learn about how to manage policies in the [Libraries policies page](/chainguard/chainguard-repository/library-policies/). --- ### Using CVE visualizations URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/vulnerability-management/cve-visualizations.md Last Modified: October 6, 2026 Tags: Chainguard Containers Chainguard provides CVE Visualizations for all of its container images. This feature creates reports with CVE comparisons between Chainguard Containers and popular alternatives, as well as historical CVE remediation metrics. CVE Visualizations provide insight into image health and can help teams measure the engineering, security, and economic benefits gained from using Chainguard Containers. This guide outlines how you can access a container image's CVE Visualization in both the Chainguard Console and in the Containers Directory. ## Accessing CVE visualizations in the Console You can find CVE visualizations in two places in the [Chainguard Console](https://console.chainguard.dev): the **Reports** page in the left-hand navigation menu, and the **Comparison** tab of an individual image. ### Reports section Select [**Reports**](https://console.chainguard.dev/reports) in the left-hand navigation menu. The Reports page has two tabs: **Compare images** and **Organization comparison**. #### Compare images The **Compare images** tab compares one Chainguard Container with a popular alternative. It has three controls at the top: * A search box for choosing the Chainguard Container to compare. Its list shows your organization's images first, followed by the Chainguard catalog. The tab opens with the first image already selected. * An **Alternative** drop-down that lists the alternative images available for comparison. When more than one is available, choose the one you want. * A **Period** drop-down that sets the time range: **Last month**, **Last 3 months**, **Last 6 months**, **Last 12 months**, or **Custom**. Below the controls are these sections: * An overview of each image's **Latest CVE count**, **Daily average** CVE count, and **Compressed size**. * **CVEs by severity**, with bar graphs of each image's daily CVE count, broken down by severity. Its **Export** button downloads this data as a JSON file. * **Total CVEs over time**, a line graph of each image's total CVE count per day. It shows the difference in CVE count between the images. * **Net new CVEs identified**, a line graph of the CVEs newly identified in each image during the time range. It shows how quickly each image accumulates CVEs.
Screenshot showing Total CVEs over time and Net new CVEs identified graphs. The Total CVEs over time graph shows that the Chainguard Container regularly has few or zero CVEs while the alternative jumps between 800 and over 1000 CVEs. The Net new CVEs identified graph ends with nearly zero CVEs for the Chainguard Container and over 300 for the alternative.

#### Organization comparison The **Organization comparison** tab totals CVE data across your organization's images and compares it with their alternatives. It covers only images in your organization, not the rest of the Chainguard catalog.

Note: If you’re a member of more than one organization, you can switch organizations with the drop-down menu in the top left corner of the Console.

There are several controls at the top of the tab: * An image selector, with all of your organization's images selected by default. Remove images to narrow the comparison. The copy button next to the image count copies a link to your selection. * A **Compare** drop-down that switches between **All organization images** and **Images with known alternatives**. * **Export to PDF** and **Export to JSON** buttons. Below the controls, a summary states how many vulnerabilities the selected images eliminate compared to their alternatives. Next is a severity section titled with your image count, such as **CVEs by severity for your 287 images**. It has side-by-side bar graphs of the daily CVE counts for **Chainguard** and **Alternatives** over the last 30 days, color-coded by severity. Select **What's being compared?** to list each image with the alternative it's compared against. ### Comparison tab You can find the same comparison data for a single image under **Images**, on either the **Organization** or **Chainguard catalog** tab. Select or search for a container image, which opens on its **Tags** tab by default. Select the **Comparison** tab, which shows the same sections as the **Compare images** tab on the Reports page, with **Alternative** and **Period** drop-downs at the top. An **Export** button appears only for images you open from the **Organization** tab. ## Accessing CVE visualizations in the Containers Directory Similar to the CVE reports found under **Images** in the Chainguard Console, you can find CVE reports for every one of Chainguard's container images in the [Containers Directory](https://images.chainguard.dev/). In the directory, select or search for any container image. The image opens on its **Tags** tab. Select the **Comparison** tab to view the CVE comparison data. ## Limitations Some container images don't have a comparable alternative. For these images, the **Comparison** tab shows data only for the Chainguard Container. The **Comparison** tab isn't available for customized images. ## Learn more The CVE data used in these reports is from the [Grype vulnerability scanner](/chainguard/containers/security-and-compliance/working-with-scanners/grype-tutorial/). Vulnerability data is constantly evolving, so we scan container images each day and store the results. The results shown are the vulnerabilities found on the day in question; scanning the container images again with a newer database can produce different results. For more information on CVEs refer to [What are software vulnerabilities and CVEs](https://www.chainguard.dev/supply-chain-security-101/what-is-a-cve). You may also find our guide on [Using the Chainguard Directory and Console](/platform/console/images-directory/) to be of interest. --- ### How to pull packages from Chainguard package repositories through Artifactory URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/artifactory-packages-pull-through.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural This tutorial details how to set up remote Alpine package (apk) repositories with [JFrog Artifactory](https://jfrog.com/artifactory/), which can provide pull-through caches for Chainguard package repositories. Specifically, this guide walks you through how to set up remote Artifactory repositories to serve as pull-through caches for a [Chainguard private APK repository](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) as well as Chainguard's public package repositories. The guide also outlines how to configure a container image build to pull APK packages from these remote repositories using tokens generated by Artifactory. ## Prerequisites In order to complete this tutorial, you need the following: * Administrative privileges over an Artifactory instance. * [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) * Administrative privileges within your Chainguard organization to create role-bindings (`role_bindings.create`); this capability is available to users with [the `owner` role](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/#chainguard-role-capabilities). ## Creating a Chainguard pull token for remote repository authentication When configuring an Artifactory remote repository to function as a pull-through cache for packages from a Chainguard private APK repository, the remote repository must authenticate to Chainguard. This section outlines the steps necessary to create a Chainguard pull token and configure the required permissions to access your Chainguard organization's private APK repository: Generate a pull token: ```shell chainctl auth pull-token --repository=apk -o env ``` This `chainctl` command's `--repository=apk` flag creates a role binding to bind the pull token identity the `apk.pull` role, enabling the identity to download packages from the private APK repository of the parent organization. This command's output shows the pull token credentials: ```output Creating new APK registry pull-token in example.org To use this pull token in another environment, supply the following for Basic authorization: Username: Password: ``` Be sure to note down both the `Username` and `Password` values returned by this command, as you will need these when setting up a remote repository on Artifactory. ## Setting up a pull-through cache for a private APK repository Now that you have an identity with permissions to do so, you can set up a remote Artifactory repository to reach your private APK repository. To set up the remote repository: 1. Log in to the JFrog platform. 2. Select the **Administration** tab near the top of the screen. 3. Select **Repositories** from the left-hand navigation menu. 4. On the Repositories page, click the **Create a Repository** button. 5. Select the **Remote** option. 6. In the **Select Package Type** window, select **Alpine**. This takes you to a **Basic** configuration tab where you can enter the following details for the remote repository: * **Repository Key** — This is a name used to identify your remote repository, for example `cg-private`. * **URL** — This must be set to `https://apk.cgr.dev/`, replacing `` with your organization's name as it appears in the Chainguard Console. For example, if your organization is named `example` use `https://apk.cgr.dev/example`. * **User Name** — This is used by Artifactory to authenticate to Chainguard and access your private APK repository. Use the pull token `Username` value you generated with `chainctl` in the previous step. * **Password / Access Token** — This is used along with the user name to authenticate to Chainguard. Here, enter the `Password` value returned by the `chainctl auth pull-token` command in the previous section. Then, navigate to the **Advanced** configuration tab and enter the following details: * **Disable URL Normalization** — This must be enabled because, in some cases, the normalization will invalidate the URLs that packages are served from. Click the **Create Remote Repository** button to create the remote repository and return to the **Repositories** page. > **Note**: For more information on how to set up a remote repository in Artifactory, refer to [the official documentation](https://jfrog.com/help/r/jfrog-artifactory-documentation/remote-repositories). ### Generating a token for the remote repository Before testing whether you're able to pull packages through the remote Artifactory repository, you must retrieve a token generated for it by Artifactory. 1. On the Repositories page, find your repository. 2. Hover your cursor over the repository's row and click the ellipsis (**⋯**) all the way to the right. 3. Select **Set me up**. 4. Click the **Generate Token & Create Instructions** button. 5. Copy or take note of the token generated for your repository. With this token in hand, run the following `export` command to create an environment variable, replacing `my-token` with the token you noted down. You will use this environment variable in the following section as you test out the remote repository: ```shell export CG_PRIVATE_TOKEN=my-token ``` Additionally, create two more environment variables to hold your Artifactory username and server name: ```shell export ARTIFACTORY_USERNAME=my-user-profile export ARTIFACTORY_SERVER_NAME=my-artifactory-hostname ``` If you aren't sure of these values, you can find them in the command from the **Set Up An Alpine Client** window where you retrieved the token: ```shell sudo sh -c "echo 'https://linky:@example-server-name.jfrog.io/artifactory/cg-private//'" >> /etc/apk/repositories ``` In this example, the Artifactory username is `linky` and the hostname is `example-server-name`. > **Note**: If your Artifactory username is an email address, you must percent-encode the `@` sign, as in `export ARTIFACTORY_USERNAME=linky%40example.com`. Here, the Artifactory username is `linky@example.com`, but it must be entered into the environment variable as `linky%40example.com`. ### Testing pull-through from private APK repository This section outlines how to build a container image using a Chainguard image as a base and configure it to pull packages from the private APK repositories through your remote Artifactory repository. Open a terminal and create a Dockerfile. Note the use of single quotes around `'EOF'` to prevent the shell from expanding the `$` variables — these will be processed by Docker during the build: ```shell cat > Dockerfile <<'EOF' FROM cgr.dev/chainguard/python:latest-dev USER root ARG ARTIFACTORY_USERNAME ARG ARTIFACTORY_SERVER_NAME RUN --mount=type=secret,id=cg_private_token \ cp /etc/apk/repositories /etc/apk/repositories.disabled && \ echo "https://${ARTIFACTORY_USERNAME}:$(cat /run/secrets/cg_private_token)@${ARTIFACTORY_SERVER_NAME}.jfrog.io/artifactory/cg-private/" > /etc/apk/repositories && \ apk add sed && \ rm /etc/apk/repositories USER nonroot EOF ``` This Dockerfile uses the `python:latest-dev` image. You don't have to use this particular image, but because we are using the `apk` command to install a package from `cgr-private` in this Dockerfile, you should use a Chainguard container image that has this package manager available. Note that this Dockerfile combines the repository configuration, package installation, and cleanup into a single `RUN` instruction. This is important because it ensures the Artifactory token is never stored in an image layer. The `--mount=type=secret` option mounts the token only for the duration of this `RUN` instruction and it is not written to the image or its history. The final `rm` removes the `/etc/apk/repositories` file, which contained the token in plain text for the duration of the step. Additionally, be aware that there are limitations to what packages are available from your organization's private APK repository. For instance, your organization may not have access to the `sed` package. Refer to our [private APK repository documentation](/chainguard/containers/building-and-modifying/packages/private-apk-repos/#about-private-apk-repositories) for more information. After creating the Dockerfile, build the image. Here, we tag the image `ar-build` and pass the Artifactory token as a Docker build secret along with the username and server name as build arguments: ```shell docker build \ --secret id=cg_private_token,env=CG_PRIVATE_TOKEN \ --build-arg ARTIFACTORY_USERNAME=$ARTIFACTORY_USERNAME \ --build-arg ARTIFACTORY_SERVER_NAME=$ARTIFACTORY_SERVER_NAME \ -t ar-build . ``` The `--secret` flag passes `CG_PRIVATE_TOKEN` as a build secret that is accessible only during the build and is never stored in the image or its history. This command's output will show that the `sed` package was installed as expected: ```output . . . => [4/4] RUN apk add sed 2.8s . . . ``` You can confirm that this package was pulled through Artifactory with the [**Artifact Repository Browser**](https://jfrog.com/help/r/jfrog-artifactory-documentation/browsing-artifacts) in the Artifactory dashboard: 1. Navigate to the **Platform** tab. 2. Find **Artifactory** in the left-hand navigation menu. 3. Select **Artifacts**. 4. Find and expand the menu option for the `cg-private` Artifactory repository. There, you will find the `sed` package listed, along with any other packages you've pulled through the remote repository. ## Configuring pull-through caches for Chainguard's public repositories You also have access to the public `chainguard` and `extra-packages` repositories. Because these repositories are public, they do not require authentication. To set up a pull-through cache for these package repositories on Artifactory, you can follow the same procedure outlined previously for the private APK repository. You must create two remote repositories within Artifactory — one for each public package repo — by following the same steps as before: 1. On the Repositories page, click the **Create a Repository** button. 2. Select the **Remote** option. 3. In the **Select Package Type** window, select **Alpine**. Enter the following details for the two remote repositories: * **Repository Key** — This is the name used to identify your remote repository. Again, you can choose whatever names you like here but this guide's examples use the names `cg-chainguard` and `cg-extras`. * **URL** — This must be set to `https://virtualapk.cgr.dev//chainguard` for the `chainguard` repository and `https://virtualapk.cgr.dev//extra-packages` for the `extra-packages` repository. * For both of these URLs, you need to replace the `` placeholder with your Chainguard organization's UID. You can find this by running the `chainctl iam organizations list -o table`; the UID is the value in your organization's `ID` column. Alternatively, you can find it by checking the **Settings** ⇒ **General** page in the [Chainguard Console](https://console.chainguard.dev). You **do not** need to set any values for the **User Name** or **Password / Access Token** fields, as the public repositories do not require authentication. Next, for both repositories, navigate to the **Advanced** configuration tab and check the **Disable URL Normalization** option. This will prevent URL normalization from invalidating the URLs that packages are served from. Keep all the remaining fields set to their default values and click the **Create Remote Repository** button. This creates the remote repository and returns you to the **Repositories** page. After creating both repositories, generate and retrieve a token for each one as you did for the `cg-private` remote repository: 1. On the Repositories page, find both remote repositories you just created. 2. Click the ellipsis (**⋯**) all the way to the right of their respective rows. 3. Select **Set me up**. 4. Click the **Generate Token & Create Instructions** button. 5. Copy or take note of the tokens generated for your repositories. Next, copy the resulting tokens to create a pair of environment variables. For the `cg-chainguard` repository's token: ```shell export CG_TOKEN= ``` Then for the `cg-extras` repo's token: ```shell export EXTRAS_TOKEN= ``` Following that, you can create a Dockerfile for the public repositories. As before, use single quotes around `'EOF'` to prevent shell expansion: ```shell cat > Dockerfile.repos <<'EOF' FROM cgr.dev/chainguard/python:latest-dev USER root ARG ARTIFACTORY_USERNAME ARG ARTIFACTORY_SERVER_NAME RUN --mount=type=secret,id=cg_token \ --mount=type=secret,id=extras_token \ cp /etc/apk/repositories /etc/apk/repositories.disabled && \ echo "https://${ARTIFACTORY_USERNAME}:$(cat /run/secrets/cg_token)@${ARTIFACTORY_SERVER_NAME}.jfrog.io/artifactory/cg-chainguard/" > /etc/apk/repositories && \ echo "https://${ARTIFACTORY_USERNAME}:$(cat /run/secrets/extras_token)@${ARTIFACTORY_SERVER_NAME}.jfrog.io/artifactory/cg-extras/" >> /etc/apk/repositories && \ apk add c-ares && \ rm /etc/apk/repositories EOF ``` Following that, build the image, passing each Artifactory token as a Docker build secret: ```shell docker build \ --secret id=cg_token,env=CG_TOKEN \ --secret id=extras_token,env=EXTRAS_TOKEN \ --build-arg ARTIFACTORY_USERNAME=$ARTIFACTORY_USERNAME \ --build-arg ARTIFACTORY_SERVER_NAME=$ARTIFACTORY_SERVER_NAME \ -t ar-repos -f Dockerfile.repos . ``` As this command runs, it installs the `c-ares` package into the image. ## Debugging pull-through from Chainguard’s package repositories to Artifactory If you run into issues when trying to pull from Chainguard's package repositories through Artifactory, you can try checking for these common pitfalls: * You may run into issues if your Artifactory username is an email address; specifically, the `@` sign can lead to errors. Be sure that you're using a user profile with a name that only contains letters and numbers. If you must use a profile with an email address for a name, try percent-encoding the `@` sign by replacing it with `%40`. * Ensure that all [network requirements](/chainguard/containers/registry/network-requirements/) are met. * When configuring a remote Artifactory repository, ensure that the **URL** field is set correctly. * If necessary, ensure that you've set the correct UID for your organization in the URL field. * It may help to [clear the Artifactory cache](https://jfrog.com/help/r/artifactory-cleanup-best-practices/clearing-an-oversized-cache). * It could be that your Artifactory repository was misconfigured. In this case, create and configure a new Remote Artifactory repository to test with. * If your output returns `package mentioned in index not found`, it usually means Artifactory or a CDN is normalizing or rewriting APK URLs (often by stripping query strings, collapsing path segments, or altering tokens). To prevent this, ensure that Artifactory's **Disable URL Normalization** option is checked in order to preserve exact filenames and tokens. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by referring to our [documentation](/chainguard/containers/overview/), and learn more about working with the Chainguard platform by reviewing our [Administration documentation](/platform/administration/). If you'd like to learn more about JFrog Artifactory, we encourage you to refer to the [official Artifactory documentation](https://jfrog.com/help/r/jfrog-artifactory-documentation). --- ### Chainguard's container variants URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/container-variants.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard's container images follow a distroless philosophy, meaning that only software absolutely necessary for a specific workload is included in an image. Designed to be as minimal as possible, Chainguard's standard container images do not contain package managers such as apk, shells such as b/a/sh, or development utilities such as Git or text editors. However, this distroless approach isn't suitable for every use case. For this reason, most of these images have what's called a *development* variant. These variants are designed for development tasks such as building, testing, or debugging. They can be used to build software artifacts that are then copied into standard images as part of a multi-stage build, or to test workflows interactively in an environment similar to a standard image. Development images contain familiar utilities such as package managers and shells. While our standard images have advantages related to security, development images are also secure and production-ready. Development images are tagged `:latest-dev`. To benefit from the most minimal potential attack surface, we recommend using a multi-stage build that leverages the development container image as a builder for a distroless standard container image. However, development images are useful as they are throughout the development lifecycle. This article explains some of the key features of development container variants and how they differ from our standard container images and outlines ways these variants come together in creating a secure deployment. > **Note**: Any time this article mentions Chainguard's "standard" container images, it's referring to our minimal, distroless container images. In the context of this article, any non-development variant is considered a "standard" container image. ## Chainguard Container security Chainguard's standard container images have the following advantages: - Our standard container images contain fewer packages. While Chainguard moves quickly to patch CVEs in all images, our standard, distroless images still experience fewer CVEs overall. Reducing the number of packages also reduces the potential number of unknown vulnerabilities that might apply to an image. - Not all executables are created equal. Shells such as bash, package managers such as apk, and communication-ready utilities such as Git and curl are general-purpose tools that are broadly exploitable. - A smaller image can use fewer resources and reduce deployment time. In some cases, especially with already-large images, a smaller version can make a deployment more stable or robust. - Removing unnecessary components increases the observability and transparency of the image. Reducing the number of components can facilitate risk assessment or post-incident reporting. While our standard images can be considered to have advantages for security, the development variants of Chainguard Containers are also low-to-no CVE, include useful attestations such as SLSA provenance and SBOMs, and follow other security best practices. You should feel comfortable using these secure development images in production if they better fit your use case. ## Using development images Though using Chainguard's standard container images in your final deployment will give you the benefits of distroless, development images have many use cases. These include: - **Building**: In many Dockerfile builds, you will need to generate software artifacts such as static binaries or virtual environments as part of the build process. Development images are ideal for this use case, and after these artifacts have been generated they can be copied to a standard image for use. Refer to [How to port a sample application to Chainguard Containers](/chainguard/containers/migration/porting-apps-to-chainguard/) for a detailed example. - **Debugging**: Our development images contain a number of useful utilities, but are otherwise designed to be as close as possible to the standard variant. This makes them useful for debugging, since you can test out build steps or the build environment using interactive shells and package managers. Refer to [Debugging distroless images](/chainguard/containers/troubleshooting/debugging-distroless-images/) for more on this use case. - **Training**: In the case of AI images, you can use a development variant to train a model, then run the model in inference using a standard image. - **Deploying**: Development images are low-to-no CVE and are suitable for production. ## Slim container variants In addition to standard and development variants, some of Chainguard's container images also provide slim variants. Slim variants, whose tags are appended with `-slim`, are a further-reduced subset of our standard container images. As outlined previously, Chainguard's standard container images are already designed to be minimal, distroless runtimes: they omit general-purpose shells, package managers, and development utilities, while still providing everything you need to run the application in production with a low-to-no CVE profile. Slim variants start from that same security posture and then strip away even more. Where a standard image might still include some compatibility or helper tooling, slim variants remove additional components — such as BusyBox, shells, or language-specific tools like `npm` — so that only the essentials for running the workload remain. The result is a smaller, more opinionated runtime with the lowest possible footprint and attack surface, at the cost of some convenience and compatibility compared to the corresponding standard variant. In many workflows, you might build or debug using a development image, then deploy with a corresponding slim (or other standard) variant once your application artifacts are ready. Because slim variants remove general-purpose tools, they can require more deliberate configuration than other variants, particularly around entrypoints, logging, and debugging. When using a slim variant, always review the image documentation to understand its expected entrypoint, available utilities, and any behavioral differences from the corresponding standard or development image. ## Full container variants Some of Chainguard's container images also provide *full* variants, whose tags are appended with `-full`. Where standard images strip away everything but the essentials, full variants aim for parity with their upstream equivalent — typically the Debian-based image on Docker Hub. They include the packages, environment variables, and entrypoint scripts that the standard Chainguard image intentionally omits. We currently offer full variants for 10 images. Full variants exist primarily to ease migration. When you move a workload onto Chainguard Containers, your build and test pipelines may depend on components from your previous upstream image, even when the application itself doesn't need them to run. These dependencies can cause runtime crashes or pipeline failures during a switch to a more minimal image. Starting with a full variant lets you adopt Chainguard Containers without first untangling every such dependency. We recommend treating full variants as a starting point rather than a destination. Once you've migrated, review which packages your production pipeline actually requires, then move to a standard or development variant that includes only what you need. Doing so gives you the smaller attack surface and lower CVE count that make Chainguard Containers worth adopting in the first place. ## Special considerations It’s likely already clear that switching to our standard images requires a few changes in development and deployment. Here are a few additional considerations: - Since we don’t include general-purpose shells in most standard container images, the entrypoint to these images will vary by each image’s use case. Check the documentation for each image, and note that Dockerfile commands such as `CMD` will be directed to the image-specific entrypoint. Because we aim to keep our development images as close as possible to our standard images, these changes to entrypoint also affect development container images. - Chainguard's container images use a less privileged user by default. When using our development images, you will need to explicitly access the image with the root user — such as by using the `--user root` option — to perform tasks such as installing packages with apk. ## Resources - [Blog: Minimal container images: Towards a more secure future](https://www.chainguard.dev/unchained/minimal-container-images-towards-a-more-secure-future) - [Chainguard Academy: Overview of Chainguard Containers](/chainguard/containers/overview/) - [Chainguard Academy: Debugging distroless images](/chainguard/containers/troubleshooting/debugging-distroless-images/) --- ### Using wolfictl to manage Security Advisories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/security-advisories/managing-advisories.md Last Modified: October 8, 2026 Tags: Chainguard Containers, CVE > **Note**: This document is deprecated as of June 2025. Chainguard operates its own [Security Advisories](https://images.chainguard.dev/security/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-security-advisories-managing-advisories) page to alert users about the status of vulnerabilities found in Chainguard Containers. To maintain this database, we use [`wolfictl`](https://github.com/wolfi-dev/wolfictl/), a tool developed for working with the [Wolfi un-distro](https://github.com/wolfi-dev/). In this guide, you will walk through using `wolfictl` to create an advisory for a vulnerable package. You’ll also learn how to update this advisory as more information about the vulnerability is disclosed over time. To follow along, you will need to have [`git`](https://git-scm.com/) and the [Go programming language](https://go.dev/dl/) installed on your machine. This guide will focus on the packages and advisories issued for Wolfi. ## How to install `wolfictl` To work with security advisories, you will need to install the `wolfictl` tool onto your machine. First, execute the following command in your terminal to clone the `wolfictl` repository locally, and navigate to it. ```sh git clone git@github.com:wolfi-dev/wolfictl.git wolfictl && cd $_ ``` Then, using `go`, install the `wolfictl` tool: ```sh go install ``` If you encounter any errors during installation, your installed version of `go` may be out of date. You can check what version of `go` you have installed by running `go version` in your terminal. Check the [`go.mod` file](https://github.com/wolfi-dev/wolfictl/blob/main/go.mod) in the `wolfictl` repository to determine what version of `go` you will need, and be sure to update `go` to this version or a later release to continue. Alternatively, you may need to add the `wolfictl` binary to your `$PATH` after installation if your system does not recognize the command. You can verify that you have successfully installed `wolfictl` by executing the `wolfictl version` command in your terminal. ```sh wolfictl version ``` A successful installation of `wolfictl` will display output similar to the following. Note that the exact output will vary over time as new `wolfictl` versions are released. ```Output __ __ ___ _ _____ ___ ____ _____ _ \ \ / / / _ \ | | | ___| |_ _| / ___| |_ _| | | \ \ /\ / / | | | | | | | |_ | | | | | | | | \ V V / | |_| | | |___ | _| | | | |___ | | | |___ \_/\_/ \___/ |_____| |_| |___| \____| |_| |_____| wolfictl: A CLI helper for developing Wolfi GitVersion: devel GitCommit: 6c98dc69a559192575d085d87fd916d8281dd67d GitTreeState: clean BuildDate: 2024-07-23T01:57:17 GoVersion: go1.22.5 Compiler: gc Platform: darwin/arm64 ``` In the next section, you will complete your local setup so you can begin using the `wolfictl` tool to work with advisories. ## Cloning package and advisory repositories Before you can interact with security advisories, the `wolfictl` tool will need access to existing Wolfi package and advisory information. You will need to clone two additional repositories: the [`wolfi-dev/os`](https://github.com/wolfi-dev/os) repository and the [`wolfi-dev/advisories`](https://github.com/wolfi-dev/advisories) repository. Execute the following commands to clone each of these repositories to your machine and then navigate to the `advisories` directory. ```sh git clone git@github.com:wolfi-dev/os.git git clone git@github.com:wolfi-dev/advisories.git && cd advisories ``` With `wolfictl` installed and these two repositories cloned locally, you are now ready to interact with the security advisory database. ## Viewing existing advisories First we will take a look at the existing advisories issued for packages in Wolfi. Keep in mind that the results shown here, and on your own machine, are snapshots in time. You should regularly check for changes to the upstream repository as new packages and advisories are issued. You will be using the `wolfictl advisory list` command to view existing advisories. There are a variety of flags which you can append to assist in your search. * `-p` lists all advisories for a given package name. * `-V` lists all advisories for a given CVE ID, across all packages. * `-t` lists all advisories by their most recent event type. * `-c` lists all advisories by detected component type. * `--history` reports the full list of events for displayed advisories. You can combine multiple flags together to conduct a more granular search. For a complete listing of available command options, you can run the `wolfictl advisory list -h` command in your terminal. For example, let’s say you want to find advisories for the `glibc` package as well as the full history of these advisories. ```sh wolfictl advisory list -p glibc --history ``` The following shows a small sample of output from this command at the time of this writing. Note that the output of this command may change as vulnerability entries are added and updated over time. ```Output glibc CGA-49g3-q5cv-7m6g (CVE-2023-4527, GHSA-hmf7-f8gf-8f4p) 2023-09-22T14:14:01Z fixed (2.38-r2) CGA-573p-mg38-75fh (CVE-2019-1010024, GHSA-3q29-89cr-qgvj) 2023-03-06T13:22:06Z false positive (vulnerability-record-analysis-contested) CGA-57wh-hj4x-5342 (CVE-2023-4911, GHSA-m77w-6vjw-wh2f) 2023-10-03T22:58:32Z fixed (2.38-r5) CGA-5vfg-gqch-hcj5 (CVE-2010-4756, GHSA-x2r9-jfjp-jvp9) 2023-03-06T17:47:28Z false positive (vulnerability-record-analysis-contested) 2024-07-26T16:49:42Z fixed (2.40-r0) CGA-7fr2-v9gg-pg28 (CVE-2024-33600, GHSA-jv3g-6pg3-v9j8) 2024-05-14T11:00:51Z detected (glibc) 2024-05-15T04:40:16Z fixed (2.39-r5) … ``` From this snapshot, you can get an idea of the timeline of a vulnerability’s remediation process. For example, CVE-2024-33600 was detected on May 14th, 2024, and was remediated within the next 24 hours. Similar results are shown for other advisories, including the versions of the package in which the vulnerability was remediated. We encourage you to experiment with these flags to find what information you can gather from your various searches. ## Creating and updating advisories To begin creating and updating security advisories, you will need to navigate back to the Wolfi package repository you cloned. ```sh cd ../os ``` Here, you will run the `wolfictl advisory create` command to begin the process of adding a new advisory. This command will respond with a few prompts in the terminal requesting input of information used to create the advisory. First, you will be asked to enter the package in which the CVE was found. For this demonstration we will use the `glibc` package. Then, you will be asked for the vulnerability ID that you wish to make an advisory for. This ID must be a CVE, CGA, GHSA, or Go vulnerability ID. Finally, you will be asked what the status for the advisory should be, from one of the following options: * `detection` (Under investigation) * `true-positive-determination` (Affected) * `fixed` (Fixed) * `false-positive-determination` (Not affected) * `fix-not-planned` (Fix not planned) * `pending-upstream-fix` (Pending upstream fix) To learn more about the meanings of each of these identifiers, please refer to our [documentation on event types](https://github.com/wolfi-dev/advisories/blob/main/docs/event_types.md). The following shows an example of the `wolfictl advisory create` command in action. **Please note that the vulnerability referenced is an arbitrary CVE ID for demonstration purposes and does not reflect an actual vulnerability found in the `glibc` package.** ```sh Auto-detected distro: Wolfi Package: glibc Vulnerability: CVE-2024-57230 Type: detection ``` You can now check that your advisory has been successfully added with the `wolfictl advisory diff` command, as follows: ```sh wolfictl advisory diff ``` From this command, you can see the local addition of your new advisory for the `glibc` package. ```Output Auto-detected distro: Wolfi ( - removed / ~ modified / + added ) ~ document "glibc" + advisory "CGA-pwm8-5rj4-phww" ``` Let’s say that you upgrade a vulnerable package to a newer, patched version. You are now ready to update the security advisory so its status is now “Fixed”. You can do so using the `wolfictl advisory update` command. Again, this command will walk you through the steps of updating an advisory by requesting information about the advisory you wish to modify. The following shows an example of this workflow in action. ```sh Auto-detected distro: Wolfi Package: glibc Vulnerability: CVE-2024-57230 Type: fixed Fixed Version: 2.39-r7 ``` The same process can be followed for other status updates, whether you wish to mark a vulnerability as “Not affected” in the case of a [false positive finding](/chainguard/containers/security-and-compliance/working-with-scanners/false-results/), “Fix not planned”, or another applicable status. ## Further reading In this guide, you learned how to use the `wolfictl` tool to interact with Chainguard’s Security Advisories feed. You used `wolfictl` to explore existing advisories, and also created and updated a new security advisory. The steps shown in this guide allowed you to make local changes to your advisory feed. If you wish to contribute to the open source Wolfi OS advisory feed, please read through our [How To Patch CVEs]( https://github.com/wolfi-dev/os/blob/main/HOW_TO_PATCH_CVES.md) guide and our [How Chainguard issues Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/) article first. Be sure to routinely check [our Security Advisories page](https://images.chainguard.dev/security/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-security-advisories-managing-advisories) when your scanners pick up new CVEs in your images. If you want to learn more about how you can interpret a security advisory and what its status means for your security, read our [article on using advisories](/chainguard/containers/security-and-compliance/security-advisories/how-to-use/). --- ### Using Trivy to scan software artifacts URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/working-with-scanners/trivy-tutorial.md Last Modified: October 2, 2026 Tags: Conceptual, CVE [Trivy](https://github.com/aquasecurity/trivy) is a vulnerability scanner for a wide variety of software artifacts and deployments. Trivy is written in the Go programming language and is maintained by [Aqua Security](https://www.aquasec.com/). Trivy targets container images, VMs, filesystems, remote GitHub repositories, and Kubernetes and Amazon Web Services deployments. The tool can be used to detect known vulnerabilities (CVEs), generate SBOMs, analyze licenses, and scan for misconfigurations and exposed secrets. Trivy can be installed from [package managers](#package-managers) or as a [binary](#binary-installation), and can also be run as a [container image](#container-image). ## Installation ### Package managers For Homebrew, use: ```shell brew install trivy ``` Aqua Security [maintains sources and packages for a variety of additional operating systems and distributions](https://trivy.dev/docs/latest/getting-started/installation/) on their installation page. ### Binary installation Aqua Security provides an [installation script for Trivy](https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh). To install Trivy with the script, change the `/usr/local/bin` argument to the desired installation location on your path before running the following command: ```shell curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.52.2 ``` On many system configurations, you may need to provide elevated permissions via `sudo`: ```shell curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.52.2 ``` You can also manually install Trivy by downloading the binary for your operating system and architecture from the [Trivy releases page](https://github.com/aquasecurity/trivy/releases/tag/v0.52.2) and manually placing the binary on your path. ### Container image Container images for Trivy are hosted on a variety of registries. When running Trivy as a container image, it is recommended to mount a cache directory as a volume. For scanning container images, it is also recommended to mount `docker.sock`. The following command will pull Trivy from Docker Hub, mount the two volumes, run the Trivy container, and use the running container to scan the official nginx image on Docker Hub: ```shell docker run \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $HOME/Library/Caches:/root/.cache/ \ aquasec/trivy:0.52.2 \ image nginx ``` ## Basic usage Throughout this tutorial, we'll use the `trivy` command to run Trivy. If you're running Trivy as a container image, replace this command with the [appropriate `docker run` command](#container-image). To use Trivy, provide a subcommand indicating the type of artifact or deployment to be scanned along with the location of the target. For example, to scan the official Python image on Docker Hub: ```shell trivy image python ``` Trivy will output a series of informational messages, a short summary of CVEs found, including severity, and an itemized list of CVEs. ### Valid targets Trivy can scan a wide variety of artifacts, collections, or deployments, collectively called targets. Each type of software artifact has a specific set of Trivy scanners enabled by default. For example, when scanning container images, Trivy will look for vulnerabilities and exposed secrets by default. ### Scanning a container image To scan a container image on Docker Hub, use the `image` subcommand and the name of the image as an argument: ```shell trivy image nginx ``` For images on other registries: ```shell trivy image cgr.dev/chainguard/nginx:latest ``` ### Scanning a filesystem Trivy can recursively scan directories on a local machine. To start a filesystem scan, run: ```shell trivy fs ``` where `` indicates the root folder where the scan will begin. Trivy looks out for specific files containing lists of packages, such as Python's `requirements.txt` or `poetry.lock`, PHP's `composer.lock`, or Node's `package-lock.json`. The following creates a Python project folder with virtual environment, installs a set of older packages, generates a `requirements.txt` file itemizing all transitive dependencies, and scans the project folder using Trivy: ```shell mkdir python-project && cd python-project python -m venv venv ./venv/bin/pip install WTForms==2.3.3 Werkzeug==2.0.1 ./venv/bin/pip freeze > requirements.txt trivy fs . ``` The following creates a default Node project, installs an older package with `npm`, and scans the project with Trivy: ```shell mkdir node_project && cd node_project npm init -y npm install qs@6.5.2 trivy fs . ``` You should see a summary and itemized list of CVEs for the outdated Node package. ### Scanning clusters To scan a Kubernetes cluster: ```shell trivy k8s --report summary ``` To try out the above command on a test Kubernetes cluster, First install [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation), a utility allowing Kubernetes to be run on your local machine. Once Kind is installed and accessible on your path, run the following to create a cluster: ```shell kind create cluster --name test-cluster ``` Run the following to scan the new cluster with Trivy: ```shell trivy k8s --report summary kind-test-cluster ``` When scanning clusters, requesting only summary output is recommended, as tables in more verbose output may not display correctly. ### Scanning SBOMs Trivy can both generate and scan SBOMs.
What is an SBOM?

A Software Bill of Materials (SBOM) is a formally structured list of libraries, modules, licenses, and version information that make up any given piece of software. An SBOM provides a comprehensive view of the components contained in a software artifact, allowing for systematic analysis, investigation, and automation. SBOMs are most useful in identifying known vulnerabilities, surveying licenses for compliance, and making decisions based on project status or supplier trust.

Read more about SBOMs.

First, generate an SBOM file in CycloneDX format to scan ```shell trivy image -f cyclonedx -o results.cdx.json nginx ``` Trivy can scan this generated CycloneDX SBOM with the following: ```shell trivy sbom results.cdx.json ``` By default, the `sbom` subcommand scans only for vulnerabilities. License scanning can be enabled using the `--scanners license` flag. Some image providers, such as Chainguard, associate images with an [SBOM attestation](https://www.chainguard.dev/supply-chain-security-101/sboms-vs-attestations) verifying that the image has not been tampered with since the time of creation. Trivy provides functionality to query attestations registered in the [Rekor transparency log](https://github.com/sigstore/rekor). To retrieve an SBOM attestation from a Rekor transparency log, set the `--sbom-sources` flag to `rekor` and provide the `--rekor-url` flag to the instance of the transparency log you wish to query against. The following will perform a scan using the SBOM attestation for Chainguard's `nginx` image as registered on the [Rekor public server](https://rekor.sigstore.dev/): ```shell trivy image --sbom-sources rekor --rekor-url https://rekor.sigstore.dev/ cgr.dev/chainguard/nginx ``` Learn more about SBOMs and other output formats in the section on [specifying output formats](#specifying-output-formats). ## Comprehending Trivy output When run with default output and formatting, Trivy first prints a series of informational messages and warnings, then the name of the image and a one-line summary of the number and severity of issues found, and finally a table itemizing each issue. In this section, we'll use an Alpine version of the official Python image as an example. Since we're specifying an older version, you may encounter more CVEs when following the examples than are shown here, as CVEs will accumulate on an image over time. Scan the image with the following command: ```shell trivy image python:3.10.14-alpine3.20 ``` You will receive output similar to the following: ![Screenshot of Trivy output showing first informational messages on which scanners are enabled, then the name of the image, then a listing of issues by severity, and finally a table formatted with ASCII separator (pipes and underscores) showing itemized issues](trivy_output.png) ### Interpreting Trivy output The initial logging portion of Trivy's output indicates which [scanners](#scanners) are enabled and shows warnings if Trivy has an issue performing the scan. In the initial portion of its results output, Trivy summarizes information on the scanned artifact and gives an overview of known vulnerabilities. In the case of a scanned image, the output includes the image digest. This is a unique hash of the image that can be used as an identifier. Following the log, Trivy shows the name of the image and a count of issues by severity. ```output Total: 8 (UNKNOWN: 0, LOW: 0, MEDIUM: 8, HIGH: 0, CRITICAL: 0) ``` When scanning for vulnerabilities, this severity categorization sorts CVEs into four categories based on the Common Vulnerability Scoring System (CVSS).
What is CVSS?

CVEs are assigned numerical scores according to the Common Vulnerability Scoring System (CVSS), an open framework for communicating the severity of software vulnerabilities.

CVSS scores correspond to four categories

  • Critical (9.0-10.0)
  • High (7.0-8.9)
  • Medium (4.0-6.9)
  • Low (0.1-3.9)

In general, the higher the score, the more serious the vulnerability. Higher scores should be prioritized during CVE remediation.

In the case of a license scan, Trivy instead uses its own assessment of the business risk posed by specific license clauses. Similarly, explosed secrets and misconfigurations have their own severity mapping as determined by Aqua Security. ### Itemized CVEs In addition to the log and brief summary, Trivy provides an itemized list of issues. By default, these are in table format, and for a vulnerability scan list the library, vulnerability, severity, status, installed version, and fixed version of each issue. Other types of scan list different data—for example, a license scan lists the package, license, license classification, and perceived severity of business risk. When scanning for vulnerabilities, information on fixed version can show which CVEs can be resolved by bumping the library version. Trivy also provides a short prose description of the nature of each issue. By default, Trivy's table output is relatively verbose, and Trivy does not respect the traditional 80-character line limit on terminal output. Refer to [Output formats and verbosity](#output-formats-and-verbosity) for information on more granular control over Trivy's output. ## Scanners ### Specifying scanners Trivy can scan not only for known vulnerabilities, but also for misconfigurations, exposed secrets, and license risks. When scanning container images or filesystems, Trivy scans for vulnerabilities and exposed secrets by default. Individual scanners can be selected by passing a comma-separated list after the `--scanners` flag. To add a misconfigurations scan to an analysis of a container image: ```shell trivy image --scanners vuln,misconfig,secret nginx ``` To recursively scan only for exposed secrets on a filesystem: ```shell trivy fs --scanners secret . ``` This will perform a recursive scan of all files and folders in the current working directory. ### License scanning Trivy provides an opinionated license scan that flags license clauses that may pose a business risk. To perform a license scan on a container image: ```shell trivy image --scanners license nginx ``` By default, the Trivy license scan only looks at packages installed by managers such as `apt` or `apk`. To scan other files, add the `--license-full` flag: ```shell trivy image --scanners license --license-full nginx ``` Using the `--license-full`flag will also show results for "loose" licenses, such as those provided as text files in project folders. ## Output formats and verbosity ### Specifying output formats Trivy allows output in JSON, SARIF, CycloneDX, SPDX, SPDX-JSON, and GitHub formats. If no output or format flags are specified, Trivy first prints a series of informative messages and warnings to `stderr` and then prints a table of results to `stdout`. The `-q` or `--quiet` flag suppresses the logging output normally printed to `stderr`. The following returns just the line summarizing the number and severity of issues: ```shell trivy image -q nginx | grep Total: ``` ```output Total: 173 (UNKNOWN: 2, LOW: 88, MEDIUM: 59, HIGH: 22, CRITICAL: 2) ``` The `-f` or `--format` flag specifies the output format, and the `-o` or `--output` flag specifies an output file. The following writes a JSON-formatted report to a `results.json` file. ```shell trivy image -f json -o results.json nginx ``` Similarly, the following would write a report in SARIF format: ```shell trivy image -f sarif -o results.sarif nginx ``` Other formats can be generated by passing the appropriate format type with the `-f` or `--format` flag. ### Generating SBOMs The CycloneDX, SPDX, and SPDX-JSON output formats are considered SBOMs, and can be scanned with the `trivy sbom` subcommand. The following command will generate an SBOM in CycloneDX format: ```shell trivy image -f cyclonedx -o results.cdx.json nginx ``` Refer to [Scanning SBOMs](#scanning-sboms) for more on scanning these output formats. ### Generating a report from a template Trivy can generate reports in additional formats from user-contributed templates. To use templates, first clone the Trivy GitHub repository to your home folder: ```shell git clone https://github.com/aquasecurity/trivy.git ~/.trivy ``` To generate a report using the HTML template, specify the path to the template in the cloned repository: ```shell trivy image --format template --template "@.trivy/contrib/html.tpl" -o report.html nginx ``` This HTML output can be significantly more readable than Trivy's default table output: ![Screenshot of rendered HTML generated by the Trivy HTML template](trivy-html-report.png) Other template-based output formats can be browsed in the [Trivy contrib directory](https://github.com/aquasecurity/trivy/tree/main/contrib). ## Trivy resources The following resources may complement your use of Trivy: - [Trivy Documentation](https://trivy.dev/docs/latest/) — Documentation on the latest version of Trivy - [Trivy Operator for Kubernetes](https://github.com/aquasecurity/trivy-operator) — An operator to continuous scan a Kubernetes cluster for issues - [Trivy Announcements](https://github.com/aquasecurity/trivy/discussions/categories/announcements) — News on Trivy from Aqua Security --- ### Reproducibility and Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/how-we-build-and-test/repro.md Last Modified: October 8, 2026 Tags: Chainguard Containers A build is reproducible when the same inputs produce the same output, bit for bit, regardless of who runs it and when they run it. Chainguard builds its containers with [apko](https://github.com/chainguard-dev/apko) from a declarative configuration, and publishes that configuration as a signed attestation on every build. You can retrieve the configuration, rebuild the container yourself, and check the digest you get against the one Chainguard published. ## What reproducibility requires Reproducibility requires more than a build that succeeds twice. Binary identical means every byte matches, so a reproducible build has to control three things: - **The versions of its inputs.** Not only source code, but every dependency, each pinned to an exact version. - **The version of the build tooling.** The same inputs run through two versions of a build tool can produce two different results. - **Anything that varies from run to run.** Timestamps and generated unique IDs are the most common causes. They change on every build, and they change the output along with them. When a build meets those conditions, anyone can verify its output independently: rather than trusting that a container was built from the configuration it claims, you can rebuild it and compare the digests. ## Reproduce a Chainguard Container You need [cosign](https://github.com/sigstore/cosign), [apko](https://github.com/chainguard-dev/apko), [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane), `jq`, and a registry you can push to. cosign and apko are both distributed as signed binaries on their GitHub releases pages, and apko can also run from a container if you would rather not install it. The examples use `cgr.dev/chainguard/nginx`, one of Chainguard's [Free containers](/chainguard/containers/concepts/container-categories/#free-containers), so they run as written. ### Record the digest you want to match ```sh crane digest cgr.dev/chainguard/nginx:latest ``` ```output sha256: ``` Chainguard moves the `latest` tag as it publishes rebuilds, so start by pinning down which build you're reproducing. The `` you get back is the value to match at the end; it differs from the one anyone else gets unless you both pull the same build. [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/) covers digests in more detail. ### Retrieve the build configuration Every build carries its apko configuration as an attestation. The following command verifies that attestation and writes the configuration to a file: ```shell cosign verify-attestation \ --type https://apko.dev/image-configuration \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ cgr.dev/chainguard/nginx:latest | jq -r .payload | base64 -d | jq .predicate > latest.apko.json ``` The two certificate flags are what make that a verification. They tell cosign to accept the attestation only if it was signed by the GitHub Actions workflow that builds Chainguard's containers, using a certificate from that workflow's OIDC issuer. Without them you would be reading an attestation that is present, rather than one that is genuine. The rest of the pipeline unwraps the result: `jq -r .payload` takes the in-toto payload, `base64 -d` decodes it, and `jq .predicate` keeps the apko configuration itself. Read the configuration back from the file that the configuration was piped into: ```shell jq . latest.apko.json ``` The following shows this command's abridged output, highlighting a few fields worth calling out: ```json { "contents": { "packages": [ "ca-certificates-bundle=20260611-r1", "glibc-2.44-locale-posix=2.44-r6", "glibc-2.44=2.44-r6", "ld-linux-2.44=2.44-r6", "..." ], "repositories": [ "https://apk.cgr.dev/chainguard" ] }, "accounts": { "run-as": "65532", "users": [ { "gid": 65532, "homedir": "/home/nginx", "uid": 65532, "username": "nginx" } ] }, "archs": [ "amd64", "arm64" ], "entrypoint": { "command": "/usr/sbin/nginx" }, "stop-signal": "SIGQUIT" } ``` apko builds are declarative: the configuration names a list of APK packages and some metadata, and nothing more. Two details in it matter. Each package is pinned to an exact version, and the list is complete — `glibc` does not quietly pull in a dependency that the file doesn't name. Everything else in the file is metadata that the build applies to the finished container: the user account to run as, the architectures to build, the entrypoint, the stop signal. ### Rebuild and compare Point apko at the configuration and give it somewhere to push: ```sh apko publish latest.apko.json ttl.sh/nginx-repro ``` apko builds one image per architecture in the `archs` list, assembles them into an index, pushes the result, and prints the digest of what it pushed as its final line: ```output ttl.sh/nginx-repro@sha256: ``` That digest is the same `` the first step recorded, which means the rebuild is byte-for-byte identical to the container Chainguard published. The registry it was pushed to has no bearing on the value: a digest covers the content, not where it lives. To run apko from its container instead of installing it, mount the directory holding the configuration and pass the same arguments: ```shell docker run --rm -v "$PWD":/work -w /work cgr.dev/chainguard/apko:latest publish latest.apko.json ttl.sh/nginx-repro ``` `apko publish` needs a registry to push to. These examples use [ttl.sh](https://ttl.sh), a free registry whose images expire after a short time, which suits a throwaway comparison. Any registry you can write to works. ### Compare images that don't match When two digests differ, [diffoci](https://github.com/reproducible-containers/diffoci) reports the specific differences between the images rather than leaving you to compare two hashes: ```sh diffoci diff cgr.dev/chainguard/nginx:latest ttl.sh/nginx-repro ``` When the images match, the command exits without reporting anything. When they differ, it lists the differing files and where they differ. Add `--ignore-timestamps` to set aside timestamp differences, or `--semantic` to ignore everything diffoci treats as non-substantive, including file ordering and redundant file mode bits. ## What limits reproducibility Three factors can prevent an exact match, and each is worth understanding before concluding that a rebuild has failed. - **The version of apko matters.** A change in how a build tool handles something as small as a symbolic link is enough to change a digest. Every Chainguard Container records the version that built it in its SLSA provenance attestation: ```shell cosign verify-attestation \ --type https://slsa.dev/provenance/v1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ cgr.dev/chainguard/nginx:latest | jq -r .payload | base64 -d | jq .predicate.runDetails.builder ``` ```output { "id": "https://github.com/chainguard-dev/terraform-provider-apko", "version": { "apko": "v1.2.43", "terraform-provider-apko": "v1.2.20" } } ``` If a rebuild produces a different digest, check that version first. - **APKs cannot be rebuilt bit for bit from source.** You can build the packages themselves from source, and their contents match, but each APK embeds a signature made with Chainguard's private signing key. Without that key you cannot produce an identical package file. - **The pinned package versions have to be available.** apko installs the exact versions the configuration names, so reproducing an old container depends on those versions still being served from the repository it points at. ## Correction to the video The following video says that Chainguard keeps older package versions only for a short time, and that reproducing an older container therefore means holding your own copies of the APKs. That statement is not accurate. Chainguard retains every package version it has issued, so you can rebuild containers from months ago without arranging your own storage. Chainguard may age older versions out in the future to keep the package index manageable, and only the latest versions are serviced with fixes, but retention today is indefinite. [Watch the video on YouTube](https://www.youtube.com/watch?v=0Qn2J89UEvI) ## Related reading - [How to retrieve SBOMs and attestations for Chainguard Containers](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) — the other attestation types published alongside the apko configuration, and how to fetch them. - [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/) — identifying a build by digest, and reading the software versions inside it. - [Verifying Chainguard Containers and metadata signatures with Cosign](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/) — verifying signatures and provenance more generally. - [How Chainguard tests its container images](/chainguard/containers/concepts/how-we-build-and-test/images-testing/) — what happens to a container before it's published. --- ### Getting started with the Laravel Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/laravel.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [Laravel container image](https://images.chainguard.dev/directory/image/laravel/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-laravel) provides a secure foundation for [Laravel](https://laravel.com) applications with minimal vulnerabilities compared to traditional PHP images. This specialized image includes all necessary PHP extensions and tooling for Laravel development while maintaining Chainguard's security-first approach, enabling developers to build complex applications without compromising on security. In this guide, we'll set up a demo and demonstrate how you can use Chainguard Containers to develop, build, and run Laravel applications. This tutorial requires Docker to be installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/).
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## 1. Setting up a demo application We'll start by getting the demo application ready. The demo is called **OctoFacts**, and it shows a random fact about Octopuses alongside a random Octopus image each time the page is reloaded. Quotes are loaded from a `.txt` file into the database through a database migration. By default, the application uses an [SQLite](https://www.sqlite.org/) database. This allows us to test the application with the built-in web server provided by the `artisan serve` command, without having to set up a full PHP development environment first. In the next step, we'll configure a multi-node environment using Docker Compose to demonstrate a typical LEMP (Linux, (E)Nginx, MariaDB, and PHP) environment using Chainguard Containers. Start by cloning the demos repository to your local machine: ```shell git clone git@github.com:chainguard-dev/edu-images-demos.git ``` Locate the `octo-facts` demo and cd into its directory: ```shell cd edu-images-demos/php/octo-facts ``` The demo includes a `.env.dev` file with the app's configuration for development. You should create a copy of this file and save it as `.env`, so that the application can load default settings: ```shell cp .env.dev .env ``` You can now use the builder Laravel container image to install dependencies via Composer. Notice that we're using the **laravel** system user in order to be able to write to the shared folder without issues: ```shell docker run --rm -v ${PWD}:/app --entrypoint composer --user laravel \ cgr.dev/chainguard/laravel:latest-dev \ install ``` You can now run the database migrations and seed the database with sample data. This will populate a `.sqlite` database with facts obtained from the `octofacts.txt` file in the root of the application. ```shell docker run --rm -v ${PWD}:/app --entrypoint php --user laravel \ cgr.dev/chainguard/laravel:latest-dev \ /app/artisan migrate --seed ``` Next, run `npm install` to install Node dependencies. You can use the `node:latest-dev` container image for that, but you'll need to use the **root** container user in order to be able to write to the shared folder using this image: ```shell docker run --rm -v ${PWD}:/app --entrypoint npm --user root \ cgr.dev/chainguard/node:latest-dev \ install ``` Then, fix permissions on node modules with: ```shell sudo chown -R ${USER} node_modules/ ``` The next step is to build front end assets. You can use the `npm run build` command for that. Like with `npm install`, you'll need to set the container user to **root**. ```shell docker run --rm -v ${PWD}:/app --entrypoint npm --user root \ cgr.dev/chainguard/node:latest-dev \ run build ``` Fix permissions on the public folder: ```shell sudo chown -R ${USER} public/ ``` The application is all set. Next, run the built-in web server with: ```shell docker run -p 8000:8000 --rm -it -v ${PWD}:/app \ --entrypoint /app/artisan --user laravel \ cgr.dev/chainguard/laravel:latest-dev serve --host=0.0.0.0 ``` This command will create a port redirect that allows you to access the built-in web server running in the container. It uses a volume share for the application files, and sets the entrypoint to the `artisan serve` command. You should now be able to access the application from your browser at `localhost:8000`. You'll get a page similar to this: ![Preview of the OctoFacts demo Laravel application](https://github.com/chainguard-dev/edu-images-demos/raw/main/php/octo-facts/public/octofacts.png) ## 2. Creating a LEMP environment with Docker Compose To demonstrate a full LEMP setup using Chainguard Containers, we'll now set up a Docker Compose environment to serve the application via Nginx. This setup can be used as a more robust development environment that replicates a production setting based on secure container images. The following `docker-compose.yaml` file is already included in the root of the application folder: ```yaml services: app: image: cgr.dev/chainguard/laravel:latest-dev restart: unless-stopped working_dir: /app volumes: - .:/app networks: - wolfi nginx: image: cgr.dev/chainguard/nginx restart: unless-stopped ports: - 8000:8080 volumes: - .:/app - ./nginx.conf:/etc/nginx/nginx.conf networks: - wolfi mariadb: image: cgr.dev/chainguard/mariadb restart: unless-stopped environment: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 MARIADB_USER: laravel MARIADB_PASSWORD: password MARIADB_DATABASE: octofacts ports: - 3306:3306 networks: - wolfi networks: wolfi: driver: bridge ``` This `docker-compose.yaml` file defines 3 services to run the application (**app**, **nginx**, **mariadb**), using volumes to share the application files within the container and a configuration file for the Nginx server, which we'll showcase in a moment. Notice the database credentials within the `mariadb` service: these environment variables are used to set up the database. This is done automatically by the MariaDB container image entrypoint upon container initialization. We'll use these credentials to configure the new database within Laravel's `.env` file. >Please note this Docker Compose setup is intended for local development only. For production environments, you should never keep sensitive data like database credentials in plain text. Check the [Docker Compose Documentation](https://docs.docker.com/compose/use-secrets/) for more information on how to handle sensitive data in Compose files. The following `nginx.conf` file is also included within the root of the application folder. This file is based on the [recommended Nginx deployment configuration](https://laravel.com/docs/11.x/deployment#nginx) from official Laravel docs. ```nginx configuration pid /var/run/nginx.pid; events { worker_connections 1024; } http { server { listen 8080; root /app/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; location / { include /etc/nginx/mime.types; try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ /\.(?!well-known).* { deny all; } } } ``` This file sets up the document root to `app/public` and configures Nginx to redirect `.php` requests to the container running PHP-FPM (`app:9000`). You can bring the environment up with: ```shell docker compose up ``` This command will block your terminal and show live logs from each of the running services. With the MariaDB database up and running, you can now update Laravel's main `.env` file to change the database connection from **sqlite** to **mysql**. Open the `.env` file in your editor of choice and locate the database settings. The `DB_CONNECTION` parameter is set to `sqlite`, you should change that to `mysql` and uncomment the remaining variables to reflect the settings from your `docker-compose.yaml` file. This is how the database section should look like when you're finished editing: ```ini DB_CONNECTION=mysql DB_HOST=mariadb DB_PORT=3306 DB_DATABASE=octofacts DB_USERNAME=laravel DB_PASSWORD=password ``` Save and close the file. If you reload the application on your browser now, you should get a database error, because the database is empty. You'll need to re-run migrations and seed the database. To do that, you can run a `docker exec` command on the live container. First, look for the container running the `app` service and copy its name. ```shell docker compose ps ``` ```shell NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS octo-facts-app-1 cgr.dev/chainguard/laravel:latest-dev "/bin/s6-svscan /sv" app 11 seconds ago Up 10 seconds octo-facts-mariadb-1 cgr.dev/chainguard/mariadb "/usr/local/bin/dock…" mariadb 11 seconds ago Up 10 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp octo-facts-nginx-1 cgr.dev/chainguard/nginx "/usr/sbin/nginx -c …" nginx 11 seconds ago Up 10 seconds 0.0.0.0:8000->8080/tcp, :::8000->8080/tcp ``` Then, you can run migrations like this: ```shell docker exec octo-facts-app-1 php /app/artisan migrate --seed ``` You can use the same method to execute other Artisan commands while the environment is up. After running migrations and seeding the database, you should be able to reload the app from your browser at `localhost:8000` and get a new octopus fact. ## 3. Creating a distroless Laravel runtime for the application So far, we have been using the `laravel:latest-dev` builder image to run the application in a development setting. For production workloads, the recommended approach for additional security is to create a [distroless](/chainguard/containers/concepts/getting-started-distroless/) runtime for the application that will contain only what's absolutely necessary for running the app on production. This is done by combining a **build** phase in a **multi-stage** Dockerfile. To demonstrate this approach, we'll now build a distroless container image and test it using the Docker Compose setup exemplified in the previous section. The following Dockerfile is included within the root of the application: ```Dockerfile FROM cgr.dev/chainguard/laravel:latest-dev AS builder USER root RUN apk update && apk add nodejs npm COPY . /app RUN cd /app && chown -R php.php /app USER php RUN composer install --no-progress --no-dev --prefer-dist RUN npm install && npm run build FROM cgr.dev/chainguard/laravel:latest COPY --from=builder /app /app ``` This Dockerfile starts with a build stage that copies the application files to the container, installs Node and NPM, installs the application dependencies, and dumps front-end assets to the public folder. A second stage based on `laravel:latest` copies the application from the build stage to the final distroless image. It's important to notice that we don't run any database migrations here, because at build time the database might not be ready yet. In other production scenarios, you may be able to include the migration within the Dockerfile, as long as the database is already up and running. The file `docker-compose-distroless.yaml` included within the root of the application folder has a few changes compared to the previous `docker-compose.yaml` file. The `app` service now uses the `octofacts` image, which is built from the Dockerfile referenced above. The `user: laravel` directive is also removed so commands run as the default **php** user. This is how the `app` service looks like in the new file: ```yaml app: image: octofacts build: context: . restart: unless-stopped working_dir: /app user: laravel volumes: - .:/app networks: - wolfi ... ``` If your environment is still running, you should stop it before proceeding. Hit `CTRL+C` and then run: ```shell docker compose down ``` This will stop and remove all containers, networks, and volumes created by the previous `docker-compose.yaml` file. You can now bring the new environment up with: ```shell docker compose -f docker-compose-distroless.yaml up ``` Once the environment is up and running, you can now run the database migrations with: ```shell docker exec octo-facts-app-1 php /app/artisan migrate --seed ``` You should now be able to reload your browser and obtain a new octopus fact. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose Laravel Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Migrate a Node.js application to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-guides/node.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Migration Chainguard's Node.js container images offer a streamlined migration path for applications seeking enhanced security posture through minimal, distroless design. Built on [Wolfi](/open-source/wolfi/), they include only the packages your application needs, which keeps both the attack surface and the image size small. Daily automated builds ensure your applications always have the latest security patches without manual intervention. For current CVE data on each image and tag, see the [Node entry in the Chainguard Containers directory](https://images.chainguard.dev/directory/image/node/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-node).
What is Distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi OS? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

This article is intended as a guide to porting existing Dockerfiles for Node.js applications to a Chainguard Containers base. ## Node.js Chainguard Containers The [Node.js](https://images.chainguard.dev/directory/image/node/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-node) images come in two main flavors; runtime images intended for production usage and builder images intended for use in the build-step of multi-stage builds. The builder images are distinguished by the `-dev` suffix (e.g., `latest-dev`). The production images are intentionally as minimal as possible. They have enough dependencies to run a Node.js application but no more. There is no package manager or shell, which reduces the attack surface of the image, but can also make it difficult to extend the image. The builder images have more build tooling as well as a shell and package manager, allowing them to be easily extended. We still aim to keep CVE counts as low as possible in the builder images and they can be used in production if necessary (such as when the application requires extra system dependencies at runtime), but we recommend using the builder image as the first step in a multi-stage build with the production image as the base for the final image. This extremely minimal approach to the runtime image is sometimes known as "distroless". For a deeper exploration of distroless images and their differences from standard base images, refer to the guide on [Getting Started with Distroless images](/chainguard/containers/concepts/getting-started-distroless/). ## Migrating from other distributions Dockerfiles will often contain commands specific to the Linux Distribution they are based on. Most commonly this will be package installation instructions (e.g. `apt` vs `yum` vs `apk`) but also differences in default shell (e.g. `bash` vs `ash`) and default utilities (e.g. `groupadd` vs `addgroup`). Our high-level guide on [Migrating to Chainguard Containers](/chainguard/containers/migration/migrating-to-chainguard-images/) contains details about distro-based migration and package compatibility when migrating from Debian, Alpine, Ubuntu and Red Hat UBI base images. ## Installing further dependencies Sometimes your applications will require further dependencies, either at build-time, runtime or both. Wolfi has large number of software packages available, so you are likely to be able to install common packages via `apk add`, but be aware that packages may be named differently than in other distributions. The easiest way to search for packages is via apk tools. For example: ```shell docker run -it --rm cgr.dev/chainguard/wolfi-base f273a9aa3242:/# apk update fetch https://packages.wolfi.dev/os/aarch64/APKINDEX.tar.gz [https://packages.wolfi.dev/os] OK: 53914 distinct packages available f273a9aa3242:/# apk search cairo cairo-1.18.0-r1 cairo-dev-1.18.0-r1 cairo-gobject-1.18.0-r1 cairo-static-1.18.0-r1 cairo-tools-1.18.0-r1 harfbuzz-8.4.0-r1 harfbuzz-dev-8.4.0-r1 pango-1.52.2-r1 pango-dev-1.52.2-r1 py3-cairo-1.26.0-r0 py3-cairo-dev-1.26.0-r0 ``` These packages can then be easily added to your Dockerfile. For more searching tips, check the [Searching for Packages](/chainguard/containers/migration/migrating-to-chainguard-images/#searching-for-packages) section of our base migration guide. ## Differences from the official Docker image If you are migrating from the [official Docker image](https://hub.docker.com/_/node) there are a few differences that are important to be aware of. - Our images run as the `node` user with UID 65532 by default. If you need elevated privileges for a task, such as installing a dependency, you will need to change to the root user. For example add a `USER root` statement into a Dockerfile. For security reasons you should make sure that the production application runs with a lower privilege user such as `node`. - `WORKDIR` is set to `/app` which is owned by the `node` user. - The Docker Official images have a "smart" entrypoint that interprets the CMD setting. So `docker run -it node` will launch the Node.js interpreter but `docker run -it node /bin/sh` will launch a shell. The latter does not work with Chainguard Containers. In the non `-dev` images, there is no shell to launch, and in the `-dev` images you will need to change the entrypoint e.g. `docker run --entrypoint /bin/sh -it cgr.dev/chainguard/node`. - The image has a defined `NODE_PORT=3000` environment variable which can be used by applications - Our Node.js images include [dumb-init](https://github.com/Yelp/dumb-init) which can be used to wrap the Node process in order to handle signals properly and allow for graceful shutdown. You can use dumb-init by setting an entrypoint such as: `ENTRYPOINT ["/usr/bin/dumb-init", "--"]` - In general there are many fewer libraries and utilities in the Chainguard Container. You may find that your application has an unexpected dependency which needs to be added into the Chainguard Container. ## Migration example This section has a short example of migrating a Node.js application with a Dockerfile building on `node:latest` to use the Chainguard Node.js Containers. The code for [this example can be found on GitHub](https://github.com/chainguard-dev/cg-images-node-migration). Our starting Dockerfile uses the `node:latest` image from Docker Hub in a single-stage build: ```Docker FROM node:latest ENV NODE_ENV production WORKDIR /usr/src/app COPY package.json . RUN npm install USER node COPY . . EXPOSE 3000 CMD npm start ``` If you've cloned the GitHub repository, you can build this image with: ```shell docker build -t node-classic-image -f Dockerfile-classic . ``` Directly porting to Chainguard Containers with the least number of changes results in this Dockerfile: ```Docker FROM cgr.dev/chainguard/node:latest-dev ENV NODE_ENV production WORKDIR /usr/src/app COPY package.json . RUN npm install USER node COPY . . EXPOSE 3000 ENTRYPOINT npm start ``` Here we've changed the image to `cgr.dev/chainguard/latest-dev` and the `CMD` command became `ENTRYPOINT`. We can still do better in terms of size and security. A multi-stage Dockerfile would look like: ```Docker FROM cgr.dev/chainguard/node:latest-dev AS builder ENV NODE_ENV production WORKDIR /usr/src/app COPY package.json . RUN npm install USER node COPY . . FROM cgr.dev/chainguard/node:latest COPY --from=builder --chown=node:node /usr/src/app /app EXPOSE 3000 ENV NODE_ENV=production ENV PATH=/app/node_modules/.bin:$PATH WORKDIR /app ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["node", "app.js"] ``` If you've cloned the GitHub repository, you can build this image with: ```shell docker build -t node-multi-image -f Dockerfile-multi . ``` The advantages of this build are: - we are using `dumb-init` so the container shuts down cleanly in response to `docker stop`. - we do not have all the build tooling in the final image, resulting in a smaller and more secure production image Note that in a production app you may want to use a `package-lock.json` file and the `npm ci` command instead of `npm install` to ensure the correct version of all dependencies is used. ### Using slim images If Chainguard's Node.js image has been added to your organization's Chainguard Registry, you will have access to more tags than just `latest`, including *slim tags*. These represent Chainguard's [slim variants](/chainguard/containers/concepts/container-variants/#slim-container-variants), which have an even smaller attack surface than our standard container images. In the case of Node.js, the slim variants omit some packages that are included in the standard image for compatibility purposes, including `npm` and `busybox`. Because they lack these compatibility packages, the slim Node.js images are often used in multi-stage builds. The following example updates the `Dockerfile-multi` file shown previously to point to one of Chainguard's slim Node.js images: ```Docker FROM cgr.dev/chainguard/node:latest-dev AS builder ENV NODE_ENV production WORKDIR /usr/src/app COPY package.json . RUN npm install USER node COPY . . FROM cgr.dev/$ORGANIZATION/node:25-slim COPY --from=builder --chown=node:node /usr/src/app /app EXPOSE 3000 ENV NODE_ENV=production ENV PATH=/app/node_modules/.bin:$PATH WORKDIR /app ENTRYPOINT ["/usr/bin/dumb-init", "--"] CMD ["node", "app.js"] ``` This example specifies the `:25-slim` tag, but be aware that Chainguard includes slim variants for every version of Node.js it offers. To build an image with a Dockerfile like this, your organization would need to have Chainguard's Node.js image included in its registry. You would also need to update `$ORGANIZATION` to reflect the name of your organization's registry. ## Additional resources - The [Node.js image documentation](https://images.chainguard.dev/directory/image/node/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-node) contains full details on our images, including usage documentation, provenance and security advisories. - The [How to Port a Sample Application to Chainguard Containers](/chainguard/containers/migration/porting-apps-to-chainguard/) article contains an example of porting a Node.js Dockerfile for a legacy application. - The [How to migrate a Node.js application to Chainguard Containers](https://www.youtube.com/watch?v=hfpVS-UP4Yw) video works through an example of porting a Node.js Dockerfile. - Bret Fisher has an excellent [guide to creating Node.js container images](https://github.com/BretFisher/nodejs-rocks-in-docker/), including advice for using distroless. - The [Debugging distroless](/chainguard/containers/troubleshooting/debugging-distroless-images/) guide contains important information for debugging issues with distroless images. You can also refer to the [Verifying containers](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/) resource for details around provenance, SBOMs, and image signatures. --- ### Migrating Dockerfiles to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migrating-to-chainguard-images.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard's container images provide enhanced security through minimal design and built-in provenance attestation, requiring some adjustments when migrating from traditional base images. Built on the [Wolfi](https://www.chainguard.dev/supply-chain-security-101/wolfi-overview) Linux distribution, these images offer compatibility with most applications while significantly reducing attack surface and vulnerabilities. A general migration process would involve the following steps: 1. **Identify the base image you need**. Check out the [Chainguard Containers Directory](https://images.chainguard.dev/directory?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-to-chainguard-images) to identify the image that is the closest match to what you currently use. You may also use [wolfi-base](https://images.chainguard.dev/directory/image/wolfi-base/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-to-chainguard-images) as a flexible starting point for your experimentation. 2. **Try the `-dev` variant of the image first.** Chainguard's container images typically have a **distroless** variant, which is very minimal and doesn't include `apk`, and a **dev** variant that contains tooling necessary to build applications and install new packages. Start with the **dev** variant or the **wolfi-base** image to have more room for customization. 3. **Identify packages you need to install**. Depending on your current base image, you may need to include additional packages to meet dependencies. Refer to the [Searching for packages](#searching-for-packages) section for more details on how to find packages. Make sure the packages you intend to install will work with the base image you select — for example, if you select an older base image built with an older release of `glibc` and want to install newer packages built with a newer release, you will encounter problems. It's a good rule of thumb to use the newest base image you can with the newest packages that match the build. 4. **Migrate to a distroless image**. Evaluate the option of using a Docker multi-stage build to create a final distroless image containing only what you need. Check the [Getting started with distroless images](/chainguard/containers/concepts/getting-started-distroless/) for more details of how to work with distroless images. Although not required, this process should give you a smaller image with additional safeguards. There are some differences in Wolfi's `busybox` and `coreutils` packages when compared to their counterparts in distros such as Debian or even Alpine. Some binaries and scripts are not included by default, which contributes to a smaller package size. This was done in order to keep images to a minimum, but be aware that some commands might still be available through separate packages. The next sections of this page contain distro-specific information that should help you streamline the migration process from your current base images to Chainguard images. ## Migrating from Debian and Ubuntu Dockerfiles Chainguard's container images use the [apk](https://wiki.alpinelinux.org/wiki/Package_management) package format, which differs from the Debian-based `apt` in several ways. Some of these features contribute in making packages smaller and more accountable, resulting in smaller images with traceable provenance information based on cryptographic signatures. The page [Why apk](/open-source/wolfi/apk-package-manager/) from the official Wolfi documentation explains in more detail why we use apk. If you are coming from a Debian-based Dockerfile, you'll need to adapt some of your commands to be compatible with the apk ecosystem: | Command Description | Debian-based Dockerfile | Wolfi-based Equivalent | |------------------------------|-------------------------|------------------------| | Install a package | `apt install` | `apk add` | | Remove a package | `apt remove` | `apk del` | | Update package manager cache | `apt update` | `apk update` | Our [Debian compatibility](/chainguard/containers/migration/compatibility/debian-compatibility/) page has a table listing common tools and their corresponding package(s) in both Wolfi and Debian distributions. For Ubuntu-based Dockerfiles, check our [Ubuntu compatibility](/chainguard/containers/migration/compatibility/ubuntu-compatibility/) page. ## Migrating from Red Hat UBI Dockerfiles If you are coming from a Red Hat UBI (Universal Base Image) Dockerfile, you'll also need to adapt some of your commands to be compatible with the apk ecosystem. Wolfi uses BusyBox utilities, which offer a smaller footprint compared to GNU coreutils in Red Hat images. Our [Red Hat compatibility](/chainguard/containers/migration/compatibility/red-hat-compatibility/) page has a table listing common tools and their corresponding package(s) in both Wolfi and Red Hat distributions. If you are coming from a Red Hat UBI based Dockerfile, you'll need to adapt some of your commands to be compatible with the apk ecosystem: | Command Description | Red Hat UBI Dockerfile | Wolfi-based Equivalent | |------------------------------|------------------------|------------------------| | Install a package | `yum install` | `apk add` | | Remove a package | `yum remove` | `apk del` | | Update package manager cache | `yum makecache` | `apk update` | ## Migrating from Alpine Dockerfiles If your Dockerfile is based on Alpine, the process for migrating to Chainguard Containers should be more straightforward, since you're already using `apk` commands. Wolfi packages typically match what is available in Alpine, with some exceptions. For instance, the Wolfi busybox package is slimmer and doesn't include all tools available in Alpine's busybox. Check the [Alpine compatibility](/chainguard/containers/migration/compatibility/alpine-compatibility/) page for a list of common tools and their corresponding packages in Wolfi and Alpine. Be aware that binaries are not compatible between Alpine and Wolfi. You **should not** attempt to copy Alpine binaries into a Wolfi-based container image. ## Searching for packages Packages from Debian and other base distributions might have a different name in Wolfi. To search for packages, log into an ephemeral container based on `cgr.dev/chainguard/wolfi-base`: ```shell docker run -it --rm --entrypoint /bin/sh cgr.dev/chainguard/wolfi-base ``` Then, run `apk update` to update the local apk cache with latest Wolfi packages: ```shell apk update ``` You'll get output similar to this: ``` fetch https://packages.wolfi.dev/os/x86_64/APKINDEX.tar.gz [https://packages.wolfi.dev/os] OK: 46985 distinct packages available ``` Now you can use `apk search` to look for packages. The following example searches for PHP 8.2 XML extensions: ```shell apk search php*8.2*xml* ``` You should get output similar to this: ``` php-8.2-simplexml-8.2.17-r0 php-8.2-simplexml-config-8.2.17-r0 php-8.2-xml-8.2.17-r0 php-8.2-xml-config-8.2.17-r0 php-8.2-xmlreader-8.2.17-r0 php-8.2-xmlreader-config-8.2.17-r0 php-8.2-xmlwriter-8.2.17-r0 php-8.2-xmlwriter-config-8.2.17-r0 php-simplexml-8.2.11-r1 php-xml-8.2.11-r1 php-xmlreader-8.2.11-r1 php-xmlwriter-8.2.11-r1 ``` ### Searching which package has a command To search in which package you can find a command, you can use the syntax `apk search cmd:command-name`. For instance, if you want to discover which package has the command `useradd`, you can use: ```shell apk search cmd:useradd ``` You'll get output indicating that the `shadow` package has the command you are looking for. ``` shadow-4.15.1-r0 ``` ### Searching for package dependencies To check for package dependencies, you can use the syntax `apk search -R info package-name`. For example, to search which packages are listed as dependencies for the `shadow` package that we've seen in the previous section, you can run: ```shell apk -R info shadow ``` And this will give you a list of dependencies for each version of the `shadow` package currently available: ``` ... shadow-4.15.1-r0 depends on: so:ld-linux-x86-64.so.2 so:libbsd.so.0 so:libc.so.6 so:libcrypt.so.1 so:libpam.so.0 so:libpam_misc.so.0 ``` ### Searching for packages that include a shared object To search which packages include a shared object, you can use the syntax `apk search so:shared-library`. As an example, if you want to check which packages include the `libxml2` shared library, you can run something like: ```shell apk search so:libxml2.so* ``` And this should give you output indicating that this shared object is included within the `libxml2-2.12.6-r0` package. For detailed information about apk options and flags when searching for packages, check the [official documentation](https://docs.alpinelinux.org/user-handbook/0.1a/Working/apk.html#_searching_for_packages). ## Resources to learn more Our [Getting started guides](/chainguard/containers/getting-started/) have detailed examples for different language ecosystems and stacks. Make sure to also check image-specific information in our [Chainguard Containers Directory](https://images.chainguard.dev/directory?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-to-chainguard-images). If you can't find an image that is a good match for your use case, or if your build has dependencies that cannot be met with the regular catalog, [get in touch with us](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) for alternative options. To automate the process of migrating your Dockerfiles, check out [Guardener](/chainguard/guardener/dockerfile-migration/), an AI-powered agent that iteratively converts, builds, and validates your Dockerfiles for use with Chainguard Containers. --- ### Verifying Chainguard Containers and metadata signatures with Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard signs all container images and their attestations (including SBOMs) to ensure supply chain security and enable verification of image authenticity. These cryptographic signatures allow you to confirm that images come from Chainguard and haven't been tampered with, while attestations provide detailed information about image contents and build provenance. This guide outlines how you can use Cosign to download and verify container image signatures and attestations. ## Prerequisites The following examples require [Cosign](/open-source/sigstore/cosign/how-to-install-cosign/) and [jq](https://stedolan.github.io/jq/) to be installed on your machine to download and verify image attestations. ## Registry and tags for Chainguard Containers Attestations are provided per image build, so you'll need to specify the correct tag and registry when pulling attestations from an image with `cosign`. This guide works with Chainguard's public and private registries: - `cgr.dev/chainguard`: The public registry contains Chainguard's **Free container images**, which only include the `:latest` versions of an image. - `cgr.dev/YOUR-ORGANIZATION`: A private/dedicated registry contains your organization's **Production container images**, which include all versioned tags of an image and special images that are not available in the public registry (including FIPS images and other custom builds). The commands listed on this page default to the `:latest` tag, but you can specify a different tag to fetch attestations for. ## Chainguard's signing identities Chainguard uses an identity associated with its official GitHub account to sign images in the public registry that contains the Free tier of images. For private images, Chainguard signs all images in your private registry with one of two different identities in your organization: - The `image-syncer` identity is used to sign images that have been imported directly from the Chainguard Containers catalog. - The `custom-image-builder` identity is used to sign any images that have been customized for your organization, such as those built with [Custom Assembly](/chainguard/containers/custom-assembly/overview/).

Note: Older Chainguard organizations use catalog_syncer and apko_builder instead of these identities. Each pair is functionally identical, because each references the same account association: image-syncer and catalog_syncer both reference the CATALOG_SYNCER association, while custom-image-builder and apko_builder both reference the APKO_BUILDER association.

These identities are created and added to every [verified Chainguard organization](/platform/administration/iam-organizations/verified-orgs/) automatically. To follow along with the **Private Registry** examples in this guide, you need the *unique identifier paths* (UIDPs) of these Chainguard identities. To this end, create a few environment variables, the first of which should point to the name of your Chainguard organization: ```shell PARENT=your-organization ``` Next, create two more variables to hold the UIDPs of your organization's `image-syncer` and `custom-image-builder` identities (`catalog_syncer` and `apko_builder` for older organizations), respectively: ```shell IMAGE_SYNCER=$(chainctl iam account-associations describe $PARENT -o json | jq -r '.[].chainguard.service_bindings.CATALOG_SYNCER') CUSTOM_IMAGE_BUILDER=$(chainctl iam account-associations describe $PARENT -o json | jq -r '.[].chainguard.service_bindings.APKO_BUILDER') ``` The **Private Registry** examples in this guide include these environment variables, letting you verify that Chainguard used those identities to sign the given image. You can also find these values in the Chainguard Console. After logging in, go to **Settings**, and then **Users**. From there, scroll or search for either `image-syncer` or `custom-image-builder` (or `catalog_syncer` or `apko_builder` for older organizations) and click its row to find the identity's UIDP. ## Verifying container image signatures Chainguard signs its containers with Sigstore, and you can check the included signatures using `cosign`. The `cosign verify` command pulls detailed information about all signatures found for the provided image. ### Public registry ```shell IMAGE=go cosign verify \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ cgr.dev/chainguard/${IMAGE} | jq ``` ### Private/dedicated registry ```shell IMAGE=go cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev/(${IMAGE_SYNCER}|${CUSTOM_IMAGE_BUILDER})" \ cgr.dev/${PARENT}/${IMAGE} | jq ``` Be aware that you need to change the `IMAGE` environment variable to reflect a container image your organization is entitled to. > **Note**: The environment variables used in this command (other than `${IMAGE}`) were created in the previous section. By default, this command fetches signatures for the `:latest` tag. If you'd like, you can specify the tag you want to fetch signatures for: ```shell IMAGE=go TAG=1.23.8 cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev/(${IMAGE_SYNCER}|${CUSTOM_IMAGE_BUILDER})" \ cgr.dev/${PARENT}/${IMAGE}:${TAG} | jq ``` ## Downloading container attestations Attestations are signed metadata about the artifact, which can include SBOMs, vulnerability scans, or other custom predicates. The [attestations](https://slsa.dev/attestation-model) for a container image can be obtained and verified using Cosign or directly in the Chainguard Console. Refer to [How to retrieve attestations and SBOMs for Chainguard Containers](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) for more information. ### Public registry ```shell IMAGE=go cosign download attestation \ --predicate-type=https://spdx.dev/Document \ --platform=linux/amd64 \ cgr.dev/chainguard/${IMAGE} | jq -r .payload | base64 -d | jq .predicate ``` ### Private/dedicated registry ```shell IMAGE=go cosign download attestation \ --predicate-type=https://spdx.dev/Document \ --platform=linux/amd64 \ cgr.dev/${PARENT}/${IMAGE} | jq -r .payload | base64 -d | jq .predicate ``` ## Verifying container image attestations You can use the `cosign verify-attestation` command to check the signatures of the desired container image [attestations](https://slsa.dev/attestation-model): ### Public registry ```shell IMAGE=go cosign verify-attestation \ --type https://spdx.dev/Document \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ cgr.dev/chainguard/${IMAGE} | jq ``` This pulls in the signature for the attestation specified by the `--type` parameter, which in this case is the SPDX attestation for SBOMs. The output verifies the SBOM attestation signature in Cosign's transparency log: ```shell Verification for cgr.dev/chainguard/go -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates Certificate subject: https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main Certificate issuer URL: https://token.actions.githubusercontent.com GitHub Workflow Trigger: schedule GitHub Workflow SHA: da283c26829d46c2d2883de5ff98bee672428696 GitHub Workflow Name: .github/workflows/release.yaml GitHub Workflow Trigger chainguard-images/images GitHub Workflow Ref: refs/heads/main ... ``` ### Private/dedicated registry ```shell IMAGE=go cosign verify-attestation \ --type https://spdx.dev/Document \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev/(${IMAGE_SYNCER}|${CUSTOM_IMAGE_BUILDER})" \ cgr.dev/${PARENT}/${IMAGE} | jq ``` ## Air-gapped and egress-restricted environments The commands in this guide reach the public Sigstore infrastructure to fetch the trust root Cosign verifies against. If that infrastructure is unreachable, you can export the trust root on a connected machine and pass it to Cosign as a file. Transparency-log verification still works, because the signature carries its own signed entry timestamp. The signature is a separate artifact from the image, so mirroring the image alone leaves it behind. For the full procedure, including how to move signatures and attestations across an air gap, see [Verifying signatures in air-gapped environments](/open-source/sigstore/cosign/verifying-in-air-gapped-environments/). ## Note about the examples in this guide The examples in this guide invariably pass command output through `jq`, a JSON processor. This is helpful, as it makes the output more easily readable. However, if you're running these commands in a script, this can cause problems if validation fails. For example, if Cosign returns an error but it is passed into `jq`, then `jq` overwrites the exit codes from Cosign, causing them to be silently ignored. To avoid this problem, you could include either or both of the following `set` options in your script: - `set -e` ensures that your script exits with an error if any of the commands in your script exit with an error. - `set -o pipefail` ensures that status codes from Cosign aren't masked when piped to `jq`. ## Learn more To get up to speed with Sigstore, you can review the [Sigstore](/open-source/sigstore/) section of Chainguard Academy, visit the upstream [Sigstore Docs](https://docs.sigstore.dev/) site, and check out the [Sigstore organization on GitHub](https://github.com/sigstore). You can learn more about verifying software artifacts with Cosign by reading [How to verify file signatures with Cosign](/open-source/sigstore/cosign/how-to-verify-file-signatures-with-cosign/). Navigate to our [container images](/chainguard/containers/) landing page or [Getting started guides](https://edu.chainguard.dev/chainguard/containers/getting-started/) to understand more about Chainguard Containers and how its images offer low-to-zero CVEs. --- ### Red Hat UBI compatibility URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/compatibility/red-hat-compatibility.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Reference Chainguard Containers and Red Hat UBI base images have different binaries and scripts included in their respective `busybox` and `coreutils` packages. Note that Red Hat UBI images by default do not have a `busybox` package. The following table lists common tools and their corresponding package(s) in both Wolfi and Red Hat distributions. Note that `$PATH` locations like `/usr/bin` or `/sbin` are not included here. If you have compatibility issues with tools that are included in both `busybox` and `coreutils`, be sure to check `$PATH` order and confirm which version of a tool is being run. Generally, if a tool exists in `busybox` but does not have a `coreutils` counterpart, there will be a specific package that includes it. For example the `zcat` utility is included in the `gzip` package in both Wolfi and Red Hat. You can use the `apk search` command in Wolfi, or the `yum search` or `dnf search` commands in Red Hat to find out which package includes a tool. | Utility | Wolfi busybox | Redhat-ubi busybox | Wolfi coreutils | Redhat-ubi coreutils | | --------------- | :-----------: | :----------------: | :-------------: | :------------------: | | `[` | ✅ | | ✅ | ✅ | | `[[` | ✅ | | | | | `add-shell` | ✅ | | | | | `addgroup` | ✅ | | | | | `adduser` | ✅ | | | | | `adjtimex` | ✅ | | | | | `arch` | ✅ | | | ✅ | | `arping` | ✅ | | | | | `ash` | ✅ | | | | | `awk` | ✅ | | | | | `b2sum` | | | ✅ | ✅ | | `base32` | | | ✅ | ✅ | | `base64` | ✅ | | ✅ | ✅ | | `basename` | ✅ | | ✅ | ✅ | | `basenc` | | | ✅ | | | `bbconfig` | ✅ | | | | | `bc` | ✅ | | | | | `beep` | ✅ | | | | | `bunzip2` | ✅ | | | | | `bzcat` | ✅ | | | | | `bzip2` | ✅ | | | | | `cal` | ✅ | | | | | `cat` | ✅ | | ✅ | ✅ | | `chattr` | ✅ | | | | | `chcon` | | | ✅ | ✅ | | `chgrp` | ✅ | | ✅ | ✅ | | `chmod` | ✅ | | ✅ | ✅ | | `chown` | ✅ | | ✅ | ✅ | | `chpasswd` | ✅ | | | | | `chroot` | ✅ | | ✅ | ✅ | | `chrt` | ✅ | | | | | `cksum` | ✅ | | ✅ | ✅ | | `clear` | ✅ | | | | | `cmp` | ✅ | | | | | `comm` | ✅ | | ✅ | ✅ | | `coreutils` | | | ✅ | ✅ | | `cp` | ✅ | | ✅ | ✅ | | `cpio` | ✅ | | | | | `cryptpw` | ✅ | | | | | `csplit` | | | ✅ | ✅ | | `cut` | ✅ | | ✅ | ✅ | | `date` | ✅ | | ✅ | ✅ | | `dc` | ✅ | | | | | `dd` | ✅ | | ✅ | ✅ | | `delgroup` | ✅ | | | | | `deluser` | ✅ | | | | | `df` | ✅ | | ✅ | ✅ | | `diff` | ✅ | | | | | `dir` | | | ✅ | ✅ | | `dircolors` | | | ✅ | ✅ | | `dirname` | ✅ | | ✅ | ✅ | | `dmesg` | ✅ | | | | | `dnsdomainname` | ✅ | | | | | `dos2unix` | ✅ | | | | | `du` | ✅ | | ✅ | ✅ | | `echo` | ✅ | | ✅ | ✅ | | `ed` | ✅ | | | | | `egrep` | ✅ | | | | | `env` | ✅ | | ✅ | ✅ | | `expand` | ✅ | | ✅ | ✅ | | `expr` | ✅ | | ✅ | ✅ | | `factor` | ✅ | | ✅ | ✅ | | `fallocate` | ✅ | | | | | `false` | ✅ | | ✅ | ✅ | | `fgrep` | ✅ | | | | | `find` | ✅ | | | | | `findfs` | ✅ | | | | | `flock` | ✅ | | | | | `fmt` | | | ✅ | ✅ | | `fold` | ✅ | | ✅ | ✅ | | `free` | ✅ | | | | | `fsync` | ✅ | | | | | `fuser` | ✅ | | | | | `getopt` | ✅ | | | | | `getty` | ✅ | | | | | `grep` | ✅ | | | | | `groups` | ✅ | | | ✅ | | `gunzip` | ✅ | | | | | `gzip` | ✅ | | | | | `hd` | ✅ | | | | | `head` | ✅ | | ✅ | ✅ | | `hexdump` | ✅ | | | | | `hostid` | ✅ | | ✅ | ✅ | | `hostname` | ✅ | | | | | `id` | ✅ | | ✅ | ✅ | | `inotifyd` | ✅ | | | | | `install` | ✅ | | ✅ | ✅ | | `ionice` | ✅ | | | | | `iostat` | ✅ | | | | | `ipcrm` | ✅ | | | | | `ipcs` | ✅ | | | | | `join` | | | ✅ | ✅ | | `kill` | ✅ | | | | | `killall` | ✅ | | | | | `killall5` | ✅ | | | | | `less` | ✅ | | | | | `link` | ✅ | | ✅ | ✅ | | `linux32` | ✅ | | | | | `linux64` | ✅ | | | | | `ln` | ✅ | | ✅ | ✅ | | `logger` | ✅ | | | | | `login` | ✅ | | | | | `logname` | | | ✅ | ✅ | | `ls` | ✅ | | ✅ | ✅ | | `lsattr` | ✅ | | | | | `lsof` | ✅ | | | | | `lzcat` | ✅ | | | | | `lzma` | ✅ | | | | | `lzop` | ✅ | | | | | `lzopcat` | ✅ | | | | | `md5sum` | ✅ | | ✅ | ✅ | | `microcom` | ✅ | | | | | `mkdir` | ✅ | | ✅ | ✅ | | `mkfifo` | ✅ | | ✅ | ✅ | | `mknod` | ✅ | | ✅ | ✅ | | `mkpasswd` | ✅ | | | | | `mktemp` | ✅ | | ✅ | ✅ | | `more` | ✅ | | | | | `mountpoint` | ✅ | | | | | `mpstat` | ✅ | | | | | `mv` | ✅ | | ✅ | ✅ | | `netstat` | ✅ | | | | | `nice` | ✅ | | ✅ | ✅ | | `nl` | ✅ | | ✅ | ✅ | | `nmeter` | ✅ | | | | | `nohup` | ✅ | | ✅ | ✅ | | `nologin` | ✅ | | | | | `nproc` | ✅ | | ✅ | ✅ | | `nsenter` | ✅ | | | | | `numfmt` | | | ✅ | ✅ | | `od` | ✅ | | ✅ | ✅ | | `passwd` | ✅ | | | | | `paste` | ✅ | | ✅ | ✅ | | `pathchk` | | | ✅ | ✅ | | `pgrep` | ✅ | | | | | `pidof` | ✅ | | | | | `ping` | ✅ | | | | | `ping6` | ✅ | | | | | `pinky` | | | ✅ | ✅ | | `pipe_progress` | ✅ | | | | | `pivot_root` | ✅ | | | | | `pkill` | ✅ | | | | | `pmap` | ✅ | | | | | `pr` | | | ✅ | ✅ | | `printenv` | ✅ | | ✅ | ✅ | | `printf` | ✅ | | ✅ | ✅ | | `ps` | ✅ | | | | | `pstree` | ✅ | | | | | `ptx` | | | ✅ | ✅ | | `pwd` | ✅ | | ✅ | ✅ | | `pwdx` | ✅ | | | | | `rdev` | ✅ | | | | | `readahead` | ✅ | | | | | `readlink` | ✅ | | ✅ | ✅ | | `realpath` | ✅ | | ✅ | ✅ | | `remove-shell` | ✅ | | | | | `renice` | ✅ | | | | | `reset` | ✅ | | | | | `resize` | ✅ | | | | | `rev` | ✅ | | | | | `rm` | ✅ | | ✅ | ✅ | | `rmdir` | ✅ | | ✅ | ✅ | | `run-parts` | ✅ | | | | | `runcon` | | | ✅ | ✅ | | `sed` | ✅ | | | | | `seq` | ✅ | | ✅ | ✅ | | `setpriv` | ✅ | | | | | `setserial` | ✅ | | | | | `setsid` | ✅ | | | | | `sh` | ✅ | | | | | `sha1sum` | ✅ | | ✅ | ✅ | | `sha224sum` | | | ✅ | ✅ | | `sha256sum` | ✅ | | ✅ | ✅ | | `sha384sum` | | | ✅ | ✅ | | `sha3sum` | ✅ | | | | | `sha512sum` | ✅ | | ✅ | ✅ | | `shred` | ✅ | | ✅ | ✅ | | `shuf` | ✅ | | ✅ | ✅ | | `sleep` | ✅ | | ✅ | ✅ | | `sort` | ✅ | | ✅ | ✅ | | `split` | ✅ | | ✅ | ✅ | | `stat` | ✅ | | ✅ | ✅ | | `stdbuf` | | | ✅ | ✅ | | `strings` | ✅ | | | | | `stty` | ✅ | | ✅ | ✅ | | `su` | ✅ | | | | | `sum` | ✅ | | ✅ | ✅ | | `sync` | ✅ | | ✅ | ✅ | | `sysctl` | ✅ | | | | | `tac` | ✅ | | ✅ | ✅ | | `tail` | ✅ | | ✅ | ✅ | | `tar` | ✅ | | | | | `tee` | ✅ | | ✅ | ✅ | | `test` | ✅ | | ✅ | ✅ | | `time` | ✅ | | | | | `timeout` | ✅ | | ✅ | ✅ | | `top` | ✅ | | | | | `touch` | ✅ | | ✅ | ✅ | | `tr` | ✅ | | ✅ | ✅ | | `traceroute` | ✅ | | | | | `traceroute6` | ✅ | | | | | `tree` | ✅ | | | | | `true` | ✅ | | ✅ | ✅ | | `truncate` | ✅ | | ✅ | ✅ | | `tsort` | ✅ | | ✅ | ✅ | | `tty` | ✅ | | ✅ | ✅ | | `ttysize` | ✅ | | | | | `tunctl` | ✅ | | | | | `uname` | ✅ | | ✅ | ✅ | | `unexpand` | ✅ | | ✅ | ✅ | | `uniq` | ✅ | | ✅ | ✅ | | `unix2dos` | ✅ | | | | | `unlink` | ✅ | | ✅ | ✅ | | `unlzma` | ✅ | | | | | `unlzop` | ✅ | | | | | `unxz` | ✅ | | | | | `unzip` | ✅ | | | | | `uptime` | ✅ | | | | | `users` | | | ✅ | ✅ | | `usleep` | ✅ | | | | | `uudecode` | ✅ | | | | | `uuencode` | ✅ | | | | | `vconfig` | ✅ | | | | | `vdir` | | | ✅ | ✅ | | `vi` | ✅ | | | | | `vlock` | ✅ | | | | | `watch` | ✅ | | | | | `wc` | ✅ | | ✅ | ✅ | | `which` | ✅ | | | | | `who` | ✅ | | ✅ | ✅ | | `whoami` | ✅ | | ✅ | ✅ | | `xargs` | ✅ | | | | | `xxd` | ✅ | | | | | `xzcat` | ✅ | | | | | `yes` | ✅ | | ✅ | ✅ | | `zcat` | ✅ | | | | --- ### Getting started with the Chainguard Istio Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/platform-and-infrastructure/istio.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [Istio](https://istio.io) container images provide a security-hardened foundation for service mesh deployments with significantly reduced vulnerabilities compared to standard Istio images. Istio extends Kubernetes to establish a programmable, application-aware network using the Envoy service proxy, bringing traffic management, telemetry, and security to complex deployments. Built on Wolfi OS, Chainguard's minimal Istio images maintain full compatibility while enhancing security posture. We will demonstrate how to get started with the Chainguard Istio container images on an example kind cluster. To get started, you'll need Docker, kind, `kubectl`, and `istioctl` installed. If you are missing any, you can follow the relevant link to get started. * [Docker](https://docs.docker.com/get-docker/) * [kind](https://kind.sigs.k8s.io/docs/user/quick-start/) * [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) * [istioctl](https://istio.io/latest/docs/setup/getting-started/#download)

Note: In November 2024, after this article was first written, Chainguard made changes to its free tier of container images. In order to access the non-free container images used in this guide, you will need to be part of an organization that has access to them. For a full list of container images that will remain in Chainguard's free tier, please refer to this support page.

What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Start up a kind cluster First, we'll start up a kind cluster to install Istio. ```sh kind create cluster ``` This will return output similar to the following: ```output Creating cluster "kind" ... ✓ Ensuring node image (kindest/node:v1.27.3) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kind" You can now use your cluster with: kubectl cluster-info --context kind-kind Thanks for using kind! 😊 ``` Following that, you can install the Istio Chainguard Containers with `istioctl`. ## Install Istio using Chainguard Containers We will be using the `istioctl` command to install Istio. In order to use the Chainguard Containers, we will need to set these following values: * `hub = cgr.dev/$ORGANIZATION` > **Note**: Be aware that you will need to change `cgr.dev/$ORGANIZATION` to reflect the name of your organization's repository within Chainguard's registry. * `tag = latest` * `values.pilot.image = istio-pilot` * `values.global.proxy.image = istio-proxy` * `values.global.proxy_init.image = istio-proxy` We can set these values with the following `istioctl` command: ```sh istioctl install --set tag=latest --set hub=cgr.dev/$ORGANIZATION \ --set values.pilot.image=istio-pilot \ --set values.global.proxy.image=istio-proxy \ --set values.global.proxy_init.image=istio-proxy ``` The Istio Chainguard Container is now running on the kind cluster you created previously. In the next section, you'll set up an Istio gateway and a VirtualService to test out this container. ## Stand up a gateway and a VirtualService To see the Istio installation in action, we will create two Istio resources: * An Istio gateway serving the "http://hello.example.com" domain * A VirtualService to always reply with "Hello, world!" to requests to the "http://hello.example.com" domain Create a YAML manifest file with the following contents to define the Istio resources: ```sh cat > example.yaml <If your project requires a more specific set of packages that aren't included within the general-purpose Istio Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Chainguard Containers network requirements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/network-requirements.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Reference This document provides an overview of network requirements for using [Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). To use Chainguard tools and Containers in environments with firewalls, VPNs, and IDS/IPS systems, you will need to add some rules to allow traffic into and out of your networks. Chainguard's container images do not call Chainguard services while running, so no network changes would be required to the runtime environment. Review the **Notes** column for more info on each Hostname. ## Chainguard Containers hosts This table lists the DNS hostnames, associated ports, and protocols that will need to be allowed through firewalls and proxies to use Chainguard Containers: | Hostname | Port | Protocol | IP | Notes | |-------------------------|------|----------|---------|---------------------------------------| | cgr.dev | 443 | HTTPS | v4 | Main container image registry | | console.chainguard.dev | 443 | HTTPS | v4 | Chainguard dashboard | | data.chainguard.dev | 443 | HTTPS | v4 | Console API endpoint | | console-api.enforce.dev | 443 | HTTPS | v4 | Registry API endpoint | | enforce.dev | 443 | HTTPS | v4 | Registry authentication | | dl.enforce.dev | 443 | HTTPS | v4 | `chainctl` downloads | | issuer.enforce.dev | 443 | HTTPS | v4 | Registry STS (Security Token Service) | | apk.cgr.dev | 443 | HTTPS | v4 | Package repository | | virtualapk.cgr.dev | 443 | HTTPS | v4 | Package repository | | packages.cgr.dev | 443 | HTTPS | v4 | Package repository (Extra packages) | | packages.wolfi.dev | 443 | HTTPS | v4 & v6 | Package repository (Free containers) | | tarballs.cgr.dev | 443 | HTTPS | v4 | Upstream source archives referenced by SBOM `downloadLocation` fields | | auth.chainguard.dev | 443 | HTTPS | v4 | Social login, reached by the browser that completes the login | Most of these hosts are needed to pull containers and packages. `tarballs.cgr.dev` is the exception: it is only needed if you resolve the source archives that Chainguard SBOMs point at. Where an upstream project has no stable, downloadable source archive, Chainguard mirrors a source tarball and records that URL as the package's SPDX `downloadLocation`. Tooling that follows those URLs (source-provenance checks, license and compliance scanners, air-gapped source mirroring) needs egress to this host. Container and package pulls do not. The two social login hosts, `auth.chainguard.dev` and `chainguard.us.auth0.com`, are reached by the browser that completes a `chainctl` login. With `--headless`, that browser can run on a different device from `chainctl`. If a proxy inspects TLS traffic, exempt the login hosts from inspection, as described in [Troubleshoot chainctl login](/platform/chainctl-usage/authentication-options/#troubleshoot-chainctl-login). > If you experience networking issues while trying to use Chainguard Containers, please ensure that your firewall allows traffic to and from these hosts, and that it doesn't have any rules to block `.dev` domains. ## Chainguard Containers third-party hosts This table lists the third-party DNS hostnames, associated ports, and protocols that will need to be allowed through firewalls and proxies to use Chainguard Containers: | Hostname | Port | Protocol | IP | Notes | |-----------------------------------------------------------|------|----------|---------|----------------------------------------------------------| | 9236a389bd48b984df91adc1bc924620.r2.cloudflarestorage.com | 443 | HTTPS | v4 & v6 | Blob storage for *.cgr.dev | | support.chainguard.dev | 443 | HTTPS | v4 | Support access for customers | | tuf-repo-cdn.sigstore.dev | 443 | HTTPS | v4 | Sigstore trust root for `chainctl` signature verification | | chainguard.us.auth0.com | 443 | HTTPS | v4 | Social login, reached by the browser that completes the login | > Note that the `9236a389bd48b984df91adc1bc924620.r2.cloudflarestorage.com` host is used to serve both image data and packages via `*.cgr.dev`. ## Ingress and egress Connections to the hosts listed on this page are generally initiated as new outbound connections. If you are using stateless firewall rules, then you will need to add symmetric rules to ensure that traffic flows correctly. You will need egress rules that allow new traffic to the hosts listed here. You will need corresponding ingress rules that allow related and established traffic. ## DNS records and TTLs Many of the hosts listed on this page use multiple DNS A records or CNAME aliases. Additionally, many A records have a short time to live of 60 seconds, and the majority are less than an hour (3600s). If your network filters traffic based on IP addresses, ensure that any firewalls update their rules at an appropriate interval to match the TTL for each DNS record. ## Minimum TLS parameters requirements For guaranteed connectivity, the following TLS requirements must be at minimum supported by clients and servers communicating with Chainguard Containers and endpoints: Protocol Versions: - TLSv1.3 - TLSv1.2 TLS Cipher Suites: - TLS_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS Supported Groups: - X25519MLKEM768 - secp384r1 TLS Signature Schemes: - ecdsa_secp256r1_sha256 - rsa_pss_pss_sha256 Protocol support: - Support for encrypted HTTP/2 is required, including by any proxies in use The requirements can be approximately tested with the following OpenSSL client command: ```shell openssl s_client -cipher @SECLEVEL=2:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 -ciphersuites TLS_AES_256_GCM_SHA384 -groups X25519MLKEM768:secp384r1 -alpn h2 -connect cgr.dev:443 < /dev/null ``` > Note that in the case of TLSv1.2 connectivity you must check the output for `Extended master secret: yes`. You can replace `cgr.dev:443` with your own deployments. Many of the endpoints for Chainguard products require support for the encrypted [HTTP/2 protocol](https://http2.github.io/). Some decrypting proxies might not support HTTP/2.

Zscaler, a popular decrypting proxy, may not support encrypted HTTP/2 traffic by default. If you are having connection issues using chainctl behind Zscaler, follow these steps to enable support for HTTP/2.

  1. Open a Support Ticket with Zscaler

    Before any UI configuration option appears you may need to contact Zscaler Support (TAC) and request that HTTP/2 support be provisioned/activated for your tenant. This is currently a backend setting only Support can toggle.

    You typically need to provide:

    • Your Company ID
    • Tenant details
    • Reason/use case for enabling HTTP/2 inspection

    This will allow HTTP/2 negotiation to be available in policies.

  2. Enable HTTP/2 in SSL Inspection Policies

    Once Support has enabled HTTP/2 on your tenant:

    1. Log in to the Zscaler Admin Portal.
    2. Navigate to Policy → SSL Inspection (or Administration → Advanced Settings depending on UI version).
    3. Edit your relevant SSL Inspection Policy.
    4. Look for the option "Enable HTTP/2" and turn it ON for the required rules/policies.

    This setting ensures Zscaler will maintain HTTP/2 where possible rather than downgrading to HTTP/1.1. You may need to enable it on multiple rules if you use granular SSL policies (for example, by URL category).

  3. Check Advanced settings for API/CLI traffic

    Zscaler has an advanced setting under Admin → Advanced that controls how non-browser (API or CLI) HTTP/2 traffic is handled. This setting is disabled by default, which means Zscaler will downgrade non-browser HTTP/2 traffic to HTTP/1.1. For chainctl usage, this must be set to Enabled.

See this Zscaler blog post for more details.

If the above approach is not available to you, you may need to reconfigure Zscaler to disable its "Block Undecryptable Traffic" setting. Another option is to configure direct (proxyless) access to HTTP/2 endpoints and use the no_proxy environment variable, depending on your IT policies. Please consult your proxy software's documentation for guidance on adjusting these settings in order to authenticate.

--- ### Using Renovate with Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/updating-containers/renovate.md Last Modified: October 15, 2026 Tags: Chainguard Containers [Renovate](https://github.com/renovatebot/renovate) can be used to alert on updates to Chainguard Containers, Helm charts and APK packages. This can be an effective way to keep your images up-to-date and free of CVEs. This article explains how to configure Renovate for each. ## Prerequisites To follow this guide, you need: * Renovate installed and configured. Refer to Renovate's [installation instructions](https://docs.renovatebot.com/getting-started/installing-onboarding/) if you haven't set this up. * `chainctl`, Chainguard's command-line interface, installed on your local machine. Several examples in this guide use it. Refer to [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you haven't set this up. ## Set up credentials for Renovate In order to support versioned images from a private repository, you must provide Renovate with credentials to access the [Chainguard registry](/chainguard/containers/registry/overview/) at `cgr.dev`. You can do this by creating a token with `chainctl`, as in this example: ```shell chainctl auth configure-docker --pull-token ``` This command responds with output such as: ```shell To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" ``` By default, this credential is good for 30 days. You can now configure `hostRules` in Renovate to support the Chainguard registry. Depending on how Renovate was set up, you can add this to your Renovate configuration with a setting such as: ```json { ... "hostRules": [ { "hostType": "docker", "matchHost": "cgr.dev", "username": "", "password": "" }] } ``` Be aware that you **SHOULD NOT** check this file into source control with the exposed secret. Instead, you can use environment variables that you pass in at runtime if you use a `config.js` file: ```json module.exports = { ... "hostRules": [ { "hostType": "docker", "matchHost": "cgr.dev", "username": process.env.CGR_USERNAME, "password": process.env.CGR_PASSWORD, }] }; ``` But an even more secure solution would be to create a script that automatically updates the configuration with the correct values by calling `chainctl`. If you do this, you should also set the credential lifetime to a much shorter period with the `--ttl` flag: ```shell chainctl auth configure-docker --pull-token --ttl 10m ``` This sets the pull token's lifetime to 10 minutes, which limits the risk posed if the token leaks. You can also set the lifetime to a longer period for more manual configurations. ## Update versioned container images By default, Renovate will now open pull requests for any out-of-date versions of images it finds. For example, you can run Renovate by pushing the following Dockerfile to a repository overseen by Renovate: ```dockerfile FROM cgr.dev/chainguard.edu/python:3.11-dev AS builder ... FROM cgr.dev/chainguard.edu/python:3.11 ... ``` At the time of writing, version 3.12 was the current version of the Python image, so Renovate opened a pull request to update the reference. Not all images use semantic versioning. Refer to the [Renovate documentation](https://docs.renovatebot.com/) for details on how to support different schemes. Ideally, image references should also be pinned to a digest, as shown in the following section. ## Update `:latest` container images Renovate also supports updating image references that are pinned to digests. This lets you keep mutable tags such as `:latest` in sync with the most up-to-date version. As an example, the following Dockerfile prompts Renovate to open two similar pull requests: ```dockerfile FROM cgr.dev/chainguard/go:latest-dev@sha256:ff187ecd4bb5b45b65d680550eed302545e69ec4ed45f276f385e1b4ff0c6231 AS builder WORKDIR /work COPY go.mod /work/ COPY cmd /work/cmd COPY internal /work/internal RUN CGO_ENABLED=0 go build -o hello ./cmd/server FROM cgr.dev/chainguard/static:latest@sha256:5e9c88174a28c259c349f308dd661a6ec61ed5f8c72ecfaefb46cceb811b55a1 COPY --from=builder /work/hello /hello ENTRYPOINT ["/hello"] ``` ## Pin digests The `pinDigests` option configures Renovate to add digests to image references that don't contain them. The following example Renovate configuration includes this option: ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "packageRules": [ { "matchDatasources": ["docker"], "pinDigests": true } ] } ``` This configures Renovate to open pull requests that pin a reference like `cgr.dev/chainguard/python:3.12` to a digest like the following: ``` cgr.dev/chainguard/python:3.12@sha256:e3b524a97c37c32ba590aae0ebcebe3a983c1f69a5093b670fdba980f97a09b3 ``` You can also use the `matchUpdateTypes` option to disable updates for any types other than `digest`. Here is an example Renovate configuration that does this: ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "packageRules": [ { "matchDatasources": ["docker"], "matchUpdateTypes": [ "major", "minor", "patch" ], "enabled": false } ] } ``` This configures Renovate to update the digest for a reference but not the tag. The benefit of this approach is that it lets you define your update strategy for each image reference through a mutable tag, rather than having separate rules for different images in your Renovate configuration, similar to Chainguard's [Digestabot](https://github.com/chainguard-dev/digestabot) GitHub Action. ## Update packages in Dockerfiles > **Note**: **Pin APK packages and images together.** Newer images introduced by mutable tags may include newer packages that conflict with your older pinned package versions. If you are pinning package versions then you should also pin the base image to a digest and use Renovate to keep both up to date. This ensures a higher degree of reproducibility and avoids unexpected build failures. > **Note**: **Renovate only supports exact package names.** It doesn't resolve `provides` aliases, so pin the fully-qualified name (e.g `argo-cd-2.14`, not `argo-cd`). Pinned package versions accumulate CVEs over time and may become unavailable as Chainguard [removes older versions from its repositories](/chainguard/containers/building-and-modifying/packages/package-model/#package-retention-in-public-repositories). Renovate's [APK datasource](https://docs.renovatebot.com/modules/datasource/apk/) (introduced in version 44.97.1) can update `apk add pkg=version` pins in Dockerfiles, keeping them current as new package versions are released. For example, the following Dockerfile pins two APK packages that Renovate can bump: ```dockerfile FROM cgr.dev//chainguard-base@sha256:aaaa... RUN apk add --no-cache \ curl=8.12.1-r0 \ jq=1.8.1-r3 ``` ### Default repositories Chainguard's container images ship with their `/etc/apk/repositories` file already populated with two public, org-scoped mirrors served from `virtualapk.cgr.dev`. Neither requires authentication: * **`virtualapk.cgr.dev//chainguard`** — open source packages used in Chainguard's Free container images. * **`virtualapk.cgr.dev//extra-packages`** — additional packages that aren't fully open source but can still be redistributed by Chainguard. If you aren't modifying `/etc/apk/repositories` in your build then you should add a `packageRules` entry to your `renovate.json` that matches the `apk` datasource and lists both of the default URLs. ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "packageRules": [ { "matchDatasources": ["apk"], "registryUrls": [ "https://virtualapk.cgr.dev//chainguard?arch=x86_64", "https://virtualapk.cgr.dev//extra-packages?arch=x86_64" ] } ] } ``` Replace `` with your organization's ID, which you can find by running `chainctl iam org list -o table`. Set `arch=aarch64` if you are building exclusively for that architecture. In practice, Chainguard publish almost every package with the same versions for each architecture. However, there are some exceptions, so if you are building on both, you may consider having duplicate URLs for each architecture, or having specific `packageRules` for different Dockerfiles depending on target architecture. ### Private repository Your organization-scoped [private repository](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) (**`apk.cgr.dev/`**) serves the packages your organization is entitled to and provides packages that are not available from the public mirrors. To get access to the largest range of packages and versions, you should use it in addition to the default, public repositories. If you are modifying the `/etc/apk/repositories` file in your builds to include it, then you should also include it in your Renovate configuration. Since the private repository requires authentication, add a `hostRules` entry alongside `packageRules`. The `username` and `password` are sourced from [Renovate secrets](https://docs.renovatebot.com/self-hosted-configuration/#secrets) so the credentials themselves stay out of source control: ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended" ], "hostRules": [ { "matchHost": "apk.cgr.dev", "username": "{{ secrets.PULL_TOKEN_USERNAME }}", "password": "{{ secrets.PULL_TOKEN_PASSWORD }}" } ], "packageRules": [ { "matchDatasources": ["apk"], "registryUrls": [ "https://virtualapk.cgr.dev//chainguard?arch=x86_64", "https://virtualapk.cgr.dev//extra-packages?arch=x86_64", "https://apk.cgr.dev/?arch=x86_64" ] } ] } ``` Replace `` with your organization's name and `` with your organization's ID, which you can find by running `chainctl iam org list -o table`. If your build is exclusively using the private repository then you should remove the public `virtualapk.cgr.dev` URLs from the list. For the username and password, you can generate a pull token for long-lived static credentials: ```shell chainctl auth pull-token create --repository=apk --ttl=259200m ``` Or for short-lived credentials, either locally or when using [assumable identities](/platform/administration/assumable-ids/assumable-ids/), generate an access token that is valid for an hour and use it with the username `_token`: ```shell chainctl auth token --audience=apk.cgr.dev ``` Either way, inject the credentials into the `PULL_TOKEN_USERNAME` and `PULL_TOKEN_PASSWORD` secrets at runtime via the `RENOVATE_SECRETS` environment variable: ```shell export RENOVATE_SECRETS="{\"PULL_TOKEN_USERNAME\": \"\", \"PULL_TOKEN_PASSWORD\": \"\"}" ``` Alternatively, you can supply the credentials to Renovate in one of two other ways: * **Environment variables** — set `RENOVATE_DETECT_HOST_RULES_FROM_ENV=true` and expose the credentials as `RENOVATE_APK_APK_CGR_DEV_USERNAME` and `RENOVATE_APK_APK_CGR_DEV_PASSWORD`. Renovate assembles a `hostRules` entry from these at runtime, so you can drop the `hostRules` block from `renovate.json`. The [Run Renovate in GitHub Actions](#run-renovate-in-github-actions) and [Run Renovate with Docker](#run-renovate-with-docker) sections below use this pattern. * **`hostRules` in a self-hosted [`config.js`](https://docs.renovatebot.com/self-hosted-configuration/)** — put the `hostRules` block in the self-hosted config file, reading credentials from `process.env`. The [Set up credentials for Renovate](#set-up-credentials-for-renovate) section above uses this pattern for `cgr.dev`. ## Update Chainguard Helm charts in Helmfiles Renovate supports updating [Helmfile](https://helmfile.readthedocs.io/) releases with its [built-in `helmfile` manager](https://docs.renovatebot.com/modules/manager/helmfile/). However, it doesn't presently support updating [digest references](/chainguard/containers/troubleshooting/inspecting-containers/) for OCI chart URLs, which is a [recommended practice when deploying Chainguard Helm charts](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts/#pin-to-digest). See [renovatebot/renovate#45054](https://github.com/renovatebot/renovate/discussions/45054) for more details. To pin Chainguard Helm charts to digests and update them with Renovate, you can use a [custom `jsonata` manager](https://docs.renovatebot.com/modules/manager/jsonata/) as a workaround. Given a `helmfile.yaml` such as: ```yaml releases: - name: kube-prometheus-stack chart: oci://cgr.dev//charts/kube-prometheus-stack@sha256:833bd55297054df0afdbe47750013b8e2eff930059c63c0746447fa8d0b729d3 version: 87.4.0 namespace: monitoring - name: nginx chart: oci://cgr.dev//iamguarded-charts/nginx@sha256:7b88d44da254fc764171da809471d10c6cf15b9ab0ddcb4b475b9a8f380aeb79 version: 22.1.0 namespace: nginx ``` Configure Renovate with the following example, replacing every instance of `cgr.dev/` with your Chainguard organization or internal mirror or proxy. ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "packageRules": [ { "matchManagers": ["helmfile"], "matchPackagePatterns": [ "^cgr\\.dev//(charts|iamguarded-charts)/" ], "enabled": false } ], "customManagers": [ { "customType": "jsonata", "fileFormat": "yaml", "fileMatch": ["(^|/)helmfile\\.ya?ml$"], "matchStrings": [ "releases[$contains(chart, 'cgr.dev//charts/')].($n := $substringAfter($substringBefore(chart & '@', '@'), 'charts/'); $exists(version) ? { 'depName': $n, 'packageName': 'cgr.dev//charts/' & $n, 'currentValue': version, 'currentDigest': $substringAfter(chart, '@') } : { 'depName': $n, 'packageName': 'cgr.dev//charts/' & $n, 'currentDigest': $substringAfter(chart, '@') })" ], "datasourceTemplate": "docker" }, { "customType": "jsonata", "fileFormat": "yaml", "fileMatch": ["(^|/)helmfile\\.ya?ml$"], "matchStrings": [ "releases[$contains(chart, 'cgr.dev//iamguarded-charts/')].($n := $substringAfter($substringBefore(chart & '@', '@'), 'iamguarded-charts/'); $exists(version) ? { 'depName': $n, 'packageName': 'cgr.dev//iamguarded-charts/' & $n, 'currentValue': version, 'currentDigest': $substringAfter(chart, '@') } : { 'depName': $n, 'packageName': 'cgr.dev//iamguarded-charts/' & $n, 'currentDigest': $substringAfter(chart, '@') })" ], "datasourceTemplate": "docker" } ] } ``` ## Update Chainguard Helm charts in ArgoCD applications Renovate supports updating [ArgoCD](https://argo-cd.readthedocs.io/) `Application` manifests with its [built-in `argocd` manager](https://docs.renovatebot.com/modules/manager/argocd/). However, it doesn't presently support updating [digest references](/chainguard/containers/troubleshooting/inspecting-containers/) for OCI chart URLs, which is a [recommended practice when deploying Chainguard Helm charts](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts/#pin-to-digest). See [renovatebot/renovate#45055](https://github.com/renovatebot/renovate/discussions/45055) for more details. To pin Chainguard Helm charts to digests and update them with Renovate, you can use a [custom `jsonata` manager](https://docs.renovatebot.com/modules/manager/jsonata/) as a workaround. Given `Application` manifests such as: ```yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: kube-prometheus-stack spec: source: repoURL: oci://cgr.dev//charts chart: kube-prometheus-stack targetRevision: 87.4.0@sha256:833bd55297054df0afdbe47750013b8e2eff930059c63c0746447fa8d0b729d3 --- apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: nginx spec: source: repoURL: oci://cgr.dev//iamguarded-charts chart: nginx targetRevision: 22.1.0@sha256:7b88d44da254fc764171da809471d10c6cf15b9ab0ddcb4b475b9a8f380aeb79 ``` Configure Renovate as in the following example, replacing every instance of `cgr.dev/` with your Chainguard organization or internal mirror or proxy. ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "packageRules": [ { "matchManagers": ["argocd"], "matchPackagePatterns": [ "^cgr\\.dev//(charts|iamguarded-charts)/" ], "enabled": false } ], "customManagers": [ { "customType": "jsonata", "fileFormat": "yaml", "fileMatch": ["\\.ya?ml$"], "matchStrings": [ "spec.source[$contains(repoURL, 'cgr.dev//charts')].($tr := targetRevision; $substring($tr, 0, 7) = 'sha256:' ? { 'depName': chart, 'packageName': 'cgr.dev//charts/' & chart, 'currentDigest': $tr } : { 'depName': chart, 'packageName': 'cgr.dev//charts/' & chart, 'currentValue': $substringBefore($tr & '@', '@'), 'currentDigest': $substringAfter($tr, '@') })" ], "datasourceTemplate": "docker" }, { "customType": "jsonata", "fileFormat": "yaml", "fileMatch": ["\\.ya?ml$"], "matchStrings": [ "spec.source[$contains(repoURL, 'cgr.dev//iamguarded-charts')].($tr := targetRevision; $substring($tr, 0, 7) = 'sha256:' ? { 'depName': chart, 'packageName': 'cgr.dev//iamguarded-charts/' & chart, 'currentDigest': $tr } : { 'depName': chart, 'packageName': 'cgr.dev//iamguarded-charts/' & chart, 'currentValue': $substringBefore($tr & '@', '@'), 'currentDigest': $substringAfter($tr, '@') })" ], "datasourceTemplate": "docker" } ] } ``` ## Update Chainguard Helm charts in Flux Renovate natively supports updating [Flux](https://fluxcd.io/) `OCIRepository` resources with its [built-in `flux` manager](https://docs.renovatebot.com/modules/manager/flux/). Given a Flux manifest such as: ```yaml --- apiVersion: source.toolkit.fluxcd.io/v1 kind: OCIRepository metadata: name: kube-prometheus-stack namespace: monitoring spec: interval: 5m url: oci://cgr.dev//charts/kube-prometheus-stack ref: tag: 87.4.0 digest: sha256:833bd55297054df0afdbe47750013b8e2eff930059c63c0746447fa8d0b729d3 --- apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: kube-prometheus-stack namespace: monitoring spec: interval: 5m chartRef: kind: OCIRepository name: kube-prometheus-stack ``` Configure Renovate with the following example, adjusting the `flux.fileMatch` patterns to cover your repository layout. The `pinDigests` rule enforces the [recommended practice of pinning charts to a digest](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts/#pin-to-digest): if an `OCIRepository` has a `tag` but no `digest`, Renovate opens a pull request to add one. ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "flux": { "fileMatch": [ "(^|/)flux\\.ya?ml$", "(^|/)gotk-components\\.ya?ml$" ] }, "packageRules": [ { "matchManagers": ["flux"], "matchDatasources": ["docker"], "matchPackagePatterns": [ "^cgr\\.dev//(charts|iamguarded-charts)/" ], "pinDigests": true } ] } ``` ## Run Renovate in GitHub Actions You can use [`renovatebot/github-action`](https://github.com/renovatebot/github-action) to run Renovate from a GitHub Actions workflow. This can be combined with an [assumable identity](/platform/administration/assumable-ids/assumable-ids/) to authenticate to `cgr.dev` and update references to Chainguard container images in your repository. > **Note**: This section assumes you have permissions to create identities in your Chainguard organization. First, create a Renovate configuration file at the root of your GitHub repository. Refer to the [official documentation](https://docs.renovatebot.com/configuration-options/) for all the supported options. This is an example of a minimal configuration: ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json" } ``` Push this file to the `main` branch of your repository. Next, create an assumable identity for your GitHub repository. The `--github-repo` value embeds GitHub's immutable numeric owner and repository IDs; refer to [Finding your repository's numeric identifiers](/platform/administration/assumable-ids/identity-examples/github-identity/#finding-your-repositorys-numeric-identifiers) for how to retrieve them and when the format applies. ```shell chainctl iam identities create github \ --github-repo=@/@ \ --github-ref=refs/heads/main \ --role=registry.pull,apk.pull ``` Create a workflow file named `.github/workflows/renovate.yaml` with the following content. Replace `` with the ID returned by the previous command. ```yaml name: Renovate on: workflow_dispatch: schedule: - cron: "0 3 * * *" permissions: contents: read jobs: renovate: name: Renovate runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: write id-token: write steps: - uses: chainguard-dev/setup-chainctl@be0acd273acf04bfdf91f51198327e719f6af978 # v0.4.0 with: identity: "" - shell: bash run: | RENOVATE_DOCKER_CGR_DEV_PASSWORD=$(chainctl auth token --audience=cgr.dev) echo "::add-mask::$RENOVATE_DOCKER_CGR_DEV_PASSWORD" echo "RENOVATE_DOCKER_CGR_DEV_PASSWORD=$RENOVATE_DOCKER_CGR_DEV_PASSWORD" >> $GITHUB_ENV RENOVATE_APK_APK_CGR_DEV_PASSWORD=$(chainctl auth token --audience=apk.cgr.dev) echo "::add-mask::$RENOVATE_APK_APK_CGR_DEV_PASSWORD" echo "RENOVATE_APK_APK_CGR_DEV_PASSWORD=$RENOVATE_APK_APK_CGR_DEV_PASSWORD" >> $GITHUB_ENV - name: Run Renovate uses: renovatebot/github-action@6927a58a017ee9ac468a34a5b0d2a9a9bd45cac3 # v43.0.11 env: RENOVATE_TOKEN: ${{ secrets.GITHUB_TOKEN }} RENOVATE_REPOSITORIES: ${{ github.repository }} RENOVATE_DETECT_HOST_RULES_FROM_ENV: "true" RENOVATE_DOCKER_CGR_DEV_USERNAME: "_token" RENOVATE_APK_APK_CGR_DEV_USERNAME: "_token" ``` This workflow performs the following steps: * Installs chainctl and logs in as the assumable identity you created. * Exports short-lived tokens for cgr.dev and apk.cgr.dev as `RENOVATE_DOCKER_CGR_DEV_PASSWORD` and `RENOVATE_APK_APK_CGR_DEV_PASSWORD`. * Runs Renovate with `RENOVATE_DETECT_HOST_RULES_FROM_ENV=true` so that it uses the passwords exported by the previous step. Push this file to your repository's `main` branch. This workflow is scheduled to run at 3:00 a.m. every morning. You can trigger it manually by navigating to **Actions > Renovate** and selecting **Run workflow**. Once the workflow has run successfully, you'll find pull requests in your repository for any image references that need to be updated. ## Run Renovate with Docker Chainguard provides [an image for Renovate](https://images.chainguard.dev/directory/image/renovate/overview). This is an example of how you can run this image to keep references to Chainguard images up to date in a GitHub repository. > **Note**: To follow along with this section, you must have access to Chainguard's `renovate` container image. To begin, generate a Personal Access Token for your GitHub user as described in Renovate's [official documentation](https://docs.renovatebot.com/modules/platform/github/#authentication). Export the token as an environment variable named `RENOVATE_TOKEN`: ```shell export RENOVATE_TOKEN=ghp_XXXXXXXXXXXXXXXXXX ``` Next, create a Renovate configuration file at the root of any GitHub repositories you want to target with Renovate. Refer to the [official documentation](https://docs.renovatebot.com/configuration-options/) for all the supported options. This is an example of a minimal configuration: ```json { "$schema": "https://docs.renovatebot.com/renovate-schema.json" } ``` Then, log in with `chainctl`: ```shell chainctl auth login ``` Finally, run Renovate. Substitute `` with the name of your Chainguard organization and provide any GitHub repositories that you want to target as arguments in the form `/`: ```shell docker run \ -it \ --rm \ -e RENOVATE_TOKEN="${RENOVATE_TOKEN}" \ -e RENOVATE_DETECT_HOST_RULES_FROM_ENV=true \ -e RENOVATE_DOCKER_CGR_DEV_USERNAME=_token \ -e RENOVATE_DOCKER_CGR_DEV_PASSWORD=$(chainctl auth token --audience cgr.dev) \ -e RENOVATE_APK_APK_CGR_DEV_USERNAME=_token \ -e RENOVATE_APK_APK_CGR_DEV_PASSWORD=$(chainctl auth token --audience apk.cgr.dev) \ cgr.dev//renovate \ / ``` This example passes short-lived tokens for `cgr.dev` and `apk.cgr.dev` using the `RENOVATE_DOCKER_CGR_DEV_PASSWORD` and `RENOVATE_APK_APK_CGR_DEV_PASSWORD` environment variables. ## Troubleshooting ### Renovate doesn't behave as expected Run Renovate in debug mode and dump the resolved configuration to understand how it interpreted your settings. For example: ``` LOG_LEVEL=debug renovate --print-config ... "hostRules": [ { "hostType": "docker", "matchHost": "cgr.dev", "username": "", "password": "***********", "resolvedHost": "cgr.dev" }, {"matchHost": null, "hostType": "local"} ] ... DEBUG: hostRules: basic auth for https://cgr.dev (repository=local) DEBUG: getLabels(https://cgr.dev, ORGANIZATION/static, latest) (repository=local) DEBUG: getManifestResponse(https://cgr.dev, ORGANIZATION/static, latest, get) (repository=local) DEBUG: getManifestResponse(https://cgr.dev, ORGANIZATION/static, sha256:76d71eb53b1b44ec955529ece91c6da222a54fed660ca6b25124935bdd96e133, get) (repository=local) DEBUG: found labels in manifest (repository=local) "labels": { "dev.chainguard.package.main": "static", "org.opencontainers.image.authors": "Chainguard Team https://www.chainguard.dev/", "org.opencontainers.image.created": "2024-12-04T19:55:37Z", "org.opencontainers.image.source": "https://github.com/chainguard-images/images-private/tree/main/images/static", "org.opencontainers.image.url": "https://images.chainguard.dev/directory/image/static/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-renovate", "org.opencontainers.image.vendor": "Chainguard" } ``` ### Connections to `cgr.dev` fail If you have problems getting Renovate to monitor `cgr.dev`, double-check the connection details. Make sure the token is still valid (you can verify with `chainctl iam identities list`) and it has access to the repository you are referring to. You can test these credentials by running a `docker login` and `docker pull` in a clean environment. ### The log shows a `getReleaseList` error You may encounter errors such as the following: ``` DEBUG: getReleaseList error (repository=chainguard-images/images-private, branch=renovate/cgr.dev-chainguard.edu-python-3.x) "type": "github", "apiBaseUrl": "https://api.github.com/", "err": { "message": "`chainguard-images` forbids access via a personal access token (classic). Please use a GitHub App, OAuth App, or a personal access token with fine-grained permissions.", "stack": "Error: `chainguard-images` forbids access via a personal access token (classic). Please use a GitHub App, OAuth App, or a personal access token with fine-grained permissions.\n at … ``` These can be safely ignored. They are caused by Renovate using the `org.opencontainers.image.source` label on our images to look for a changelog. As this source is set to the private `images-private` GitHub repository, this request fails. ## Learn more * [Using Digestabot with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/digestabot/) covers Chainguard's own GitHub Action for keeping digest-pinned references current. * [Using Dependabot with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/dependabot/) covers the equivalent setup for teams already using Dependabot. * [Stay secure: Strategies and tooling for updating container images](https://www.chainguard.dev/unchained/stay-secure-strategies-and-tooling-for-updating-container-images), on the Chainguard blog, compares the wider range of update tools. * [Considerations for keeping containers up to date](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/) covers the tradeoffs behind an update policy. * [Authenticating to the Chainguard registry](/chainguard/containers/registry/authenticating/) documents pull tokens and the other authentication options in full. * [Renovate's APK datasource documentation](https://docs.renovatebot.com/modules/datasource/apk/) covers every `registryUrl` query parameter Renovate supports. --- ### Building minimal container images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/building-and-modifying/building-minimal-containers.md Last Modified: September 28, 2026 Tags: Chainguard Containers The less software a container image holds, the less there is to transfer, to maintain, and to attack. Chainguard's [distroless](/chainguard/containers/concepts/getting-started-distroless/) container images take that as far as it goes: no shell, no package manager, and only the libraries the application needs. That raises a practical question: without a shell or a package manager, how do you get your application into the image? The answer is a multi-stage build. One stage compiles or assembles your application with all the tooling that requires, and the final stage copies the result into a minimal base. This page covers both shapes that build takes, depending on whether your application needs a language runtime once it's running. ## Applications that compile to a static binary Go, Rust, and other toolchains that produce a statically linked binary can use the smallest base Chainguard publishes. Build in the language's own image, then copy the binary into `cgr.dev/chainguard/static`: ```Dockerfile FROM cgr.dev/chainguard/go:latest AS build COPY main.go /main.go RUN CGO_ENABLED=0 go build -o /hello /main.go FROM cgr.dev/chainguard/static:latest COPY --from=build /hello /usr/local/bin/ CMD ["hello"] ``` `CGO_ENABLED=0` matters here. The `static` image ships no C library, so the binary must not link against one dynamically. ### Why not `scratch`? For a binary that needs nothing else, the empty `scratch` image is smaller still, and it works. Most applications need more than that, though: TLS root certificates to make outbound HTTPS calls, time zone data to handle local times, and directories such as `/tmp`, `/etc`, and `/home` that libraries expect to find. The `static` image provides those and close to nothing else, which makes it a better default than `scratch` for a statically compiled application. The following video walks through this build and compares the result against other minimal base images: [Watch the video on YouTube](https://www.youtube.com/watch?v=ZT6177U0fUM) ## Applications that need a language runtime Java, Python, and .NET applications can't run on `static` or `scratch`, because the runtime files have to be in the container image. The build keeps the same two stages; only the final base changes, from an empty image to a runtime image. For a Maven project, the build stage runs Maven against your sources to produce a JAR, and the final stage starts from `cgr.dev/chainguard/jre`, copies in that JAR, and sets an entrypoint that runs it. The build stage is large, since it holds the JDK, Maven, and everything Maven downloaded. The final image holds the JRE and your JAR, and like the `static` image it has no shell and no package manager. You can find complete, working Dockerfiles for this example — a Spring Pet Clinic application built with the Chainguard Maven and JRE images — in the [minimal_images_for_language_runtimes](https://github.com/chainguard-dev/minimal_images_for_language_runtimes) repository. ### Choosing a runtime version An untagged build uses the latest Maven and the latest JRE. That suits many projects, but sometimes you need to pin the Java version in both stages. The latest version of each container is free for everyone; tagged major and minor versions come with a subscription, as described in the [Containers overview](/chainguard/containers/overview/#production-and-free-containers). If a tagged runtime isn't available to you, you can assemble your own runtime layer on `cgr.dev/chainguard/wolfi-base`, installing the JDK and Maven packages you need with `apk` and setting `JAVA_HOME` yourself. The minimal_images_for_language_runtimes repository includes this variant too. However, this method comes with two important tradeoffs: * `wolfi-base` includes a shell and a package manager, so the image you finish with is no longer distroless. * The JRE image ships locale data that `wolfi-base` leaves out. Java applications that depend on locale data need that package added back, which increases the image's size. For a supported way to add packages to a distroless image without giving up its properties, check out [Custom Assembly](/chainguard/containers/custom-assembly/). This video works through the Maven and JRE build, then the `wolfi-base` variant: [Watch the video on YouTube](https://www.youtube.com/watch?v=P7pmV-s5ZYY) ## Learn more * Confirm what your finished image contains with [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/). * If the application fails in the distroless stage but works in the build stage, refer to [Debugging distroless container images](/chainguard/containers/troubleshooting/debugging-distroless-images/). * To add APK packages to a distroless variant directly, refer to [Installing APK packages in distroless variants](/chainguard/containers/building-and-modifying/install-apks-in-distroless-variants/). --- ### Inspecting Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/troubleshooting/inspecting-containers.md Last Modified: October 8, 2026 Tags: Chainguard Containers Two questions come up repeatedly once you're running Chainguard Containers: which exact build is this, and what software versions are inside it? A digest answers the first. The container's SBOM answers the second. The following examples pull from `cgr.dev/chainguard/`, the namespace that holds Chainguard's Free containers, so they run as written. If you pull from your organization's own registry, substitute `cgr.dev//`. ## Identify a build by its digest A digest is a content-based hash of a container image. No two images share one, so pulling by digest returns the same image every time. Tags don't behave that way. A tag such as `latest`, or a version tag like `3.0`, points at the newest build in its version stream, and Chainguard moves it as it publishes rebuilds. If you pull the same tag twice on two different days, you may end up pulling two different images, making it difficult to reproduce a build later. [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/) explains how those tags float. ### Retrieve the digest of a container `docker pull` reports the digest of whatever it pulled: ```sh docker pull cgr.dev/chainguard/node ``` ```output . . . Digest: sha256:ede7ef4ca485553f5313f7a02ad3537db1fe337079fc7cfb879f44cf709326db Status: Downloaded newer image for cgr.dev/chainguard/node:latest cgr.dev/chainguard/node:latest ``` That digest refers to the image *index*, which lists one image per platform rather than pointing at a single image. In most cases the index is what you want to reference, since it lets the same digest work on every architecture you deploy to. To find out what the index holds, inspect it: ```sh docker manifest inspect cgr.dev/chainguard/node@sha256:ede7ef4ca485553f5313f7a02ad3537db1fe337079fc7cfb879f44cf709326db | jq ``` The output lists a digest for each platform in the index, typically `linux/amd64` and `linux/arm64`. You can reference one of those directly, but an image pinned to a platform-specific digest only runs on that platform, so be deliberate about it. [`crane`](https://github.com/google/go-containerregistry/tree/main/cmd/crane) prints the digest alone with nothing to parse, making it useful for scripts: ```sh crane digest --full-ref cgr.dev/chainguard/node:latest ``` Add `--platform` to get the digest for one platform: ```sh crane digest --full-ref --platform linux/arm64 cgr.dev/chainguard/node:latest ``` ### Pin a reference to a digest Append the digest to the reference to pull one specific build: ```sh docker pull cgr.dev/chainguard/node:latest@sha256:ede7ef4ca485553f5313f7a02ad3537db1fe337079fc7cfb879f44cf709326db ``` Registries ignore the tag when a reference carries a digest, which frees the tag to carry a version hint for whoever reads the file next: ``` cgr.dev/chainguard/go:1.22@sha256:7e60584b9ae1eec6ddc6bc72161f4712bcca066d5b1f511d740bcc0f65b05949 ``` Chainguard recommends that form, and both [Dependabot](/chainguard/containers/security-and-compliance/updating-containers/dependabot/) and [Renovate](/chainguard/containers/security-and-compliance/updating-containers/renovate/) update the tag and the digest together when they find it. While you *can* use digests on the command line, they're much more commonly found in configuration files, such as a Dockerfile, a Compose file, or a Kubernetes manifest: ```Dockerfile FROM cgr.dev/chainguard/go:latest@sha256:7e60584b9ae1eec6ddc6bc72161f4712bcca066d5b1f511d740bcc0f65b05949 AS build WORKDIR /src RUN CGO_ENABLED=0 go build -o /bin/server ./src FROM cgr.dev/chainguard/static:latest AS prod COPY --from=build /bin/server /bin/ EXPOSE 8000 ENTRYPOINT [ "/bin/server" ] ``` Every run of this Dockerfile build uses the same Go compiler, so a build that works today works the same way next month, even if Chainguard has since rebuilt the container image. Note the tradeoff: a pinned digest stops receiving patches, because the whole point is that it never changes. Pair digest pinning with something that updates the pin, such as [Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/), and refer to [Considerations for image updates](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/) for how to think about the schedule. The following video covers the same ground, including what the index looks like from the inside: [Watch the video on YouTube](https://www.youtube.com/watch?v=xYlLfjgG64E) ## Read software versions from a container Every Chainguard Container records the version of each package it installs. One way to find this is to list `/var/lib/db/sbom` inside the container. It holds one SPDX document per installed package, and the filenames carry the versions: ```sh docker run cgr.dev/chainguard/wolfi-base ls /var/lib/db/sbom ``` ```output apk-tools-2.14.10-r14.spdx.json busybox-1.38.0-r2.spdx.json ca-certificates-bundle-20260611-r1.spdx.json glibc-2.44-2.44-r5.spdx.json . . . ``` That works because `wolfi-base` includes a shell, and with it `ls`. Most of Chainguard's container images are distroless and include neither. For those, run the `-dev` variant, which does have a shell: ```sh docker run --entrypoint /bin/sh cgr.dev/chainguard/python:latest-dev -c "ls /var/lib/db/sbom" ``` Or copy the directory out of the distroless container without running anything in it: ```sh id=$(docker create cgr.dev/chainguard/python) docker cp "$id":/var/lib/db/sbom ./sbom docker rm "$id" ``` Chainguard also publishes a signed, image-level SBOM for every build, which you can retrieve without pulling the container. Refer to [How to retrieve SBOMs and attestations for Chainguard Containers](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) for details. The following video walks through both routes: [Watch the video on YouTube](https://www.youtube.com/watch?v=K60-jhVf2I4) ## Related reading * [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) — when the container or version you need isn't there to inspect. * [Unique tags](/chainguard/containers/reference/unique-tags/) — an alternative to digests for teams whose workflows require a stable tag. * [Debugging distroless container images](/chainguard/containers/troubleshooting/debugging-distroless-images/) — further techniques for looking inside a container with no shell. --- ### Getting started with the nginx Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/web-and-data-services/nginx.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's nginx container images provide a security-hardened foundation for web server deployments. Available in both development (`:latest-dev`) and production (`:latest`) variants, these images maintain full nginx functionality while reducing attack surface. The production variant uses a distroless approach, removing shells and package managers to enhance security for production workloads. For current CVE data on each image and tag, see the [nginx entry in the Chainguard Containers directory](https://images.chainguard.dev/directory/image/nginx/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-nginx). In this tutorial, we will create a local demo website using nginx to serve static HTML content to a local port on your machine. Then we will use the nginx Chainguard Container to build and execute the demo in a lightweight containerized environment. If you'd like, you can watch our [Getting Started with the nginx Chainguard Container video](https://youtu.be/KirTeDMzzxk) as you work through this tutorial, which will walk through the same steps that are detailed here. [Watch the video on YouTube](https://www.youtube.com/watch?v=KirTeDMzzxk)
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Prerequisites You will need to have [nginx](https://nginx.org/en/download.html) and [Docker Engine](https://docs.docker.com/engine/install/) installed on your machine to follow along with this demonstration. For this tutorial, you will be copying code to files you create locally. You can find the demo code throughout this tutorial, or you can find the complete demo at the [demo GitHub repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/nginx). ## Step 1: Setting up a demo website We'll start by serving static content to a local web server with nginx. With nginx installed, create a directory for your demo. In this guide we'll call the directory `nginxdemo`: ```shell mkdir ~/nginxdemo/ && cd $_ ``` Within this directory, we will create a `data` directory to contain our content to be hosted by the web server. ```shell mkdir data && cd $_ ``` Using a text editor of your choice, create a new file `index.html` for the HTML content that will be served. We will use `nano` as an example. ```shell nano index.html ``` The following HTML file displays a graphic of Linky alongside a fun octopus fact. ```HTML Chainguard nginx Demo Website

nginx Demo Website

from the Chainguard Academy

Did you know?

The Wolfi octopus is the world's smallest octopus, weighing in on average at less than a gram!

They are found near the coastlines of the west Pacific Ocean.

``` Copy this code to your `index.html` file, save, and close it. Next, create a CSS file named `stylesheet.css` using the text editor of your choice. We'll use `nano` to demonstrate. ```shell nano stylesheet.css ``` Copy the following code to your `stylesheet.css` file. ```CSS /* Chainguard Academy nginx Demo Website */ body { text-align: center; background-color: #fcebfc; font-family: Arial, sans-serif; } h1 { color: #df45e6; } h2, h3 { color: #9745e6; } p { color: #583ce8 } .corners { border-radius: 10px; } ``` After copying the code into the `stylesheet.css` file, save and close it. Next, you will pull down the `linky.png` file using `curl`. Always [inspect the URL](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/fb54a9767a5474716398ac33de81d66e263d4c6f/nginx/data/linky.png) before downloading it to ensure it comes from a safe and trustworthy location. ```shell curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/fb54a9767a5474716398ac33de81d66e263d4c6f/nginx/data/linky.png ``` Now, return to the `nginxdemo` directory you made earlier. ```shell cd ../ ``` Here we will create the `nginx.conf` configuration file used by nginx to run the local web server. We will demonstrate this using `nano`. ```shell nano nginx.conf ``` Copy the following code into the configuration file you created. In the `location` directive, be sure to update the configuration to reference the path from root to the `data` directory you created in a previous step. The file path which you are not using should be commented out using the `#` symbol to prevent syntax errors. ```nginx worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 8080; server_name localhost; charset koi8-r; location / { root /Users/username/nginxdemo/data; # Update the file path for your system #root /home/username/nginxdemo/data; # Linux file path example } } include servers/*; } ``` Once you are finished editing the configuration file, save and close it. Create a new file named `mime.types` using a text editor of your choice. We will use `nano` to demonstrate. ```shell nano mime.types ``` Copy and paste the following code into your `mime.types` file. This file will allow nginx to handle the HTML, CSS, and png files we created when rendering the webserver. ```nginx types { text/html html htm shtml; text/css css; image/png png; } ``` Save and close the `mime.types` file when you are finished editing it. Next, copy the absolute filepath to the `nginx.conf` file you created earlier. Replacing the example path with this copied path, execute the following command to initialize the nginx server: ```shell nginx -c /Users/username/nginxdemo/nginx.conf ``` Please note that you may encounter some permissions errors when executing this command. You will need to update the permissions of the default nginx logging directory on some systems to proceed. For example, nginx installed with Homebrew stores its log files at `/opt/homebrew/var/log/nginx/`, while on a Linux machine, the logs are stored in `/var/log/nginx/`. To update the permissions of these directories, execute the following command, updating the log file path if need be. ```shell chmod +wx /opt/homebrew/var/log/nginx/ ``` With the directory permissions updated, you should now be able to initialize the nginx server. To view the HTML content, navigate to `localhost:8080` in your web browser of choice. You will see a simple landing page with a picture of Linky and a fun fact about the Wolfi octopus. If you make any changes to the files nginx is serving, run `nginx -s reload` in your terminal to allow the changes to render. When you are finished with your website, run `nginx -s quit` to allow nginx to safely shut down. ## Step 2: Creating the Dockerfile We will now use a Dockerfile to build an image containing the demo. In the `nginxdemo` directory, create the `Dockerfile` with the text editor of your choice. We will use `nano`: ```shell nano Dockerfile ``` The following Dockerfile will: 1. Start a new build based on the `cgr.dev/chainguard/nginx:latest` container image; 2. Note which container port we need to expose for nginx to listen on; 3. Copy the HTML content from the data directory into the image. Copy this content to your own `Dockerfile`: ```Dockerfile FROM cgr.dev/chainguard/nginx:latest EXPOSE 8080 COPY data /usr/share/nginx/html/ ``` Save the file when you're finished. You can now build the container image with: ```shell docker build . --pull -t nginx-demo ``` Once the build is complete, run the container image with: ```shell docker run -d --name nginxcontainer -p 8080:8080 nginx-demo ``` The `-d` flag configures our container to run as a background process. The `--name` flag will name our container `nginxcontainer`, making it easy to identify from other containers. The `-p` flag publishes the port that the container listens on to a port on your local machine. This allows us to navigate to `localhost:8080` in a web browser of our choice to view the HTML content served by the container. You should see the same HTML page as before, with Linky and an octopus fun fact. If you wish to publish to a different port on your machine, such as `1313`, you can do so by altering the command-line argument as shown: ```shell docker run -d --name nginxcontainer -p 1313:8080 nginx-demo ``` When you are done with your container, you can stop it with the following command: ```shell docker container stop nginxcontainer ``` ## Advanced usage In this demo, we did not copy the configuration file into the container image built from the Dockerfile. This is because the default configuration file in the image was sufficient for the scope of this demo. If you wish to use a custom configuration file, you must ensure that file paths, ports, and other system-specific settings are configured to match the container environment. You can find more information about making these changes at the [Chainguard nginx Container Overview](https://images.chainguard.dev/directory/image/nginx/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-nginx).

If your project requires a more specific set of packages that aren't included within the general-purpose nginx Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Variations, inputs, services, and caches in Chainguard Checks URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/variations-services-caches.md Last Modified: October 13, 2026 Tags: Chainguard Checks, Configuration, Reference Most checks need only the keys that the [Chainguard Checks configuration reference](/chainguard/checks/configuration/) describes. This page describes the keys for checks that need more: running a check once for each of several variations, passing files between checks, starting services such as a database, running as an unprivileged account, and reusing build output from earlier runs. ## Run a check once per variation {#run-a-check-once-per-variation} The `foreach` key runs a check once for each entry in a list, each in its own VM. All of a check's variations report as one GitHub check. For example, this check runs three variations, two at a time: ```yaml checks: test: script: go test -tags "$TAGS" ./... vars: TAGS: unit network: egress: [proxy.golang.org, sum.golang.org] parallelism: 2 foreach: - name: api workdir: api - name: api-integration workdir: api vars: TAGS: integration timeout: 30m - name: cli workdir: cli resources: medium ``` The `parallelism` key sets how many variations can run at once. Each entry can set the following keys. Any other key, such as `cmd` or `needs`, makes the configuration invalid. | Key | Description | | --- | ----------- | | `name` | The variation's name. Every entry's name must be different. | | `vars` | Variables that override the check's `vars` with the same names. Each value can be up to 1,024 bytes. | | `workdir` | The directory to run in, in place of the check's `workdir`. | | `resources` | The resource class, in place of the check's. | | `timeout` | The time limit, in place of the check's. | | `when` | Conditions for the variation, which apply after the check's own `when`. They can't use `requested`. | | `network` | An `egress` list that replaces the check's for this variation. It can list only hosts that the check's `egress` allows, and `egress: []` gives the variation no network access. Without it, the variation uses the check's `network`. | An entry's values reach the check's commands only as environment variables and as the working directory. A `cmd` doesn't expand variables, so read them in a `script`, as in the example. An entry without a `name` is named after its `vars` values, sorted by variable name and joined with `-`. Set `name` when a value has characters that a name can't have, such as `:`, `=`, or `@`, or when it doesn't start and end with a letter or digit. `foreach: []` runs no variation. ### Compute variations during a run {#compute-variations-during-a-run} To compute the variations during the run, such as one for each module in your repository, define a planner under the top-level `plans` key and set the check's `foreach` to `plans.`: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go] plans: modules: environment: packages: [bash, git] cmd: ./ci/list-modules.sh checks: test: foreach: plans.modules cmd: go test ./... network: egress: [proxy.golang.org, sum.golang.org] ``` The planner runs once, before the checks that use it, and writes its variations as JSON to the file that the `CG_PLAN` environment variable names. Each variation can have the same keys as a `foreach` entry, except that its `when` can hold only `changed`. For example, `ci/list-modules.sh` could write the following: ```json [{"name": "api", "workdir": "api"}, {"name": "cli", "workdir": "cli"}] ``` A planner works as follows: - It needs its own `environment` and a `cmd`, and can have a `network`. Its environment can't name caches. Without `network`, it has no network access. - It runs in a VM with your repository, with `CHAINGUARD_BASE_REVISION` set the same way as for a check, and can run for up to five minutes. - It can write a JSON array of variations, or an object of named arrays, such as `{"unit": [...], "integration": [...]}`. A check uses one array with `foreach: plans..`, such as `plans.modules.unit`. - A variation's `network` can only narrow the check's, as for a `foreach` entry. - A variation without a `name` is named `shard-1`, `shard-2`, and so on. An empty array runs no variation of the check. - Its output can be up to 1 MiB, with up to 1,000 variations across all arrays and names up to 128 bytes. If the planner fails or writes output that breaks these rules, the checks that use it don't run and report a failed dependency. If every check that uses it is advisory, a failed planner doesn't block merging. - A check can use a planner only from the same file. Planner names use letters, digits, `_`, and `-`, start with a letter, and can be up to 128 characters. ## Inputs and outputs {#inputs-and-outputs} A check can save a directory for later checks in the same run with `outputs`, and add files to its VM with `inputs`, either from another check's output or from another repository. ### Pass files between checks {#pass-files-between-checks} To pass files from one check to another, declare an output on the check that writes them, and an input with `from` on the check that reads them: ```yaml checks: build: cmd: go build -o out/ ./cmd/... outputs: bin: path: out network: egress: [proxy.golang.org, sum.golang.org] smoke: cmd: bin/app --version inputs: - path: bin from: {check: build, output: bin} ``` In this example, `build` writes its binaries to `out/`, and `smoke` runs one of them from `bin/` after `build` passes. Inputs and outputs work as follows: - An output's `path` is a directory relative to the repository's root. It starts empty, and your repository must not have files at that path, or the run fails. - After the producing check passes, the consuming check gets a read-only copy of the output at its input's `path`. - `from` works like `needs`: the producing check must be in the same file and run on the same triggers, and the consuming check waits for it and doesn't run if it fails. The same limits as for `needs` apply, as described in [Advisory checks](/chainguard/checks/configuration/#advisory-checks). - If the producing check has `foreach` variations, the input holds one subdirectory for each variation, named after the variation, with `/` written as `%2F`. - A check that [runs as another user](#run-as-another-user) can't declare outputs. - A check can declare up to 32 outputs and read up to 32 outputs of other checks. Each output can hold up to 10,000 files and directories and 64 MiB. Output names can't contain `.` or `/`, and inputs and outputs can't overlap each other or `.git`. Outputs pass files only between checks in a run. You can't download them. ### Check out another repository {#check-out-another-repository} To add another repository's files to a check's VM, use an input with `git`: ```yaml checks: integration: cmd: go test ./integration/... inputs: - path: fixtures git: repo: https://github.com/acme/fixtures ref: main ``` Chainguard Checks fetches the repository outside the VM, so the check doesn't need network access for it. The repository must be public, and `repo` must be an `https://github.com` URL. Chainguard Checks resolves `ref`, a branch, tag, or commit SHA, to a commit when it creates the run. Without `ref`, it uses the repository's default branch. The files arrive at `path` without a `.git` directory. If the repository is private or the ref doesn't exist, the summary check shows **Run creation failed**. `chainctl checks validate` can't check either of these offline. ## Services {#services} A service is a process, such as a database, that Chainguard Checks starts in a check's VM before the check's commands run. In the directory layout, define each service in `.chainguard/ci/services//.yaml`, with the script that starts it in the same directory: ```yaml {title=".chainguard/ci/services/postgres/postgres.yaml"} version: 1 name: postgres packages: [busybox, postgresql-18, postgresql-18-client, su-exec] cmd: [sh, ./postgres.sh] timeout: 60s inputs: port: type: integer default: "5432" env: PGPORT exports: PGHOST: 127.0.0.1 PGUSER: postgres ``` The script starts the database, waits until it accepts connections, prints `::service-ready::`, and then waits on the database: ```shell {title=".chainguard/ci/services/postgres/postgres.sh"} #!/bin/sh set -eu : "${PGDATA:=/tmp/postgres}" : "${PGPORT:=5432}" export PGDATA PGPORT # PostgreSQL refuses to run as root, so run it as a postgres account. if [ "$(id -u)" = 0 ]; then id postgres >/dev/null 2>&1 || adduser -S -D -H -s /bin/sh postgres mkdir -p "$PGDATA" chown postgres "$PGDATA" chmod 700 "$PGDATA" exec su-exec postgres sh "$0" fi if [ ! -f "$PGDATA/PG_VERSION" ]; then initdb --no-sync --pgdata="$PGDATA" --username=postgres --auth=trust --encoding=UTF8 --locale=C fi postgres -D "$PGDATA" -h 127.0.0.1 -p "$PGPORT" -k "$PGDATA" & daemon=$! trap 'kill "$daemon" 2>/dev/null || true' EXIT trap 'exit 1' HUP INT TERM until pg_isready -h 127.0.0.1 -p "$PGPORT" -U postgres >/dev/null 2>&1; do kill -0 "$daemon" 2>/dev/null || exit 1 sleep 0.1 done printf '\n::service-ready::\n' wait "$daemon" exit 1 ``` A check uses the service by naming it in its environment's `services`: ```yaml {title=".chainguard/ci/db.yaml"} version: 1 environment: packages: [go] services: [postgres] checks: integration: cmd: go test ./db/... network: egress: [proxy.golang.org, sum.golang.org] ``` A service works as follows: - Chainguard Checks adds the service's `packages` to the environment, and runs its `cmd` without a shell, from the directory of the file that defines it. - The command must stay in the foreground, and print `::service-ready::` on a line of its own to standard output when the service is ready. The check's commands start after every service is ready. - If a service isn't ready within its `timeout`, which defaults to one minute, or its command exits while the check runs, the check fails. Chainguard Checks never restarts a service. - The service's output appears in the check's log. - Connections to `127.0.0.1` stay inside the VM, so a check doesn't need a `network` entry to reach its services. - `exports` become environment variables for the check's commands and the service. `inputs` turn values that a check passes with `with` into environment variables that only the service's command gets. - A definition can be up to 64 KiB. Service names use lowercase letters, digits, and `-`, start with a letter, and can be up to 63 characters. To run a service under another name, or to pass it inputs, use a mapping with `uses`, `name`, and `with`: ```yaml environment: packages: [go] services: - uses: postgres name: db with: port: "5433" ``` Services in `services/` need the directory layout. In the single-file layout, define a service inline, with the same keys as a definition file. Its command runs from the `.chainguard/` directory, which can hold only `.yaml` files, so keep its script elsewhere in your repository: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go] services: - name: postgres packages: [busybox, postgresql-18, postgresql-18-client, su-exec] cmd: [sh, ../ci/postgres.sh] exports: PGHOST: 127.0.0.1 PGUSER: postgres checks: integration: cmd: go test ./db/... network: egress: [proxy.golang.org, sum.golang.org] ``` ## Run as another user {#run-as-another-user} By default, a check's commands run as `root`. To run them as an unprivileged account, set `user` to the account's name: ```yaml checks: test: cmd: go test ./... user: build ``` Chainguard Checks creates the account with UID and GID 65532 and the home directory `/home/`, and makes it the owner of `/work`. A name uses lowercase letters, digits, `_`, and `-`, starts with a lowercase letter or `_`, and can be up to 32 characters. Don't use a name that the VM already has: `bin`, `daemon`, `adm`, `lp`, `sync`, `shutdown`, `halt`, `mail`, `news`, `uucp`, `operator`, `man`, `postmaster`, `cron`, `ftp`, `sshd`, or `nobody`. `chainctl checks validate` accepts these names, but the run refuses them. A check also fails to start if one of its packages creates an account with the same name. The account owns `/work`, but not your repository's files and `.git` directory, so `git` refuses to work in `/work` because of their ownership. For a check that runs `git`, mark the repository as safe with Git's configuration variables: ```yaml checks: test: cmd: go test ./... user: build vars: GIT_CONFIG_COUNT: "1" GIT_CONFIG_KEY_0: safe.directory GIT_CONFIG_VALUE_0: "*" ``` A check that runs as another user can't declare `outputs`. A check that uses a [cache](#caches) must run as `build`. ## Caches {#caches} By default, each check starts with nothing from earlier runs, so a Go check compiles every package on every run. A check can reuse build output from earlier runs through a cache that its environment names under `caches`. During the beta, only the Go build cache, `gobuild`, stores anything. The `gomod`, `python`, `node`, and `directory` caches pass validation but cache nothing, so keep the hosts that your tools download from in `egress`. ### Build caches {#build-caches} To reuse Go build output, name `gobuild` in the environment, and set `user: build` on each check that uses the environment: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go] caches: gobuild: {} checks: test: cmd: go test ./... user: build trigger: [presubmit, postsubmit] network: egress: [proxy.golang.org, sum.golang.org] ``` The cache does nothing until your default branch defines cache pools, as described in [Set up cache pools](#set-up-cache-pools). A Go build cache works as follows: - `gobuild: {}` takes no settings, and the environment's packages must include `go` or a `go-` package. - Every check in an environment that names a cache must set `user: build`, so its commands run as an unprivileged account, as described in [Run as another user](#run-as-another-user). A command that needs root fails. In the directory layout, give the checks that use a cache an environment of their own. - Chainguard Checks sets `GOCACHE` to the cache's directory, so don't set `GOCACHE` in `vars`. - Chainguard Checks adds `-count=1` to `GOFLAGS`, so `go test` always runs your tests, and only build output is reused. - A cache fills only from checks that pass. To fill the cache that pull requests start from, run the check after merging too, with `trigger: [presubmit, postsubmit]`. - Go modules still download on every run, so keep `proxy.golang.org` and `sum.golang.org` in `egress`. ### Set up cache pools {#set-up-cache-pools} Chainguard Checks mounts a cache only from a *cache pool*, which you define in the `trust` section of `.chainguard/ci/identities.yaml` on your default branch. Each pool lists the [rings](/chainguard/checks/identity-and-trust/#rings) whose runs share it, so pull requests from people you don't trust can't change the build output that your default branch's runs reuse. This file defines two pools: ```yaml {title=".chainguard/ci/identities.yaml"} version: 1 trust: groups: members: github: {org: acme} subrings: member: authors: [members] github_token: 1.member cache: trunk: rings: [2] trusted: rings: [1.member] seed: trunk identities: {} ``` With this file, runs after merging, which are in ring `2`, read and write the `trunk` pool. Pull requests opened by members of the `acme` GitHub organization, which are in ring `1.member`, read and write the `trusted` pool, and also start from a read-only copy of `trunk`, which `seed` names. Other pull requests are in ring `0` and get no cache, because no pool lists ring `0`. Pools follow these rules: - A pool's name uses lowercase letters, digits, and `-`, and starts with a letter. Each pool lists one or more `rings`, and a ring can be in at most one pool. - A `seed` must name a pool whose `rings` are exactly `[2]`, and which has no seed of its own. - Changing a pool's `rings`, or raising its optional `generation` number, starts the pool empty. - Chainguard Checks reads the pools from your default branch, so the pull request that adds or changes them runs with the pools your default branch had before it. - [Chainguard Workspaces sessions](/chainguard/workspaces/session-environment/#start-with-a-warm-go-build-cache) started with `--cache` read only a pool whose `rings` are exactly `[2]`. A `trust` section that you add for caches also applies to workload identities, so every identity in the file then needs a `ring`. For the section's other keys, refer to [Limit which pull requests get an identity](/chainguard/checks/identity-and-trust/#limit-which-pull-requests-get-an-identity). To see what each check would mount in each pool, run `chainctl checks explain`. ### Who can read and write a cache Cache pools keep a pull request from changing the build output that other runs use: - Your default branch's `identities.yaml` decides which pool a pull request's run uses, so a pull request can't change its own pool. - A ring in no pool gets no cache. With separate pools for ring `2` and for a subring, as in the example, pull requests from members read your default branch's cache through the seed but never write to it. Put ring `2` and a subring in the same pool only if you trust every author in that subring with the cache that your default branch's runs use. - A pool that lists ring `0` keeps a separate cache for each pull request, and runs after merging don't use it. - Only checks that pass, and aren't canceled, add to a pool. - A check that uses a workload identity, or whose outputs feed a check that does, never adds to a pool. It reads a pool only if every ring in the pool can get the same identities. - Workspaces sessions never add to a pool. If you suspect a pool holds bad output, raise its `generation` to start it empty. ### How caches fill {#how-caches-fill} A pool fills and empties as follows: - A new pool starts empty. Chainguard Checks adds the build output of passing checks to it about every 15 minutes at most, so a pool warms up over several runs. - Each check adds up to 2 GiB and 200,000 files and directories to each cache. - A pool holds up to about 128 GiB. Chainguard Checks drops entries that no run uses for about three weeks. - When a pull request closes, Chainguard Checks deletes its cache in a pool that lists ring `0`. - **Re-run** on GitHub and `chainctl checks rerun` run every check without a cache. --- ### Workload identities and trust in Chainguard Checks URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/identity-and-trust.md Last Modified: October 14, 2026 Tags: Chainguard Checks, Configuration, Security A check that must authenticate to Google Cloud or to Chainguard, such as to read a Cloud Storage bucket or to pull private images from `cgr.dev`, can use a *workload identity*: short-lived credentials that Chainguard Checks gives the check while it runs. This page explains how workload identities work, how to declare and set one up, how to limit which pull requests get one, and how to revoke access. ## How workload identities work A check's VM holds no long-lived credentials. When a check uses a workload identity, Chainguard Checks issues the check an OpenID Connect (OIDC) token that lasts five minutes, renews the token while the check runs, and keeps the real credentials outside the VM: | Provider | What the check's VM holds | | --- | --- | | Google Cloud | `GOOGLE_APPLICATION_CREDENTIALS`, set to `/run/chainguard/identities//adc.json`, a credential configuration that Google Cloud client libraries use without further setup. The tokens that code in the VM can read are sealed stand-ins. Chainguard Checks swaps in the real tokens only on requests to `sts.googleapis.com` and other `googleapis.com` subdomains, such as `storage.googleapis.com`. | | Chainguard | Nothing. Chainguard Checks adds a credential to the check's requests to each Chainguard host the identity lists, so the check doesn't sign in or need a credential file. | Code in the check can't read the real tokens, but it can use them: each request it sends to those hosts carries the identity's access. If that access lets the code create a credential, such as a Google Cloud service account key, that credential works outside the check until you delete it. Grant each identity only the access its checks need. What code can use depends on where it runs: | Where code runs | Credentials it can use | | --- | --- | | A check on a pull request | The identity the check names, if `identities.yaml` on the pull request's base branch allows it for that check and branch. If your default branch has a [`trust` section](#limit-which-pull-requests-get-an-identity), the pull request must also reach the identity's ring. | | A check after a change merges | The identity the check names, if `identities.yaml` on your default branch allows it for that check and branch. | | A Chainguard Workspaces session, including the checks that `run-ci` runs in it | The session's own credentials, as described in [Manage Chainguard Workspaces session credentials](/chainguard/workspaces/credentials/). Sessions don't get workload identities, so [`run-ci`](/chainguard/checks/run-checks/) skips checks that use one. | ## Declare an identity You declare workload identities in `.chainguard/ci/identities.yaml`, which works with either [file layout](/chainguard/checks/configuration/#file-layouts). The file has the following top-level keys: | Key | Required | Description | | --- | --- | --- | | `version` | Yes | The format version. Must be `1`. | | `identities` | Yes | A map from each identity's name to its definition. Refer to [Identity keys](#identity-keys). | | `trust` | No | Which pull requests get identities, decided by who opened them. Refer to [Limit which pull requests get an identity](#limit-which-pull-requests-get-an-identity). | Chainguard Checks refuses keys it doesn't recognize. To check the file before you merge it, run `chainctl checks validate .`, as described in [Validate your configuration offline](/chainguard/checks/run-checks/#validate-your-configuration-offline). The following file declares an identity named `gcs-read` that the `integration` check in `.chainguard/ci.yaml` can use on pull requests into `main`: ```yaml {title=".chainguard/ci/identities.yaml"} version: 1 identities: gcs-read: description: Read test fixtures from Cloud Storage. checks: - ci.checks.integration audiences: - https://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/checks/providers/checks gcp: provider: projects/123456789012/locations/global/workloadIdentityPools/checks/providers/checks audience: https://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/checks/providers/checks allow: - run: presubmit target_ref: refs/heads/main ``` A check uses an identity by naming it with `identity`. For a Google Cloud identity, the check's `network.egress` must list `sts.googleapis.com`, where client libraries exchange the identity's token, and each Google API host the check calls: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go] checks: integration: cmd: go test -tags=integration ./... identity: gcs-read network: egress: - proxy.golang.org - sum.golang.org - sts.googleapis.com - storage.googleapis.com ``` Merge an identity's definition before you use it. Chainguard Checks reads definitions from the base branch, so on the pull request that adds `gcs-read`, the `integration` check can't get the identity and fails. A check that uses an identity can't set `GOOGLE_APPLICATION_CREDENTIALS` in its `vars` or in its environment's `vars`. If it does, the whole run fails. ### Identity keys Each identity has the following keys: | Key | Required | Description | | --- | --- | --- | | `description` | No | A description of the identity. | | `checks` | Yes | A list of selectors for the checks that can use the identity. | | `audiences` | Yes | A list of the audiences that the identity's tokens can carry. It must include `gcp.audience` and each Chainguard host's `exchange_audience`. | | `allow` | Yes | A list of the runs that can get the identity. | | `gcp` | No | Google Cloud settings, both required: `provider`, the workload identity pool provider's resource name, in the form `projects//locations/global/workloadIdentityPools//providers/`, and `audience`, an audience that the provider accepts. | | `chainguard` | No | A map from each Chainguard host to its settings. Refer to [Chainguard hosts](#chainguard-hosts). | | `ring` | With a `trust` section | Which pull requests get the identity. Refer to [Limit which pull requests get an identity](#limit-which-pull-requests-get-an-identity). | An identity needs `gcp`, `chainguard`, or both to give a check any credentials. Identity names are 1 to 48 lowercase letters, digits, and `-`, and start with a letter. Each `checks` entry has the form `.checks.`. `` is a check file's name without `.yaml`, such as `go` for `.chainguard/ci/go.yaml`, or `ci` in the single-file layout. `` is a check's name in that file. Either part can be `*` to match any file or any check, but only as the whole part, so `go.checks.test-*` is an error. Quote a selector that starts with `*`, such as `"*.checks.lint"`, because YAML reads a leading `*` as an alias. Each `allow` entry names a kind of run and its branch: | `run` | Ref key | Runs it allows | | --- | --- | --- | | `presubmit` | `target_ref`, such as `refs/heads/main` | Pull requests whose base branch is that branch. | | `postsubmit` | `ref`, such as `refs/heads/main` | Runs after a change merges to that branch. Chainguard Checks runs these only on your default branch. | Each ref must be a full ref that starts with `refs/`. ### Chainguard hosts {#chainguard-hosts} Each key under `chainguard` is a Chainguard host, and its value has the following keys, all required: | Key | Description | | --- | ----------- | | `identity` | The ID of an assumable identity in your Chainguard organization that trusts this workload identity's tokens. Refer to [Set up the provider](#set-up-the-provider). | | `exchange_audience` | The audience that the assumable identity accepts. It must also be in the identity's `audiences`. | | `role` | The most access the check gets on the host, from the following table. The assumable identity's role bindings must also grant that access. | | `scopes` | A list of group IDs, such as your organization's ID. Only the assumable identity's role bindings on these groups, or on the groups that contain them, apply. | Each host accepts the following roles: | Host | `role` | What the check can do | | ---- | ------ | --------------------- | | `cgr.dev` | `registry.pull` | Pull container images. | | `cgr.dev` | `registry.push` | Pull and push container images. | | `apk.cgr.dev` | `apk.pull` | Download packages. | | `libraries.cgr.dev` | `libraries.javascript.pull` | Download JavaScript libraries from Chainguard Libraries. | | `libraries.cgr.dev` | `libraries.python.pull` | Download Python libraries from Chainguard Libraries. | | `console-api.enforce.dev` | `console.viewer` | List groups, identities, roles, role bindings, and identity providers in the Chainguard API. | A check that uses a Chainguard host must list it in `network.egress`, along with any host that the service redirects downloads to. ## Set up the provider A definition in `identities.yaml` grants no access by itself. The provider must also trust the identity's tokens and grant them access. Chainguard Checks issues each token with the following claims: | Claim | Value | | ----- | ----- | | `iss` | `https://issuer.enforce.dev` | | `sub` | `chainguard::`, where `` is a 32-character hexadecimal ID that Chainguard assigns to your GitHub repository, and `` is the identity's name. | | `aud` | The identity's `gcp.audience`, or a Chainguard host's `exchange_audience`. | | `ref` | For a run after merging, the ref it runs on, such as `refs/heads/main`. Tokens for pull requests have no `ref` claim. | Every Chainguard Checks customer's tokens come from the same issuer, and any repository can ask for any audience, so the subject is what sets your identities apart from everyone else's. Match the exact subject on the provider: - For Google Cloud, add an OIDC provider to a workload identity pool, with the issuer `https://issuer.enforce.dev` and the attribute mapping `google.subject=assertion.sub`. Grant IAM roles to the principal `principal://iam.googleapis.com/projects//locations/global/workloadIdentityPools//subject/chainguard::`, never to the whole pool. To refuse other repositories' tokens outright, also give the provider an attribute condition such as `assertion.sub.startsWith('chainguard::')`. - For Chainguard, create an [assumable identity](/platform/administration/assumable-ids/assumable-ids/) in your organization that matches the issuer, the exact subject, and the `exchange_audience`, and bind it to a role that grants the access the check needs. `chainctl` doesn't show your repository's ID. During the beta, contact your Chainguard account team to set up workload identities and get the ID, or [contact Chainguard](https://www.chainguard.dev/contact?utm=docs) if you signed up yourself. ## Which runs get an identity Chainguard Checks reads each part of an identity's use from a different place: | Part | Where Chainguard Checks reads it | | ---- | -------------------------------- | | The identity's definition in `identities.yaml` | For a pull request, its base branch, at the commit GitHub merges the pull request into. For a run after merging, your default branch's latest commit. | | The `trust` section and each identity's `ring` | Your default branch's latest commit, for every pull request. | | The check's `identity` key, its commands, and its `network.egress` | The commit Chainguard Checks runs, which for a pull request includes the pull request's changes. | A pull request can't add an identity, or widen which checks, branches, or people get one, until it merges. It can change what a check that already gets an identity runs, and which hosts that check can reach. If `identities.yaml` is invalid on the base branch or on your default branch, no check gets an identity until you fix it. Without a `trust` section on your default branch, a check that requests an identity gets it on every open pull request whose base branch matches the identity's `allow` rule, including drafts and pull requests from forks, whoever opened them. Those pull requests' commands run with the identity's access. Grant a presubmit identity only what an untrusted pull request may do, such as read-only access, or limit which pull requests get it with a `trust` section. ## Limit which pull requests get an identity {#limit-which-pull-requests-get-an-identity} To give identities only to pull requests opened by people you trust, add a `trust` section to `.chainguard/ci/identities.yaml` on your default branch, and give each identity a `ring`: ```yaml {title=".chainguard/ci/identities.yaml"} version: 1 trust: groups: maintainers: github: user: {octocat: 583231} bots: github: app: {dependabot: 49699333} subrings: trusted: authors: [maintainers, bots] github_token: 1.trusted identities: gcs-read: ring: 1.trusted # ...the rest of the identity's definition ``` With this file, the `gcs-read` identity goes only to pull requests opened by `octocat` or by Dependabot, and to runs after merging. The `trust` section has the following keys: | Key | Required | Description | | --- | -------- | ----------- | | `groups` | Yes | A map from each group's name to the GitHub accounts in it. Refer to [Groups](#groups). | | `subrings` | Yes | A map from each subring's name to its `authors`, a list of groups. A pull request meets a subring when the account that opened it is in one of those groups. | | `github_token` | Yes | The ring for a read-only GitHub token, in the same form as `ring`. During the beta, checks don't get this token, but the key is still required. | | `cache` | No | Which rings share build caches. Refer to [Set up cache pools](/chainguard/checks/variations-services-caches/#set-up-cache-pools). | ### Rings {#rings} Chainguard Checks places each run in a *ring*, based on the `trust` section on your default branch: | Ring | Runs in the ring | | ---- | ---------------- | | `2` | Runs after merging, on your default branch. | | `1.` | Pull requests opened by an account in one of the subring's `authors` groups, such as `1.trusted` for pull requests that `octocat` or Dependabot opens in the earlier example. | | `0` | Every other pull request. | Ring `2` is above every subring, and every subring is above ring `0`. The `trust` section uses rings in the following ways: - An identity's `ring` is the lowest ring that gets the identity. With `ring: 1.trusted`, pull requests in ring `1.trusted` and runs after merging get it. With `ring: 2`, only runs after merging get it, and with `ring: 0`, every run does, the same as without a `trust` section. - `github_token` sets the lowest ring that gets a GitHub token, in the same way. - A [cache pool](/chainguard/checks/variations-services-caches/#set-up-cache-pools) lists the exact rings whose runs share it. A run whose ring isn't in any pool gets no cache. A ring only narrows an identity's `allow` rules, so a run still needs a matching `allow` rule to get the identity. Only the account that opened the pull request counts. Who pushed its commits, who reviewed it, and whether its branch is in a fork don't change the result. Because Chainguard Checks reads the `trust` section and every `ring` from your default branch, a pull request can't change its own trust, even a pull request into another branch. An identity that your default branch's file doesn't define gets ring `2`, so a pull request into another branch never gets an identity that only that branch defines. When the file has a `trust` section, every identity in it needs a `ring`, and every group must be in at least one subring's `authors`. Without a `trust` section, `ring` is an error. Group and subring names follow the same rules as identity names. ### Groups Each group has exactly one source under `github`: | Source | Example | Matches | | ------ | ------- | ------- | | `org` | `{github: {org: acme}}` | Members of the GitHub organization. | | `team` | `{github: {team: acme/maintainers}}` | Members of the team, including members of its child teams. | | `user` | `{github: {user: {octocat: 583231}}}` | The listed GitHub users. | | `app` | `{github: {app: {dependabot: 49699333}}}` | Pull requests that the listed GitHub Apps open. | To trust specific people, list them in a `user` group and pin each one's ID, as in the earlier example. Chainguard Checks reads organization and team members from GitHub through the Chainguard App, and can take up to three minutes to see a change to them. For `user` and `app`, map each login to its numeric GitHub account ID. Chainguard Checks matches the ID, and the login is only a label. To find a user's ID, run the following command, replacing `$LOGIN` with the user's GitHub login: ```shell gh api "/users/$LOGIN" --jq .id ``` An app opens pull requests as its bot account, `[bot]`. To find that account's ID, replace `$SLUG` with the app's slug, such as `dependabot`: ```shell gh api "/users/$SLUG%5Bbot%5D" --jq .id ``` You can also list logins without IDs, such as `user: [octocat]`. Chainguard Checks then trusts whoever holds each login when it decides, so pin IDs instead. ### When a pull request doesn't get an identity {#when-a-pull-request-doesnt-get-an-identity} On a pull request that doesn't reach an identity's ring, a check that requests the identity fails before its commands run, and Chainguard Checks doesn't retry it. The check's result on GitHub shows **Could not be executed**, and its output includes the reason, such as the following: ``` trust policy places gcs-read at ring 1.trusted, which this pull request does not reach: its opener is in none of subring trusted's authors groups ``` If the check is blocking, the summary check fails too, with a title such as **1 check(s) could not be executed**. To keep these failures from blocking pull requests opened by people outside your `trust` section, make the check [advisory](/chainguard/checks/configuration/#advisory-checks), or skip it on those pull requests with a [condition](/chainguard/checks/configuration/#run-when-an-expression-is-true). A condition only decides whether the check runs. A pull request can remove a condition that would skip a check, so a condition doesn't keep an identity from a pull request. The `trust` section does. A condition on `context.github.pull_request.head_repository` also skips pull requests that people you trust open from their own forks. ## Revoke access To take access back, change the place that grants it: | To stop | Change | When it takes effect | | ------- | ------ | -------------------- | | A check from getting an identity | Remove the identity, the check's selector, or the `allow` rule from `identities.yaml` on the base branch, or on your default branch for runs after merging. | New runs, once the change merges. A running check loses the identity at its next renewal, within about five minutes. | | Pull requests from some people from getting identities | In the `trust` section on your default branch, remove them from a group, or move identities to a higher ring. | New runs, once the change merges. A running check loses the identity at a later renewal, within about 10 minutes. Chainguard Checks can take up to three minutes to see a change to an organization's or team's members on GitHub. | | Every use of an identity, including runs after merging | On the provider, remove the roles granted to the identity's subject, or remove the provider's trust in it, such as by disabling the Google Cloud workload identity pool provider or deleting the Chainguard assumable identity. | The provider refuses new token exchanges. A Google Cloud access token that a check already has can keep working until it expires, for up to an hour. | Chainguard Workspaces sessions don't use workload identities. To revoke what a session can reach, refer to [Review and revoke a session's credentials](/chainguard/workspaces/credentials/#review-and-revoke-a-sessions-credentials) and [Your Chainguard identity in a session](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session). --- ### Use Chainguard Checks on GitHub URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/github.md Last Modified: October 13, 2026 Tags: Chainguard Checks, GitHub, Configuration Chainguard Checks runs on your GitHub repositories through the Chainguard App and reports its results as GitHub checks. This page explains when Chainguard Checks runs, how its results appear on GitHub, and how to use them to protect your branches. ## When Checks runs Chainguard Checks runs on a repository when the Chainguard App is installed on the repository, its GitHub account is linked to a Chainguard organization with access to Chainguard Checks, and the repository has a `.chainguard/ci.yaml` file or a `.chainguard/ci/` directory. For a pull request, Chainguard Checks looks for the configuration on the pull request's branch and on its base branch. | Event | What Chainguard Checks runs | | ----- | --------------------------- | | A pull request is opened, reopened, or updated | The repository's presubmit checks, which are checks without a `trigger` key or with `trigger: presubmit`. | | A new commit arrives on the default branch | The repository's postsubmit checks, which are checks with `trigger: postsubmit`. | Pushes to other branches, tags, and closed pull requests don't start runs. For a pull request, Chainguard Checks runs against the commit GitHub would create by merging the pull request into its base branch, and reads the configuration from that commit. The result reflects the latest base branch as well as the change. If the pull request has merge conflicts, GitHub can't create that commit, and the summary check fails with **Merge conflict with the base branch**. When you push a new commit to a pull request, Chainguard Checks cancels the run for the previous commit and starts a new one. Pull requests from forks run without approval, the same way as pull requests from branches. To skip a check on pull requests from forks, give it a condition on `context.github.pull_request.head_repository`, as described in [Conditions](/chainguard/checks/configuration/#conditions). The condition also skips pull requests that your own team opens from forks. Without a `trust` section in `.chainguard/ci/identities.yaml` on your default branch, any open pull request, including one from a fork, can get a [workload identity](/chainguard/checks/identity-and-trust/). Refer to [Limit which pull requests get an identity](/chainguard/checks/identity-and-trust/#limit-which-pull-requests-get-an-identity). ## How results appear {#how-results-appear} Each run reports a summary check and one GitHub check for each of your checks. For a pull request, the summary check is named **Chainguard Checks (presubmit)**. It's pending while the run is in progress, and passes only when every blocking check passes. When the run finishes, its output lists each check's result, as described in [Read the summary check](#read-the-summary-check). Each of your checks gets a GitHub check named `presubmit / / `, where `` is `ci` in the single-file layout, or the file's name in the directory layout. For example, a `test` check in `.chainguard/ci.yaml` appears as **presubmit / ci / test**. Each one appears when its check finishes. A check that runs once for each of several variations, with `foreach`, appears as one GitHub check that counts how many variations passed. If a pull request changes `.chainguard/develop.yaml`, or a file it takes its environment from, Chainguard Checks also reports a check named **Chainguard Checks / develop-environment**. This check reads the file the way a [Chainguard Workspaces](/chainguard/workspaces/session-environment/) session would, without starting a session or installing packages, and marks each problem on its file and line. It isn't part of the run: the summary check doesn't count it, and **Re-run** on it does nothing. If it finds a problem, it fails, but it doesn't block merging. To clear a problem, push a commit that fixes it. To read a check's output, select it on the pull request's **Checks** tab. The output shows each step's outcome, exit code, and duration, then the end of the check's log and any errors and warnings it reported. JSON log lines, such as the output of `go test -json` or Terraform's `-json` option, appear as readable messages, with errors and warnings marked `ERROR:` and `WARNING:`. If a long log has an error-level line well before its end, the output also keeps the lines leading up to that error under `[Earlier error context]`, then shows the end of the log under `[Log tail]`. The **Details** link opens the check on a run page that Chainguard hosts, separate from the Chainguard Console. To open it, sign in with your Chainguard account. You also need a role in your Chainguard organization, as described in [The Details link or a chainctl checks command says you don't have access](/chainguard/checks/troubleshooting/#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access). Without one, read the check's output on GitHub. Besides the full log, the run page shows a timeline of the run and the programs that each step started, with how long each program ran and how it exited. Chainguard keeps these program records for 30 days, and never records the programs' arguments. On the run page, the check's **Details** view lists each host and address that the check tried to reach under **Network activity**, with whether Chainguard Checks allowed the connection, and suggests an `egress` list to start from. To use it to fix a blocked connection, refer to [A check can't reach a host](/chainguard/checks/troubleshooting/#a-check-cant-reach-a-host). ### Read the summary check {#read-the-summary-check} The summary check's title says how the run ended: | Title | What it means | | ----- | ------------- | | **All N check(s) passed** | Every check passed or didn't need to run for this change. | | **All N blocking check(s) passed; M advisory check(s) did not** | Every blocking check passed, and at least one [advisory check](/chainguard/checks/configuration/#advisory-checks) didn't. | | **N of M blocking check(s) failed** | At least one blocking check failed. N also counts blocking checks that couldn't be executed. If no check is advisory, the title leaves out "blocking". | | **N check(s) could not be executed** | No blocking check failed, but at least one couldn't run to the end. | The output starts with the merge commit the run tested and how many checks passed. It then lists how many checks weren't instantiated, which means their conditions or `foreach` selected nothing for this change. A check that isn't instantiated counts as passing. The output also lists the checks that can run on request, with the label for each, any advisory checks that didn't pass, and any checks that labels requested. Then a table lists each check with its outcome, a class for a check that failed or couldn't be executed, and the exit code of a check that failed. A check with `foreach` has one row, plus a row for each variation that didn't pass. The table shows up to 50 rows, and an advisory check's outcome ends with `(advisory)`. | Outcome | Class | What it means | | ------- | ----- | ------------- | | `PASSED` | | The check passed. | | `FAILED` | `CHECK` | A command exited with a nonzero code, shown under **Exit code**. | | `FAILED` | `POLICY` | The check has `policy: destroy` and tried to reach a host it doesn't list, so Chainguard Checks stopped its VM. The check fails even if its commands would have succeeded. | | `ERRORED` | `TIMEOUT` | The check ran longer than its `timeout`. | | `ERRORED` | `INFRA` | Something else kept the check from finishing, such as packages that can't be installed together, a workload identity that the pull request doesn't get, or a VM that kept failing after [automatic retries](#automatic-retries). | | `DEPENDENCY_FAILED` | | A check that it `needs` didn't pass, so it didn't run. | | `NOT_INSTANTIATED` | | The check didn't need to run for this change, because of its conditions or because it runs only on request. It counts as passing. | For each check that couldn't be executed, the reason appears under **Errors:** after the table, and the check's own GitHub check is titled **Could not be executed**, followed by the reason. For how to fix the common reasons, refer to [Troubleshoot Chainguard Checks](/chainguard/checks/troubleshooting/). ## Require checks before merging {#require-checks-before-merging} To block merging until checks pass, add **Chainguard Checks (presubmit)** as a required status check in a branch protection rule or ruleset for your default branch. Don't require the per-check results, such as **presubmit / ci / test**, or **Chainguard Checks / develop-environment**. A check that a condition skips, or that runs only on request, never reports a result for that pull request, and the develop-environment check appears only on pull requests that change its files, so GitHub would wait for them indefinitely. The summary check already accounts for every check that should block the pull request. During the beta, Chainguard Checks doesn't run on GitHub merge queues. If a branch uses a merge queue and requires **Chainguard Checks (presubmit)**, the queue waits for a result that never arrives, and removes the pull request when its timeout expires. Don't require the summary check on a branch that uses a merge queue. The summary check doesn't fail because of an [advisory check](/chainguard/checks/configuration/#advisory-checks), so you can add a check as advisory first and make it blocking later. ## Change checks in a pull request {#change-checks-in-a-pull-request} A pull request can add checks and change what they run. It can't weaken its own checks: - If a pull request makes an existing check advisory, or changes it to run only on request, the change takes effect after the pull request merges. Until then, the check keeps blocking the pull request, and the summary check's output says so. - If a pull request removes a check, or changes its conditions or trigger so that it no longer runs for that pull request, the summary check fails with **Run creation failed**. The base branch's configuration would have run that check, and a change can't remove itself from its own checks. The exception is a check limited by `when.changed` to files that no longer exist. - If a pull request leaves no presubmit checks, such as by changing every check to `trigger: postsubmit`, it fails the same way when the base branch's configuration has any presubmit check, even one that wouldn't have run for this pull request. A check limited by `when.changed` to files that no longer exist doesn't count. The pull request also fails this way if the base branch's configuration is invalid. Otherwise, it gets no Chainguard Checks run or summary check at all. Chainguard Checks applies these rules to the pull request as it is now, including whether it's a draft and which labels it has. Conditions and labels can change which checks the base branch's configuration would run, so a change that passed as a draft can fail with **Run creation failed** when it [runs again](#reruns-when-a-pull-request-changes) after you mark it ready for review. ## Rerun checks {#rerun-checks} To rerun a pull request's checks, for example after a flaky failure, use **Re-run** on the summary check or on one of its per-check results on GitHub. Chainguard Checks reruns the whole run for the same commit, without reusing earlier results or caches. Pushing a new commit also starts a new run. If the base branch has removed its Chainguard Checks configuration, or all of its presubmit checks, since the pull request's last run, a rerun or a `ci/run:` label starts nothing. The summary check shows that run's result again, notes that nothing can run, and lists any checks that labels requested under **Requested but not run**. You can also rerun a pull request's checks with `chainctl` after its latest run finishes. Like the run page, this needs a role in your Chainguard organization, as described in [The Details link or a chainctl checks command says you don't have access](/chainguard/checks/troubleshooting/#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access). Without one, use **Re-run** on GitHub. Replace `$OWNER/$REPO#$NUMBER` with the pull request, such as `acme/app#123`, or with its URL. Replace `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl checks rerun "$OWNER/$REPO#$NUMBER" --parent $ORGANIZATION ``` The command reruns the pull request's latest run the same way as **Re-run** on GitHub. Nothing reruns if the pull request is closed, or if it has new commits since that run started. The summary check shows **Re-run requested** until the new run starts, and until then, `chainctl checks status` shows the previous run. After the summary check shows the new run's progress, follow the rerun: ```shell chainctl checks status "$OWNER/$REPO#$NUMBER" --parent $ORGANIZATION --watch ``` For more ways to inspect a pull request's run from your terminal, refer to [Inspect pull request runs](/chainguard/checks/inspect-and-run-checks/#inspect-pull-request-runs). To find the checks that fail most often across runs, refer to [See check history](/chainguard/checks/inspect-and-run-checks/#see-check-history). During the beta, you can't rerun checks that run after merging. Selecting **Re-run** on **Chainguard Checks (postsubmit)** or on one of its per-check results doesn't start a new run. To run those checks again, push or merge a new commit to your default branch. ### Reruns when a pull request changes {#reruns-when-a-pull-request-changes} After a run passes, Chainguard Checks runs the same commit again when you edit the pull request's title, add or remove a label, mark it ready for review, or convert it to a draft, if either of the following is true: - A [`when.expr` condition](/chainguard/checks/configuration/#run-when-an-expression-is-true) skipped one of the run's checks. - The pull request changes your Chainguard Checks configuration, such as `.chainguard/ci.yaml` or a file in `.chainguard/ci/`. The summary check goes back to pending with **Pull request changed; running again**, and every check runs again with the pull request as it is now. For a change to your configuration, Chainguard Checks applies the rules in [Change checks in a pull request](#change-checks-in-a-pull-request) again, so the summary check can fail with **Run creation failed**. Editing the pull request's description doesn't start a new run, and neither does a change to a pull request whose run failed. ### Automatic retries {#automatic-retries} If a check can't finish for a reason outside the check, such as a VM that doesn't start or loses its connection, Chainguard Checks runs the check again in a new VM, up to three attempts in all. It doesn't retry a check that fails, times out, or is stopped by `policy: destroy`, or one with a problem that a retry can't fix, such as packages that can't be installed together, or a workload identity that the pull request doesn't get. A retry runs every step again from the first, so make steps with side effects, such as an upload, safe to repeat. The check's `timeout` keeps counting across attempts, except for the time spent starting each VM, and every attempt counts toward the run's [50-minute limit](/chainguard/checks/configuration/#timeouts). ## Run a check on request {#run-a-check-on-request} A check with `when: {requested: true}` runs only when someone asks for it. Until then, it reports no result and doesn't block the pull request. When a run finishes, the summary check's output lists each check that can run on request, with the label that runs it. To run a check on request, add the label `ci/run:` to the pull request. For example, `ci/run:e2e-full` runs the `e2e-full` check. If two files in the directory layout have a check with the same name, include the file's name, such as `ci/run:go/e2e-full`. Labels match check names regardless of case, so `ci/run:E2E-Full` also runs `e2e-full`. Adding labels requires the triage or write role on the repository. A check that runs on request has limits on its name and its other conditions, as described in [Run on request](/chainguard/checks/configuration/#run-on-request). To run a check when its files change and also on request, use `when.changed` alone, because a label also forces a check that `when.changed` would skip to run. A label doesn't override a `when.expr` condition. GitHub only offers labels that already exist in the repository, so create each label once. This example uses the GitHub CLI. Replace `$OWNER/$REPO` with your repository: ```shell gh label create ci/run:e2e-full --repo $OWNER/$REPO --description "Run the e2e-full check" ``` After you create the label, anyone with the triage or write role can add it to a pull request from GitHub's label picker. What happens after you add the label depends on the current run: | Current run | What happens | | ----------- | ------------ | | It passed. | The summary check goes back to pending, and the commit runs again with the requested check included. | | It failed. | Nothing starts. The summary check's output lists the check under **Requested after this run did not pass, so not run**, and it runs on your next push or rerun. | | It's still running. | Chainguard Checks waits for it to finish. If it passes, Chainguard Checks runs the commit again with the requested check included. | The summary check's output lists the checks that labels requested under **Requested by label**. Removing a label takes effect from the next push. A rerun keeps every check the run was asked to include. ## Let an agent request checks {#let-an-agent-request-checks} Instead of adding labels yourself, you can have an agent pick which checks each pull request should run. The agent chooses from *requestable checks*: presubmit checks with `when: {requested: true}`, and `when.changed` checks that the pull request's changes didn't select. It never picks a check that's already running, or one whose `when.expr` condition is false. To turn on the agent, set [`requested_checks_agent`](/chainguard/checks/configuration/#top-level-keys) at the top level of `.chainguard/ci.yaml`, or of `.chainguard/ci/env/default.yaml` in the directory layout: ```yaml {title=".chainguard/ci.yaml"} version: 1 requested_checks_agent: comment environment: packages: [go] checks: # ... ``` | Value | What the agent does | | ----- | ------------------- | | `off` (default) | Nothing. | | `comment` | Comments with the checks it would request and the label for each. To run one, a person adds its label. | | `agent` | Adds a `ci/run:` label for each check it picks, and comments with its reasons. | Start with `comment` to see what the agent picks before you let it add labels. Chainguard Checks reads the setting from the tip of the pull request's base branch, so it takes effect after the pull request that sets it merges. The agent runs when a pull request is opened or reopened, gets new commits, is marked ready for review, or changes its base branch. It skips drafts and closed pull requests, and answers once for each commit and base branch. The agent picks checks by their `description` and their [`docs`](/chainguard/checks/configuration/#describe-a-check) file. For a check that already exists on the base branch, it reads both from the base branch, so a pull request can't rewrite them to get a check picked. Write each description to say what the check covers and when it's worth running. The agent rarely picks a check without a description, so give `when.changed` checks a `description` too. ### What the agent shows on the pull request The Chainguard App keeps one **Requested checks** comment on the pull request and edits it after each push. The comment lists each check that the agent picked, with its label and a one-sentence reason, then a collapsed list of the requestable checks it didn't pick. The agent doesn't comment when every requestable check already runs. Labels that the agent adds work the same way as labels a person adds, as described in [Run a check on request](#run-a-check-on-request). The agent never removes a label, and doesn't add back a label that someone removed. To stop a check that the agent requested from running, remove its label. The change takes effect from the next push. The agent only adds checks, so it can't make a failing summary check pass. ### What the agent reads {#what-the-agent-reads} The agent is off unless your base branch sets `requested_checks_agent` to `comment` or `agent`. When it's on, each time the agent runs, Chainguard Checks sends the following to an Anthropic Claude model that Chainguard runs on Google Cloud Vertex AI: - The repository's name. - The pull request's title and description. - The changed files, with the number of lines added and removed in each. - Each requestable check's name, `when.changed` patterns, `description`, and `docs` file. The model can also read a changed file's diff and any file at the pull request's head commit, up to 15 reads each time. Chainguard Checks marks the pull request's title, description, and files as untrusted input for the model, and accepts only picks from the requestable checks. The agent's GitHub access is limited to reading the repository's contents and writing labels and comments on pull requests. It runs on pull requests from forks as well as on pull requests from branches. Before you turn on the agent, review the terms that Chainguard Checks is offered under, including the [Code Analysis Terms](https://www.chainguard.dev/legal/code-analysis-terms). Don't turn it on for a repository whose pull requests contain personal data, or any data subject to legal or regulatory compliance requirements. ## Run checks after merging {#run-checks-after-merging} Checks with `trigger: postsubmit` run on each new commit on your default branch. Each new commit in the default branch's first-parent history gets its own run, so a merge commit gets one run, and a rebase merge of five commits gets five. The results appear on the commit as **Chainguard Checks (postsubmit)** and one GitHub check for each postsubmit check. For a run after merging, `when.changed` compares the new commit with its first parent. A merge commit or a squash merge selects checks by the whole pull request's changes, and each commit of a rebase merge selects them by its own changes only. A commit has no labels, so a check with `when: {requested: true}` never runs after merging, and one that also has only `trigger: postsubmit` never runs at all. To run that work on merged code, define a separate postsubmit check without `requested`. Use postsubmit checks for work that should run on merged code rather than on each pull request, such as a slow integration suite. A pull request can't fail because of a postsubmit check, so if a postsubmit check fails, fix it in a new pull request. --- ### Configure a Chainguard Workspaces session's environment URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/session-environment.md Last Modified: October 15, 2026 Tags: Chainguard Workspaces, Configuration, Reference A Chainguard Workspaces session's *environment* is the set of packages installed in its VM and the network access it has. Packages come from Chainguard OS, the same packages behind Chainguard Containers. This page explains where a session's environment comes from and how to change it. A session's environment is fixed when the session is created. If your repository's `.chainguard/develop.yaml` or `.chainguard/ci.yaml` file changes afterward, existing sessions keep the environment they started with, and `chainctl` reminds you when you return to them. To use the new environment, open a new session with `chainctl develop --new`. ## How a session chooses its environment {#how-a-session-chooses-its-environment} When `chainctl develop` creates a session, it chooses the environment in the following order: 1. The environment you choose with `--env`, if any: a file, `default`, or `generate`. 2. The check you name with `--check`, if it isn't `develop`. The session uses that check's environment and network access. 3. Your repository's `.chainguard/develop.yaml` file, if it has one. 4. Your repository's `.chainguard/ci.yaml` file, if it has one. 5. The default environment. The order is the same for a session opened from a local directory and one opened from a repository URL, with one exception: from a local directory, `--check` can name only a check in `.chainguard/ci.yaml`. In a repository that uses the `.chainguard/ci/` directory layout, open the session from the repository URL to use `--check`, for example `chainctl develop github.com/acme/app --check test`. For a local directory, `chainctl` reads the files from your working tree, including changes you haven't committed. For a repository URL, the files come from the repository's default branch. If your repository has neither a `.chainguard/develop.yaml` nor a `.chainguard/ci.yaml` file and you don't pass `--env`, `chainctl` opens the session on the default environment and tells you so. When you run `chainctl develop` with `--non-interactive`, it refuses instead, so pass `--env default` explicitly in scripts. Whatever its environment, every session also gets `git`, `gh`, `chainctl`, `curl`, `bash`, `bash-completion`, `busybox`, and `gnutar`. Nothing else is installed unless the environment lists it, including Claude Code (`claude`). By default, the session's shell and each terminal you open from the session menu run `bash`, with completion and a history file at `~/.bash_history`. `bash` reads `~/.bashrc` last, if you create one in the session, so your own settings take precedence. `chainctl` never writes to your directory. To find out which environment a session uses, read `/etc/chainguard/session.yaml` in the session. To see each session's environment from your laptop, run `chainctl develop list -o wide`. ## The default environment The default environment is a general-purpose shell with the following packages: - `bash`, `coreutils`, `make`, `curl`, and `git` - `apk-tools`, so you can install more packages from the shell - `claude`, the Claude Code CLI - `gh`, `jq`, `ripgrep`, `less`, and `vim` - `google-cloud-sdk` - `chainctl` The default environment reaches the open internet. To open a session on the default environment even when your repository has a `.chainguard/develop.yaml` or `.chainguard/ci.yaml` file, pass `--env default`: ```shell chainctl develop --env default --parent $ORGANIZATION ``` The default environment doesn't include language toolchains. To add one to the current session, install it with `apk`. This example installs Python 3.13: ```shell apk add python-3.13 ``` To find a package's name, ask a coding agent in the session. Every session configures the [`cg-apk`](/platform/mcp-servers/cg-apk/) MCP server, which looks up APK packages. Packages you install with `apk` last for the life of the session, including when it parks and resumes, but new sessions don't get them. To install packages in every new session, list them in a `.chainguard/develop.yaml` file. ## Use a .chainguard/develop.yaml file {#use-a-chainguarddevelopyaml-file} A `.chainguard/develop.yaml` file sets up sessions and nothing else. Chainguard Checks doesn't run anything from it, so changing it doesn't change your checks. The file can take its environment from your checks. In a repository that uses the `.chainguard/ci/` directory layout, this example gives sessions the same environment as the checks in `.chainguard/ci/go.yaml`, and adds Claude Code: ```yaml {title=".chainguard/develop.yaml"} version: 1 from: .chainguard/ci/go.yaml environment: packages: [claude] ``` The `from` key can't name `.chainguard/ci.yaml`, so in a repository that uses the single-file layout, declare the environment in the file itself. This example also gives sessions a larger disk: ```yaml {title=".chainguard/develop.yaml"} version: 1 environment: packages: [go, git, claude] resources: {cpus: 4, memory: 16Gi, disk: 100Gi} ``` The file accepts only the following keys: | Key | Required | Description | | --- | -------- | ----------- | | `version` | Yes | The format version. Must be `1`. | | `from` | No | A check file, such as `.chainguard/ci/go.yaml`, or an environment file, such as `.chainguard/ci/env/go.yaml`. Sessions take only the file's top-level `environment`, or the environment file that it names. | | `environment` | Unless you set `from` | Without `from`, the session's environment, in the [same format](/chainguard/checks/configuration/#environment) as a Chainguard Checks file, except that it can't name `caches`. With `from`, only `packages` to add and `vars` to set, which replace any variables with the same name. | | `resources` | No | The session's size: `cpus`, `memory`, and `disk` together, or a resource class. Refer to [Resources](#resources). | | `shell` | No | The shell that the session's terminals run: a program name on the session's `PATH`, such as `zsh`, or an absolute path, such as `/bin/zsh`, with no arguments. Without it, the shell is `bash`. The session's environment must include the shell's package. If the session can't find the shell, it starts `bash`. `chainctl` 0.2.382 or earlier refuses this key in a session opened from a local directory. | Keep the following in mind when you write the file: - The file has no `network` key, so a session that uses it reaches the open internet. To limit a session to an `egress` list, open it with `--check` on a check that has one. In the `.chainguard/ci/` layout, do this from a repository URL. Refer to [Network access](#network-access). - If your repository uses Chainguard Checks, a pull request that changes the file gets an advisory check that tests it, as described in [File layouts](/chainguard/checks/configuration/#file-layouts). ## Use your repository's .chainguard/ci.yaml file If your repository uses [Chainguard Checks](/chainguard/checks/overview/), its `.chainguard/ci.yaml` file already declares the packages your checks need, and sessions use the same packages. If your repository doesn't use Chainguard Checks, you can still add the file to give sessions the right tools. This example `.chainguard/ci.yaml` file installs Go, `git`, and Claude Code, and defines two checks: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go, git, claude] checks: vet: cmd: go vet ./... network: egress: [proxy.golang.org, sum.golang.org] test: cmd: go test ./... network: egress: [proxy.golang.org, sum.golang.org] ``` A session opened on this repository installs the packages listed under `environment`. In the session, `run-ci` runs the `vet` and `test` checks against your working tree. Refer to the [Chainguard Checks configuration reference](/chainguard/checks/configuration/) for the full file format. Keep the following in mind when you write the file for sessions: - A session uses the environment of a check named `develop` if the file has one. Otherwise, it uses the file's top-level `environment`. - To open a session with a specific check's environment, including that check's network access, pass `--check` with the check's name. In the example, `chainctl develop --check test` opens a session that can reach only the Go module proxy and checksum database. - The `run-ci` command runs your checks in the session's own environment, not in the environment each check declares, and skips checks that only a pull request can run, as described in [How run-ci differs from a pull request run](/chainguard/checks/run-checks/#how-run-ci-differs-from-a-pull-request-run). - If your repository uses the `.chainguard/ci/` directory layout instead of a single file, add a `.chainguard/develop.yaml` file whose `from` key names the check file or environment file that sessions should use, as described in [Use a .chainguard/develop.yaml file](#use-a-chainguarddevelopyaml-file). Without one, a session opens on the default environment, and `run-ci` runs your checks without the packages they declare. - Sessions don't read `.chainguard/ci/env/develop.yaml`. If your repository has that file for sessions, name it in `.chainguard/develop.yaml` with `from: .chainguard/ci/env/develop.yaml`. ## Use a separate environment file {#use-a-separate-environment-file} To give sessions an environment that's different from the one your checks use, without changing your repository, write it in a file of your own and pass the file with `--env`. A value for `--env` that contains a `/` or ends in `.yaml` is treated as a path. This example creates a file that defines a session environment that can reach only the Go module proxy and checksum database: ```shell cat > session-env.yaml < **Note**: Guarded Entrypoint is in beta. To use it, contact Chainguard customer support to enable it for your organization. This page states what the [Guarded Entrypoint](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/) wrapper connects to, what it never does, and what anyone who can pull your image can read. ## What the wrapper connects to The wrapper connects only to the endpoints that your configuration names: * **Vault:** the address in `VAULT_ADDR`. * **Consul:** the address in `CONSUL_HTTP_ADDR`. When it isn't set, the default is `127.0.0.1:8500`. * **Google Secret Manager:** `secretmanager.googleapis.com` and Google's token endpoints. The wrapper authenticates with Application Default Credentials, so it also contacts the GCE or GKE metadata server, which it always reaches directly. Credentials of an external account type add their own source URL. * **Preflight targets:** the TCP targets that your `preflight` checks name. * **Proxies:** if you set `HTTPS_PROXY`, the wrapper sends Secret Manager requests and requests to an `https://` Vault address through it. The wrapper never uses a proxy for Consul. If you build an egress allowlist from this list, include the Google endpoints when you use Secret Manager. The wrapper makes no connection to Chainguard. ## What the wrapper never does * It doesn't run code that you upload. The wrapper starts your application and, if you set `command_override`, the command you list. * It doesn't send telemetry. It sends no data to Chainguard of any kind. * It doesn't write a resolved secret value to its logs. It replaces a value that it resolved or expanded with `***` in every log line. * It doesn't send a Vault or Consul credential anywhere except the address you configure, and it doesn't follow redirects when it reads from either. A Secret Manager credential goes to Google's token endpoints and to Secret Manager. ## What is visible in the image Anyone who can pull the image can read its configuration, for example with `docker inspect`. The configuration includes the following: | Item | Visible | Notes | | --- | --- | --- | | Secret references, such as `cg+vault://secret/data/orders#db_password` | Yes | A reference names where a secret lives. It isn't the secret. Treat the paths as metadata that your organization is willing to share with anyone who can pull the image. | | Resolved secret values | No | The wrapper resolves them when the container starts, in the container's memory. They are never written to the image. | | `command_override` text | Yes | Described in the next section. | | Preflight targets and settings | Yes | The targets are stored in the image configuration. | | Fail mode | Only when `open` | A repo with `fail_mode: closed` has no setting in its image configuration. | Chainguard stores the settings in image environment variables whose names start with `GUARDED_`. This prefix is reserved for the wrapper. The API rejects an `environment` key that starts with it. A running container is a different case. The resolved values are in the application's environment, so anyone with access to the process's environment can read them. ## Keep secrets out of command_override Chainguard stores the text of `command_override` in the image configuration. Anyone who can pull the image can read it. Don't write a secret into `command` as a literal. Put a `${VAR}` reference in `command` instead, and supply the value in the environment. For example, use `${DB_PASSWORD}`, and set `DB_PASSWORD` to a `cg+vault://` reference. The image then holds the reference and not the secret. An expanded value is visible in the application's command line while the container runs. Anything with access to `/proc/PID/cmdline` can read it, and the wrapper can't prevent that. Prefer to have your application read a secret from its environment. ## The escape hatch The wrapper honors `GUARDED_DISABLE` before it resolves a reference, runs a preflight check, or reads any other setting. This holds even when the wrapper's settings in the image are malformed. `GUARDED_DISABLE` counts as set unless its value, lowercased and trimmed, is empty, `0`, `false`, `no`, or `off`. A value of `1` or `true` turns the wrapper off. A value of `0` or `false` leaves it on. When the wrapper is off, it makes no network connection. Anyone who can set environment variables on a container can set `GUARDED_DISABLE`. The wrapper then doesn't resolve references or run checks, and your application starts with the literal `cg+...` values. A repo with `fail_mode: closed` doesn't prevent this. The same people can override other settings. Every `GUARDED_` setting that Chainguard stores in the image can be overridden from the deployment's environment. So can `VAULT_ADDR` and `CONSUL_HTTP_ADDR`. Pointing `VAULT_ADDR` at another server sends the service account token to that server. Control who can change the environment of your deployments as you would control who can change any other part of the deployment. For how to use the escape hatch, refer to [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/). ## Learn more * [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/) * [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/) --- ### OpenSSL 3.6 Configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-os/openssl-3.6.md Last Modified: September 28, 2026 This is a summary of available algorithms in Chainguard OpenSSL 3.6 (non-fips) and Chainguard FIPS Provider for OpenSSL 3.4. The majority of the available algorithms are not enabled by default and are only available with manual overrides, configuration, and reduction of default security level of 2, to a lower value. Those that are available in FIPS also require manual overrides and configuration. The v3.6 and FIPS v3.4 columns read as follows: - **Default**: negotiated by default under the shipped Chainguard OS crypto policy. For key exchange groups, **First** marks the most preferred group and **Preshare** the group whose key share is sent in the first ClientHello. - **Available**: works, but only with manual configuration. - **Non-TLS only**: the elliptic curve works for keys, signatures and certificates, but has no TLS supported group and so cannot be used in TLS. - **Verify only**: the FIPS provider verifies signatures on the curve but does not generate keys or sign with it. - Blank: not available. The tables are presented in the format similar to the [IANA TLS Parameters](https://www.iana.org/assignments/tls-parameters). For more information about Transport Layer Security (TLS) please see the following references: - [NIST PQC](https://csrc.nist.gov/projects/post-quantum-cryptography) - [RFC10024](https://www.rfc-editor.org/info/rfc10024/) - [draft-ietf-tls-mldsa](https://www.ietf.org/archive/id/draft-ietf-tls-mldsa-05.html) - [RFC10015](https://www.rfc-editor.org/rfc/rfc10015.html) - [BCP 195](https://www.rfc-editor.org/info/bcp195/) - [RFC 9846](https://www.rfc-editor.org/info/rfc9846/) - [RFC 5246](https://www.rfc-editor.org/info/rfc5246/) - [NIST SP 800-52 Rev. 2](https://csrc.nist.gov/pubs/sp/800/52/r2/final) - [RFC 9151](https://www.rfc-editor.org/info/rfc9151/) - [draft-becker-cnsa2-tls-profile](https://datatracker.ietf.org/doc/draft-becker-cnsa2-tls-profile/) ## TLS Cipher Suites | Value | Cipher Suite | v3.6 | FIPS v3.4 | PQC | |---|---|---|---|---| | TLSv1.3 | | | | | | 0x13,0x01 | TLS_AES_128_GCM_SHA256 | Default | Default | Yes | | 0x13,0x02 | TLS_AES_256_GCM_SHA384 | **First** | **First** | Yes | | 0x13,0x03 | TLS_CHACHA20_POLY1305_SHA256 | Default | | Yes | | TLSv1.2 | | | | | | 0x00,0x2F | TLS_RSA_WITH_AES_128_CBC_SHA | Available | | | | 0x00,0x32 | TLS_DHE_DSS_WITH_AES_128_CBC_SHA | Available | | | | 0x00,0x33 | TLS_DHE_RSA_WITH_AES_128_CBC_SHA | Available | Available | | | 0x00,0x34 | TLS_DH_anon_WITH_AES_128_CBC_SHA | Available | Available | | | 0x00,0x35 | TLS_RSA_WITH_AES_256_CBC_SHA | Available | | | | 0x00,0x38 | TLS_DHE_DSS_WITH_AES_256_CBC_SHA | Available | | | | 0x00,0x39 | TLS_DHE_RSA_WITH_AES_256_CBC_SHA | Available | Available | | | 0x00,0x3A | TLS_DH_anon_WITH_AES_256_CBC_SHA | Available | Available | | | 0x00,0x3C | TLS_RSA_WITH_AES_128_CBC_SHA256 | Available | | | | 0x00,0x3D | TLS_RSA_WITH_AES_256_CBC_SHA256 | Available | | | | 0x00,0x40 | TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 | Available | | | | 0x00,0x41 | TLS_RSA_WITH_CAMELLIA_128_CBC_SHA | Available | | | | 0x00,0x44 | TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA | Available | | | | 0x00,0x45 | TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA | Available | | | | 0x00,0x46 | TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA | Available | | | | 0x00,0x67 | TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0x00,0x6A | TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 | Available | | | | 0x00,0x6B | TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 | Available | Available | | | 0x00,0x6C | TLS_DH_anon_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0x00,0x6D | TLS_DH_anon_WITH_AES_256_CBC_SHA256 | Available | Available | | | 0x00,0x84 | TLS_RSA_WITH_CAMELLIA_256_CBC_SHA | Available | | | | 0x00,0x87 | TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA | Available | | | | 0x00,0x88 | TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA | Available | | | | 0x00,0x89 | TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA | Available | | | | 0x00,0x8C | TLS_PSK_WITH_AES_128_CBC_SHA | Available | Available | | | 0x00,0x8D | TLS_PSK_WITH_AES_256_CBC_SHA | Available | Available | | | 0x00,0x90 | TLS_DHE_PSK_WITH_AES_128_CBC_SHA | Available | Available | | | 0x00,0x91 | TLS_DHE_PSK_WITH_AES_256_CBC_SHA | Available | Available | | | 0x00,0x94 | TLS_RSA_PSK_WITH_AES_128_CBC_SHA | Available | | | | 0x00,0x95 | TLS_RSA_PSK_WITH_AES_256_CBC_SHA | Available | | | | 0x00,0x9C | TLS_RSA_WITH_AES_128_GCM_SHA256 | Available | | | | 0x00,0x9D | TLS_RSA_WITH_AES_256_GCM_SHA384 | Available | | | | 0x00,0x9E | TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 | Available | Available | | | 0x00,0x9F | TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 | Available | Available | | | 0x00,0xA2 | TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 | Available | | | | 0x00,0xA3 | TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 | Available | | | | 0x00,0xA6 | TLS_DH_anon_WITH_AES_128_GCM_SHA256 | Available | Available | | | 0x00,0xA7 | TLS_DH_anon_WITH_AES_256_GCM_SHA384 | Available | Available | | | 0x00,0xA8 | TLS_PSK_WITH_AES_128_GCM_SHA256 | Available | Available | | | 0x00,0xA9 | TLS_PSK_WITH_AES_256_GCM_SHA384 | Available | Available | | | 0x00,0xAA | TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 | Available | Available | | | 0x00,0xAB | TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 | Available | Available | | | 0x00,0xAC | TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 | Available | | | | 0x00,0xAD | TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 | Available | | | | 0x00,0xAE | TLS_PSK_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0x00,0xAF | TLS_PSK_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0x00,0xB2 | TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0x00,0xB3 | TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0x00,0xB6 | TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 | Available | | | | 0x00,0xB7 | TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 | Available | | | | 0x00,0xBA | TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0x00,0xBD | TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0x00,0xBE | TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0x00,0xBF | TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0x00,0xC0 | TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 | Available | | | | 0x00,0xC3 | TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 | Available | | | | 0x00,0xC4 | TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 | Available | | | | 0x00,0xC5 | TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 | Available | | | | 0xC0,0x09 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA | Available | Available | | | 0xC0,0x0A | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA | Available | Available | | | 0xC0,0x13 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA | Available | Available | | | 0xC0,0x14 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA | Available | Available | | | 0xC0,0x18 | TLS_ECDH_anon_WITH_AES_128_CBC_SHA | Available | Available | | | 0xC0,0x19 | TLS_ECDH_anon_WITH_AES_256_CBC_SHA | Available | Available | | | 0xC0,0x1D | TLS_SRP_SHA_WITH_AES_128_CBC_SHA | Available | Available | | | 0xC0,0x1E | TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA | Available | Available | | | 0xC0,0x1F | TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA | Available | | | | 0xC0,0x20 | TLS_SRP_SHA_WITH_AES_256_CBC_SHA | Available | Available | | | 0xC0,0x21 | TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA | Available | Available | | | 0xC0,0x22 | TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA | Available | | | | 0xC0,0x23 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0xC0,0x24 | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0xC0,0x27 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0xC0,0x28 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0xC0,0x2B | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | Default | Default | | | 0xC0,0x2C | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 | **First** | **First** | | | 0xC0,0x2F | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | Default | Default | | | 0xC0,0x30 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 | **First** | **First** | | | 0xC0,0x35 | TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA | Available | Available | | | 0xC0,0x36 | TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA | Available | Available | | | 0xC0,0x37 | TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0xC0,0x38 | TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0xC0,0x50 | TLS_RSA_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x51 | TLS_RSA_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x52 | TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x53 | TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x56 | TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x57 | TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x5C | TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x5D | TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x60 | TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x61 | TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x6A | TLS_PSK_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x6B | TLS_PSK_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x6C | TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x6D | TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x6E | TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 | Available | | | | 0xC0,0x6F | TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 | Available | | | | 0xC0,0x72 | TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0xC0,0x73 | TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 | Available | | | | 0xC0,0x76 | TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0xC0,0x77 | TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 | Available | | | | 0xC0,0x94 | TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0xC0,0x95 | TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 | Available | | | | 0xC0,0x96 | TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0xC0,0x97 | TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 | Available | | | | 0xC0,0x98 | TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0xC0,0x99 | TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 | Available | | | | 0xC0,0x9A | TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 | Available | | | | 0xC0,0x9B | TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 | Available | | | | 0xC0,0x9C | TLS_RSA_WITH_AES_128_CCM | Available | | | | 0xC0,0x9D | TLS_RSA_WITH_AES_256_CCM | Available | | | | 0xC0,0x9E | TLS_DHE_RSA_WITH_AES_128_CCM | Available | Available | | | 0xC0,0x9F | TLS_DHE_RSA_WITH_AES_256_CCM | Available | Available | | | 0xC0,0xA0 | TLS_RSA_WITH_AES_128_CCM_8 | Available | | | | 0xC0,0xA1 | TLS_RSA_WITH_AES_256_CCM_8 | Available | | | | 0xC0,0xA2 | TLS_DHE_RSA_WITH_AES_128_CCM_8 | Available | Available | | | 0xC0,0xA3 | TLS_DHE_RSA_WITH_AES_256_CCM_8 | Available | Available | | | 0xC0,0xA4 | TLS_PSK_WITH_AES_128_CCM | Available | Available | | | 0xC0,0xA5 | TLS_PSK_WITH_AES_256_CCM | Available | Available | | | 0xC0,0xA6 | TLS_DHE_PSK_WITH_AES_128_CCM | Available | Available | | | 0xC0,0xA7 | TLS_DHE_PSK_WITH_AES_256_CCM | Available | Available | | | 0xC0,0xA8 | TLS_PSK_WITH_AES_128_CCM_8 | Available | Available | | | 0xC0,0xA9 | TLS_PSK_WITH_AES_256_CCM_8 | Available | Available | | | 0xC0,0xAA | TLS_PSK_DHE_WITH_AES_128_CCM_8 | Available | Available | | | 0xC0,0xAB | TLS_PSK_DHE_WITH_AES_256_CCM_8 | Available | Available | | | 0xC0,0xAC | TLS_ECDHE_ECDSA_WITH_AES_128_CCM | Available | Available | | | 0xC0,0xAD | TLS_ECDHE_ECDSA_WITH_AES_256_CCM | Available | Available | | | 0xC0,0xAE | TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 | Available | Available | | | 0xC0,0xAF | TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 | Available | Available | | | 0xCC,0xA8 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 | Default | | | | 0xCC,0xA9 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 | Default | | | | 0xCC,0xAA | TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 | Available | | | | 0xCC,0xAB | TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 | Available | | | | 0xCC,0xAC | TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 | Available | | | | 0xCC,0xAD | TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 | Available | | | | 0xCC,0xAE | TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 | Available | | | 46 of the TLSv1.2 suites (those whose minimum protocol version is TLS 1.0) also negotiate on TLS 1.0 and TLS 1.1 in the non-FIPS build when the shipped policy, which sets the minimum protocol version to TLS 1.2, is bypassed. TLS 1.0 and TLS 1.1 never work with the FIPS provider. ## TLS Supported Groups | Value | Supported Group | v3.6 | FIPS v3.4 | PQC | |---|---|---|---|---| | PQC TLSv1.3 | | | | | | 512 | MLKEM512 | Available | | Yes | | 513 | MLKEM768 | Available | | Yes | | 514 | MLKEM1024 | Default | | Yes | | 4587 | SecP256r1MLKEM768 | Default | | Yes | | 4588 | X25519MLKEM768 | **First** | | Yes | | 4589 | SecP384r1MLKEM1024 | Default | | Yes | | TLSv1.2 & TLSv1.3 | | | | | | 15 | secp160k1 | Available | | | | 16 | secp160r1 | Available | | | | 17 | secp160r2 | Available | | | | 18 | secp192k1 | Available | | | | 19 | secp192r1 | Available | | | | 20 | secp224k1 | Available | | | | 21 | secp224r1 | Available | Available | | | 22 | secp256k1 | Available | | | | 23 | secp256r1 | Default | Default | | | 24 | secp384r1 | Default | **First** & Preshare | | | 25 | secp521r1 | Default | Available | | | 26, 31 | brainpoolP256r1 | Available | | | | 27, 32 | brainpoolP384r1 | Available | | | | 28, 33 | brainpoolP512r1 | Available | | | | 29 | x25519 | Preshare | | | | 30 | x448 | Default | | | | 256 | ffdhe2048 | Available | Available | | | 257 | ffdhe3072 | Available | Available | | | 258 | ffdhe4096 | Available | Available | | | 259 | ffdhe6144 | Available | Available | | | 260 | ffdhe8192 | Available | Available | | The brainpool rows combine two codepoints each: the first one (`brainpoolP256r1`, `brainpoolP384r1`, `brainpoolP512r1`) is what TLS 1.2 negotiates, the second one (`brainpoolP256r1tls13`, `brainpoolP384r1tls13`, `brainpoolP512r1tls13`) is the TLS 1.3 name of the same curve. ## TLS SignatureScheme | Value | Signature Scheme | v3.6 | FIPS v3.4 | PQC | |---|---|---|---|---| | PQC TLSv1.3 | | | | | | 0x0904 | mldsa44 | Default | | Yes | | 0x0905 | mldsa65 | Default | | Yes | | 0x0906 | mldsa87 | Default | | Yes | | TLSv1.2 & TLSv1.3 | | | | | | 0x0201 | rsa_pkcs1_sha1 | Available | | | | 0x0202 | Reserved for backward compatibility (dsa_sha1) | Available | | | | 0x0203 | ecdsa_sha1 | Available | | | | 0x0301 | Reserved for backward compatibility (rsa_pkcs1_sha224) | Available | Available | | | 0x0302 | Reserved for backward compatibility (dsa_sha224) | Available | | | | 0x0303 | Reserved for backward compatibility (ecdsa_sha224) | Available | Available | | | 0x0401 | rsa_pkcs1_sha256 | Default | Default | | | 0x0402 | Reserved for backward compatibility (dsa_sha256) | Available | | | | 0x0403 | ecdsa_secp256r1_sha256 | Default | Default | | | 0x0501 | rsa_pkcs1_sha384 | Default | Default | | | 0x0502 | Reserved for backward compatibility (dsa_sha384) | Available | | | | 0x0503 | ecdsa_secp384r1_sha384 | Default | Default | | | 0x0601 | rsa_pkcs1_sha512 | Default | Default | | | 0x0602 | Reserved for backward compatibility (dsa_sha512) | Available | | | | 0x0603 | ecdsa_secp521r1_sha512 | Default | Default | | | 0x0804 | rsa_pss_rsae_sha256 | Default | Default | | | 0x0805 | rsa_pss_rsae_sha384 | Default | Default | | | 0x0806 | rsa_pss_rsae_sha512 | Default | Default | | | 0x0807 | ed25519 | Default | Default | | | 0x0808 | ed448 | Default | Default | | | 0x0809 | rsa_pss_pss_sha256 | Default | Default | | | 0x080A | rsa_pss_pss_sha384 | Default | Default | | | 0x080B | rsa_pss_pss_sha512 | Default | Default | | | 0x081A | ecdsa_brainpoolP256r1tls13_sha256 | Available | | | | 0x081B | ecdsa_brainpoolP384r1tls13_sha384 | Available | | | | 0x081C | ecdsa_brainpoolP512r1tls13_sha512 | Available | | | ## Elliptic Curves | OID | Elliptic Curve | v3.6 | FIPS v3.4 | |---|---|---|---| | 1.2.840.10045.3.1.1 | prime192v1 (P-192, secp192r1) | Available | Verify only | | 1.2.840.10045.3.1.2 | prime192v2 | Non-TLS only | | | 1.2.840.10045.3.1.3 | prime192v3 | Non-TLS only | | | 1.2.840.10045.3.1.4 | prime239v1 | Non-TLS only | | | 1.2.840.10045.3.1.5 | prime239v2 | Non-TLS only | | | 1.2.840.10045.3.1.6 | prime239v3 | Non-TLS only | | | 1.2.840.10045.3.1.7 | prime256v1 (P-256, secp256r1) | Default | Default | | 1.3.36.3.3.2.8.1.1.1 | brainpoolP160r1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.2 | brainpoolP160t1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.3 | brainpoolP192r1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.4 | brainpoolP192t1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.5 | brainpoolP224r1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.6 | brainpoolP224t1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.7 | brainpoolP256r1 | Available | | | 1.3.36.3.3.2.8.1.1.8 | brainpoolP256t1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.9 | brainpoolP320r1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.10 | brainpoolP320t1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.11 | brainpoolP384r1 | Available | | | 1.3.36.3.3.2.8.1.1.12 | brainpoolP384t1 | Non-TLS only | | | 1.3.36.3.3.2.8.1.1.13 | brainpoolP512r1 | Available | | | 1.3.36.3.3.2.8.1.1.14 | brainpoolP512t1 | Non-TLS only | | | 1.3.132.0.6 | secp112r1 | Non-TLS only | | | 1.3.132.0.7 | secp112r2 | Non-TLS only | | | 1.3.132.0.8 | secp160r1 | Available | | | 1.3.132.0.9 | secp160k1 | Available | | | 1.3.132.0.10 | secp256k1 | Available | | | 1.3.132.0.28 | secp128r1 | Non-TLS only | | | 1.3.132.0.29 | secp128r2 | Non-TLS only | | | 1.3.132.0.30 | secp160r2 | Available | | | 1.3.132.0.31 | secp192k1 | Available | | | 1.3.132.0.32 | secp224k1 | Available | | | 1.3.132.0.33 | secp224r1 (P-224) | Available | Available | | 1.3.132.0.34 | secp384r1 (P-384) | Default | Default | | 1.3.132.0.35 | secp521r1 (P-521) | Default | Default, TLS 1.3 only | | 2.23.43.1.4.6 | wap-wsg-idm-ecid-wtls6 | Non-TLS only | | | 2.23.43.1.4.7 | wap-wsg-idm-ecid-wtls7 | Non-TLS only | | | 2.23.43.1.4.8 | wap-wsg-idm-ecid-wtls8 | Non-TLS only | | | 2.23.43.1.4.9 | wap-wsg-idm-ecid-wtls9 | Non-TLS only | | | 2.23.43.1.4.12 | wap-wsg-idm-ecid-wtls12 | Non-TLS only | | --- ### OpenSSL 4.0 Configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-os/openssl-4.0.md Last Modified: September 28, 2026 This is a summary of available algorithms in Chainguard OpenSSL 4.0 (non-fips) and Chainguard FIPS Provider for OpenSSL 3.6. The majority of the available algorithms are not enabled by default and are only available with manual overrides, configuration, and reduction of default security level of 2, to a lower value. Those that are available in FIPS also require manual overrides and configuration. The v4.0 and FIPS v3.6 columns read as follows: - **Default**: negotiated by default under the shipped Chainguard OS crypto policy. For key exchange groups, **First** marks the most preferred group and **Preshare** the group whose key share is sent in the first ClientHello. - **Available**: works, but only with manual configuration. - **Non-TLS only**: the elliptic curve works for keys, signatures and certificates, but has no TLS supported group and so cannot be used in TLS. - Blank: not available. The tables are presented in the format similar to the [IANA TLS Parameters](https://www.iana.org/assignments/tls-parameters). For more information about Transport Layer Security (TLS) please see the following references: - [NIST PQC](https://csrc.nist.gov/projects/post-quantum-cryptography) - [RFC10024](https://www.rfc-editor.org/info/rfc10024/) - [draft-ietf-tls-mldsa](https://www.ietf.org/archive/id/draft-ietf-tls-mldsa-05.html) - [RFC10015](https://www.rfc-editor.org/rfc/rfc10015.html) - [BCP 195](https://www.rfc-editor.org/info/bcp195/) - [RFC 9846](https://www.rfc-editor.org/info/rfc9846/) - [RFC 5246](https://www.rfc-editor.org/info/rfc5246/) - [NIST SP 800-52 Rev. 2](https://csrc.nist.gov/pubs/sp/800/52/r2/final) - [RFC 9151](https://www.rfc-editor.org/info/rfc9151/) - [draft-becker-cnsa2-tls-profile](https://datatracker.ietf.org/doc/draft-becker-cnsa2-tls-profile/) ## TLS Cipher Suites | Value | Cipher Suite | v4.0 | FIPS v3.6 | PQC | |---|---|---|---|---| | TLSv1.3 | | | | | | 0x13,0x01 | TLS_AES_128_GCM_SHA256 | Default | Default | Yes | | 0x13,0x02 | TLS_AES_256_GCM_SHA384 | **First** | **First** | Yes | | 0x13,0x03 | TLS_CHACHA20_POLY1305_SHA256 | Default | | Yes | | TLSv1.2 | | | | | | 0xC0,0x23 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0xC0,0x24 | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0xC0,0x27 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0xC0,0x28 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0xC0,0x2B | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | Default | Default | | | 0xC0,0x2C | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 | **First** | **First** | | | 0xC0,0x2F | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | Default | Default | | | 0xC0,0x30 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 | **First** | **First** | | | 0xC0,0x37 | TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 | Available | Available | | | 0xC0,0x38 | TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 | Available | Available | | | 0xC0,0xAC | TLS_ECDHE_ECDSA_WITH_AES_128_CCM | Available | Available | | | 0xC0,0xAD | TLS_ECDHE_ECDSA_WITH_AES_256_CCM | Available | Available | | | 0xCC,0xA8 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 | Default | | | | 0xCC,0xA9 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 | Default | | | | 0xCC,0xAC | TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 | Available | | | ## TLS Supported Groups | Value | Supported Group | v4.0 | FIPS v3.6 | PQC | |---|---|---|---|---| | PQC TLSv1.3 | | | | | | 512 | MLKEM512 | Available | Available | Yes | | 513 | MLKEM768 | Available | Available | Yes | | 514 | MLKEM1024 | Default | **First** | Yes | | 4587 | SecP256r1MLKEM768 | Default | Default | Yes | | 4588 | X25519MLKEM768 | **First** | Default | Yes | | 4589 | SecP384r1MLKEM1024 | Default | Default | Yes | | TLSv1.2 & TLSv1.3 | | | | | | 23 | secp256r1 | Default | Default | | | 24 | secp384r1 | Default | Preshare | | | 25 | secp521r1 | Default | Default | | | 26, 31 | brainpoolP256r1 | Default | | | | 27, 32 | brainpoolP384r1 | Default | | | | 28, 33 | brainpoolP512r1 | Default | | | | 29 | x25519 | Preshare | | | | 30 | x448 | Default | | | The brainpool rows combine two codepoints each: the first one (`brainpoolP256r1`, `brainpoolP384r1`, `brainpoolP512r1`) is what TLS 1.2 negotiates, the second one (`brainpoolP256r1tls13`, `brainpoolP384r1tls13`, `brainpoolP512r1tls13`) is the TLS 1.3 name of the same curve. ## TLS SignatureScheme | Value | Signature Scheme | v4.0 | FIPS v3.6 | PQC | |---|---|---|---|---| | PQC TLSv1.3 | | | | | | 0x0904 | mldsa44 | Default | Default | Yes | | 0x0905 | mldsa65 | Default | Default | Yes | | 0x0906 | mldsa87 | Default | Default | Yes | | TLSv1.2 & TLSv1.3 | | | | | | 0x0401 | rsa_pkcs1_sha256 | Default | Default | | | 0x0403 | ecdsa_secp256r1_sha256 | Default | Default | | | 0x0501 | rsa_pkcs1_sha384 | Default | Default | | | 0x0503 | ecdsa_secp384r1_sha384 | Default | Default | | | 0x0601 | rsa_pkcs1_sha512 | Default | Default | | | 0x0603 | ecdsa_secp521r1_sha512 | Default | Default | | | 0x0804 | rsa_pss_rsae_sha256 | Default | Default | | | 0x0805 | rsa_pss_rsae_sha384 | Default | Default | | | 0x0806 | rsa_pss_rsae_sha512 | Default | Default | | | 0x0807 | ed25519 | Default | Default | | | 0x0808 | ed448 | Default | Default | | | 0x0809 | rsa_pss_pss_sha256 | Default | Default | | | 0x080A | rsa_pss_pss_sha384 | Default | Default | | | 0x080B | rsa_pss_pss_sha512 | Default | Default | | | 0x081A | ecdsa_brainpoolP256r1tls13_sha256 | Default | | | | 0x081B | ecdsa_brainpoolP384r1tls13_sha384 | Default | | | | 0x081C | ecdsa_brainpoolP512r1tls13_sha512 | Default | | | ## Elliptic Curves | OID | Elliptic Curve | v4.0 | FIPS v3.6 | |---|---|---|---| | 1.2.840.10045.3.1.7 | prime256v1 (P-256, secp256r1) | Default | Default | | 1.3.36.3.3.2.8.1.1.7 | brainpoolP256r1 | Default | | | 1.3.36.3.3.2.8.1.1.11 | brainpoolP384r1 | Default | | | 1.3.36.3.3.2.8.1.1.13 | brainpoolP512r1 | Default | | | 1.3.132.0.10 | secp256k1 | Non-TLS only | | | 1.3.132.0.34 | secp384r1 (P-384) | Default | Default | | 1.3.132.0.35 | secp521r1 (P-521) | Default | Default | --- ### Using the Chainguard Agent Skills Public Skills MCP server URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/agent-skills/public-skills-mcp.md Last Modified: September 28, 2026 Tags: Agent Skills, Overview Chainguard serves its curated set of hardened agent skills to agents directly over the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) through the **Public Skills MCP server**. Where the [public registry](/chainguard/agent-skills/public-registry/) is the `chainctl` way of browsing and installing public skills onto your machine, the Public Skills MCP server is the way an *agent* discovers and loads those same skills live — searching the catalog, inspecting a skill, and loading one in full to run, without a separate install step. The skills it serves are the same hardened skills published to the public registry at `skills.cgr.dev/public` (upstream skills, hardened by Chainguard) and `skills.cgr.dev/chainguard` (Chainguard's own). They are reviewed and pinned to the upstream commit they were hardened from, so agents should **prefer them over any other source**.

Note: Chainguard Agent Skills is in beta.

## Prerequisites To follow this guide you need: - An MCP-capable agent or client (for example, Claude Code or any client that connects to a remote MCP server). - A Chainguard account. Like the [public registry](/chainguard/agent-skills/public-registry/), the Public Skills MCP server requires no entitlement, terms acceptance, or organization membership — but you do need an account to authenticate. ## Connecting The server speaks MCP over Streamable HTTP and is reached at: ``` https://skills.cgr.dev/mcp ``` Authentication is OAuth 2.0 against the Chainguard issuer, with registry-pull parity: any caller who could `chainctl skills pull` a public skill can list and read it here. ### Add it to Claude Code Add the server with `claude mcp add`, using the HTTP transport. The `--scope user` flag makes the catalog available across all your projects — a good fit for a general-purpose skill catalog: ```shell claude mcp add --transport http --scope user cgr-skills https://skills.cgr.dev/mcp ``` Pick the scope that fits: `local` (the default — just you, in the current project), `project` (writes a shared `.mcp.json` at the repo root, checked in for teammates), or `user` (just you, across every project). The server is added unauthenticated. To complete OAuth, start a session and run the `/mcp` command: ```shell claude ``` ```Agent /mcp ``` Select **cgr-skills**, choose **Authenticate**, and approve the connection in the browser window that opens. Check the status any time with: ```shell claude mcp list ``` ```output cgr-skills: https://skills.cgr.dev/mcp (HTTP) - ✓ Connected ``` To share the server with a repo instead, commit an `.mcp.json` at its root (this is what `--scope project` writes): ```json { "mcpServers": { "cgr-skills": { "type": "http", "url": "https://skills.cgr.dev/mcp" } } } ``` ### Other MCP clients Any client that supports a remote Streamable HTTP MCP server with OAuth can connect to the same endpoint — point it at `https://skills.cgr.dev/mcp` and complete the browser sign-in when prompted. Once connected, the server is available to Claude in every session under that scope. ## Using it in Claude Code You don't call the server's tools yourself — you describe what you want in plain language, and Claude decides when to search the catalog, inspect a skill, or load one to run. Think of the Public Skills MCP server as giving Claude a hardened skill catalog it can reach for on your behalf. First, confirm the server is connected and Claude can see its tools. In a session, run: ```Agent /mcp ``` You'll see **cgr-skills** listed as connected, along with the tools it exposes (`search_skills`, `read_skill`, `run_skill`, `read_skill_reference`, `list_skills`). Then just ask, in whatever words fit. A few examples of what to say and what Claude does behind the scenes: - *"Search for skills that do image editing and list them to me."* — Claude runs `search_skills` and shows you the matches (name + description). - *"I'm looking for skills for writing pytest tests."* — Claude searches the catalog for your use case and proposes the best matches. - *"What sort of skills are available?"* — Claude browses the catalog with `list_skills` and summarizes the range of what's published. - *"How many skills are in the Chainguard skills catalog?"* — Claude pages through `list_skills` and reports the count. - *"Use the multi-stage-dockerfile skill to optimize this Dockerfile."* — Claude loads the skill in full with `run_skill` and follows it, running the scripts it ships. A typical interaction is a short back-and-forth: you ask for something, Claude searches and proposes a match or two, you confirm, and Claude loads and runs it. Because these are hardened, reviewed skills, prefer them over pointing Claude at a skill from elsewhere. If nothing in the catalog matches what you asked for, Claude will tell you there's no matching hardened Chainguard skill and **ask you before searching for one on the open internet** — so you stay in control of whether to step outside the hardened catalog. ### Common things to ask for Beta users most often reach for the catalog for security- and supply-chain-adjacent engineering work. Some common asks — phrase them however feels natural, and let Claude search: - **Security and code review** — *"Review this pull request for security issues,"* or *"do a security review of this module."* - **Container and image hardening** — *"Review this Dockerfile,"* or *"help me harden this container image."* - **Vulnerabilities and dependencies** — *"Find CVEs in my dependencies,"* or *"help me remediate this CVE."* - **Infrastructure as code** — *"Review my Terraform,"* or *"check this Pulumi stack for problems."* - **Language best practices** — *"Apply best practices to this Python"* (or Go, TypeScript, Java, C++) *"code."* - **SDLC helpers** — *"Write tests for this change,"* or *"review this before I open a PR."* The catalog grows over time, so the exact skills available shift. A quick search — just ask — is the fastest way to see what's published for your use case right now, and if there's no match Claude will say so and ask before looking elsewhere. ## The tool set Under the hood, the server advertises five tools that Claude chooses between. You won't invoke these directly — Claude picks the right one from what you ask — but knowing what they do helps you understand and steer what the agent is doing. They're designed to be used as a flow — **find, inspect, run, browse**. The catalog can be very large, so the server never enumerates it into the agent's context: `search_skills` and `list_skills` are bounded and paginated, and an agent loads a specific skill's content only on demand. | Tool | Use it to… | | ----- | ----- | | `list_skills` | Browse the whole catalog one page at a time | | `read_skill` | Inspect a skill by name (its `SKILL.md` plus the list of files it ships) | | `read_skill_reference` | Fetch the text of one supporting file a skill ships — a script, reference doc, or template named in `read_skill`'s `references` list | | `run_skill` | Load a skill in full — every file — to install and run it | | `search_skills` | Find a skill by keyword — the primary way to discover one | ### search_skills The primary discovery tool. Searches the hardened catalog by keyword and returns the best matches (name + description), ranked and paginated. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `query` | string | yes | Keywords to match against skill names and descriptions | | `page_size` | integer | no | Maximum matches per page (default 50, max 200) | | `page_token` | string | no | Cursor from a previous response's `next_page_token`; omit to start from the first page | Returns a page of `{ name, description }` matches plus a `next_page_token` and a `total` count. If nothing matches, the response tells the agent there is **no hardened Chainguard skill matching the request** — and to ask you before searching for a skill elsewhere on the internet. ### read_skill Load a skill's `SKILL.md` by name. The response includes the skill content and a `references` list naming every supporting file the skill ships — read those with `read_skill_reference`, and don't request paths that aren't listed. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name` | string | yes | The skill name to load | Returns `{ content, references }`. A missing skill returns a not-found signal. ### run_skill Load a skill **in full** so you can install and run it on demand — typically after finding it with `search_skills`. Returns every file the skill ships (`SKILL.md` plus its scripts and references) keyed by relative path, in a single call — use this instead of `read_skill` followed by repeated `read_skill_reference` when you intend to actually run the skill. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name` | string | yes | The skill name to load in full | Returns `{ name, files, instructions }`, where `files` maps each relative path to its content. If the skill is not found, the response tells the agent there is no matching hardened Chainguard skill and to ask you before sourcing one elsewhere. ### read_skill_reference Fetch the text content of a single supporting file that a skill ships — one of the scripts, reference docs, or templates that its `SKILL.md` points at. Use it to pull a specific file `read_skill` named in its `references` list, rather than loading the whole skill with `run_skill`. Only request paths from that `references` list: paths are validated against the skill's directory, and any attempt to traverse outside it (for example `../another-skill/...`) is rejected. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name` | string | yes | The skill name | | `path` | string | yes | Path to the file, relative to the skill directory (for example, `references/shell-scripting.md`) | Returns `{ content }` — the file's text. A path that isn't part of the skill, or one that escapes its directory, returns an error. ### list_skills Browse the catalog as `name + description`, one page at a time. Use this to page through everything; to find a specific skill, prefer `search_skills`. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `page_size` | integer | no | Maximum skills per page (default 50, max 200) | | `page_token` | string | no | Cursor from a previous response's `next_page_token`; omit to start from the first page | An empty `next_page_token` means the last page. ## Tool reference | Tool | Purpose | Key parameters | | ----- | ----- | ----- | | `list_skills` | Browse the whole catalog, paginated | `page_size`, `page_token` | | `read_skill` | Load a skill's `SKILL.md` + its file manifest | `name` | | `read_skill_reference` | Read the text of one supporting file from a skill | `name`, `path` | | `run_skill` | Load a skill's full bundle to install and run | `name` | | `search_skills` | Ranked keyword search of the catalog | `query`, `page_size`, `page_token` | ## Next steps - To browse, inspect, install, and run public skills from the command line, see [Getting started with the Chainguard Agent Skills public registry](/chainguard/agent-skills/public-registry/). - To publish, install, and run skills scoped to your own organization, see [Getting started with the Chainguard Skills Registry](/chainguard/agent-skills/skills-registry/). - For background on what hardened agent skills are and the supply-chain risk they address, see the [Chainguard Agent Skills overview](/chainguard/agent-skills/overview/). --- ### Migrating a Python project to Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/python/migration.md Last Modified: October 13, 2026 Tags: Chainguard Libraries, Python Chainguard Libraries for Python provides a curated index of PyPI packages rebuilt from source and verified against Chainguard's build provenance. Because Chainguard Libraries uses the standard pip simple-index protocol, switching an existing project requires only an index configuration change — no changes to your application code or dependency declarations. This guide walks through migrating an existing Python project to Chainguard Libraries, covering the two most common setups: * **Direct access**: Your build tool connects directly to `libraries.cgr.dev`. This option is faster for initial evaluation and smaller-scale setups. * **Repository manager**: Your build tool connects to a repository manager (such as JFrog Artifactory or Sonatype Nexus), which proxies requests to Chainguard Libraries. This option is recommended for teams and organizations. To follow along with a ready-made project instead of your own, use the [Chainguard Libraries for Python demo repository](https://github.com/chainguard-demo/chainguard-libraries-python). It provides example projects for pip, uv, and Poetry, each with a `demo.sh` script that configures access and installs sample packages. For a reference of the configuration options for each supported build tool, check out [Configure Python build tools](/chainguard/libraries/python/build-configuration/). ## Prepare for migration A Python migration is more than changing an index URL; the application code usually stays the same, but dependency resolution can change because Python packages are distributed as different artifacts for different operating systems, Python versions, CPU architectures, and C libraries. Plan for the following possible friction patterns: * The Chainguard catalog may not have every package, version, or wheel variant in an existing lockfile. * Learn more under [Troubleshooting: Handling coverage gaps](#handling-coverage-gaps). * A dependency may be available for Linux but not for macOS, Windows, Alpine, or a particular Python/architecture combination. * A lockfile generated against PyPI contains hashes and artifact URLs that do not match Chainguard-built artifacts. * CI needs an authentication model that is safe for unattended builds and compatible with the package manager and repository topology. ### Pre-migration readiness check Before changing the index, record the dependency and environment matrix that the migration must support: * Python versions used in development, CI, and production * Operating systems and CPU architectures used by developers, CI runners, build images, and deployment jobs * The package manager and lockfile format used by each job: `pip`, `uv`, Poetry, or another supported tool * Direct dependencies and improtant transitive dependencies that include native code, GPU support, platform markers, or compiled extensions * Whether the project uses hash-pinned requirements or lockfiles * Where package caches exist: local machines, CI caches, repository managers, Docker layers, and base images Run the existing build successfully before making changes. Keep the baseline dependency versions and record the commands used to install, test, and package the project. This gives your team a comparison point when a migration failure is caused by source selection, an artifact mismatch, or an unrelated dependency change. ## Prerequisites Before you begin, you need: * An existing, working Python project with a `requirements.txt`, `poetry.lock`, `uv.lock`, `pdm.lock`, `Pipfile.lock`, or `pylock.toml` * [chainctl installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/) * An [entitlement to Chainguard Libraries](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/) for Python. If you do not have an entitlement to Chainguard Libraries for Python yet, run the following command to create an entitlement and enable upstream fallback: ```shell chainctl libraries entitlements create --ecosystems=PYTHON --policy=CHAINGUARD_AND_UPSTREAM ``` It can take up to 30 minutes for fallback policy changes to take effect. ### Authentication prerequisites For authentication, you need a pull token or the Python keyring provider.

If you plan to use a repository manager, or a non-interactive environment such as CI/CD, you will need a pull token. You must have the owner role or have the libraries.python.pull_token_creator permission to create one.

chainctl auth pull-token create --repository=python --name=my-python-token --ttl=720h

To export environment variables directly:

eval $(chainctl auth pull-token --output env --repository=python --name=my-python-token)

Learn more about creating and managing pull tokens in the Libraries access documentation.

> **Note: Do not commit credentials to version control.** Store tokens as environment variables or CI secrets, not as literal values in `pip.conf`, `.netrc`, or any file that might be checked into a repository. ## Step 1: Configure authentication and registry How you configure the index depends on your environment. Follow the instructions that match your setup. The `https://libraries.cgr.dev/python/` endpoint is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for Python. By default, it serves only Chainguard-built artifacts. When upstream fallback is enabled for your organization, the same endpoint can also serve requested versions from PyPI under Chainguard security controls. ### Direct access #### Configure authentication First, configure authentication using a pull token or the Python keyring provider. Setting pull token credentials in `.netrc` is a common approach for individual work stations. Setting authentication in `pip.conf` is preferred for CI/CD, where you need credentials isolated per project or per pipeline rather than a single shared home-directory file.

Note: .netrc only supports one set of credentials per hostname. Since all Chainguard Libraries are served from libraries.cgr.dev, configuring .netrc for Python will override credentials for any other ecosystem.

First, check for an existing registry entry in .netrc:

grep -A2 "libraries.cgr.dev" ~/.netrc

Remove or correct any stale entry before proceeding.

Then, configure your pull token credentials in .netrc:

cat >> ~/.netrc << EOF
machine libraries.cgr.dev
login ${CHAINGUARD_PYTHON_IDENTITY_ID}
password ${CHAINGUARD_PYTHON_TOKEN}
EOF
chmod 600 ~/.netrc
#### Point your build tool at Chainguard Next, point your build tool at the Chainguard index. Note that Chainguard publishes both standard and [remediated Python indexes](/chainguard/libraries/policies-and-security/cve-remediation/). Remediated versions use a `+cgr.N` local-version suffix, and Python package managers treat those as compatible higher-precedence replacements for the base version. In addition, CUDA-enabled Python libraries use separate CUDA-specific indexes such as `https://libraries.cgr.dev/cu128/simple/`, and they are not dependency-complete for NVIDIA toolkit components.

If you configured authentication via .netrc or the Python keyring provider, run the following commands to set the remediated repository first, then the simple repository:

pip config set global.index-url https://libraries.cgr.dev/python-remediated/simple/
pip config set global.extra-index-url https://libraries.cgr.dev/python/simple/

If you configured authentication via pull token credentials in pip.conf, this step is already done; the index-url you set in .pip/pip.conf already points at Chainguard with credentials embedded.

### Repository manager If your organization uses a repository manager, configure Chainguard Libraries as an upstream source in that proxy first. Follow the [global configuration documentation](/chainguard/libraries/python/global-configuration/) for your repository manager. Once configured, point your build tool at your repository manager URL instead of `libraries.cgr.dev` directly. In this setup, the credentials are your repository manager credentials — not a Chainguard pull token.
pip config set global.index-url https://<your-repo-manager-url>/repository/python-all/simple/
Example URLs by repository manager: | Repository manager | URL pattern | | ----- | ----- | | JFrog Artifactory | `https://example.jfrog.io/artifactory/api/pypi/python-all/simple/` | | Sonatype Nexus | `https://repo.example.com:8443/repository/python-all/simple/` | | Google Artifact Registry | `https://-python.pkg.dev//python-all/simple/` | | Cloudsmith | `https://dl.cloudsmith.io/...//python-all/python/simple/` | For authentication details specific to your repository manager, refer to the [global configuration documentation](/chainguard/libraries/python/global-configuration/). ## Step 2: Update your lockfile Your existing lockfile or hash-pinned `requirements.txt` contains checksums generated against packages downloaded from PyPI or through your repository manager. Because Chainguard rebuilds packages from verified source as well as providing security controls for upstream artifacts, checksums differ even for identical version numbers. If your file uses `--hash` entries or `--require-hashes`, installation will fail with a hash mismatch after switching to Chainguard until these are updated. You can update your lockfile in one of two ways. Update the checksums in place to keep your existing pinned versions, or regenerate the lockfile if you also want to refresh your dependency versions.

Use chainctl libraries update-hashes to rewrite only the integrity hashes in your existing lockfile or requirements file to match Chainguard’s artifacts, without re-resolving your dependency graph. Supported formats include requirements.txt, poetry.lock, uv.lock, pdm.lock, Pipfile.lock, and pylock.toml.

Run the following command to auto-detect and update the lockfile in the current project:

chainctl libraries update-hashes

Note: Running chainctl libraries update-hashes requires the libraries.python.pull permission or the Owner role.

Or specify the lockfile when running the command. For example:

chainctl libraries update-hashes requirements.txt

When using a repo manager, run a command similar to the following:

chainctl libraries update-hashes
  --registry-url https://repo.example.com/repository/python-all/
  --token "$REPO_TOKEN"

By default, Chainguard hashes are appended alongside your existing hashes rather than replacing them. If your installer fails on a dual-hash entry, use the --replace flag.

## Step 3: Clear caches Reinstalling after switching indexes can silently reuse a cached artifact from your previous index, with no error indicating this happened. To avoid this, clear caches.
pip cache purge
Also invalidate cached packages in any repository manager that previously proxied PyPI, and rebuild containers without cached layers if your Dockerfile installs dependencies during build: ```shell docker build --no-cache . ``` ## Step 4: Reinstall dependencies Reinstall dependencies and confirm that the lockfile reflects Chainguard as the source.
pip install --force-reinstall --no-cache-dir -r requirements.txt
## Step 5: Verify your libraries After reinstalling, you can use `chainctl` to verify which dependencies are built by Chainguard. When upstream fallback is enabled, [libraries that aren't built by Chainguard](#packages-not-available-in-chainguard-libraries) are subject to Chainguard's security controls.
chainctl libraries verify --detailed ./.venv/
> **Note**: Running `chainctl libraries verify` requires the `libraries.python.pull` permission or the Owner role. A successful result shows what percentage of your project's dependencies were built by Chainguard. For full details on verification options and output, check out [Verification: Analyze a Python wheel file](/chainguard/libraries/policies-and-security/verification/#analyze-a-python-wheel-file). ## Step 6: Commit and roll out Commit the updated lockfile and any non-sensitive configuration changes. Apply the same index, cache, and hash-update steps to other developer workstations and build servers as you migrate them, including CI/CD platforms and any infrastructure that builds the application or installs dependencies. For organization-wide rollout using a repository manager, refer to the [global configuration documentation](/chainguard/libraries/python/global-configuration/). ## Packages not available in Chainguard Libraries Chainguard Libraries covers a large and growing collection of PyPI packages, but not every package or version is available. If a package is missing, your install will fail with a 404 unless you have configured [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). With upstream fallback enabled, packages not yet available from Chainguard are proxied from PyPI, subject to Chainguard's security controls. Confirm your current policy with: ```shell chainctl libraries entitlements list ``` For repository manager setups, Chainguard recommends using the configurable fallback rather than configuring a separate public registry fallback in your repository manager, to preserve Chainguard’s security controls. Learn more about upstream fallback configurations in the [Libraries overview](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). ## Troubleshooting ### "403 Forbidden" from the index despite a working keyring Check for an old or incorrectly scoped `.netrc` entry for `libraries.cgr.dev`, which silently takes priority over keyring authentication. Run with full verbosity to confirm the credential source: ```shell pip install -vvv 2>&1 | grep -B2 -A2 "credentials\|40[13]" ``` ### A pinned dependency shows "not verified" after running `chainctl libraries verify` Check your install output for a local wheel build (`Building wheel for `), which indicates Chainguard has only a source distribution for that specific pinned version. ### Handling coverage gaps Coverage gaps can occur at more than one level. For example, a package name may not be present in the Chainguard catalog, the pinned version may not be available, a wheel may. be available for Linux but not for macOS or Windows, or a transitive dependency introduced by a version change may have different coverage from the direct dependency that caused the change. Do not use a single successful install as proof that the migration is complete. Test each environment that installs the project. A direct dependency can resolve successfully while one of its transitive dependencies, optional extras, or platform-specific wheels comes from upstream fallback. The reverse can also happen: the direct package may use fallback while its transitive dependencies are available as Chainguard-built artifacts. Evaluate the resolved artifact set, not only the names in `pyproject.toml` or `requirements.in`. Choose how to handle coverage gaps: * Confirm whether the failure is authentication or coverage. A 401/403 is an empty version list, and a genuine 404 has different causes. * Check the exact package version and wheel tags required by the failing environment. * If the artifact is not available from Chainguard, use the configured protected upstream fallback when policy allows it. * If the project cannot use fallback, choose a compatible version or move the install to a supported Linux environment only when that is an acceptable product decision. * Do not silently replace a package with a different version during migration; treat it as a separate depdendency change and test it separately. ## Next steps * To apply this configuration across your whole organization using a repository manager, refer to the [global configuration](/chainguard/libraries/python/global-configuration/) documentation. * To verify downloaded packages were built by Chainguard, refer to the [verification](/chainguard/libraries/policies-and-security/verification/) documentation. * For full per-tool configuration reference, refer to the [build configuration](/chainguard/libraries/python/build-configuration/) documentation. * To keep pinned versions stable when Chainguard publishes new builds, refer to the [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) documentation. --- ### Guardener Hardened Actions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/guardener/github/actions-security.md Last Modified: October 15, 2026 Tags: GitHub, Automation The Hardened Actions feature recommends and migrates your GitHub Actions to Chainguard's hardened, SHA-pinned equivalents. Pinning actions to a specific commit SHA — rather than a mutable tag or branch — protects your workflows from supply chain attacks in which an upstream tag is moved to point at malicious code. Hardened Actions operates in two independent modes: - **Pull request recommendations** — non-blocking review comments that suggest hardened action alternatives, with one-click suggestion blocks. - **Automated migration pull requests** — a periodically maintained pull request that updates workflows across your repository. You can also [trigger this migration on demand](#run-an-on-demand-migration) with `chainctl` instead of waiting for the next scheduled run. You can enable either mode on its own or both together. Hardened Actions keeps your workflows on GitHub Actions. To move a repository's pull request checks off GitHub Actions runners instead, refer to [Migrate from GitHub Actions to Chainguard Checks](/chainguard/checks/migrate-from-github-actions/). ## Enable Hardened Actions Add a `.chainguard/actions.yaml` file to your repository: ```yaml enabled: true ``` With just `enabled: true`, Guardener posts non-blocking recommendation comments on pull requests that touch your workflows. It does not open pull requests of its own. ## Enable automated migration pull requests To have Guardener periodically open and maintain a pull request that migrates your workflows, enable the `migrate` block: ```yaml enabled: true migrate: enabled: true period: "168h" ``` Guardener opens (and keeps updated) a single migration pull request on the cadence you set with `period`. ## Run an on-demand migration Instead of waiting for the next scheduled run — for example, right after enabling migration, or after Chainguard publishes a hardened equivalent for an action you use — you can trigger the migration immediately with `chainctl`. An on-demand run performs exactly the same migration as the scheduled flow: it opens (or updates) the repository's single migration pull request and honors the same `.chainguard/actions.yaml` configuration, including `migrate.ignore`. Because it shares the opt-in gate, the repository (or its organization, through the [org-level `.github` configuration](/chainguard/guardener/github/configuration/)) must have `migrate.enabled: true`; if migration is not enabled, the run completes as a no-op without opening a pull request. Triggering an on-demand run does not change the periodic schedule. Before you start, make sure that: - The Chainguard App is installed and your Chainguard organization is linked to your GitHub organization, as described in [Getting started](/chainguard/guardener/github/getting-started/). The migration must be requested through the Chainguard organization that owns the Chainguard App installation. - You hold the `guardener.actions.migrate` capability on that Chainguard organization. The built-in `viewer`, `editor`, and `owner` roles include it, as do `guardener.user` and `guardener.admin`. Each built-in role that includes either migration capability includes both. Refer to the [Built-in roles and capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/) for more information on roles. Run `chainctl guardener github migrate create` with the repository to migrate: ```shell chainctl guardener github migrate create / ``` The repository can be given as `owner/repo` shorthand or as a full URL (`https://github.com/owner/repo`); only github.com repositories are supported today. The migration runs under the Chainguard organization that owns the Chainguard App installation. `chainctl` selects that organization automatically when only one is available and prompts you when there are several; pass `--parent ` to name it explicitly. With `chainctl` 0.2.375 or later, the command runs every migration that the repository opts in to, so it also opens or updates the [Container Image Migration](/chainguard/guardener/github/image-migration/) pull request when `.chainguard/images.yaml` enables it. Earlier versions of `chainctl` run only the Hardened Actions migration. Each migration checks its own capability. If you hold `guardener.actions.migrate` but not `guardener.images.migrate`, the `Images:` line reports a permission error and the command exits with an error, even when the repository doesn't use image migration. By default, the command waits for the migrations to finish (up to 10 minutes, adjustable with `--timeout`) and prints one result for each: ``` Repository: https://github.com// Triggered by: you@example.com (user) Actions: https://github.com///pull/42 Images: migration is not enabled here or in repository configuration Status: completed ``` In this example, the repository opts in to Hardened Actions migration only. When there's nothing to migrate — every action is already on a Chainguard equivalent, or the ignore rules exclude everything — the `Actions:` line reads `no changes needed` instead of a pull request URL. When the repository hasn't enabled Hardened Actions migration, it reads `migration is not enabled here or in repository configuration`. If a migration fails, its line reads `failed` with the reason, the last line reads `Status: completed with failures`, and the command exits with an error. If the migration fails because the Chainguard App installation does not cover the repository (for example, the app was installed on **selected repositories** and this one isn't included), the error says so; grant the app access to the repository in your GitHub organization settings and trigger the migration again. ## Check a migration operation Pass `--wait=false` to return immediately instead of waiting. The command prints an operation name of the form `operations/migrate//`, which you can check later with `chainctl guardener github migrate get`: ```shell chainctl guardener github migrate get operations/migrate// ``` The owning organization is derived from the operation name, so no `--parent` is needed. Add `--wait` to poll until the operation completes (bounded by `--timeout`, 10 minutes by default). Interrupting a waiting `migrate create` is also safe — the migration keeps running server-side, and the command prints the operation name so you can check it later. For the complete set of flags and options, refer to the `chainctl` reference: - [`chainctl guardener github migrate`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate/) - [`chainctl guardener github migrate create`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_create/) - [`chainctl guardener github migrate get`](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_get/) ## Exclude files and actions Use the `migrate.ignore` block to exclude specific workflow files or upstream actions from automated migration. Both fields accept glob patterns: ```yaml enabled: true migrate: enabled: true period: "168h" ignore: files: - "release.yml" - "*.deprecated.yml" actions: - "actions/checkout" - "actions/*" ``` - `ignore.files` — workflow files (under `.github/workflows/`) to skip. - `ignore.actions` — upstream actions to leave untouched. ## Configuration reference | Field | Default | Purpose | | ------------------------ | ------- | ----------------------------------------------------------------------------------- | | `enabled` | `true` | Enables inline pull request recommendation comments. | | `migrate.enabled` | `false` | Opts into automated migration pull requests. | | `migrate.period` | `24h` | How often the migration pull request is refreshed. Clamped to a minimum of one day. | | `migrate.version-strategy` | `exact` | Version selection when no exact equivalent exists. Set to `smallest-major-bump` to migrate older pins to the closest Chainguard-supplied major version. | | `migrate.ignore.files` | — | Glob patterns for workflow files to skip during migration. | | `migrate.ignore.actions` | — | Glob patterns for upstream actions to skip during migration. | The `period` value is a Go-style duration string (for example, `24h`, `168h` for one week). ## Full example A complete `.chainguard/actions.yaml` enabling both recommendations and weekly automated migration, while leaving the `actions/*` family and a release workflow untouched: ```yaml enabled: true migrate: enabled: true period: "168h" ignore: files: - "release.yml" actions: - "actions/*" ``` ## Next steps - **[Commit Verification](/chainguard/guardener/github/commit-verification/)** — Require cryptographically signed commits in pull requests. - **[Configuration](/chainguard/guardener/github/configuration/)** — Review the shared `.chainguard/` configuration model. --- ### Migrating a Java project to Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/java/migration.md Last Modified: October 13, 2026 Tags: Chainguard Libraries, Java Chainguard Libraries for Java provides a curated repository of packages rebuilt from upstream sources and [scanned for malware](/chainguard/libraries/introduction/overview/#malware-and-greyware-detection). Because Chainguard Libraries uses the standard Maven repository format, switching an existing project requires only a repository configuration change — no changes to your application code or dependency versions. This guide walks through migrating an existing Java project to Chainguard Libraries, covering the two most common setups: - Direct access — your build tool connects directly to libraries.cgr.dev. This option is faster for initial evaluation and smaller-scale setups. - Repository manager — your build tool connects to a repository manager ([Cloudsmith](/chainguard/libraries/java/global-configuration/#cloudsmith), [Google Artifact Registry](/chainguard/libraries/java/global-configuration/#google-artifact-registry), [JFrog Artifactory](/chainguard/libraries/java/global-configuration/#jfrog-artifactory), or [Sonatype Nexus](/chainguard/libraries/java/global-configuration/#sonatype-nexus-repository)), which proxies requests to Chainguard Libraries. To follow along with a ready-made project instead of your own, use the [Chainguard Libraries for Java demo repository](https://github.com/chainguard-demo/chainguard-libraries-java). It provides Maven and Gradle example projects, including a minimal Spring Boot application and CVE remediation demos. For a reference of the configuration options for each supported build tool, check out [Configure Java build tools](/chainguard/libraries/java/build-configuration/). ## Prerequisites Before you begin, you need: - An existing Java project - [`chainctl` installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/) ### Create an entitlement You must have an [entitlement to Chainguard Libraries](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/) for Java with [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) enabled. To create an entitlement to Chainguard Libraries for Java and enable upstream fallback, which includes a default 7-day cooldown, run the following command: ```shell chainctl libraries entitlements create --ecosystems=JAVA --policy=CHAINGUARD_AND_UPSTREAM ``` Alternatively, you can create an entitlement in the Chainguard Console: while viewing the Java ecosystem page, follow the prompts to create an access token. You can also configure cooldown policies after you create the entitlement. For example, to create and enforce a policy that disables the cooldown: ```shell chainctl libraries policy create --name=java-disable-cooldown --cooldown-days=0 chainctl libraries policy enable java-disable-cooldown --ecosystem=JAVA --mode=ENFORCE ``` It can take up to 30 minutes for the fallback and cooldown policies to take effect. Learn more about cooldown and other policies in the [Libraries policies documentation](/chainguard/chainguard-repository/library-policies/). > **Note**: If you choose to manage your own fallback to upstream repositories, refer to the following docs pages for more information: [Build configuration](/chainguard/libraries/java/build-configuration/) for direct access instructions or [Global configuration](/chainguard/libraries/java/global-configuration/) for repo manager instructions. Note that configuring a public fallback bypasses the protections provided by Chainguard. ## Step 1: Confirm your baseline build Before making any changes, confirm your project builds cleanly against Maven Central.
cd your-project
./mvnw install
All dependencies should download from Central and tests should pass. This gives you a baseline to compare against after migration. ## Step 2: Generate a pull token You must be an Owner or have the `libraries.java.pull_token_creator` permission to create a pull token. You can [create a pull token in the Chainguard Console](/chainguard/libraries/introduction/access/#creating-pull-tokens-with-the-chainguard-console), or via `chainctl`. The following command creates the token and populates environment variables directly. If you have access to more than one Chainguard organization, add `--parent=` to choose which one the token belongs to: ```shell eval $(chainctl auth pull-token --repository=java --name=my-java-token --output=env) ``` This results in values for the `CHAINGUARD_JAVA_IDENTITY_ID` and `CHAINGUARD_JAVA_TOKEN` variables. The token is named `my-java-token`, with a default expiration of 30 days. To configure the expiration, use the `--ttl` flag. Learn more about command options in the [chainctl documentation](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/). When configuring direct access, note that environment variables do not persist between terminal sessions. You must re-export them each time you open a new terminal, or add them to your shell profile. Learn more about pull tokens in the [Access documentation](https://edu.chainguard.dev/chainguard/libraries/introduction/access/). ### Do not commit credentials to version control The Gradle `build.gradle` file and the Maven `pom.xml` are typically committed to a repository — always use environment variables for credentials rather than hardcoding token values directly in the file. Maven credentials live in `~/.m2/settings.xml`, which is outside the project directory and not committed by default, but take care not to add it to your repository. Store tokens as CI secrets referenced via environment variables instead. If you accidentally commit credentials, [delete the exposed token](/chainguard/libraries/introduction/access/#pull-token-management). For more secure credential management, consider using a secrets manager such as 1Password CLI or Bitwarden, which can dynamically inject environment variables at runtime without storing token values in shell profiles or env files. ### Verify your setup Run the following command for a connectivity and authentication check: ```bash curl -u "$CHAINGUARD_JAVA_IDENTITY_ID:$CHAINGUARD_JAVA_TOKEN" \ https://libraries.cgr.dev/java/ ``` The command returns an HTML page listing repository directories. If credentials are invalid or expired, the command returns a 403 error instead of the directory listing. ## Step 3: Configure repository access The `https://libraries.cgr.dev/java/` endpoint is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for Java. By default, it serves only Chainguard-built artifacts. When upstream fallback is enabled for your organization, the same endpoint can also serve requested versions from Maven Central under Chainguard security controls. ### Direct access

Maven repository and credentials configuration lives in a settings.xml file. For a project-local setup, place this file in the .mvn folder of your project and pass -s .mvn/settings.xml when running Maven commands.

First, create a .mvn directory if it doesn’t already exist:

mkdir -p .mvn

Next, create a .mvn/settings.xml file with the following content. This configuration sets the Chainguard remediated repository as the first source, followed by the standard Chainguard repository, with Maven Central set as invalid to avoid accidental unintended fallback to the public repository:

<settings>
  <servers>
    <server>
      <id>chainguard-remediated</id>
      <username>${env.CHAINGUARD_JAVA_IDENTITY_ID}</username>
      <password>${env.CHAINGUARD_JAVA_TOKEN}</password>
    </server>
    <server>
      <id>chainguard</id>
      <username>${env.CHAINGUARD_JAVA_IDENTITY_ID}</username>
      <password>${env.CHAINGUARD_JAVA_TOKEN}</password>
    </server>
  </servers>

  <profiles>
    <profile>
      <id>chainguard</id>
      <repositories>
        <repository>
          <id>chainguard-remediated</id>
          <url>https://libraries.cgr.dev/java-remediated/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </repository>
        <repository>
          <id>chainguard</id>
          <url>https://libraries.cgr.dev/java/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </repository>
        <repository>
          <id>central</id>
          <url>https://invalid</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </repository>
      </repositories>

      <pluginRepositories>
        <pluginRepository>
          <id>chainguard-remediated</id>
          <url>https://libraries.cgr.dev/java-remediated/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </pluginRepository>
        <pluginRepository>
          <id>chainguard</id>
          <url>https://libraries.cgr.dev/java/</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </pluginRepository>
        <pluginRepository>
          <id>central</id>
          <url>https://invalid</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>false</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>

    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>chainguard</activeProfile>
  </activeProfiles>
</settings>
### Repository manager If your organization uses a repository manager, configure it to use Chainguard Libraries as an upstream source first. Follow the [global configuration documentation](/chainguard/libraries/java/global-configuration/) for your repository manager. Once configured, point your build tool at your repository manager URL. In this setup, the credentials your build tool uses are your repository manager credentials — not a Chainguard pull token.

Create or update ~/.m2/settings.xml to point Maven at your repository manager and override Central with invalid URLs. Refer to example settings files in Chainguard’s demo repository on GitHub for different repository managers.

Use a configuration similar to the following. Make sure to update the credentials in the server section to use your repository manager account credentials, using environment variables when possible. This example uses environment variables for Artifactory credentials:

<settings>

  <mirrors>
    <mirror>
      <!-- Set the identifier for the server credentials for repository manager access
      must match server name in "servers" -->
      <id>example</id>
      <!-- Send all requests to the repository manager URL -->
      <mirrorOf>*</mirrorOf>
      <!-- Ordered repository group with
        - java-remediated
        - java
        - Maven central
        - anything else needed, including internal repos
      -->
      <!--<url>https://repo.example.com:8443/repository/java-all/</url>-->
      <url>http://localhost:8081/repository/java-all/</url>
    </mirror>
  </mirrors>

  <!-- Activate repo manager and override central repo from Maven itself with invalid URLs -->
  <activeProfiles>
    <activeProfile>repo-manager</activeProfile>
  </activeProfiles>
  <profiles>
    <profile>
      <id>repo-manager</id>
      <repositories>
        <repository>
          <id>central</id>
          <url>http://central</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
        </repository>
      </repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases>
            <enabled>true</enabled>
          </releases>
          <snapshots>
            <enabled>true</enabled>
          </snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>

  <servers>
    <server>
      <id>repo-manager</id>
      <username>${env.ARTIFACTORY_USERNAME}</username>
      <password>{env.ARTIFACTORY_PASSWORD}</password>
    </server>
  </servers>
</settings>
Replace `https://repo.example.com/java-all/` with your repo manager's virtual repository URL, and update the username and password. ## Step 4: Refresh dependencies To force re-resolution from Chainguard, you can clear local caches or clear only relevant artifacts.

Remove local cache:

rm -rf ~/.m2/repository

Remove specific artifact:

rm -rf ~/.m2/repository/<group>/<artifact>
## Step 5: Build the project Run a full build and capture the output.
cd your-project
./mvnw install 2>&1 | tee /tmp/mvn-output.txt

To check which repositories served completed downloads:

grep "Downloaded from" /tmp/mvn-output.txt

All downloads should come from chainguard or chainguard-remediated.

Look for lines beginning with `Downloaded from chainguard:` or `Downloaded from chainguard-remediated:` to confirm artifacts are being served by Chainguard. If all artifacts download from Central, your credentials may be invalid or expired. Regenerate your pull token, re-export the pull token credentials as environment variables, then clear the cache and rebuild. ## Step 6: Verify artifacts To check whether a specific artifact was built by Chainguard, use `chainctl libraries verify /full/path/to/artifact.jar`. Verify artifacts immediately after a clean build, before any repackaging. When upstream fallback is enabled, [packages that aren't built by Chainguard] are subject to Chainguard's security controls.

To list all downloaded JARs:

find ~/.m2/repository -name "*.jar"

Pick any JAR from the output to verify. For example:

chainctl libraries verify \
  ~/.m2/repository/org/apache/commons/commons-lang3/3.13.0/commons-lang3-3.13.0.jar

A checksum mismatch means the artifact came from Central rather than Chainguard. This can happen if the artifact was cached from a previous build. To resolve, delete the specific artifact and re-download it:

rm ~/.m2/repository/org/apache/commons/commons-lang3/3.13.0/commons-lang3-3.13.0.jar
./mvnw dependency:get -Dartifact=org.apache.commons:commons-lang3:3.13.0

Then run chainctl libraries verify again.

> **Note**: Running `chainctl libraries verify` requires the `libraries.java.pull` permission or the Owner role. A successful result shows what percentage of your project's dependencies were built by Chainguard: ```bash Artifact: /path/to/artifact.jar Verification Coverage: 100.00% ``` ## Packages not available in Chainguard Libraries Chainguard Libraries covers a large and growing collection of Java packages, but not every package or version is available. If a package is missing, your install will fail with a 404 unless you have configured [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). With upstream fallback enabled, packages not yet available from Chainguard are proxied from Maven Central, subject to Chainguard's security controls. Confirm your current policy with: ```shell chainctl libraries entitlements list ``` For repository manager setups, Chainguard recommends using the configurable fallback rather than configuring a separate public registry fallback in your repository manager, to preserve Chainguard’s security controls. Learn more about upstream fallback configurations in the [Libraries overview](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). When the upstream fallback is enabled, [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) keeps a version you pulled from Maven Central stable after Chainguard publishes its own build. This prevents unexpected checksum mismatches across rebuilds. --- ### Migrating a JavaScript project to Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/javascript/migration.md Last Modified: October 13, 2026 Tags: Chainguard Libraries, JavaScript Chainguard Libraries for JavaScript provides a curated registry of npm packages rebuilt from source, scanned for malware, and verified against the [OSV database](https://osv.dev/). Because Chainguard Libraries uses the standard npm registry protocol, switching an existing project requires only a registry configuration change — no changes to your application code, `package.json`, or dependency versions. This guide walks through migrating an existing JavaScript project to Chainguard Libraries, covering the two most common setups: - **Direct access** — your build tool connects directly to `libraries.cgr.dev`. This option is faster for initial evaluation and smaller-scale setups. - **Repository manager** — your build tool connects to a repository manager (such as JFrog Artifactory or Sonatype Nexus), which proxies requests to Chainguard Libraries. This option is recommended for teams and organizations. To follow along with a ready-made project instead of your own, use the [Chainguard Libraries for JavaScript demo repository](https://github.com/chainguard-demo/chainguard-libraries-javascript). It provides example projects for npm, pnpm, Yarn, and Bun, each with a `demo.sh` script that configures access and installs sample packages. For a reference of the configuration options for each supported build tool, check out [Configure JavaScript build tools](/chainguard/libraries/javascript/build-configuration/). ## Prerequisites Before you begin, you'll need: - An existing JavaScript project with a `package.json` and a lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`) - [`chainctl` installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/) - An [entitlement to Chainguard Libraries](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/) for JavaScript ### Create an entitlement To create an entitlement to Chainguard Libraries for JavaScript and enable upstream fallback, including a default 7-day cooldown, run: ```shell chainctl libraries entitlements create --ecosystems=JAVASCRIPT --policy=CHAINGUARD_AND_UPSTREAM ``` Alternatively, you can create an entitlement and pull token in the Chainguard Console: while viewing the JavaScript ecosystem page, follow the prompts to create an access token. You can also configure [cooldown policies](#packages-not-available-in-chainguard-libraries) after you create the entitlement. For example, to create and enforce a policy for a 14-day cooldown: ```shell chainctl libraries policy create --name=js-cooldown-14d --cooldown-days=14 chainctl libraries policy enable js-cooldown-14d --ecosystem=JAVASCRIPT --mode=ENFORCE ``` It can take up to 30 minutes for the fallback and cooldown policies to take effect. Learn more about cooldown and other policies in the [Libraries policies documentation](/chainguard/chainguard-repository/library-policies/). ### Create a pull token If you plan to use a repository manager, or a non-interactive environment such as CI/CD, you will need a pull token. You must be an Owner or have the `libraries.javascript.pull_token_creator` permission to create one. You can [create a pull token in the Chainguard Console](/chainguard/libraries/introduction/access/#creating-pull-tokens-with-the-chainguard-console), or via `chainctl`: ```shell chainctl auth pull-token --repository=javascript --name=my-js-token ``` This outputs an identity ID and token named `my-js-token`, with a default expiration of 30 days. To configure the expiration, use the `--ttl` flag. Learn more about command options in the [chainctl documentation](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/). Set the identity ID and token as environment variables: ```shell export CHAINGUARD_JAVASCRIPT_IDENTITY_ID="" export CHAINGUARD_JAVASCRIPT_TOKEN="" ``` > **Do not commit credentials to version control.** Configuration files > generated with a literal token value — such as `.npmrc` files written by > `chainctl auth configure-npm --pull-token` — should not be committed to a > repository, especially a public one. Add these files to `.gitignore`, and > store tokens as CI secrets referenced via environment variables instead. If > you accidentally commit credentials, [delete the exposed > token](/chainguard/libraries/introduction/access/#pull-token-management). ### Audit your current registry configuration Before making changes, identify where your current registry is configured. npm and other JavaScript build tools can read registry settings from several locations — a project-level `.npmrc`, a user-level `~/.npmrc`, environment variables, or a repository manager — and you may have configuration in more than one place. To see all currently active npm configuration and where each value comes from:
npm config list
The `registry` line shows where packages are currently being fetched from. If no registry is set, npm fetches from `https://registry.npmjs.org` by default. If you don't have an `.npmrc` file yet, that's fine; you'll create one in the next step. ## Step 1: Configure authentication and registry How you configure the registry depends on your environment. Follow the instructions that match your setup. > **Choosing what account to use:** For individual evaluation, your personal > credentials are sufficient. For shared environments, CI/CD pipelines, or team > rollouts, use a dedicated service account or machine identity rather than a > personal account. This simplifies token rotation, provides cleaner audit logs, > and avoids builds breaking if someone leaves the organization. ### Direct access Session-based authentication is the preferred option when your environment can use your current Chainguard session or an assumable identity. This keeps credentials short-lived and avoids passing around static tokens. #### Local development For npm and Bun, run the following command to write a project-level `.npmrc` with the registry URL and credentials from your current Chainguard session: ```shell chainctl auth configure-npm ``` Because [this command](/platform/chainctl/chainctl-docs/chainctl_auth_configure-npm/) uses a session-backed bearer token, you will need to re-run it when the token expires. If the command returns an error, ensure you are using the [latest version of chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl). For pnpm and Yarn, [configure the .npmrc manually](#manual-registry-configuration). `chainctl auth configure-npm` generates path-scoped credentials (`//libraries.cgr.dev/javascript/:_auth`) that do not cover pnpm's upstream-fallback path, causing HTTP 401 errors on packages Chainguard has not yet rebuilt. #### CI/CD and automated environments For CI/CD environments, [assumable identities](/platform/administration/assumable-ids/assumable-ids/) are the recommended approach. Rather than managing a static token, an assumable identity lets your CI/CD workload authenticate directly with Chainguard using its own platform identity; no long-lived credentials to rotate or accidentally expose. Once configured, authenticate and write the registry configuration in your pipeline. For example, in a GitHub Actions workflow using npm: ```yaml steps: - uses: chainguard-dev/setup-chainctl@main with: identity: "" - run: chainctl auth configure-npm ``` For pnpm, Yarn, and Bun, authenticate the workload then apply the [tool-specific registry configuration](#manual-registry-configuration). #### Pull token authentication If your platform does not support assumable identities, you can use a static pull token. **Configure the .npmrc with `chainctl`**: ```shell chainctl auth configure-npm --pull-token ``` This generates a project-level `.npmrc` with a pull token. It is supported for npm and Bun. For pnpm, [configure the registry manually](#manual-registry-configuration) to ensure host-level credentials are used. **Configure the .npmrc manually**: First create a pull token as described in the [prerequisites](#prerequisites), then configure your build tool manually using the instructions below.
Configure registry manually for pnpm, Yarn, and Bun

Note: The following commands use environment variables to supply credentials at runtime. Writing credentials to a local configuration file should still be treated as sensitive regardless of the form used. Generate credentials at build time where possible, add configuration files to .gitignore if needed, and store secrets in your CI/CD platform rather than in version control.

pnpm

Note: The Chainguard Repository upstream fallback has been tested with pnpm v11. We recommend using pnpm v11 or newer.

export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)

pnpm config set registry https://libraries.cgr.dev/javascript/ --location=project
pnpm config set //libraries.cgr.dev/:_auth "${token}" --location=project

The authentication entry is keyed to the whole libraries.cgr.dev host (//libraries.cgr.dev/) so that it also covers packages served through the upstream fallback path (/javascript-upstream/). A credential scoped only to /javascript/ causes pnpm to send no auth header for upstream-fallback packages, resulting in HTTP 401 errors.

Alternatively, use username and _password instead of _auth. Note that the _password value must still be base64-encoded; pnpm does not accept a raw token here:

pnpm config set //libraries.cgr.dev/:username "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}" --location=project
pnpm config set //libraries.cgr.dev/:_password "$(echo -n "${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)" --location=project

Yarn Berry (2.x+)

export authInfo="${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}"

yarn config set npmRegistryServer https://libraries.cgr.dev/javascript
yarn config set 'npmRegistries["//libraries.cgr.dev/javascript"].npmAuthIdent' "${authInfo}"
yarn config set 'npmRegistries["//libraries.cgr.dev/javascript"].npmAlwaysAuth' "true"

Yarn Classic (1.x)

export token=$(echo -n "${CHAINGUARD_JAVASCRIPT_IDENTITY_ID}:${CHAINGUARD_JAVASCRIPT_TOKEN}" | base64 -w 0)
cat > .npmrc << EOF
registry=https://libraries.cgr.dev/javascript/
//libraries.cgr.dev/javascript/:_auth="$token"
//libraries.cgr.dev/javascript/:always-auth=true
EOF

Bun

cat > bunfig.toml << EOF
[install.registry]
url = "https://libraries.cgr.dev/javascript/"
username = "$CHAINGUARD_JAVASCRIPT_IDENTITY_ID"
password = "$CHAINGUARD_JAVASCRIPT_TOKEN"
EOF
### Repository manager If your organization uses a repository manager, configure Chainguard Libraries as an upstream source in that proxy first. Follow the [global configuration documentation](/chainguard/libraries/javascript/global-configuration/) for your repository manager. Once configured, point your build tool at your repository manager URL. In this setup, the credentials your build tool uses are your repository manager credentials — not a Chainguard pull token.
cat > .npmrc << EOF
registry=https://repo.example.com:8443/repository/javascript-all
EOF

Learn more in the build configuration docs for npm and for Yarn Classic.

Example URLs by repository manager: | Repository manager | URL pattern | |---|---| | JFrog Artifactory | `https://example.jfrog.io/artifactory/javascript-all/` | | Sonatype Nexus | `https://repo.example.com:8443/repository/javascript-all/` | For authentication details specific to your repository manager, refer to the [global configuration documentation](/chainguard/libraries/javascript/global-configuration/). ## Step 2: Verify authentication Before reinstalling packages, confirm your credentials are valid. For direct access with npm or pnpm, run: ```shell npm ping --userconfig .npmrc ``` A successful response looks like: ``` npm notice PING https://libraries.cgr.dev/javascript/ npm notice PONG 1065ms ``` For repository manager setups, authentication is handled by your organization's proxy configuration. ## Step 3: Update your lockfile Your existing lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`) contains checksums and resolved URLs pointing to the original registry. Running your package manager's install command after changing the registry does **not** re-fetch already-resolved packages — the lockfile entries are treated as satisfied, and source URLs will still point to `registry.npmjs.org`. You can update your lockfile in one of two ways. Update the checksums in place to keep your existing pinned versions, or regenerate the lockfile if you also want to refresh your dependency versions.

Use chainctl libraries update-hashes to rewrite only the integrity hashes in your existing lockfile to match Chainguard’s artifacts, without regenerating the lockfile from scratch. This preserves your pinned dependency versions. Supported formats include package-lock.json (npm v2/v3), yarn.lock (Yarn Classic and Berry), pnpm-lock.yaml, and bun.lock.

Run the command in the directory containing the lockfile:

chainctl libraries update-hashes

Note: Running chainctl libraries update-hashes requires the libraries.javascript.pull permission or the Owner role.

If your build tool appends the Chainguard hashes to your lock file, include the flag --replace to ensure the hashes are replaced with Chainguard hashes. When using a repo manager, pass the full repository URL with --registry-url.

You can also specify a lockfile path directly:

chainctl libraries update-hashes path/to/lockfile

After running the command, ensure your .npmrc is configured with Chainguard credentials. The updated hashes are applied when you reinstall. The chainctl libraries update-hashes command will output a “Next steps” section that includes the tool-specific command for reinstalling.

Learn about using this command with repo managers in the Global configuration page.

## Step 4: Delete node_modules and clear caches
rm -rf node_modules && npm cache clean --force
## Step 5: Reinstall dependencies Reinstall dependencies and confirm that the lockfile reflects Chainguard as the source. Resolved URLs should point to `libraries.cgr.dev/javascript` (direct access) or your repository manager host, not `registry.npmjs.org`. > Note: When using the `update-hashes` command, some package managers and tool versions default to appending the hash rather than replacing it. In some cases, the resulting dual-hash format fails on install. If you encounter integrity errors, [run the command again](#step-3-update-your-lockfile) and include the `--replace` flag.

Install dependencies:

npm install

Then check the resolved field in package-lock.json:

grep "resolved" package-lock.json | head -5
## Step 6: Verify your libraries After reinstalling, you can verify which of your JavaScript dependencies are built by Chainguard by scanning your local package manager cache or `node_modules` directory. `chainctl libraries verify` auto-detects npm and pnpm caches by their directory structure. You can also verify your lockfile directly, without reinstalling. Run `chainctl libraries verify` against a lockfile (`package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, or `bun.lock`) to check which of its entries resolve to Chainguard-built packages. This reports what the lockfile claims will be installed, not the package bytes on disk. See [Verify JavaScript lockfiles without installing dependencies](/chainguard/libraries/policies-and-security/verification/#verify-javascript-lockfiles-without-installing-dependencies). When upstream fallback is enabled, [packages that aren't built by Chainguard](#packages-not-available-in-chainguard-libraries) are subject to Chainguard's security controls.

Scan the npm cache:

chainctl libraries verify ~/.npm

Verify an npm tarball:

chainctl libraries verify PACKAGE-VERSION.tgz
> **Note**: Running `chainctl libraries verify` requires the `libraries.javascript.pull` permission or the Owner role. A successful result shows what percentage of your project's dependencies were built by Chainguard. For example: ``` Artifact: ./node_modules Verification Coverage: 100.00% ``` For full details on verification options and output, refer to [Verification: Analyze JavaScript packages](/chainguard/libraries/policies-and-security/verification/#analyze-javascript-packages). ## Step 7: Commit and roll out Commit the updated lockfile and any registry configuration files that do not contain literal credentials. Apply the same registry, cache, and hash update steps to other developer workstations and build servers as you migrate them - including Jenkins, TeamCity, GitHub or other infrastructure that builds the applications or downloads dependencies. For organization-wide rollout using a repository manager, refer to the [global configuration documentation](/chainguard/libraries/javascript/global-configuration/). ## Packages not available in Chainguard Libraries Chainguard Libraries covers a large and growing collection of popular npm packages, but not every package in the npm Registry is available. If a package is missing, your install will fail with a 404 unless you have [configured upstream fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls). - For direct access, the Chainguard Repository includes a built-in, configurable npm upstream fallback. When fallback is enabled, packages not available from Chainguard are proxied from npm and subject to a configurable cooldown period and malware scanning before being served. Refer to the [JavaScript Libraries overview](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) for details. - For repository manager setups, Chainguard recommends using the built-in fallback rather than configuring a separate public registry fallback in your repository manager, to preserve Chainguard's security controls. If you have private scoped packages published to npm under a prefix like `@your-org/`, you can route them back to npm using per-scope registry configuration. Refer to [Using private npm packages alongside Chainguard Repository](/chainguard/libraries/javascript/build-configuration/#using-private-npm-packages-alongside-chainguard-repository) for details. ## Troubleshooting **Packages still resolving from `registry.npmjs.org` after migration** Running the install command after changing the registry does not re-fetch packages already satisfied by the lockfile. Follow [Step 3](#step-3-update-your-lockfile) to update checksums in place or regenerate the lockfile. **404 errors during install** The requested package or version may not yet be available in Chainguard Libraries, or may still be within the cooldown window. Check the [Chainguard Console](https://console.chainguard.dev/libraries/javascript), try an earlier version, or enable upstream fallback. Refer to [Packages not available in Chainguard Libraries](#packages-not-available-in-chainguard-libraries). Also note that upstream fallback and cooldown policies can take up to 30 minutes to go into effect. **Authentication errors or `npm ping` failure** Confirm your environment variables are set and the token has not expired. Re-run `chainctl auth pull-token --repository=javascript` to generate a new token if needed. **`npm config list` shows an unexpected registry** You may have registry configuration at the user level (`~/.npmrc`) or in environment variables that is overriding your project-level settings. Run `npm config list` to identify conflicting sources. **Stale or corrupted package data after switching registries** If packages seem to be served from a cache rather than re-fetched, you may need to clear additional cache layers beyond `node_modules`. See the "Apply registry changes" sections for your build tool on the [build configuration page](/chainguard/libraries/javascript/build-configuration/). ## Next steps - To apply this configuration across your whole organization using a repository manager, refer to the [global configuration](/chainguard/libraries/javascript/global-configuration/) documentation. - To verify downloaded packages were built by Chainguard, see the [verification](/chainguard/libraries/policies-and-security/verification/) documentation. - For full per-tool configuration reference, refer to the [build configuration](/chainguard/libraries/javascript/build-configuration/) documentation. - To keep pinned versions stable when Chainguard publishes new builds, see the [build pinning](/chainguard/libraries/policies-and-security/build-pinning/) documentation. --- ### Using GitOps to manage Custom Assembly resources URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/custom-assembly-gitops.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural, Custom Assembly Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) is a tool that lets customers create customized container images with extra packages and annotations added. This enables customers to reduce their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs. It can be managed in the [Chainguard Console](/chainguard/containers/custom-assembly/custom-assembly-console/), [with `chainctl`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/), [with the API](/chainguard/containers/custom-assembly/custom-assembly-api-demo/), or from a CI/CD pipeline. This guide shows how to use Chainguard Custom Assembly as code from a CI/CD pipeline, storing your configuration in Git and using automation to apply changes and trigger builds. The examples in this guide focus on GitHub Actions, and are adapted from [Chainguard's custom-assembly-as-code demo repository](https://github.com/chainguard-demo/custom-assembly-as-code). > **NOTE**: `chainctl` is an API client that handles common tasks like authentication and applying configuration files. You can manage Custom Assembly [interactively using `chainctl`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#editing-packages-interactively). Running `chainctl` non-interactively is a common pattern for implementing GitOps workflows. ## Prerequisites Before getting started, you need the following: * A Chainguard organization with access to Custom Assembly, as well as permission to manage Custom Assembly for your organization * A CI/CD platform in place. This guide uses GitHub Actions as an example * Custom Assembly builds need no GitHub credentials beyond the token `actions/checkout` uses by default, so the example workflow in this guide does not authenticate to the GitHub API. * A Git repository to host your apko configuration files * A configured assumable identity for your CI workload * If you have not yet set up CI identities, refer to [Chainguard's tutorials for creating and assuming identities](/platform/administration/assumable-ids/identity-examples/). * The full IDs for your [image-syncer and custom-image-builder identities](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/#chainguards-signing-identities), named `catalog_syncer` and `apko_builder` in older organizations ### Understanding apko overlay files Custom Assembly uses apko overlay YAML files to customize images. You can use them to define changes such as additional packages to install, environment variables, and annotations. This example overlay file shows the configuration options available for customizing Chainguard images: ```yaml contents: packages: - curl - jq environment: APP_ENV: production LOG_LEVEL: info annotations: org.opencontainers.image.title: "Python App with Tools" org.opencontainers.image.description: "Custom Python image with curl and jq" accounts: run-as: "appuser" users: - username: "appuser" uid: 65532 gid: 65532 homedir: "/home/appuser" groups: - groupname: "appgroup" gid: 65532 members: - "appuser" certificates: additional: - name: "certificate-name" content: | -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- ``` ### Repository structure Chainguard recommends organizing your configuration YAML files in a dedicated directory, as in the following example repository structure: ``` / ├── .github/ │ └── workflows/ │ └── build-custom-images.yaml ├── ca-images-iac/ │ ├── python-app.yaml │ ├── nginx-custom.yaml │ └── node-api.yaml └── README.md ``` In this example, the `ca-images-iac/` directory contains the apko overlay files, while the workflow file defines how and when builds are triggered. ## Step 1: Create an assumable identity First, create an identity that your CI/CD platform can assume. The process varies by platform; the following example uses GitHub Actions. ```bash chainctl iam identities create github-actions-identity \ --description="GitHub Actions identity for Custom Assembly" \ --identity-issuer=https://token.actions.githubusercontent.com \ --subject-pattern=".*" \ --claim-pattern=repository:/ \ --claim-pattern='event_name:^(push|workflow_dispatch)$' ``` Replace `/` with the repository that holds your workflow. This creates an identity that GitHub Actions workflows in that repository can assume, whether triggered by a push or started manually from the Actions tab. Claim values are patterns, not literal strings, so `^(push|workflow_dispatch)$` matches either event. Match both: the workflow later in this guide triggers on `push` and `workflow_dispatch`, and the testing steps start a run manually. An identity pinned to `event_name:push` alone rejects manual runs with `token has invalid "event_name": workflow_dispatch`. Be sure to quote the pattern so your shell does not interpret the `|` as a pipe. Repeat `--claim-pattern` once per claim, as shown in this example. Passing several `claim:pattern` pairs as a single comma-separated value does not create separate claims — `chainctl` treats the entire string as one pattern, matching a `repository` claim whose literal value is `/,event_name:push`. This fails silently, producing an identity that no workflow can assume. This example matches on the `repository` and `event_name` claims rather than the `sub` claim, so it is unaffected by GitHub's [immutable subject claims](/platform/administration/assumable-ids/identity-examples/github-identity/#finding-your-repositorys-numeric-identifiers), which change only the `sub` claim. The `repository` claim carries the repository name, and names can be reassigned. For stronger protection against namespace reuse, pin the identity to the repository's numeric ID by adding `--claim-pattern=repository_id:`. ## Step 2: Grant permissions The identity needs permission to build Custom Assembly images. You can create a [least-privilege custom role](/chainguard/containers/custom-assembly/overview/#custom-assembly-permissions-requirements) that contains the `repo.update` and `repo.create` permissions, then grant the necessary permission using `chainctl`. After creating the custom role, set an environment variable named `IDENTITY_ID` to the UIDP of the `github-actions-identity` identity you just created: ```shell IDENTITY_ID=$(chainctl iam identities list -o json | jq -r '.items[] | select(.name=="github-actions-identity") | .id') ``` Then use this variable to create a role binding that grants the custom role to the identity: ```shell chainctl iam role-bindings create \ --identity=$IDENTITY_ID \ --role= ``` Be sure to replace `` with the name of the custom role you created. If you have access to more than one organization, add `--parent ` to choose where the role binding is created. ## Step 3: Note your identity ID You'll need your identity ID for your CI/CD workflow configuration. Save it for use in the next section: ```bash chainctl iam identities list -o table ``` ## Trigger builds with `chainctl` in CI/CD workflows Regardless of which CI/CD platform you use, you trigger Custom Assembly builds with the same `chainctl images repos build apply` command: ```bash chainctl images repos build apply --file ca-images-iac/custom-jre.yaml \ --parent \ --repo \ --yes ``` This command follows the example repo structure that appears earlier on this page, where `ca-images-iac` is the directory that contains the apko overlay files. This command: * Reads your apko overlay configuration from the YAML file * Applies it to build a custom image * Pushes the result to your Chainguard registry * Skips the interactive confirmation when you pass `--yes`, making it suitable for automated workflows ### GitHub Actions example This section provides a complete example for automating Custom Assembly builds with GitHub Actions. Create `.github/workflows/build-custom-images.yaml` in your repository. This example is based on Chainguard's [custom-assembly-as-code demo](https://github.com/chainguard-demo/custom-assembly-as-code): ```yaml # Trigger builds automatically when the specified file changes. Only runs on pushes to the main branch. Use a wildcard to trigger on any file in a specified directory. name: build on: push: branches: [main] paths: - 'ca-images-iac/custom-jre.yaml' workflow_dispatch: # Images are signed by either the image-syncer or custom-image-builder identity in # your organization. Find these values under "Assumed Identities" in your # organization settings. They are defined here, at workflow level, so every step can # read them. env: CUSTOM_IMAGE: "cgr.dev//" IMAGE_SYNCER: "/" CUSTOM_IMAGE_BUILDER: "/" # Top-level permissions follow the principle of least privilege. Job-level permissions grant only what's needed. permissions: {} jobs: build-custom-image-as-code: runs-on: ubuntu-latest permissions: actions: read contents: read id-token: write steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0 with: egress-policy: audit # Nothing after this step uses git or the GitHub API, so there is no reason to # leave the checkout token behind in .git/config. - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: main persist-credentials: false - name: Setup Go environment uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: cache: false # Pin Crane to a release rather than @latest so a run cannot pick up an # unreviewed version. Check for newer releases periodically. - name: Install Crane run: go install github.com/google/go-containerregistry/cmd/crane@v0.21.9 - name: Install Cosign uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 # Authenticate to Chainguard using assumable identity - uses: chainguard-dev/setup-chainctl@8d93dcbef466d3cf3533f67084f52eb74ef9d262 # v0.2.4 with: identity: "/" - name: 'Auth to Registry' run: | chainctl auth configure-docker chainctl auth status # Verify existing image signature before rebuilding. - name: Verify signature && pull existing image id: cosign-verify continue-on-error: false run: | cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev/(${IMAGE_SYNCER}|${CUSTOM_IMAGE_BUILDER})" \ "$CUSTOM_IMAGE:latest" | jq # Extract and display packages from the SBOM attestation. - name: Print created time and list packages id: crane-config continue-on-error: false run: | echo "Created time: $(crane config "$CUSTOM_IMAGE:latest" | jq -r .created)" crane manifest "$CUSTOM_IMAGE:latest" | jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest' | xargs -I {} cosign verify-attestation --type=spdx \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev/(${IMAGE_SYNCER}|${CUSTOM_IMAGE_BUILDER})" \ "$CUSTOM_IMAGE@{}" 2> /dev/null | jq -r .payload | base64 -d | jq '.predicate' | jq '.packages[] | select(.externalRefs[]?.referenceCategory == "PACKAGE_MANAGER") | .externalRefs[] | select(.referenceCategory == "PACKAGE_MANAGER") | .referenceLocator' # Apply the apko configuration file to trigger the build. The --yes flag skips the confirmation prompt. - name: Trigger custom build id: start-custom-build continue-on-error: false run: | chainctl images repos build apply -f ca-images-iac/custom-jre.yaml \ --parent --repo --yes ``` #### Extending the workflow with GitHub API access Some extensions to this workflow do need GitHub credentials, such as committing an updated overlay file, opening a pull request that reports which CVEs a rebuild fixed, or commenting build results on an existing pull request. Rather than storing a long-lived Personal Access Token, add an [Octo STS](https://github.com/apps/octo-sts) step. Octo STS exchanges the workflow's OIDC token for a GitHub token that is scoped to the permissions you declare and expires with the run: ```yaml - uses: octo-sts/action@6177b4481c00308b3839969c3eca88c96a91775f # v1.0.0 id: octo-sts with: scope: / identity: build ``` Pass the result to whichever step needs it as `${{ steps.octo-sts.outputs.token }}`. Using Octo STS also requires installing its GitHub App on your organization and committing a trust policy to `.github/chainguard/build.sts.yaml`, where `build` matches the `identity` input. For installation and trust policy details, refer to [Set up and use Octo STS](/open-source/octo-sts/set-up-octo-sts/). ## Testing your workflow Before deploying your CI/CD workflow to production, test it thoroughly to ensure builds complete successfully and authentication works correctly. Start by triggering a manual build and reviewing the logs for each step. Verify that images are built with the expected packages and configurations, and confirm that signatures and attestations are properly generated. Testing in a non-production environment or with a dedicated test repository helps catch configuration issues early without impacting your production image builds. ### Testing the GitHub Action example Before using the GitHub action in this guide, make sure to update the placeholders: * `/`: The full ID of the identity you created in Step 1 * `CUSTOM_IMAGE: "cgr.dev//"`: Your image registry path * `IMAGE_SYNCER: "/"`: Your `image-syncer` identity * `CUSTOM_IMAGE_BUILDER: "/"`: Your `custom-image-builder` identity * `--parent --repo `: Your Chainguard organization and image repository names * `ca-images-iac/custom-jre.yaml`: The directory in your GitHub repository that holds the apko overlay files, and the overlay file name

Note: Older Chainguard organizations name the image-syncer and custom-image-builder identities catalog_syncer and apko_builder instead. The names are interchangeable: each pair points at the same account association, so the signatures verify the same way. The workflow’s environment variable names are arbitrary — only the identity IDs they hold matter.

To test your GitHub Action: 1. In GitHub, go to the **Actions** tab, select your workflow, then click **Run workflow**. 2. Check the detailed logs for each step. 3. Confirm that the images appear in your Chainguard registry. ## Additional resources * [Custom Assembly overview](/chainguard/containers/custom-assembly/overview/) * [apko documentation](/open-source/build-tools/apko/) * [Assumable identity documentation](/platform/administration/assumable-ids/assumable-ids/) * [Demo Repository: custom-assembly-as-code](https://github.com/chainguard-demo/custom-assembly-as-code) * [Get support](/get-started/get-support/) --- ### Vulnerability scanners and Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/policies-and-security/scanners.md Last Modified: September 28, 2026 Tags: Chainguard Libraries Vulnerability scanners enable you to understand the potential security risks from libraries used within your applications. Chainguard Libraries provides a trusted source for libraries typically downloaded from public repositories. Chainguard Libraries are rebuilt from the upstream open source project code repository content only. This prevents malware without published source code and reduces almost all risk for software supply chain attacks. In addition, some library versions are available with CVE fixes applied. These fixes are backported from newer versions of the open source project by Chainguard to create new libraries of older versions containing these newer changes. Find more details in [CVE Remediation](/chainguard/libraries/policies-and-security/cve-remediation/). This page provides an overview of vulnerability scanning for libraries and the use of specific scanning applications in the following sections. For more information on scanning containers, refer to our guide on [Working with Container Image Scanners](/chainguard/containers/security-and-compliance/working-with-scanners/). ## Vulnerability scanning Vulnerability scanning can be performed at various stages throughout the software development lifecycle. Scanning earlier in the process helps identify and remediate issues before they reach production environments. Common scenarios for scanning include: - **Before libraries enter the organization:** Scan dependencies as they are added to repository managers, developer machines, or during initial CI runs. - **During application development:** Scan libraries and dependencies on the developer machine to catch vulnerabilities early. - **At build time:** Scan application binaries, such as tarballs or other deployment artifacts, during the build process on the developer machine or any CI infrastructure. - **During container image creation:** Scan container images as they are built to ensure included libraries are secure. - **In container registries:** Scan images stored in registries before deployment to production. - **During deployment:** Scan running applications or containers to detect vulnerabilities in the deployed environment. Scanners can be used in different ways depending on the workflow: - **Standalone CLI tools:** Run scans manually or as part of scripts. - **Integration with repository managers:** Automatically scan dependencies as they are added or updated. - **Integration with build tools:** Incorporate scanning into build pipelines for continuous security checks. - **IDE integrations:** Many vulnerability scanners offer plugins or extensions for popular integrated development environments (IDE). These integrations allow developers to scan dependencies and code for vulnerabilities directly within their development workflow, providing immediate feedback and helping to remediate issues early in the process. - **Other integrations:** Use scanners with CI/CD platforms, deployment tools, or monitoring systems. Selecting the appropriate scanning approach and timing helps maintain a secure software supply chain and reduces the risk of introducing vulnerabilities. All the preceding considerations for vulnerability scanning apply when scanning for Chainguard Libraries. Different vulnerability scanners offer varying features, capabilities, and integration options for detecting vulnerabilities in these libraries. Details about how specific scanners work with Chainguard Libraries are provided in the following sections. ## Supported scanners | Scanner | Python | Java | | --- | --- | --- | | Amazon Inspector | ✓ | ✓ | | Anchore Enterprise | ✓ | ✓ | | Grype | ✓ | ✓ | | Trivy | ✓ | ✓ | | Upwind | ✓ | ✓ | | Wiz | ✓ | ✓ | ### Amazon Inspector Chainguard Libraries for Python and Java are supported by Amazon Inspector’s enhanced scanning for Amazon ECR. This integration brings high-impact CVE remediation directly into your AWS vulnerability management workflows. Refer to the [AWS documentation](https://docs.aws.amazon.com/inspector/latest/user/supported.html#:~:text=Supported%20programming%20languages%3A%20Amazon%20ECR%20scanning) for additional details. ### Anchore Enterprise Anchore Enterprise supports the detection of remediated Chainguard Libraries for Python starting with version 5.23.0, once the required configuration is applied. Support for remediated Chainguard Libraries for Java is also available through Anchore's vulnerability data updates. Anchore Data Service v0.32.0 adds support for Maven package remediations from Chainguard Libraries. To ensure remediated CVEs are filtered out by default, disable CPE matching for the ecosystem in which you are using Chainguard Libraries. Instructions for disabling CPE matching are available in the [Anchore documentation](https://docs.anchore.com/current/docs/vulnerability_management/). ### Grype [Grype](https://github.com/anchore/grype) supports detection of remediated Chainguard Libraries for Python and Java starting with Grype **version 0.100.0**. You can use Grype in multiple ways: - Scan the Python virtual environment directly. If your Python application is not containerized, this is recommended. - Alternatively, scan the container image for your application. Grype detects and accounts for remediated library versions inside the image. When scanning a Python project source directory that contains a dependency file such as `requirements.txt`, Grype reports against the declared versions rather than the installed versions. As a result, Chainguard’s remediated Python package versions are not recognized in this mode. To ensure accurate results, we recommend scanning the installed environment, such as a Python virtual environment directory, instead. For example, the entry `werkzeug==3.0.2` in the `requirements.txt` file results in the use of the local version `werkzeug==3.0.2+cg4.1` that includes the remediation for the CVE. This is apparent in the log output from `pip install`: ```output Collecting werkzeug==3.0.2 (from -r requirements.txt (line 11)) Downloading https://repo.example.com:8443/repository/python-all-remediated/packages/werkzeug/3.0.2%2Bcgr.1/werkzeug-3.0.2%2Bcgr.1-py3-none-any.whl (236 kB) ... Installing collected packages: MarkupSafe, werkzeug Successfully installed MarkupSafe-3.0.3 werkzeug-3.0.2+cgr.1 ``` Use the following command to scan the project directory and, accordingly, the `requirements.txt` file content: ```shell grype . ``` The resulting output shows entries for both versions, including the high severity vulnerability that is fixed in `3.0.2+cgr.1`: ```output NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISK werkzeug 3.0.2 3.0.6 python GHSA-q34m-jh98-gwm2 Medium 0.9% (74th) 0.5 werkzeug 3.0.2+cgr.1 3.0.6 python GHSA-q34m-jh98-gwm2 Medium 0.9% (74th) 0.5 werkzeug 3.0.2 3.0.3 python GHSA-2g68-c3qc-8985 High 0.2% (43rd) 0.2 werkzeug 3.0.2 3.0.6 python GHSA-f9vj-2wh5-fj8j Medium < 0.1% (19th) < 0.1 werkzeug 3.0.2+cgr.1 3.0.6 python GHSA-f9vj-2wh5-fj8j Medium < 0.1% (19th) < 0.1 ``` Scan the virtual environment in the `venv` directory instead: ```shell grype venv ``` The output only shows entries for `3.0.2+cgr.1`, the version that is actually used. The output indicates that the high vulnerability `GHSA-2g68-c3qc-8985` is no longer applicable: ```output NAME INSTALLED FIXED IN TYPE VULNERABILITY SEVERITY EPSS RISK werkzeug 3.0.2+cgr.1 3.0.6 python GHSA-q34m-jh98-gwm2 Medium 0.9% (74th) 0.5 werkzeug 3.0.2+cgr.1 3.0.6 python GHSA-f9vj-2wh5-fj8j Medium < 0.1% (19th) < 0.1 ``` For additional guidance for Grype users, refer to our guide [Using Grype to Scan Software Artifacts](/chainguard/containers/security-and-compliance/working-with-scanners/grype-tutorial/) and the [official documentation](https://github.com/anchore/grype). ### Trivy [Trivy](https://github.com/aquasecurity/trivy) versions 0.54 and newer support detection of remediated Chainguard Libraries for Python and Java after applying necessary configuration. Use the experimental VEX Repo feature of Trivy with the [VEX feed for Chainguard Libraries](/chainguard/libraries/policies-and-security/cve-remediation/#vex). Configure the Chainguard VEX feed locally: ```shell trivy vex repo init ``` The command logs the path to the created configuration file: ```output INFO [vex] The default repository config has been created file_path="~/.trivy/vex/repository.yaml" ``` The default configuration includes only the feed from the makers of Trivy, Aqua Security: ```yaml repositories: - name: default url: https://github.com/aquasecurity/vexhub enabled: true username: "" password: "" token: "" ``` Add the Chainguard feed to the top of the repository list: ```yaml repositories: - name: chainguard-libraries url: https://libraries.cgr.dev/openvex/v1 enabled: true - name: default url: https://github.com/aquasecurity/vexhub enabled: true username: "" password: "" token: "" ``` Run a scan with the Trivy CLI by explicitly specifying the `--vex repo` flag. Use the `--show-suppressed` flag to show which CVEs have been resolved by Chainguard: ```shell trivy filesystem . --vex repo --show-suppressed ``` Running the command on a Python project managed with `pip` and a dependency declaration in `requirements.txt` of `werkzeug==3.0.2+cgr.1` shows the suppressed vulnerability `CVE-2024-340691`: ```output Suppressed Vulnerabilities (Total: 1) ┌──────────┬────────────────┬──────────┬────────┬───────────┬────────────────────────────────────────┐ │ Library │ Vulnerability │ Severity │ Status │ Statement │ Source │ ├──────────┼────────────────┼──────────┼────────┼───────────┼────────────────────────────────────────┤ │ werkzeug │ CVE-2024-34069 │ HIGH │ fixed │ N/A │ VEX Repository: chainguard-libraries │ │ │ │ │ │ │ (https://libraries.cgr.dev/openvex/v1) │ └──────────┴────────────────┴──────────┴────────┴───────────┴────────────────────────────────────────┘ ``` Note that using the definition `werkzeug==3.0.2` without the local version qualifier in `requirements.txt` causes Trivy to wrongly assume the use of that specific version and therefore reports an invalid vulnerability. For additional guidance for Trivy users, refer to our guide [Using Trivy to Scan Software Artifacts](/chainguard/containers/security-and-compliance/working-with-scanners/trivy-tutorial/) as well as the [official documentation](https://trivy.dev/docs/latest/). ### Upwind [Upwind](https://www.upwind.io/) can scan container images that use Chainguard Libraries for Python and Java, and recognize Chainguard backported fixes in [remediated library](/chainguard/libraries/policies-and-security/cve-remediation/) versions. It is supported for container scanning in CI/CD only. For Python applications, support is available for: - `uv` / `pyproject.toml` - `pip` / `requirements.txt` - Poetry When Upwind recognizes a remediated Chainguard library version, the scan results reflect the installed remediated package version rather than only the original upstream version. Vulnerabilities that Chainguard has already remediated in that installed version no longer appear as active findings. ### Wiz [Wiz](https://www.wiz.io/) supports Chainguard Libraries for Python and Java when scanning applications and images that include CVE-remediated libraries. Remediated Chainguard libraries are recognized appropriately in supported scan results rather than being treated the same as original vulnerable upstream versions. Chainguard publishes remediation data through its public VEX feed, which supported scanners can use to identify remediated versions correctly. --- ### How to use Chainguard Containers with OpenShift URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/using-and-deploying/use-with-openshift.md Last Modified: October 8, 2026 Tags: Chainguard Containers, OpenShift Chainguard Containers is fully compatible with Red Hat OpenShift Container Platform, providing enhanced security while requiring some configuration adjustments for OpenShift's security context constraints. This guide explains how to successfully deploy Chainguard's minimal, security-hardened container images in OpenShift environments. [Red Hat OpenShift](https://www.redhat.com/en/technologies/cloud-computing/openshift) is an application platform that orchestrates and manages your systems and resources. While it is based on open source software like Kubernetes, OpenShift includes a suite of applications with additional functionality that are configured to work together. Using Chainguard Containers in your OpenShift deployment significantly reduces CVE remediation efforts and accelerates security compliance through minimal attack surface and daily security updates. When [Using Chainguard Containers](/chainguard/containers/using-and-deploying/using-containers/) with OpenShift, there are some adjustments that need to be made to the usual process. This guide provides guidance. Refer to the [OpenShift docs](https://docs.redhat.com/en/documentation/openshift_container_platform/) for more details. ## Adjust ownership and permissions By default, OpenShift Container Platform runs containers using an arbitrarily assigned User ID (UID), as described in the [Red Hat documentation](https://docs.redhat.com/en/documentation/openshift_container_platform/4.17/html/images/creating-images#use-uid_create-images). There are required access settings for an image to support running as an arbitrary user: - Directories and files that are written to by processes in the image must be owned by the `root` group and that group must have both `read` and `write` permissions - Files that will be executed must also have group execute permissions Following the Red Hat requirements, to use Chainguard Containers you must make a change in your Dockerfile to set the required ownership and permissions. For example, if you have one or more files that you need to execute stored in `/some/directory`, then you would do this: ``` RUN chgrp -R 0 /some/directory && \ chmod -R g=u /some/directory ``` ## Create `/app` directory and `HOME` When running on OpenShift clusters, you will find that the OpenShift user cannot create config files inside their home directory. This is because [OpenShift is designed to start container instances using a random User ID](https://www.redhat.com/en/blog/a-guide-to-openshift-and-uids). To avoid this being an issue when using Chainguard Containers: 1. Set a `HOME` variable for the user (it would otherwise be set as `/` for root) 1. Create an `/app` directory in every Dockerfile 1. Set `/app` directory permissions to `775` and ownership to `65532:0` This can help you avoid or limit switching to the `root` user during the build phase when no package installation is required. Here's a sample Dockerfile covering this process. ``` # Change this to reference the image you want to pull and # if needed, to use the location of your custom image repo FROM cgr.dev/$ORGANIZATION/aspnet-runtime:9-dev USER 0 RUN mkdir -m 775 /app && chown -R 65532:0 /app COPY --chown=65532:0 src/Sample.Service/bin/Release/net9.0/publish /app USER 65532 WORKDIR /app ENV HOME=/app ENTRYPOINT ["dotnet", "Sample.Service.dll"] ``` ## Use special container images for hard-coded user IDs There are cases where Red Hat hard codes UIDs for specific applications, for example, the user for Postgres is set to UID 26. Refer to the [Red Hat documentation](https://access.redhat.com/solutions/6996195) for more details. In this instance, Chainguard has built a special image for Postgres on OpenShift with a different release tag. Where the Postgres release version is `17.5` and the regular Chainguard Container would be released with the tag `17.5`, there is another image released with the tag `17.5-openshift`. ## Understand security context constraints (SCCs) OpenShift Container Platform includes [security context constraints](https://docs.redhat.com/en/documentation/openshift_container_platform/4.18/html-single/authentication_and_authorization/index#managing-pod-security-policies) (SCCs) that you can use to control permissions for the pods in your cluster. SCCs determine the actions that a pod can perform and what resources it can access. SCCs in OpenShift will by default prevent a root user from running with the Chainguard Containers `-dev` variants. --- ### Chainguard Libraries network requirements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/introduction/network-requirements.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Reference [Chainguard Libraries](/chainguard/libraries/introduction/overview/) require specific network access to ensure secure delivery of hardened dependencies to your development environment. This guide details the domains and ports needed for authentication, package downloads, and verification tools. ## Access for chainctl and other tools For initial configuration with chainctl and for in-process verification of downloaded libraries, you must allow HTTPS access to the following domains: * `dl.enforce.dev` for download and update of chainctl * `issuer.enforce.dev` for authentication with the Chainguard Console and with chainctl * `console-api.enforce.dev` for Chainguard Console and chainctl to administrate and use your Chainguard accounts. * `console.chainguard.dev` for the Chainguard Console to administrate and use your Chainguard accounts. * `tuf-repo-cdn.sigstore.dev` for the Sigstore trust root that `chainctl libraries verify` uses to verify library signatures. ## Access for repository managers When using a repository manager, ensure your network allows outbound HTTPS access to the following domains from your repository manager. Your workstations and build infrastructure typically require no additional network access, as libraries are served through your repository manager. * `libraries.cgr.dev` and `9236a389bd48b984df91adc1bc924620.r2.cloudflarestorage.com` for library access ## Access for development tools If accessing Chainguard Libraries directly — for example, testing with curl or running builds without a repository manager — ensure your network allows outbound HTTPS access to the following domains from your workstation: * `libraries.cgr.dev` and `9236a389bd48b984df91adc1bc924620.r2.cloudflarestorage.com` for library access If you're also using `chainctl` to verify downloaded libraries in this workflow, see [Access for chainctl and other tools](#access-for-chainctl-and-other-tools) for the additional domains required. > Note that the `9236a389bd48b984df91adc1bc924620.r2.cloudflarestorage.com` host is used to serve files via `libraries.cgr.dev`. The same host is also used to serve Chainguard Container images. --- ### Tips for migrating to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-tips.md Last Modified: October 8, 2026 Tags: Chainguard Containers The process of migrating over to Chainguard Containers isn't always straightforward. To help customers become acquainted with Chainguard Containers as they go through the migration process, we've assembled this list of tips and strategies for migrating over their applications. ## Use development variants when you need a shell Chainguard provides development (or `-dev`) variants of its containers which include a shell and package manager to allow users to more easily debug and modify the image. To illustrate, if you try to get a shell in the `cgr.dev/chainguard/nginx:latest` image it will return an error: ```bash docker run -it --entrypoint /bin/sh --user root cgr.dev/chainguard/nginx:latest docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/bin/sh": stat /bin/sh: no such file or directory: unknown. ``` But this is possible with the `latest-dev` variant: ```bash docker run -it --entrypoint /bin/sh --user root cgr.dev/chainguard/nginx:latest-dev / # apk add php fetch https://packages.wolfi.dev/os/aarch64/APKINDEX.tar.gz (1/6) Installing xz (5.4.6-r0) (2/6) Installing libxml2 (2.12.6-r0) (3/6) Installing php-8.2-config (8.2.18-r0) (4/6) Installing readline (8.2-r3) (5/6) Installing sqlite-libs (3.45.1-r0) (6/6) Installing php-8.2 (8.2.18-r0) OK: 66 MiB in 38 packages / # ``` Although the `-dev` variants have similar security features as their distroless counterparts — such as complete SBOMs and signatures — they feature additional software that is typically not necessary in production environments. The general recommendation is to use the `-dev` variants only to build the application and then copy all application artifacts into a distroless image, which will result in a final container image that has a minimal attack surface and won't allow package installations or logins. That being said, it's worth noting that `-dev` variants of Chainguard Containers are completely fine to run in production environments. After all, the `-dev` variants are still **more secure** than many popular container images based on fully-featured operating systems such as Debian and Ubuntu since they carry less software, follow a more frequent patch cadence, and offer attestations for what they include. ## Install a different shell The `-dev` variants and `chainguard-base` image use the [ash](https://en.wikipedia.org/wiki/Almquist_shell) shell from BusyBox by default. This is nice from a minimalism perspective, but it's not so great if you need to port a bash and Debian centric entrypoint script to Chainguard Containers. In these cases you have a choice — you can update your scripts to work in ash, or you can install the shell that works with your scripts. There's no reason to be stuck on the ash shell if you really need bash or zsh. For example: ```bash docker run -it cgr.dev/$ORGANIZATION/chainguard-base 423450e3fd52:/# echo {1..5} {1..5} 423450e3fd52:/# apk add bash fetch https://packages.wolfi.dev/os/aarch64/APKINDEX.tar.gz (1/3) Installing ncurses-terminfo-base (6.4_p20231125-r1) (2/3) Installing ncurses (6.4_p20231125-r1) (3/3) Installing bash (5.2.21-r1) OK: 20 MiB in 17 packages 423450e3fd52:/# bash 423450e3fd52:/# echo {1..5} 1 2 3 4 5 423450e3fd52:/# ``` Note that this example uses the `chainguard-base` image, which is only available as a paid Production Container. To follow along with this example, you would need to be part of an organization that has access to this image. ## Use `apk search` Following on from the last point, you'll often need to install extra utilities to provide required dependencies for applications and scripts. These dependencies are likely to have different package names compared to other Linux distributions, so the `apk search` command can be very useful for finding the package you need. For example, say we are porting a Dockerfile that uses the `groupadd` command. We could convert this to the BusyBox `addgroup` equivalent, but it's also perfectly fine to add the `groupadd` utility. The only issue is that there's no `groupadd` package, so we have to search for it: ```bash docker run -it cgr.dev/$ORGANIZATION/chainguard-base ae154854dc6d:/# groupadd /bin/sh: groupadd: not found ae154854dc6d:/# apk add groupadd ERROR: unable to select packages: groupadd (no such package): required by: world[groupadd] ae154854dc6d:/# apk search groupadd shadow-4.15.1-r0 ae154854dc6d:/# apk add shadow (1/4) Installing libmd (1.1.0-r1) (2/4) Installing libbsd (0.12.2-r0) (3/4) Installing linux-pam (1.6.1-r0) (4/4) Installing shadow (4.15.1-r0) OK: 20 MiB in 18 packages ae154854dc6d:/# groupadd Usage: groupadd [options] GROUP Options: -f, --force exit successfully if the group already exists, and cancel -g if the GID is already used -g, --gid GID use GID for the new group -h, --help display this help message and exit -K, --key KEY=VALUE override /etc/login.defs defaults -o, --non-unique allow to create groups with duplicate (non-unique) GID -p, --password PASSWORD use this encrypted password for the new group -r, --system create a system account -R, --root CHROOT_DIR directory to chroot into -P, --prefix PREFIX_DIR directory prefix -U, --users USERS list of user members of this group ``` Another useful trick is the `cmd:` syntax for finding packages that provide commands. For example, searching for `ldd` returns multiple results: ```bash ae154854dc6d:/# apk search ldd dpkg-dev-1.22.6-r0 nfs-utils-2.6.4-r1 posix-libc-utils-2.39-r1 ``` But if we use the `cmd:` syntax we only get a single result: ```bash ae154854dc6d:/# apk search cmd:ldd posix-libc-utils-2.39-r1 ``` And we can even use the syntax directly in `apk add`: ```bash ae154854dc6d:/# apk add cmd:ldd (1/4) Installing ncurses-terminfo-base (6.4_p20231125-r1) (2/4) Installing ncurses (6.4_p20231125-r1) (3/4) Installing bash (5.2.21-r1) (4/4) Installing posix-libc-utils (2.39-r1) OK: 27 MiB in 22 packages ``` ## Watch out for entrypoint differences In some cases, the entrypoint of Chainguard Containers can have a different behavior from their equivalent images based on other distros. This happens because many popular container images use an entrypoint script that allows running commands on the image through a shell. Since our images typically don't have a shell by default, this can lead to unexpected behavior. For example, if you run Docker Hub's official Python image, it opens the Python interpreter by default: ```bash docker run -it python Python 3.12.3 (main, Apr 10 2024, 11:26:46) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> ``` And the Chainguard Container works in the same way: ```bash docker run -it cgr.dev/chainguard/python Python 3.12.3 (main, Apr 9 2024, 16:36:34) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> exit() ``` But if you pass a Linux command to the Docker Hub image, it will be run from a shell: ```bash docker run -it python echo "in a shell" in a shell ``` The Chainguard Python image doesn't use an entrypoint script. It relies on the Python interpreter as single entrypoint for both the `latest` and the `latest-dev` variants. So instead of executing the command through a shell, it tries to parse the command as an argument to the Python interpreter: ```bash docker run -it cgr.dev/chainguard/python echo "in a shell" /usr/bin/python: can't open file '//echo': [Errno 2] No such file or directory ``` The same behavior can be observed in the `latest-dev` variant, which does contain a shell, but uses the Python interpreter as entrypoint to keep consistency with the `latest` variant: ```bash docker run -it cgr.dev/chainguard/python:latest-dev echo "in a shell" /usr/bin/python: can't open file '//echo': [Errno 2] No such file or directory ``` Other images, such as our [WordPress container images](https://images.chainguard.dev/directory/image/wordpress/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrations-overview), will have a different entrypoint behavior in their `-dev` variant to allow for customization and to facilitate migration from other base images. It's important to always read the image's documentation to understand how the entrypoint works, and if there are any major differences from other images you may be used to work with. ## Containers don't run as root by default Although there are exceptions, Chainguard's container images typically don’t run as the root user. The reason for this is that distroless containers should have no privileged capabilities, and containers that run as a non-root user and use a minimal seccomp profile are ideal from a security perspective. Because they don't run as the root user, you may need to include a `USER root` statement in your Dockerfile before installing software on a Chainguard Container. Additionally, be aware that `-dev` images also do not run as root in most cases, which can result in permission errors like the following: ```bash docker run -it --entrypoint bin/bash chainguard/python:latest-dev bash-5.2$ mkdir test mkdir: can't create directory 'test': Permission denied bash-5.2$ sudo mkdir test bash: sudo: command not found ``` In cases like this, you can instead run a command like the following to access the container's shell as the root user: ```bash docker run -it --user root --entrypoint bin/bash chainguard/python:latest-dev ``` Here, the `--user` option tells Docker to assume the root user role. ## Packages not found Container images are usually meant to support every possible use case. Because of this, they often contain packages that aren't always necessary, which increases the container image's attack surface and makes it more likely to contain CVEs. Chainguard's container images are built with minimalism in mind, and thus contain the bare minimum packages needed for an image to function. However, this also means that these images may not contain the packages that you'd expect to find in third-party alternatives. If a Chainguard Container is missing certain packages that are required for your application, we recommend using a base image and installing the required dependencies on top of it, preferably in a multi-stage Docker build. Our guides on [How to use Chainguard Containers](/chainguard/containers/using-and-deploying/using-containers/#extending-chainguard-base-containers) and [Getting started with distroless](/chainguard/containers/migration/migrations-overview/) include guidance on how you can extend Chainguard base images. For a number of our most popular Containers, Chainguard offers a *full* variant (tagged `-full`) that maps to the upstream image, including the packages you'd expect from the third-party alternative. If a full variant is available for an image you're migrating, it can serve as a low-friction starting point while you determine which packages your workload actually needs. Refer to [Full container variants](/chainguard/containers/concepts/container-variants/#full-container-variants) for details. Alternatively, you can take advantage of Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) and [Private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) features to extend your container images. Custom Assembly allows users to create customers container images with extra packages added. This reduces their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs. Private APK Repositories, meanwhile, allow customers to pull secure apk packages from Chainguard. The list of packages available in an organization’s private repository is based on the apk repositories that the organization already has access to. For example, say your organization has access to the [Chainguard MySQL container image](https://images.chainguard.dev/directory/image/mysql/versions). Along with `mysql`, this image comes with other apk packages, including `bash`, `openssl`, and `pwgen`. This means that you'll have access to these apk packages through your organization's private APK repository, along with any others that appear in Chainguard container images that your organization has access to. In some cases you may have Docker builds that copy in binaries to run agents or similar tooling. You may find these binaries don’t work as expected as they are designed to run on a different Linux distribution. Be aware that Chainguard Containers may not have the dependencies required by third-party binaries, or they may be stored at a different path. ## Learn more For more resources on migrating to Chainguard Containers, please refer to our [Containers migration documentation](/chainguard/containers/migration/). In particular, our [Migration overview](/chainguard/containers/migration/migrations-overview/) may be of interest. Chainguard Academy also hosts a number of [Compatibility resources](/chainguard/containers/migration/compatibility/) and [Migration guides](/chainguard/containers/migration/migration-guides/) for specific platforms and tools. --- ### How to set up pull through from Chainguard's registry to Cloudsmith URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/cloudsmith-pull-through.md Last Modified: October 8, 2026 Tags: Chainguard Containers Organizations often have their own internal software repositories and registries integrated into their systems. This guide explains how to set up the Cloudsmith artifact repository to ingest [Chainguard Containers](/chainguard/containers/overview/) by acting as a pull-through cache. This tutorial outlines how to set up a remote repository with [Cloudsmith](https://cloudsmith.com/). It will walk you through how to set up a Cloudsmith repository you can use as a pull through cache for Chainguard's Free containers or for Production containers originating from a private Chainguard repository. ## Prerequisites In order to complete this tutorial, you will need the following: * Docker installed on your local machine. Follow the [official installation instructions](https://docs.docker.com/engine/install/) to set this up. * Administrative privileges over a Cloudsmith project. You can set up an account by visiting the [Cloudsmith website](https://cloudsmith.com/). * If you plan to set up a Cloudsmith repository to serve as a pull through cache for Production container images, then you will also need to have privileges to create a pull token from Chainguard. * Additionally, you'll need `chainctl` installed to create the pull token. If you haven't already installed this, follow the [installation guide](/platform/chainctl-usage/how-to-install-chainctl/). ## Setting up Cloudsmith as a pull through for free containers Chainguard's [Free container images](/chainguard/containers/concepts/container-categories/#free-containers) are free to use, publicly available, and always represent versions tagged as `:latest`. To set up a remote repository in Cloudsmith through which you can pull Free container images, log in to the [Cloudsmith App](https://cloudsmith.io/user/login/). Once there, navigate to the **Repositories** tab and click **+ Create Repository**. A window will appear where you can enter the following details for your new remote repository: * **Name** — This is used to refer to your repository. You can choose whatever name you like here, but this guide's examples will use the name `chainguard-public`. * **Storage Region** — Here, select the region closest to your location. Following that, you will need to set an [upstream proxy](https://help.cloudsmith.io/docs/upstream-proxying-caching) for this repository. This is what will let Cloudsmith know where to pull container images from. In the lower left-hand navigation menu, select **Upstream Proxying**. From there, click **➕ Create Upstream** and select **Docker** as the upstream source. This will open a window where you can enter the details for the upstream source. This window has a few fields for which you need to enter values. The **Name** field can include any name you'd like for the upstream source, but it can be helpful to choose something descriptive. In our example the name is "Chainguard Public Upstream." Likewise, you can choose whatever **Priority** value you prefer. This dictates the order in which requests are resolved, with `1` being resolved first, `2` second, and so on. The most important field in this window is the **Proxy URL** value. In order to use Cloudsmith as a pull through cache for Free images, this **must** be set to `https://cgr.dev/chainguard`. Lastly, be sure that the **Mode** is set to **Cache and Proxy** and the **Verify SSL Certificates** option is selected. Then, click **Create upstream proxy**. If you entered all the details correctly, then the upstream proxy will be created successfully and you can test pulling a Free container image through Cloudsmith. ### Testing pull through of a free container image Before testing whether you can pull a Free container through Cloudsmith, you'll need to log in to the Cloudsmith registry with `docker`: ```sh docker login docker.cloudsmith.io ``` This command will prompt you to enter your Cloudsmith username and password. Your username appears in the top-right corner of the Cloudsmith web app. If you click on this, a drop-down menu will appear. Select **API Settings**; on the resulting page you'll find a field named **API Key** containing a 40-character string. You can use this API key to access Cloudsmith programmatically or, as in the case of this example, use it as a password in a `docker login` command. After running the command, you will be able to pull a Free container through your new Cloudsmith repository. The following example pulls the `nginx` container image: ```sh docker pull docker.cloudsmith.io///nginx:latest ``` Be sure to replace `` and `` with the names of your Cloudsmith organization and repository, respectively. If everything worked correctly, the image appears in your repository. If you run into issues pulling images like this, ensure that your `docker pull` command specifies the correct Cloudsmith organization and repository. ## Setting up Cloudsmith as a pull through for production container images Chainguard's Production images are enterprise-ready container images that come with patch SLAs and features such as [Federal Information Processing Standard](/platform/fips/fips-images/) (FIPS) readiness. The process for setting up a Cloudsmith repository that you can use as a pull through cache for Production containers is similar to the one outlined previously for Free containers, but with a few extra steps. You can create a new Cloudsmith repository or use the same repository you used as a pull through cache for Free containers. Next, you'll need to create [a pull token](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) for your organization's registry through Chainguard. Pull tokens are longer-lived tokens that can be used to pull Containers from other environments that don't support OIDC, such as some CI environments, Kubernetes clusters, or with registry mirroring tools like Cloudsmith. Log in with `chainctl`: ```sh chainctl auth login ``` Then configure a pull token: ```sh chainctl auth configure-docker --pull-token ``` By default, this will create a pull token that lasts for 30 days. You can adjust this by appending the command with the `--ttl` flag (for example, `--ttl=24h`). This command will prompt you to select an organization. Be sure to select the organization whose Production Containers you want to pull through your Cloudsmith repository. This will create a pull token and print a `docker login` command that can be run in a CI environment to log in with the token. This command includes both `--username` and `--password` arguments. You don't need to run this `docker login` command, but you will need the username and password values in a moment so note them down. Following that, you'll need to create another upstream source. Return to the Cloudsmith web app and navigate to the **Upstream Proxying** page. Click **➕ Create Upstream** and select **Docker** as the upstream source. Again, set a **Name** and **Priority** level for this source and ensure that the **Mode** is set to **Cache and Proxy**. When pulling from a private registry through Chainguard, the **Upstream URL** must be set to `https://cgr.dev/`; any other URL here will cause an error. Lastly, you need to add the username and password you received when you generated the pull token to the upstream source. To do this, expand the **Authentication** section and under **Method** select **Username and Password**. Then enter the username and password you noted down earlier in their respective fields. Finally, click **Create upstream proxy**. With that, you're ready to test a Chainguard Production Container through Cloudsmith. ### Testing pull through of a Chainguard production container: As with testing pull through of a Free container image, you'll first need to authenticate to Cloudsmith: ```sh docker login docker.cloudsmith.io ``` After running the command, you will be able to pull any Production containers that your organization has access to through Cloudsmith. To do so, you would run a command with syntax like the following: ```sh docker pull docker.cloudsmith.io////IMAGE ``` As with the `docker pull` command used in the previous section, you will need to change the `` and `` values to reflect your own Cloudsmith setup. Additionally, be sure to change `` to the name of your organization's Chainguard registry. As an example, the following command will pull the `python:3.9-dev` image from a Chainguard registry named `chainguard.edu` and through a Cloudsmith repository named `chainguard-private` owned by a Cloudsmith organization named `chainguard-example`: ```sh docker pull docker.cloudsmith.io/chainguard-example/chainguard-private/chainguard.edu/python:3.9-dev ``` Once this command is completed you will find the Production container you pulled in your Cloudsmith repository. If you run into issues pulling images like this, be sure that your `docker pull` command specifies the correct Cloudsmith organization and repository as well as the correct Chainguard registry. ## Debugging pull through from Chainguard's registry to Cloudsmith If you run into issues when trying to pull Containers from Chainguard's registry to Cloudsmith, please make sure the following requirements are met: * Ensure that all Containers [network requirements](/chainguard/containers/registry/network-requirements/) are met. * When configuring a remote Cloudsmith repository, ensure that the **URL** field is set correctly. For Free container images, this should be `https://cgr.dev/chainguard`; for Production containers this should be `https://cgr.dev/`. This field **must not** contain any additional components. * You can troubleshoot by running `docker login` from another node (using the Cloudsmith pull token credentials) and try pulling an image from `cgr.dev/chainguard/` or `cgr.dev//`, using your own organization's registry name in place of ``. * It could be that your Cloudsmith repository was misconfigured. In this case, create and configure a new Cloudsmith repository to test with. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by checking out our [Containers documentation](/chainguard/containers/overview/). If you'd like to learn more about Cloudsmith, we encourage you to refer to the [official documentation](https://help.cloudsmith.io/docs/welcome-to-cloudsmith-docs). --- ### STIGs for Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/stigs.md Last Modified: October 8, 2026 Tags: Chainguard Containers Security Technical Implementation Guides (STIGs) trace their origin to the United States Department of Defense (DoD). They work in two layers. The Defense Information Systems Agency (DISA) publishes Security Requirements Guides (SRGs) — category-level security baselines covering technology types such as databases, web servers, or general purpose operating systems, written without vendor participation. Vendors then collaborate with DISA to produce a product-specific STIG — a formally reviewed configuration guide for a particular product, derived from the relevant SRG. If an organization runs software like MySQL 8.0 in a DoD environment, it must be configured to meet that product's STIG. Beyond the DoD, compliance frameworks such as FedRAMP and CMMC have come to recognize STIGs as accepted security baselines. [Chainguard has published](https://www.chainguard.dev/unchained/stig-hardening-container-images) a hardening profile based on the [General Purpose Operating System (GPOS) SRG](https://stigviewer.com/stigs/general_purpose_operating_system_security_requirements_guide), the DISA framework that covers security requirements for general purpose operating systems in a networked environment. Because DISA has never published a container-specific SRG, no formally approved container-only STIG exists from any vendor today. Applying the GPOS SRG to containers is the established industry approach for closing this gap, consistent with how other Linux-based container vendors address container security requirements. Throughout this documentation, "STIG" refers to this GPOS SRG-based hardening profile. The goal is to help customers in compliance-sensitive environments confidently validate Chainguard Containers against recognized security requirements and integrate them into DoD and related workflows. The Chainguard GPOS SRG profile applies to all Chainguard Containers — including both FIPS and non-FIPS images. The Getting Started (#getting-started) section demonstrates how to use OpenSCAP to validate hardening checks against any Chainguard Container. ## Getting started The instructions below use OpenSCAP. For an InSpec-based alternative using cinc-auditor, Chainguard also maintains [chainguard-inspec](https://github.com/chainguard-dev/chainguard-inspec), which is a good fit for teams already using InSpec for compliance automation or working with distroless and short-lived containers. The recommended way to get started with Chainguard's STIG for the GPOS SRG is to use the Chainguard [`openscap`](https://images.chainguard.dev/directory/image/openscap/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-image-stigs) Container. This includes the `openscap` tool itself, the `oscap-docker` libraries, and the Chainguard GPOS STIG profile. This image is built with the same capabilities and low-to-zero CVEs as every other Chainguard Container, and makes the `openscap` tool — which can be difficult to set up — more portable. The following instructions assume that you have `docker` installed and running on your system, and are intended to be performed on a non-production system, similar to the process outlined in [DISA's Container Hardening Whitepaper](https://dl.dod.cyber.mil/wp-content/uploads/devsecops/pdf/Final_DevSecOps_Enterprise_Container_Hardening_Guide_1.2.pdf). For ease of use, we'll use [the datastream file](https://raw.githubusercontent.com/chainguard-dev/stigs/main/gpos/xml/scap/ssg/content/ssg-chainguard-gpos-ds.xml) sourced from [the Chainguard STIGs repository](https://github.com/chainguard-dev/stigs/tree/main/gpos/xml/scap/ssg/content), and available within Chainguard's `openscap` container image. This file serves as a sort of checklist, outlining each of the requirements that must be met in order to conform with the STIG. Download the datastream file — named `ssg-chainguard-gpos-ds.xml` — with the following command: ```bash curl -fsSLO https://raw.githubusercontent.com/chainguard-dev/stigs/main/gpos/xml/scap/ssg/content/ssg-chainguard-gpos-ds.xml ``` The `-O` option redirects the file's contents into a local file of the same name in your working directory. You can then view the checklist locally. We'll refer to Chainguard's `openscap` container image as the `scan` image, and the `target` image we'll be scanning will be: `cgr.dev/chainguard/wolfi-base:latest`. This is a non-FIPS image and is used as an example; you can substitute any Chainguard Container image as the target. You can scan either a registry image or a running container. ### Scanning a registry image First, ensure the target image is present in your local Docker daemon. ```bash docker pull cgr.dev/chainguard/wolfi-base:latest ``` Next, run the scan image against the target image. ```bash docker run -i --rm -u 0:0 --pid=host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd)/out:/out \ --entrypoint sh \ cgr.dev/chainguard/openscap:latest-dev <<_END_DOCKER_RUN oscap-docker image cgr.dev/chainguard/wolfi-base:latest xccdf eval \ --profile "xccdf_basic_profile_.check" \ --report /out/report.html \ --results /out/results.xml \ /usr/share/xml/scap/ssg/content/ssg-chainguard-gpos-ds.xml _END_DOCKER_RUN ``` ### Scanning a running container First, start the target image: ```bash docker run --name target -d cgr.dev/chainguard/wolfi-base:latest tail -f /dev/null ``` Next, run the scan image against the target image. ```bash docker run -i --rm -u 0:0 --pid=host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v $(pwd)/out:/out \ --entrypoint sh \ cgr.dev/chainguard/openscap:latest-dev <<_END_DOCKER_RUN oscap-docker container target xccdf eval \ --profile "xccdf_basic_profile_.check" \ --report /out/report.html \ --results /out/results.xml \ /usr/share/xml/scap/ssg/content/ssg-chainguard-gpos-ds.xml _END_DOCKER_RUN ``` > **Warning:** This is a highly privileged container because it scans a container running under the host Docker daemon. Results are written to a new `out/` subdirectory in the current working directory. The `report.html` file contains a human-readable report of the scan results; `results.xml` contains the raw results. ## What are STIGs? "STIG" stands for Security Technical Implementation Guide. A STIG is a technology-specific implementation of a Security Requirements Guide (SRG) that a security administrator follows to ensure that a given piece of software has been hardened against cybersecurity threats. A STIG is typically written by the developer or vendor of the given piece of software against a published DOD Security Requirements Guide (SRG). STIGs are presented in the XCCDF (Extensible Configuration Checklist Description Format), allowing them to be ingested into a SCAP-validated tool to validate that a given target is in compliance with them. After drafting the STIG, the vendor will submit it to the [Defense Information Systems Agency (DISA)](https://www.disa.mil/), an agency within the DoD. One of DISA's responsibilities is publishing and maintaining STIGs on the [DoD Cyber Exchange website](https://www.cyber.mil/stigs/downloads/), and the process from a STIG being submitting to it being published by DISA can take years. As of this writing, DISA has published over 450 STIGs for a wide variety of software applications. ## How STIGs can be used to harden container images STIGs are typically published for hardware, firmware, and specific applications. However, in recent years containerization has grown in popularity and is now the modern way to deploy applications. This has resulted in there being a gap in terms of clear instructions on how to securely deploy containerized applications. Chainguard produces hardened, minimal container images containing few to zero CVEs. Because of this, they adhere to many compliance standards where there's a control for vulnerability or risk management, including [FedRAMP](https://www.fedramp.gov/) and [PCI DSS v4.0](https://www.pcisecuritystandards.org/document_library/?category=pcidss). For many risk management frameworks, the system categorization will result in varying levels of hardening guidelines. Using FedRAMP as an example, FIPS-199 defines the security categorization which will result in a baseline set of controls (defined in NIST 800-53) that must be implemented. Because the NIST 800-53 controls are technology-neutral, the STIGs published by DISA provide technology-specific configurations on how to satisfy the applicable NIST 800-53 control set. As stated in the CM-6 (a) Requirement 1 of the FedRAMP System Security Plan: _"The service provider shall use the DoD STIGs to establish configuration settings; Center for Internet Security up to Level 2 (CIS Level 2) guidelines shall be used if STIGs are not available; Custom baselines shall be used if CIS is not available."_ However, the requirements for how a STIG applies to a container image are rather unclear. For example, some controls apply to the host operating system instead of the image. Similarly, other controls apply to the container runtime instead of the container itself. Knowing what controls are relevant for containers and how to check for them in a STIG are key to achieving and maintaining FedRAMP compliance. DISA understands that containers have different requirements than traditional operating systems. In an effort to highlight these differences, the DOD DevSecOps Initiative released the [Container Hardening Process Guide](https://dl.dod.cyber.mil/wp-content/uploads/devsecops/pdf/Final_DevSecOps_Enterprise_Container_Hardening_Guide_1.2.pdf) which describes the Initiative's approach to hardening images and how other agencies should handle applying STIGs to containers. [Appendix C](https://dl.dod.cyber.mil/wp-content/uploads/devsecops/pdf/Final_DevSecOps_Enterprise_Container_Hardening_Guide_1.2.pdf#%5B%7B%22num%22%3A65%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C70%2C720%2C0%5D) of the Hardening Process Guide highlights an important point about STIG compliance for containers: _"With a properly locked down hosting environment, containers inherit most of the security controls and benefits from infrastructure to host OS-level remediation requirements."_ Deploying containers on a STIG hardened host provides many of the security features that are difficult or sometimes impossible to implement inside a container. What's left then is the application-level security configuration — in particular vulnerability remediation — which Chainguard provides through our guaranteed vulnerability remediation SLAs. ## False positives and the General Purpose OS STIG Here we've assembled several explanations for requirements from Chainguard's General Purpose Operating System STIG that are likely to cause false positives when scanning containers, as well as the rationale for those requirements. By disambiguating these false positives, the following sections should be helpful to any administrators deploying containers in environments where STIG hardening is necessary both as a means to understand where to expend effort performing hardening and for discussions with assessors and compliance personnel. ### Auditing Auditing capabilities for Linux containers are implemented by the underlying host's audit configuration. In Linux, the auditing program `auditd` leverages multiple functions in the running kernel through the Linux Auditing System to capture runtime information such as starting and stopping processes, opening sockets, and accessing files. Linux containers use their host's kernel, making it impossible to install and operate a separate auditing package inside the container itself. Instead, auditing information must be configured and collected through the auditing configuration of the host. Once configured, logs of container actions are written to the host's audit log files and are readable only by the host superuser account. These logs must be collected from the host for incident response and reporting. Storage capacity limits, audit process monitoring, remote upload of logs, and associated alerts are the responsibility of the host where the containers are running. ### Isolation Containers provide process isolation by executing their applications in a constrained environment using the Linux Namespace and cgroup subsystems. Inside the namespace, container processes are only permitted to access a limited set of system resources defined when the container is launched. Processes are further restricted by limits imposed through the cgroup for access to system resources such as system memory or CPU use. Together, namespaces and cgroups isolate security functions of the host operating system from non-security functions of applications running inside the container. This separation makes it possible for container failures to not directly impact the operation of the host and its security functions when caused through processing of invalid inputs or other runtime errors. In the event of a container failure during initialization or shutdown, for example, the host operating system's security capabilities will continue to function as configured. ### Minimal container images Chainguard's container images contain only the minimal software needed for the container to perform its intended function. Nonessential capabilities such as package managers, shell environments, executables, and process launching functions have been removed from many of these images and may not be installed once the container is running. This limited implementation means that only the necessary software to operate can run on the container and restricts the installation of additional software on the image during operation. Be sure to have fixed permissions on libraries and executable files in place so that any software installed can't be modified. The host's container execution environment further reduces the risk of unauthorized modification of software through Linux container isolation capabilities including namespaces and cgroups. These restrictions prevent unauthorized modification of the host operating system environment. ### Address space layout randomization (ASLR) ASLR configuration is the responsibility of the host operating system on which containers run. Applications running within a container on a host that has ASLR enabled will automatically be protected by the configuration. No additional action is needed to ensure that container-based applications are protected. ### Host firewall Linux containers inherit the firewall configuration of their host operating system which dictates which ports on the container can be accessed from the network. Selection of which ports to make accessible on the applications running on the container is the responsibility of the host firewall configuration — an additional application-level firewall inside the container is not necessary. ### Host filesystem Linux containers use the host's filesystem for storage of their files and configuration. To protect data at rest inside containers from unauthorized access or modification, you must modify the host operating system's configuration. As an example, you might set up encrypted virtual filesystems. The host filesystem is also responsible for the size, utilization, and capacity of the physical disks that are used by containers running on that host. ### Vulnerability scanning The team deploying the container is also responsible for scanning it for vulnerabilities. This scanning can be executed from the host operating system or against the container image when it is stored in a registry. Continuous scanning can be used to detect vulnerabilities that have been identified / announced since the previous scan and determine when updated images should be built and deployed in the environment. ### Time Linux containers inherit the system time from the underlying host; likewise, containers don't operate their own separate time services. The host owner is responsible for configuring the host's time service to generate the timestamp used by its auditing system, perform periodic synchronization, and ensure that only authorized time servers are used as the authoritative source. Time synchronization of the host clock is automatically reflected in the time used by the container. ### STIGs These containers can be validated against the General Purpose Operating System STIG and other applicable STIGs using the [OpenSCAP toolset](http://www.open-scap.org/tools/). OpenSCAP can validate configuration of container images by reviewing the configuration of the image filesystem and can perform interactive checks by executing commands against running containers. ## Learn more You can use OpenSCAP to validate hardening checks against any Chainguard Container, including both FIPS and non-FIPS images, using the process described in [Getting started](#getting-started). Chainguard's STIG hardened FIPS Containers are also generally available. You can check out our [STIG repo](https://github.com/chainguard-dev/stigs?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) or [contact us](https://get.chainguard.dev/simplify-fedramp-compliance-5?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) for more information. If you'd like to learn more about how Chainguard Containers can help you meet FedRAMP compliance, we encourage you to refer to our overview of [Chainguard's FIPS-ready container images](/platform/fips/fips-images/). --- ### Migrate a PHP application to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-guides/php.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Migration Chainguard's PHP container images provide enhanced security for PHP applications through minimal, purpose-built images that reduce attack surface. Based on [Wolfi](/open-source/wolfi/), they include only the packages your application needs, while maintaining full compatibility with PHP workloads. Daily automated builds deliver the latest security patches without manual intervention. For current CVE data on each PHP image and tag, see the [PHP entry in the Chainguard Containers directory](https://images.chainguard.dev/directory/image/php/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-php). This article will assist you in the process of migrating your existing PHP Dockerfiles to leverage the benefits of Chainguard Containers, including a smaller attack surface and a more secure application footprint. ## PHP Chainguard Containers Chainguard offers multiple PHP images and variants catering to distinct use cases. In addition to the regular PHP image that includes CLI and FPM variants, we offer a dedicated [Laravel](https://images.chainguard.dev/directory/image/laravel/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-php) image designed for Laravel applications. Each variant comes in two flavors: a minimal runtime image (distroless) and a development variant distinguished by the `-dev` suffix (e.g., `latest-dev`). In a nutshell, distroless images don't include a package manager or a shell, being used exclusively as runtimes to keep the environment to a minimum. Development variants, on the other hand, include packages such as `apk` and `composer` for building PHP applications. Development variants can be used _as-is_ to provide a more straightforward migration path. Whenever possible, though, we encourage users to combine both images in a multi-stage environment to build a final distroless image that will function strictly as an application runtime.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

For a deeper exploration of distroless images and their differences from standard base images, refer to the guide on [Getting started with distroless images](/chainguard/containers/concepts/getting-started-distroless/). ## Migrating from non-apk systems When migrating from distributions that are not based on the `apk` ecosystem, you'll need to update your Dockerfile accordingly. Our high-level guide on [Migrating to Chainguard Containers](/chainguard/containers/migration/migrating-to-chainguard-images/) contains details about distro-based migration and package compatibility when migrating from Debian, Ubuntu, and Red Hat UBI base images. ## Installing PHP extensions Wolfi offers several PHP extensions as optional packages you can install with `apk`. Because PHP extensions are system-level packages, they require `apk` which is only available in our development image variants. The following extensions are already included within all Chainguard PHP image variants: - `php-mbstring` - `php-curl` - `php-openssl` - `php-iconv` - `php-mysqlnd` - `php-pdo` - `php-pdo_sqlite` - `php-pdo_mysql` - `php-sodium` - `php-phar` In addition to those, the Laravel image includes the following extensions: - `php-ctype` - `php-dom` - `php-fileinfo` - `php-simplexml` You can run a temporary container with the `php -m` command to see a list of enabled extensions: ```shell docker run --rm --entrypoint php cgr.dev/chainguard/php -m ``` To check for extensions available for installation, you can run a temporary container with the `dev` variant of a given image and use `apk` to search for packages. For instance, this will log you into a container based on the `php:latest-dev` image: ```shell docker run -it --rm --entrypoint /bin/sh --user root cgr.dev/chainguard/php:latest-dev ``` Make sure to update the package manager cache: ```shell apk update ``` If you want to search for PHP 8.2 XML extensions, for example, you can run the following: ```shell apk search php*8.2*xml* ``` And this should give you a list of all PHP 8.2 XML extensions available in Wolfi. ```output php-8.2-simplexml-8.2.17-r0 php-8.2-simplexml-config-8.2.17-r0 php-8.2-xml-8.2.17-r0 php-8.2-xml-config-8.2.17-r0 php-8.2-xmlreader-8.2.17-r0 php-8.2-xmlreader-config-8.2.17-r0 php-8.2-xmlwriter-8.2.17-r0 php-8.2-xmlwriter-config-8.2.17-r0 php-simplexml-8.2.11-r1 php-xml-8.2.11-r1 php-xmlreader-8.2.11-r1 php-xmlwriter-8.2.11-r1 ``` For more searching tips, check the [Searching for packages](/chainguard/containers/migration/migrating-to-chainguard-images/#searching-for-packages) section of our base migration guide. ## Migrating PHP CLI workloads to use Chainguard Containers Our `latest` and `latest-dev` PHP image variants are designed to run CLI applications and scripts that don't need a web server. As a first step towards migration, you might want to change your base image to the `latest-dev` variant, since that would be the closest option for a drop-in base image replacement. Once you have your dependencies and steps dialed in, you can optionally migrate to a multi-stage build to create a strict runtime containing only what the application needs to run. The following `Dockerfile` uses the `php:latest-dev` image to build the application, which in this case means copying the application files and installing dependencies via `composer`. A second build stage copies the application to a final distroless image based on `php:latest`. ```Dockerfile FROM cgr.dev/chainguard/php:latest-dev AS builder USER root COPY .. /app RUN chown -R php /app USER php RUN cd /app && \ composer install --no-progress --no-dev --prefer-dist FROM cgr.dev/chainguard/php:latest COPY --from=builder /app /app ENTRYPOINT [ "php", "/app/myscript.php" ] ``` Our [PHP getting started](/chainguard/containers/getting-started/languages-and-runtimes/php/) guide has step-by-step instructions on how to build and run a PHP CLI application with Chainguard Containers. ## Migrating PHP web applications to use Chainguard Containers For PHP web applications that serve content through a web server, you should use the `latest-fpm` and `latest-fpm-dev` variants of our PHP image. Combine it with our [nginx image](https://images.chainguard.dev/directory/image/nginx/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-php) and an optional database for a traditional LEMP setup. The overall migration process is essentially the same as described in the previous section, with the difference that you won't set up application entry points, since these images run as services. Your Dockerfile may require additional steps to set up front-end dependencies, initialize databases, and perform any additional tasks needed for the application to run through a web server. You can use [Docker Compose](https://docs.docker.com/compose/) to create a LEMP environment and test your setup locally. The following `docker-compose.yaml` file creates a local environment to serve a PHP web application using our PHP-FPM, Nginx, and MariaDB images: ```yaml version: "3.7" services: app: image: cgr.dev/chainguard/php:latest-fpm-dev restart: unless-stopped working_dir: /app volumes: - ./app:/app networks: - wolfi nginx: image: cgr.dev/chainguard/nginx restart: unless-stopped ports: - 8000:8080 volumes: - ./app:/app - ./nginx.conf:/etc/nginx/nginx.conf networks: - wolfi mariadb: image: cgr.dev/chainguard/mariadb restart: unless-stopped environment: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 MARIADB_USER: user MARIADB_PASSWORD: password MARIADB_DATABASE: php-test ports: - 3306:3306 networks: - wolfi networks: wolfi: driver: bridge ``` Notice that the environment creates a few shared volumes to share the application files and also the `nginx.conf` file with the specific web server configuration. You'll need that to tell Nginx to redirect `.php` requests to the `php-fpm` service. An example `nginx.conf` file to handle PHP requests with default settings: ```yaml pid /var/run/nginx.pid; events { worker_connections 1024; } http { server { listen 8080; index index.php index.html; root /app/public; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } } } ``` ## Migrating Laravel applications to use Chainguard Containers Chainguard has a dedicated [Laravel image](https://images.chainguard.dev/directory/image/laravel/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-php) designed for applications built on top of the [Laravel](https://laravel.com) PHP Framework. This image is based on the `php:latest-fpm` variant, with additional extensions required by Laravel. Migration should follow the same steps described in previous sections, with the `laravel:latest-dev` variant as builder and `laravel:latest` as the distroless variant of this image. In addition to including extensions required by Laravel by default, the image includes a **laravel** system user that facilitates running `composer` and `artisan` commands from a host environment, which enables users to create and develop Laravel applications with the `-dev` variant of this image. Check the section on [Developing Laravel applications](#developing-laravel-applications) for more information on how to use the development variant of the Laravel image for development environments. ## Using development containers Our PHP development images are minimal yet versatile images that include `apk` and `composer`. You can use these images to create and develop PHP applications on a containerized development environment. Development images can be identified by the `-dev` suffix (e.g: `php:latest-dev`). You can use them to execute Composer commands from a Dockerfile or directly from the command line with `docker run`. This allows users to run Composer without having to install PHP on their host system. ### Running Composer To be able to write to your host's filesystem through a shared volume, you'll need to use the **root** container user when installing dependencies with Composer using the `latest-dev` or `latest-fpm-dev` image variants. **Example 1: Installing Dependencies** ```shell docker run --rm -v ${PWD}:/app --entrypoint composer --user root \ cgr.dev/chainguard/php:latest-dev \ install ``` **Example 2: Requiring a new dependency** ```shell docker run --rm -v ${PWD}:/app --entrypoint composer --user root \ cgr.dev/chainguard/php:latest-dev \ require minicli/minicli ``` You'll need to fix file permissions once installation is finished: ```shell sudo chown -R ${USER}:${USER} . ``` ### Running the built-in web server You can use the built-in PHP web server to preview web applications using a `docker run` command with a port redirect. The following command will run the `php:latest-dev` image variant with the built-in server (`php -S`) on port `8000`, redirecting all requests to the same port on the host machine, and using the current folder as document root: ```shell docker run -p 8000:8000 --rm -it -v ${PWD}:/work \ cgr.dev/chainguard/php:latest-dev \ -S 0.0.0.0:8000 -t /work ``` The preview should be live at `localhost:8000`. ### Developing Laravel applications The `laravel:latest-dev` image has a system user with uid `1000` that can be used for development. This facilitates handling file permissions when working with shared volumes in a development environment. **Creating a New Laravel Project** The following command will create a new Laravel project called **demo-laravel** in the current folder. ```shell docker run --rm -v ${PWD}:/app --entrypoint composer --user laravel \ cgr.dev/chainguard/laravel:latest-dev \ create-project laravel/laravel demo-laravel --working-dir=/app ``` **Running Artisan Commands** To run Artisan commands, you'll need to create a volume to share your Laravel application within the container and set the image entrypoint to `/app/artisan`. The following example runs the `artisan migrate` command from the application folder: ```shell docker run --rm -v ${PWD}:/app --entrypoint /app/artisan --user laravel \ cgr.dev/chainguard/laravel:latest-dev \ migrate ``` **Running the Artisan Web Server** To quickly preview your Laravel application, you can use Artisan's built-in web server. The following command runs the built-in Artisan web server from the application folder: ```shell docker run -p 8000:8000 --rm -it -v ${PWD}:/app --entrypoint /app/artisan --user laravel \ cgr.dev/chainguard/laravel:latest-dev \ serve --host=0.0.0.0 ``` The preview should be live at `localhost:8000`. ## Additional resources Our [PHP image documentation](https://images.chainguard.dev/directory/image/php/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-php) covers details about all PHP image variants, including the list of available tags for both development and production images. For another example of a LEMP setup using MariaDB, check our guide on [Getting started with the MariaDB Chainguard Container](https://edu.chainguard.dev/chainguard/containers/getting-started/web-and-data-services/mariadb/). The [Debugging distroless](/chainguard/containers/troubleshooting/debugging-distroless-images/) guide contains important information for debugging issues with distroless images. You can also refer to the [Verifying containers](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/) resource for details around provenance, SBOMs, and image signatures. --- ### Getting started with distroless container images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/getting-started-distroless.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Overview ## About distroless container images [Distroless](https://www.chainguard.dev/unchained/minimal-container-images-towards-a-more-secure-future) container images, like the ones built by Chainguard, are a type of container image designed to include only essential software required to run an application or service. Unlike traditional images based on Debian or Ubuntu — which include package managers, utilities, and shells — Chainguard's distroless images remove these components to significantly reduce attack surface and minimize vulnerabilities. This minimal approach offers several benefits, including: - **Enhanced Security:** By stripping out unnecessary components, distroless images reduce the potential attack surface for vulnerabilities. With fewer extraneous programs, there are fewer opportunities for malicious actors to exploit. - **Simplified Dependency Management:** Traditional container images can introduce dependency bloat, making it difficult to track and manage exactly what's included. Distroless images keep things clear by only containing what's directly required for the application to function. - **Potentially Smaller Image Sizes:** By eliminating extraneous OS components, distroless images can be significantly smaller than their full-blown counterparts. Chainguard offers a mix of distroless and container images, which are minimalist and contain provenance attestations for increased security, and development (or `-dev`) images, which feature development tools like a shell or package manager. Since distroless images have fewer tools and don't come with a package manager, some adaptation might be necessary when migrating from traditional base images. A typical approach is using multi stage builds to compose a final distroless image containing the additional artifacts required by the application in order to run successfully. ## Multi stage builds A multi stage build is a technique for creating slimmer and more efficient container images. It allows you to define multiple stages within a single Dockerfile. Each stage acts like a separate build environment with its own base image and instructions. The key benefit of multi stage builds is that they enable you to separate the build process from the final runtime environment. This separation helps in reducing the final image size by: - **Using different base images**: You can leverage a larger image containing all the build tools in the initial stage and then switch to a smaller, leaner base image for the final stage that only includes the necessary runtime dependencies for your application. - **Excluding unnecessary layers**: By separating the build and runtime stages, you can exclude all the temporary files, build tools, and intermediate artifacts from the final image. These elements are only required during the build process and not needed when running the application. Overall, multi stage builds promote efficient container images by minimizing their size and optimizing their contents for execution. ### Example 1: Distroless images as runtime for static binaries Distroless images are typically designed to work as platforms for running workloads in as minimal an environment as possible. In the case of languages that can compile completely static binaries (such as C and Rust), the **static** base image can be used as a runtime. You'll still need to get your application compiled in a separate build stage that has the tooling necessary to build it. In this example, we'll build a distroless image to run a "Hello World" program in C. Start by creating a directory for the demo. We'll call it **distroless-demo**. ```sh mkdir ~/distroless-demo && cd $_ ``` Create a new Dockerfile within this directory. You can use `nano` or other command line editor of your choice. ```sh nano Dockerfile ``` The following Dockerfile will build a final distroless image using two distinct build stages. The first stage, named `build`, builds a C program using the `cgr.dev/chainguard/gcc-glibc:latest` image. The final image, which is then based on the `cgr.dev/chainguard/static:latest` distroless image, will copy the compiled binary from the `build` environment and define it as the entry point command for the final image. ```dockerfile # syntax=docker/dockerfile:1.4 FROM cgr.dev/chainguard/gcc-glibc:latest AS build COPY < int main() { printf("Hello Distroless!%c",0x0A); } EOF RUN cc -static /hello.c -o /hello FROM cgr.dev/chainguard/static:latest COPY --from=build /hello /hello CMD ["/hello"] ``` Run the following command to build the demo image and tag it as `c-distroless`: ```shell DOCKER_BUILDKIT=1 docker build -t c-distroless . ``` If you receive an error, you may try removing the top line of the Dockerfile. Now you can run the image with: ```shell docker run c-distroless ``` You should get output like this: ```output Hello Distroless! ``` You can note the size of the resulting image. ```shell docker images c-distroless ``` ```output REPOSITORY TAG IMAGE ID CREATED SIZE c-distroless latest cd3bb76a84f5 45 seconds ago 2.04MB ``` If you look into the image layers with `docker inspect c-distroless`, you'll also notice that it has only two layers: a single layer from the `static` image that serves as base for the final image, and one layer with the `COPY` command that brings in the compiled binary from the **build** stage. ```shell "RootFS": { "Type": "layers", "Layers": [ "sha256:cfc10a76380242be256af62b8782e536770dee83dcc823fce6c196c1ef5638e5", "sha256:bc7690d8bd810d969e6601d8468b4ae42fa411dfe460440e96092db454d80080" ] }, ``` ### Example 2: Incorporating application-level dependencies in distroless images When working with language ecosystems that have their own dependency management tools such as PHP (Composer) and Node (npm), a multi stage build is necessary to include application dependencies within the final distroless runtime. The next example creates a Dockerfile to run a demo PHP application that has third-party dependencies managed by [Composer](https://getcomposer.org/). The application is a single executable that queries the [cat facts API](https://catfact.ninja/) and returns a random fact. Start by creating a directory for the demo. We'll call it **distroless-php**. ```sh mkdir ~/distroless-php && cd $_ ``` The following command will create a new `composer.json` file with a single dependency, a small curl library called `minicli/curly`. We are using a shared volume so that the `vendor` folder is shared with our local directory. ```shell docker run --rm --entrypoint composer --user=root -v ${PWD}:/app cgr.dev/chainguard/php:latest-dev require minicli/curly ``` In this case, we had to use the **root** image user in order to be able to write files in the current host directory. The following command will fix file permissions for our current system user: ```shell sudo chown -R ${USER}:${USER} . ``` Now create the PHP executable. You can call it `catfact.php`: ```shell nano catfact.php ``` The following code makes a query to the cat facts API, returning the quote as output. Copy the contents to your own `catfact.php` script: ```catfact.php get("https://catfact.ninja/fact"); if ($response['code'] === 200) { echo "\n" . json_decode($response['body'], true)['fact'] . "\n"; return 0; } echo "query error."; return 1; ``` Save the file when you're done. Now you can create your Dockerfile. ```sh nano Dockerfile ``` The following Dockerfile will create a `run.php` script that makes a curl query using the library we just added as a dependency. ```Dockerfile FROM cgr.dev/chainguard/php:latest-dev AS builder USER root COPY . /app RUN chown -R php /app USER php RUN cd /app && \ composer install --no-progress --no-dev --prefer-dist FROM cgr.dev/chainguard/php:latest COPY --from=builder /app /app ENTRYPOINT [ "php", "/app/catfact.php" ] ``` Now you can build the image with: ```shell docker build . -t distroless-demo-php ``` Finally, you can run the new app with: ```shell docker run --rm distroless-demo-php ``` And you should get a cat fact as output, such as: ```output A domestic cat can run at speeds of 30 mph. ``` Upon inspection with `docker images`, you can check the image size around 38MB: ```shell docker images distroless-demo-php ``` ```output REPOSITORY TAG IMAGE ID CREATED SIZE distroless-demo-php latest 8691d09f56ca 2 minutes ago 37.9MB ``` For comparison, the `php:cli-alpine` image is almost 3 times bigger: ```shell ❯ docker images php:cli-alpine REPOSITORY TAG IMAGE ID CREATED SIZE php cli-alpine 7879e816aba0 6 days ago 104MB ``` ## Final considerations Distroless images offer a compelling approach to creating minimal and secure container images by stripping away system components that are unnecessary at execution time, such as package managers and shells. While such images offer many advantages, they might require some adjustments in your existing development and deployment workflows. In this guide we demonstrated how to use multi stage builds to create final distroless images that include additional components, such as static binaries and application-level dependencies. You can find more examples in our [Getting started guides](/chainguard/containers/getting-started/) page. Check also our article on [Debugging distroless images](/chainguard/containers/troubleshooting/debugging-distroless-images/) for important tips when you run into issues and need to debug containers running distroless images. --- ### Ubuntu compatibility URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/compatibility/ubuntu-compatibility.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Reference Chainguard Containers and Ubuntu base images have different binaries and scripts included in their respective `busybox` and `coreutils` packages. The following table lists common tools and their corresponding package(s) in both Wolfi and Ubuntu distributions. Note that `$PATH` locations like `/usr/bin` or `/sbin` are not included here. If you have compatibility issues with tools that are included in both `busybox` and `coreutils`, be sure to check `$PATH` order and confirm which version of a tool is being run. Generally, if a tool exists in `busybox` but does not have a `coreutils` counterpart, there will be a specific package that includes it. For example the `zcat` utility is included in the `gzip` package in both Wolfi and Ubuntu. You can use the `apk search` command in Wolfi, and the `apt-cache search` command in Ubuntu to find out which package includes a tool. | Utility | Wolfi busybox | Ubuntu busybox | Wolfi coreutils | Ubuntu coreutils | | ------------------- | :-----------: | :------------: | :-------------: | :--------------: | | `[` | ✅ | ✅ | ✅ | ✅ | | `[[` | ✅ | ✅ | | | | `acpid` | | ✅ | | | | `add-shell` | ✅ | | | | | `addgroup` | ✅ | | | | | `adduser` | ✅ | | | | | `adjtimex` | ✅ | ✅ | | | | `ar` | | ✅ | | | | `arch` | ✅ | ✅ | | ✅ | | `arp` | | ✅ | | | | `arping` | ✅ | ✅ | | | | `ash` | ✅ | ✅ | | | | `awk` | ✅ | ✅ | | | | `b2sum` | | | ✅ | ✅ | | `base32` | | | ✅ | ✅ | | `base64` | ✅ | | ✅ | ✅ | | `basename` | ✅ | ✅ | ✅ | ✅ | | `basenc` | | | ✅ | ✅ | | `bbconfig` | ✅ | | | | | `bc` | ✅ | ✅ | | | | `beep` | ✅ | | | | | `bin` | | | | ✅ | | `blkdiscard` | | ✅ | | | | `blockdev` | | ✅ | | | | `brctl` | | ✅ | | | | `bunzip2` | ✅ | ✅ | | | | `bzcat` | ✅ | ✅ | | | | `bzip2` | ✅ | ✅ | | | | `cal` | ✅ | ✅ | | | | `cat` | ✅ | ✅ | ✅ | ✅ | | `chattr` | ✅ | | | | | `chcon` | | | ✅ | ✅ | | `chgrp` | ✅ | ✅ | ✅ | ✅ | | `chmod` | ✅ | ✅ | ✅ | ✅ | | `chown` | ✅ | ✅ | ✅ | ✅ | | `chpasswd` | ✅ | ✅ | | | | `chroot` | ✅ | ✅ | ✅ | ✅ | | `chrt` | ✅ | | | | | `chvt` | | ✅ | | | | `cksum` | ✅ | | ✅ | ✅ | | `clear` | ✅ | ✅ | | | | `cmp` | ✅ | ✅ | | | | `comm` | ✅ | | ✅ | ✅ | | `coreutils` | | | ✅ | | | `cp` | ✅ | ✅ | ✅ | ✅ | | `cpio` | ✅ | ✅ | | | | `cryptpw` | ✅ | | | | | `csplit` | | | ✅ | ✅ | | `cttyhack` | | ✅ | | | | `cut` | ✅ | ✅ | ✅ | ✅ | | `date` | ✅ | ✅ | ✅ | ✅ | | `dc` | ✅ | ✅ | | | | `dd` | ✅ | ✅ | ✅ | ✅ | | `deallocvt` | | ✅ | | | | `delgroup` | ✅ | | | | | `deluser` | ✅ | | | | | `depmod` | | ✅ | | | | `devmem` | | ✅ | | | | `df` | ✅ | ✅ | ✅ | ✅ | | `diff` | ✅ | ✅ | | | | `dir` | | | ✅ | ✅ | | `dircolors` | | | ✅ | ✅ | | `dirname` | ✅ | ✅ | ✅ | ✅ | | `dmesg` | ✅ | ✅ | | | | `dnsdomainname` | ✅ | ✅ | | | | `dos2unix` | ✅ | ✅ | | | | `du` | ✅ | ✅ | ✅ | ✅ | | `dumpkmap` | | ✅ | | | | `dumpleases` | | ✅ | | | | `echo` | ✅ | ✅ | ✅ | ✅ | | `ed` | ✅ | | | | | `egrep` | ✅ | ✅ | | | | `env` | ✅ | ✅ | ✅ | ✅ | | `expand` | ✅ | ✅ | ✅ | ✅ | | `expr` | ✅ | ✅ | ✅ | ✅ | | `factor` | ✅ | ✅ | ✅ | ✅ | | `fallocate` | ✅ | ✅ | | | | `false` | ✅ | ✅ | ✅ | ✅ | | `fatattr` | | ✅ | | | | `fgrep` | ✅ | ✅ | | | | `find` | ✅ | ✅ | | | | `findfs` | ✅ | | | | | `flock` | ✅ | | | | | `fmt` | | | ✅ | ✅ | | `fold` | ✅ | ✅ | ✅ | ✅ | | `free` | ✅ | ✅ | | | | `freeramdisk` | | ✅ | | | | `fsfreeze` | | ✅ | | | | `fstrim` | | ✅ | | | | `fsync` | ✅ | | | | | `ftpget` | | ✅ | | | | `ftpput` | | ✅ | | | | `fuser` | ✅ | | | | | `getopt` | ✅ | ✅ | | | | `getty` | ✅ | ✅ | | | | `grep` | ✅ | ✅ | | | | `groups` | ✅ | ✅ | | ✅ | | `gunzip` | ✅ | ✅ | | | | `gzip` | ✅ | ✅ | | | | `halt` | | ✅ | | | | `hd` | ✅ | | | | | `head` | ✅ | ✅ | ✅ | ✅ | | `hexdump` | ✅ | ✅ | | | | `hostid` | ✅ | ✅ | ✅ | ✅ | | `hostname` | ✅ | ✅ | | | | `httpd` | | ✅ | | | | `hwclock` | | ✅ | | | | `i2cdetect` | | ✅ | | | | `i2cdump` | | ✅ | | | | `i2cget` | | ✅ | | | | `i2cset` | | ✅ | | | | `id` | ✅ | ✅ | ✅ | ✅ | | `ifconfig` | | ✅ | | | | `ifdown` | | ✅ | | | | `ifup` | | ✅ | | | | `init` | | ✅ | | | | `inotifyd` | ✅ | | | | | `insmod` | | ✅ | | | | `install` | ✅ | | ✅ | ✅ | | `ionice` | ✅ | ✅ | | | | `iostat` | ✅ | | | | | `ip` | | ✅ | | | | `ipcalc` | | ✅ | | | | `ipcrm` | ✅ | | | | | `ipcs` | ✅ | | | | | `ipneigh` | | ✅ | | | | `join` | | | ✅ | ✅ | | `kill` | ✅ | ✅ | | | | `killall` | ✅ | ✅ | | | | `killall5` | ✅ | | | | | `klogd` | | ✅ | | | | `last` | | ✅ | | | | `less` | ✅ | ✅ | | | | `link` | ✅ | ✅ | ✅ | ✅ | | `linux32` | ✅ | ✅ | | | | `linux64` | ✅ | ✅ | | | | `linuxrc` | | ✅ | | | | `ln` | ✅ | ✅ | ✅ | ✅ | | `loadfont` | | ✅ | | | | `loadkmap` | | ✅ | | | | `logger` | ✅ | ✅ | | | | `login` | ✅ | ✅ | | | | `logname` | | ✅ | ✅ | ✅ | | `logread` | | ✅ | | | | `losetup` | | ✅ | | | | `ls` | ✅ | ✅ | ✅ | ✅ | | `lsattr` | ✅ | | | | | `lsmod` | | ✅ | | | | `lsof` | ✅ | | | | | `lsscsi` | | ✅ | | | | `lzcat` | ✅ | ✅ | | | | `lzma` | ✅ | ✅ | | | | `lzop` | ✅ | ✅ | | | | `lzopcat` | ✅ | | | | | `md5sum` | ✅ | ✅ | ✅ | ✅ | | `md5sum.textutils` | | | | ✅ | | `mdev` | | ✅ | | | | `microcom` | ✅ | ✅ | | | | `mkdir` | ✅ | ✅ | ✅ | ✅ | | `mkdosfs` | | ✅ | | | | `mke2fs` | | ✅ | | | | `mkfifo` | ✅ | ✅ | ✅ | ✅ | | `mknod` | ✅ | ✅ | ✅ | ✅ | | `mkpasswd` | ✅ | ✅ | | | | `mkswap` | | ✅ | | | | `mktemp` | ✅ | ✅ | ✅ | ✅ | | `modinfo` | | ✅ | | | | `modprobe` | | ✅ | | | | `more` | ✅ | ✅ | | | | `mount` | | ✅ | | | | `mountpoint` | ✅ | | | | | `mpstat` | ✅ | | | | | `mt` | | ✅ | | | | `mv` | ✅ | ✅ | ✅ | ✅ | | `nameif` | | ✅ | | | | `nc` | | ✅ | | | | `netstat` | ✅ | ✅ | | | | `nice` | ✅ | | ✅ | ✅ | | `nl` | ✅ | ✅ | ✅ | ✅ | | `nmeter` | ✅ | | | | | `nohup` | ✅ | | ✅ | ✅ | | `nologin` | ✅ | ✅ | | | | `nproc` | ✅ | ✅ | ✅ | ✅ | | `nsenter` | ✅ | ✅ | | | | `nslookup` | | ✅ | | | | `nuke` | | ✅ | | | | `numfmt` | | | ✅ | ✅ | | `od` | ✅ | ✅ | ✅ | ✅ | | `openvt` | | ✅ | | | | `partprobe` | | ✅ | | | | `passwd` | ✅ | | | | | `paste` | ✅ | ✅ | ✅ | ✅ | | `patch` | | ✅ | | | | `pathchk` | | | ✅ | ✅ | | `pgrep` | ✅ | | | | | `pidof` | ✅ | ✅ | | | | `ping` | ✅ | ✅ | | | | `ping6` | ✅ | ✅ | | | | `pinky` | | | ✅ | ✅ | | `pipe_progress` | ✅ | | | | | `pivot_root` | ✅ | ✅ | | | | `pkill` | ✅ | | | | | `pmap` | ✅ | | | | | `poweroff` | | ✅ | | | | `pr` | | | ✅ | ✅ | | `printenv` | ✅ | | ✅ | ✅ | | `printf` | ✅ | ✅ | ✅ | ✅ | | `ps` | ✅ | ✅ | | | | `pstree` | ✅ | | | | | `ptx` | | | ✅ | ✅ | | `pwd` | ✅ | ✅ | ✅ | ✅ | | `pwdx` | ✅ | | | | | `rdate` | | ✅ | | | | `rdev` | ✅ | | | | | `readahead` | ✅ | | | | | `readlink` | ✅ | ✅ | ✅ | ✅ | | `realpath` | ✅ | ✅ | ✅ | ✅ | | `reboot` | | ✅ | | | | `remove-shell` | ✅ | | | | | `renice` | ✅ | ✅ | | | | `reset` | ✅ | ✅ | | | | `resize` | ✅ | | | | | `resume` | | ✅ | | | | `rev` | ✅ | ✅ | | | | `rm` | ✅ | ✅ | ✅ | ✅ | | `rmdir` | ✅ | ✅ | ✅ | ✅ | | `rmmod` | | ✅ | | | | `route` | | ✅ | | | | `rpm` | | ✅ | | | | `rpm2cpio` | | ✅ | | | | `run-init` | | ✅ | | | | `run-parts` | ✅ | ✅ | | | | `runcon` | | | ✅ | ✅ | | `sbin` | | | | ✅ | | `sed` | ✅ | ✅ | | | | `seq` | ✅ | ✅ | ✅ | ✅ | | `setkeycodes` | | ✅ | | | | `setpriv` | ✅ | ✅ | | | | `setserial` | ✅ | | | | | `setsid` | ✅ | ✅ | | | | `sh` | ✅ | ✅ | | | | `sha1sum` | ✅ | ✅ | ✅ | ✅ | | `sha224sum` | | | ✅ | ✅ | | `sha256sum` | ✅ | ✅ | ✅ | ✅ | | `sha384sum` | | | ✅ | ✅ | | `sha3sum` | ✅ | | | | | `sha512sum` | ✅ | ✅ | ✅ | ✅ | | `shred` | ✅ | ✅ | ✅ | ✅ | | `shuf` | ✅ | ✅ | ✅ | ✅ | | `sleep` | ✅ | ✅ | ✅ | ✅ | | `sort` | ✅ | ✅ | ✅ | ✅ | | `split` | ✅ | | ✅ | ✅ | | `ssl_client` | | ✅ | | | | `start-stop-daemon` | | ✅ | | | | `stat` | ✅ | ✅ | ✅ | ✅ | | `stdbuf` | | | ✅ | ✅ | | `strings` | ✅ | ✅ | | | | `stty` | ✅ | ✅ | ✅ | ✅ | | `su` | ✅ | | | | | `sum` | ✅ | | ✅ | ✅ | | `svc` | | ✅ | | | | `svok` | | ✅ | | | | `swapoff` | | ✅ | | | | `swapon` | | ✅ | | | | `switch_root` | | ✅ | | | | `sync` | ✅ | ✅ | ✅ | ✅ | | `sysctl` | ✅ | ✅ | | | | `syslogd` | | ✅ | | | | `tac` | ✅ | ✅ | ✅ | ✅ | | `tail` | ✅ | ✅ | ✅ | ✅ | | `tar` | ✅ | ✅ | | | | `taskset` | | ✅ | | | | `tee` | ✅ | ✅ | ✅ | ✅ | | `telnet` | | ✅ | | | | `test` | ✅ | ✅ | ✅ | ✅ | | `tftp` | | ✅ | | | | `time` | ✅ | ✅ | | | | `timeout` | ✅ | ✅ | ✅ | ✅ | | `top` | ✅ | ✅ | | | | `touch` | ✅ | ✅ | ✅ | ✅ | | `tr` | ✅ | ✅ | ✅ | ✅ | | `traceroute` | ✅ | ✅ | | | | `traceroute6` | ✅ | ✅ | | | | `tree` | ✅ | | | | | `true` | ✅ | ✅ | ✅ | ✅ | | `truncate` | ✅ | ✅ | ✅ | ✅ | | `tsort` | ✅ | | ✅ | ✅ | | `tty` | ✅ | ✅ | ✅ | ✅ | | `ttysize` | ✅ | | | | | `tunctl` | ✅ | | | | | `ubirename` | | ✅ | | | | `udhcpc` | | ✅ | | | | `udhcpd` | | ✅ | | | | `uevent` | | ✅ | | | | `umount` | | ✅ | | | | `uname` | ✅ | ✅ | ✅ | ✅ | | `uncompress` | | ✅ | | | | `unexpand` | ✅ | ✅ | ✅ | ✅ | | `uniq` | ✅ | ✅ | ✅ | ✅ | | `unix2dos` | ✅ | ✅ | | | | `unlink` | ✅ | ✅ | ✅ | ✅ | | `unlzma` | ✅ | ✅ | | | | `unlzop` | ✅ | | | | | `unshare` | | ✅ | | | | `unxz` | ✅ | ✅ | | | | `unzip` | ✅ | ✅ | | | | `uptime` | ✅ | ✅ | | | | `users` | | | ✅ | ✅ | | `usleep` | ✅ | ✅ | | | | `usr` | | | | ✅ | | `uudecode` | ✅ | ✅ | | | | `uuencode` | ✅ | ✅ | | | | `vconfig` | ✅ | ✅ | | | | `vdir` | | | ✅ | ✅ | | `vi` | ✅ | ✅ | | | | `vlock` | ✅ | | | | | `w` | | ✅ | | | | `watch` | ✅ | ✅ | | | | `watchdog` | | ✅ | | | | `wc` | ✅ | ✅ | ✅ | ✅ | | `wget` | | ✅ | | | | `which` | ✅ | ✅ | | | | `who` | ✅ | ✅ | ✅ | ✅ | | `whoami` | ✅ | ✅ | ✅ | ✅ | | `xargs` | ✅ | ✅ | | | | `xxd` | ✅ | ✅ | | | | `xz` | | ✅ | | | | `xzcat` | ✅ | ✅ | | | | `yes` | ✅ | ✅ | ✅ | ✅ | | `zcat` | ✅ | ✅ | | | --- ### Using the Chainguard Directory URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/chainguard-directory.md Last Modified: October 8, 2026 Tags: Chainguard Containers There are hundreds of Chainguard Containers available for use. To help users explore and better understand all of these container images, we've developed the Chainguard Directory. This is a free to access web portal that does not require signing in from which you can view information about container images in the Chainguard catalog. This is great for quick searches or while exploring Chainguard's offerings. If you want more specific information about what is available to your organization, take a look at the Chainguard Console. You'll first need to [create an account and log in](https://console.chainguard.dev/auth/login) but there you can interact in a clearer, organization-specific way. If you use the *Sign In* link in the directory, it brings you to the console. Learn more about the [Chainguard Console](/platform/console/images-directory/) in the related docs page. ## Accessing the Chainguard Directory This guide is primarily framed around the Chainguard Directory and the Chainguard Console. The Directory is public facing and doesn't require any authentication to access it. The Console is also accessible to anyone, including users who aren't Chainguard customers. However, to access the Console, you'll first need to [create an account and log in](https://console.chainguard.dev/auth/login). ## Browse container images and details in the directory The [Chainguard Containers Directory](https://images.chainguard.dev/) landing page has a card-based layout of container images. Each card represents a container image available from Chainguard, and shows the image's name and logo, when it was last changed, the latest tag available. These cards include other details when relevant, like whether the image is part of Chainguard's Free tier of containers, if there is a FIPS-enabled version of the container image available, or if it's a STIG-hardened image. By default, the Directory shows a paginated list of container images. You can select categories of container images to view from the menu to the left of the cards. This menu has the following options: * **Featured** * **Free** * **Helm charts** * **AI** * **Application** * **Base** * **FIPS** Above the cards is a search box you can use to search for specific Chainguard Containers. Clicking on any card or search result takes you to that container image's details page. ## Find Helm charts in the Chainguard Directory For organizations looking to deploy their Chainguard container images with Helm, Chainguard provides upstream-produced Helm charts as well as a set of Chainguard-created charts labeled as iamguarded, designed specifically to support organizations migrating off of Bitnami. You can find these charts and information about them in the Chainguard Directory. From the Categories sidebar menu, click **Helm charts**. All Helm charts have a **Helm** icon, making them easier to see when looking through the full images list without using the Helm-specific category. Charts with the **shield** icon are iamguarded charts, created specifically to help people who are switching from Bitnami. Click any chart name to learn the chart details. ## Learn more The Chainguard Containers Directory is a useful tool for understanding which container images are available. To better understand how to work with individual container images, you can see if we have a [getting started guide](/chainguard/containers/getting-started/) available. We also provide a guide on [how to view Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-to-use/) through our [self-service public Security Advisories page](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-directory). --- ### Using Digestabot with Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/updating-containers/digestabot.md Last Modified: October 15, 2026 Tags: Chainguard Containers [Digestabot](https://github.com/chainguard-dev/digestabot) is a GitHub Action that keeps digest-pinned container image references current. It reads the references in your repository, looks up the current digest for each tag in the registry, and opens a pull request when a digest has changed. Chainguard rebuilds container images daily, so the digest behind a tag such as `latest` or `3.14` changes often. Digestabot lets you pin a reference to an exact digest for reproducibility and still pick up each rebuild, with a pull request as the place to test the change before it ships. This guide explains how Digestabot matches references, how to authenticate it to your organization's private registry at `cgr.dev`, and how to control which files it scans. ## Choosing between Digestabot, Dependabot, and Renovate These three tools solve different parts of the same problem: * **Digestabot** opens a pull request when the digest behind a fixed tag changes. Use it to pick up Chainguard's daily rebuilds without changing the tag you depend on. * **[Dependabot](/chainguard/containers/security-and-compliance/updating-containers/dependabot/)** opens a pull request when a newer *tag* is available, moving a reference from `go:1.22` to `go:1.26`. Use it to move between version streams. * **[Renovate](/chainguard/containers/security-and-compliance/updating-containers/renovate/)** does both, and it runs outside GitHub as well as inside it. Many teams run Digestabot alongside one of the others: Dependabot or Renovate moves the tag when a new version stream appears, and Digestabot keeps the digest current in between. Digestabot is also the better fit for a reference pinned to a mutable tag, where the tag string never changes and Dependabot has nothing to act on. ## How Digestabot matches references Digestabot updates a reference only when it contains both a tag and a digest: ``` cgr.dev/example.com/go:1.22@sha256:0d0e0f5e9b7f8ee0dbcbb6d1c40ad1bbd0da5b2bb56de2b26dfd8ceb8ed69dbb ``` Both parts are required: * Digestabot never matches a reference with a tag and no digest. It updates a digest that's already there; it doesn't add one. * Digestabot skips a reference with a digest and no tag, because it has no tag to look up. The job log records `Image in file does not have a tag, ignoring...`. This is the most common reason a Digestabot run reports nothing to do. To add digests to references that carry only a tag, use [Frizbee](https://github.com/stacklok/frizbee), as shown in [Reproducible Dockerfiles with Frizbee and Digestabot](https://www.youtube.com/watch?v=FfZZVZ-V5ho). Renovate's `pinDigests` option does the same job; refer to [Pin digests](/chainguard/containers/security-and-compliance/updating-containers/renovate/#pin-digests). Digestabot also skips two categories of reference regardless of their form: * Any reference in a file whose path contains `testdata`. * Any reference whose registry address contains `.local:`, such as `registry.local:5000/example`. ## Prerequisites To follow this guide, you need: * A GitHub repository with at least one reference to a Chainguard container image pinned in the `tag@digest` form described in the previous section. * Access to a Chainguard organization, with permission to create identities, if the images come from a private repository in `cgr.dev`. * `chainctl` installed on your local machine. Refer to [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you haven't set this up. Digestabot opens its pull requests with the workflow's `GITHUB_TOKEN`. For that to work, go to **Settings > Actions > General** in your repository and confirm that **Allow GitHub Actions to create and approve pull requests** is selected. If your organization turns this setting off, pass a token from a GitHub App or a `repo`-scoped personal access token through the `token` input instead. ## Authenticate to a private registry Digestabot reads digests with [crane](https://github.com/google/go-containerregistry), which uses the Docker credential store on the runner. Digestabot has no registry credential input of its own, so it inherits whatever credentials an earlier step configured. For a private repository in `cgr.dev`, add a [`setup-chainctl`](https://github.com/chainguard-dev/setup-chainctl) step before the Digestabot step. That action authenticates as a [Chainguard assumable identity](/platform/administration/assumable-ids/assumable-ids/) and registers a Docker credential helper for `cgr.dev`, which crane then uses without further configuration. Digestabot needs no static pull token, which is where it differs most from Dependabot. The identity mints a short-lived token for each run, so there is no credential to store or rotate. Create an identity for your repository. The `--github-repo` value embeds GitHub's immutable numeric owner and repository IDs; refer to [Finding your repository's numeric identifiers](/platform/administration/assumable-ids/identity-examples/github-identity/#finding-your-repositorys-numeric-identifiers) for how to retrieve them and when the format applies. ```shell chainctl iam identities create github digestabot \ --github-repo=@/@ \ --github-ref=refs/heads/main \ --role=registry.pull ``` The command returns an identity ID. Record it for the workflow in the next section. Digestabot only reads image digests, so the `registry.pull` role covers everything it does on its own. [Annotating pull requests with `chainctl images diff`](#annotate-pull-requests-with-chainctl-images-diff) needs a broader role. ## Create the workflow Create a file named `.github/workflows/digestabot.yaml` with the following content, replacing `` with the ID from the previous section: ```yaml name: Digestabot on: workflow_dispatch: schedule: - cron: "0 1 * * *" permissions: contents: read jobs: digestabot: name: Digestabot runs-on: ubuntu-latest permissions: contents: write # Push the branch holding the digest updates pull-requests: write # Open the pull request id-token: write # Assume the Chainguard identity and sign commits steps: - uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 with: egress-policy: audit - uses: chainguard-dev/setup-chainctl@2cddd35a2f120d9973e58094dc6878c93cf58c28 # v0.5.1 with: identity: "" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: chainguard-dev/digestabot@33d0b78e580aa0c83fe188eb3dfad6611b662479 # v1.3.2 with: token: ${{ secrets.GITHUB_TOKEN }} labels-for-pr: '' ``` Commit this file to your repository's default branch. This workflow runs at 1:00 a.m. every day and on demand from **Actions > Digestabot > Run workflow**. Each run authenticates to `cgr.dev` as the identity you created, scans the repository, and opens a pull request titled `Update images digests` on a branch named `update-digests` if any digest has changed. The example clears `labels-for-pr`, because the default value applies Chainguard's own repository labels — `automated pr`, `kind/cleanup`, and `release-note-none` — which probably don't exist in your repository. Set it to labels you use, or leave it empty. If your images come from a public repository such as `cgr.dev/chainguard`, drop the `setup-chainctl` step. Digestabot reads public digests without credentials. ### How Digestabot handles repeat runs Digestabot maintains one pull request rather than opening a new one per run. Each run resets the `update-digests` branch to the base branch, applies the current set of updates, and force-pushes, so an open pull request always reflects the latest digests. If you merge or close that pull request, the next run with pending updates opens a fresh one. This is worth knowing if you also use Dependabot, which [leaves an open pull request in place](/chainguard/containers/security-and-compliance/updating-containers/dependabot/#update-digest-pinned-references) as newer digests are published. ### Commit signing Digestabot signs its commits with [gitsign](https://github.com/sigstore/gitsign) by default, which is why the job needs the `id-token: write` permission. GitHub labels these commits as unverified. Refer to [Digestabot's commits show as unverified](#digestabots-commits-show-as-unverified) if a branch protection rule in your repository requires signed commits. ## Choose which files to scan By default, Digestabot scans files matching `*.yaml`, `*.yml`, `Dockerfile*`, `Makefile*`, `*.sh`, `*.tf`, and `*.tfvars`. It updates digests in Terraform configurations, Makefiles, shell scripts, `ko` configurations, and Kubernetes manifests, not only in Dockerfiles. Any file in the repository that holds a `tag@digest` reference and matches one of these patterns is in scope. Patterns match file names rather than paths. Digestabot passes each pattern to `find -name`, which compares against the base name of each file, so a pattern such as `manifests/*.yaml` matches nothing. To narrow Digestabot to part of a repository, set `working-dir` to a path relative to the repository root: ```yaml - uses: chainguard-dev/digestabot@33d0b78e580aa0c83fe188eb3dfad6611b662479 # v1.3.2 with: token: ${{ secrets.GITHUB_TOKEN }} working-dir: manifests include-files: '*.yaml,Dockerfile*' ``` ## Resolve digests through a registry proxy If you pull Chainguard Containers through a registry proxy, such as a remote repository in Artifact Registry, the proxy can return a stale digest for a tag. The `registry-map` input maps a proxy prefix to the registry behind it, so Digestabot resolves digests against `cgr.dev` while leaving the proxy address in your files: ```yaml - uses: chainguard-dev/digestabot@33d0b78e580aa0c83fe188eb3dfad6611b662479 # v1.3.2 with: token: ${{ secrets.GITHUB_TOKEN }} registry-map: 'us-docker.pkg.dev/my-project/cgr/=cgr.dev/' ``` Provide mappings as comma-separated `proxy=upstream` pairs. Digestabot rewrites the address it looks up, not the reference it writes back, so your files keep pulling through the proxy. Because the lookups go to `cgr.dev`, the credentials on the runner must grant access there. Keep the `setup-chainctl` step in the workflow even when every reference in your files points at the proxy. ## Extend Digestabot with its outputs Digestabot reports what it changed in two outputs: `json` describes each update, and `changed_files` lists the files it modified. Combined with `create-pr: false`, which applies the changes in the workspace without opening a pull request, these let you act on an update before it reaches a reviewer: ```yaml - uses: chainguard-dev/digestabot@33d0b78e580aa0c83fe188eb3dfad6611b662479 # v1.3.2 id: digestabot with: token: ${{ secrets.GITHUB_TOKEN }} - shell: bash run: | while read -r update; do updated_image=$(jq -r '.image + "@" + .updated_digest' <<<"${update}") echo "Do something with ${updated_image} here." done < <(jq -c '.updates // [] | .[]' <<<'${{ steps.digestabot.outputs.json }}') ``` ### Annotate pull requests with `chainctl images diff` [`chainctl images diff`](/platform/chainctl-usage/comparing-images/) compares two container images and reports the packages and vulnerabilities that differ between them. Running it over Digestabot's `json` output turns "test the update before merging" into a summary in the pull request itself: which vulnerabilities the rebuild resolves, which packages changed, and whether the update is worth merging at all. The [`digestabot-examples`](https://github.com/chainguard-demo/digestabot-examples) repository holds a complete workflow for this in `.github/workflows/chainctl-image-diff.yaml`. It runs Digestabot with `create-pr: false`, verifies that each image is signed by Chainguard, diffs the old and new digests, and discards updates that resolve no vulnerabilities. The same repository has a second workflow that scans each updated image with [Grype](https://github.com/anchore/grype) and comments the results on the pull request. `chainctl images diff` needs `grype` available on the runner and an identity with a broader role than `registry.pull`; the example workflow uses `viewer`. ## Configuration reference ### Inputs | Input | Description | Default | |-------|-------------|---------| | `working-dir` | Directory to scan, relative to the repository root. | `.` | | `include-files` | Comma-separated file name patterns to scan. Patterns match base names, not paths. | `*.yaml,*.yml,Dockerfile*,Makefile*,*.sh,*.tf,*.tfvars` | | `token` | Token used to push the branch and open the pull request. | `${{ github.token }}` | | `registry-map` | Comma-separated `proxy=upstream` prefix mappings for digest lookups. | None | | `create-pr` | Whether to open a pull request. Set to `false` to leave the changes in the workspace. | `true` | | `use-gitsign` | Whether to sign commits with gitsign. | `true` | | `signoff` | Whether to add a `Signed-off-by` line to the commit message. | `false` | | `author` | Commit author, in the form `Display Name `. | The user who triggered the run | | `committer` | Committer, in the form `Display Name `. | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` | | `branch-for-pr` | Branch that carries the updates. | `update-digests` | | `title-for-pr` | Pull request title. | `Update images digests` | | `description-for-pr` | Pull request body. Digestabot appends the diff to whatever you set. | `Update images digests` | | `labels-for-pr` | Comma- or newline-separated labels to apply to the pull request. | `automated pr, kind/cleanup, release-note-none` | | `commit-message` | Commit message. | `Update images digests` | ### Outputs | Output | Description | |--------|-------------| | `json` | The updates Digestabot made. Each entry in `updates` has a `file`, `image`, `digest`, and `updated_digest`. | | `changed_files` | Newline-separated list of the files Digestabot modified. | | `pull_request_number` | Number of the pull request Digestabot opened or updated. | ## Troubleshooting ### Digestabot's commits show as unverified Digestabot signs its commits with gitsign, which uses Sigstore's certificate authority. GitHub doesn't recognize that authority, so it marks the commits **Unverified**. The signature is valid; the badge reflects GitHub's trust configuration rather than a problem with the commit. This matters when a branch protection rule requires signed commits, because the rule then blocks Digestabot's pull requests from merging. You have two options: * Sign the commits with a GPG key that GitHub recognizes. Set `create-pr: false` so that Digestabot only edits the files, then import a key and create the pull request in later steps. Chainguard recommends [keyless signing](/open-source/sigstore/cosign/an-introduction-to-cosign/) where you have the choice, so treat this as a way to satisfy the branch protection rule rather than an improvement. * Set `use-gitsign: false` to leave the commits unsigned, if your repository doesn't require signed commits and you'd rather not carry signatures GitHub can't verify. ### Digestabot opens no pull requests If the job succeeds but no pull request appears, check the following: * **The form of the reference.** Digestabot updates only references carrying both a tag and a digest. Refer to [How Digestabot matches references](#how-digestabot-matches-references). * **The file name.** `include-files` patterns match base names, so Digestabot never reads a reference in a file the patterns don't cover. Refer to [Choose which files to scan](#choose-which-files-to-scan). * **The repository setting.** Confirm that **Allow GitHub Actions to create and approve pull requests** is selected, as described in [Prerequisites](#prerequisites). * **An existing pull request.** Digestabot updates an open `update-digests` pull request in place instead of opening a second one. ### Digest lookups fail The job summary records `Failed to retrieve digest info for ` for each reference Digestabot couldn't resolve, followed by the error from the registry. Check the following, in order: 1. Confirm that the `setup-chainctl` step runs before the Digestabot step and completes successfully. 2. Confirm the identity has the `registry.pull` role and access to the repository holding the image. Run `chainctl iam identities list` to review the identities in your organization. 3. Test the reference outside the workflow. Run `chainctl auth configure-docker`, then `crane digest ` or `docker pull `. 4. If you pull through a registry proxy, confirm whether the lookups need [`registry-map`](#resolve-digests-through-a-registry-proxy). ## Video overview This 2024 video introduces the problem Digestabot solves, using a digest-pinned multi-stage Python build as the example. The configuration in this guide has moved on since the recording, but the reasoning holds. [Watch the video on YouTube](https://www.youtube.com/watch?v=7WvzkwS9yms)
Transcript

Today, I’d like to talk about a common question I get asked.

How can you keep images up to date while avoiding breaking changes?

The basic issue is that we’d like to make sure we’re getting the latest security updates and features for our software.

But we really don’t want our applications and infrastructure to break unexpectedly.

So there’s a tension between updating all the time, which gives you the latest code and limits unexpected breakages.

In this example, we have a multi-stage Python build using Chainguard Container which are pinned to Digest.

Now, Digests are content-based hashes of images.

So if you reference an image by Digest, you will always get exactly the same image every time.

Now, this is fantastic for reproducibility.

As I know, if anybody uses this Dockerfile, they will get exactly the same images that I was using.

And this is especially important in Python, where if the version changes, so we go from Python 3.12 to Python 3.13, you might find that various libraries don’t work until they’re updated.

Now, how do you do updates then?

Well, you could manually go in and change, bump this Digest yourself.

But we’ve got a better solution for you that I want to talk about briefly today, and it’s called Digestabot.

Digestabot is a GitHub action that can be set to run on a cron job and will open a PR when it detects there’s a newer version of the image available.

You can then test the image to make sure it works with your application before merging the PR.

So for my example, it would check the Chainguard registry for the current digest of the latest tag and open a PR if it doesn’t match the digest in the file.

We use Digestabot internally at Chainguard, and this pattern nicely balances the tension between keeping images up to date and vulnerability-free with the need to test and verify changes before shipping to production.

So please try it out and let me know if you have any questions.

## Learn more * [Using Dependabot with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/dependabot/) covers the tool to pair with Digestabot for moving between version streams. * [Using Renovate with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/renovate/) covers the equivalent setup for teams outside GitHub. * [Stay secure: Strategies and tooling for updating container images](https://www.chainguard.dev/unchained/stay-secure-strategies-and-tooling-for-updating-container-images), on the Chainguard blog, compares the wider range of update tools. * [Considerations for keeping containers up to date](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/) covers the tradeoffs behind an update policy. * [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/) explains what a digest is and why pinning to one matters. --- ### Getting started with the PostgreSQL Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/web-and-data-services/postgres.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's PostgreSQL container image provides a security-hardened foundation for running Postgres databases. Built on Wolfi with a distroless design, it includes only the packages the database needs, maintaining full PostgreSQL functionality while reducing attack surface. For current CVE data on each image and tag, see the [PostgreSQL entry in the Chainguard Containers directory](https://images.chainguard.dev/directory/image/postgres/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-postgres). Through daily rebuilds with the latest patches and minimal dependencies, Chainguard's PostgreSQL image enhances database security posture. This enables you to run production Postgres workloads in containerized environments with both a smaller footprint and improved protection against supply chain attacks. In order to illustrate how the PostgreSQL Chainguard Container might be used in practice, this tutorial involves setting up an example PHP application that uses a Postgres database. This guide assumes you have Docker installed to run the demo; specifically, the procedure outlined in this guide uses [Docker Compose](https://docs.docker.com/compose/install/) to manage the environment on your local machine.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Step 1: Setting up a demo application This step involves downloading the demo application code to your local machine. To ensure that the application files don't remain on your system navigate to a temporary directory like `/tmp/`. ```sh cd /tmp/ ``` Your system will automatically delete the `/tmp/` directory's contents the next time it shuts down or reboots. The code that comprises this demo application is hosted in a public GitHub repository managed by Chainguard. Pull down the example application files from GitHub with the following command. ```sh git clone --sparse https://github.com/chainguard-dev/edu-images-demos.git ``` Because this guide's demo application code is stored in a repository with other examples, we don't need to pull down every file from this repository. For this reason, this command includes the `--sparse` option. This will initialize a sparse-checkout file; this causes the working directory to contain only the files in the root of the repository until the sparse-checkout configuration is modified. Navigate into this new directory. ```sh cd edu-images-demos ``` To retrieve the files you need for this tutorial's sample application, run the following `git` command. ```sh git sparse-checkout set postgres ``` This modifies the sparse-checkout configuration initialized in the previous `git clone` command so that the checkout only consists of the repo's `postgres` directory. Navigate into this new directory. ```sh cd postgres/ ``` From here, you can run the application and use a web browser to observe it working in real time, which we'll do in the next section. ## Step 2: Inspect, run, and test the sample application We encourage you to check out [the application code on GitHub](https://github.com/chainguard-dev/edu-images-demos/tree/main/postgres) to better understand how this application works, but we'll provide a brief overview here. This demo creates a LEPP (Linux, (E)NGINX, PostgreSQL and PHP-FPM) environment based on Wolfi Chainguard Containers. We will use Docker Compose to bring up the environment, which will spin up three containers: an `app` container, a `postgres` container, and an `nginx` container. These will run as services. Once the environment is up, you can visit the demo in your web browser. The `index.php` file contains code that does the following: * Connects to the PostgreSQL server running in the `postgres` container * Creates a new table named `data` if it doesn't already exist * Inserts a new entry into the table with a random number * Queries the table to show all the entries Every time you reload the page, a new entry will be added to the table. Note that this application includes a Dockerfile. ```sh cat Dockerfile ``` ``` FROM cgr.dev/chainguard/php:latest-fpm-dev USER root RUN apk update && apk add php-pgsql USER php ``` This Dockerfile takes the public `php:latest-fpm-dev` Chainguard Container and installs the `php-pgsql` package onto it. This container image comes with drivers that allow PHP applications to connect to MySQL or MariaDB databases by default but it doesn't have an equivalent for PostgreSQL. For this reason, we use this Dockerfile to install this package in order for the PHP application to be able to connect to the Postgres database. Execute the following command to build a container with this Dockerfile, and then create and start each of the three containers and bring up the application. ```sh docker compose up -d ``` The `-d` option is short for `--detach`; this will cause the containers to run in the background, allowing you to continue using the same terminal window. If you run into permissions issues when running this command, try running it again with `sudo` privileges. > **Note**: If at any point you'd like to stop and remove these containers, run `docker compose down`. Once all the containers have started, you'll be able to visit the application and observe it working. Open up your preferred web browser and navigate to `localhost:8000`. There, you'll be presented with text like the following ![Screenshot showing a Firefox web browser window with "localhost:8000" in the address bar. On the page is the following text: "Array ( [data_key] => code [data_value] => 8404 )"](pg-demo-success-1.png) Every time you refresh your browser, a new entry will appear. ![Screenshot showing a Firefox web browser window with "localhost:8000" in the address bar. On the page is the following text: "Array ( [data_key] => code [data_value] => 8404 ), Array ( [data_key] => code [data_value] => 5124 ) Array ( [data_key] => code [data_value] => 1527 )". This indicates that the page was refreshed twice since the previous screenshot.](pg-demo-success-2.png) This shows that the application is recording each visit in the PostgreSQL database and that the application is working correctly. After confirming that the application is functioning as expected, you can read through the next section to explore how else you can work with the `postgres` container. ## Step 3: Working with the database The `docker-compose.yml` file contains some configuration details regarding the PostgreSQL database used in this example application. Run the following command to inspect the contents of this file. ```sh cat docker-compose.yml ``` We're interested in the `postgres` service: ``` . . . postgres: image: cgr.dev/chainguard/postgres restart: unless-stopped environment: POSTGRES_USER: php POSTGRES_PASSWORD: password POSTGRES_DB: php-test ports: - 5432:5432 networks: - wolfi . . . ``` This section defines a few environment variables relating to the database used in the example application. Importantly, they specify that the application database is named `php-test` and runs under a user named `php` with the password "password". Using this information, you can connect to the `php-test` database running in the container with a command like the following. ```sh docker exec -it postgres-postgres-1 \ psql -p 5432 -U php -W -d php-test ``` `docker exec` allows you to execute commands within a running container. The `-i` argument allows you to execute an interactive command while the `-t` option allocates a pseudo-TTY to the process within the container. Because our goal is to access the sample database through the `psql` command line client, these options are necessary. Next, enter the name of the container running the PostgreSQL database; by default, this will be named `postgres-postgres-1`. Following that, the remainder of this command represents the command that will be run within the container. Here, we run the `psql` command to access the database specifying that we want to connect over port `5432` as the `php` user. The `-W` option indicates that we want to be prompted to enter the password interactively, and `-d php-test` specifies that we want to connect to the `php-test` database. ``` Password: ``` Enter `password` and you'll then be presented with the `psql` command prompt. ``` psql (15.3) Type "help" for help. php-test=# ``` From here, you can interact with the database from within the `postgres-postgres-1` container as you would with any other PostgreSQL database. For example, you could update existing tables, create new ones, and insert or delete data. To close the `psql` prompt, you can enter the following command. ```PostgreSQL \q ``` Of course, you likely won't be regularly managing your containerized databases over the command line. The purpose of this section is to only show that you can interact with the database running in this container just like you would with any other Postgres database. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose PostgreSQL Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

### Configuring the PostgreSQL container image You can extend Chainguard's PostgreSQL container image with environment variables. Chainguard's PostgreSQL container image is compatible with the environment variables available in the official PostgreSQL image, including the following: * `PGDATA`: This variable allows you to define another location for database files. The default data directory is `/var/lib/postgresql/data`. * `POSTGRES_PASSWORD`: This environment variable sets the superuser password for PostgreSQL. This variable is required to use the PostgreSQL image. * `POSTGRES_USER`: This is used with the `POSTGRES_PASSWORD` variable to set a superuser for the database and its password. If not specified, you can use the default `postgres` user. * `POSTGRES_DB`: Using this variable allows you to set a different name for the default database. If not specified, the default database will be `postgres` or the value set by `POSTGRES_USER`. * `POSTGRES_INITDB_ARGS`: This variable allows you to send arguments to `postgres initdb`. * `POSTGRES_INITDB_WALDIR`: You can set this variable to define the location for the PostgreSQL transaction log. By default, the transaction log is stored in a subdirectory of the main PostgresSQL data folder, which you can define with `PGDATA`. * `POSTGRES_HOST_AUTH_METHOD`: This variable allows you to control the `auth-method` used to authenticate when connecting to the database. Note that if you set the `POSTGRES_HOST_AUTH_METHOD` variable to `trust`, then the `POSTGRES_PASSWORD` variable is no longer required: ```shell docker run --rm -e POSTGRES_HOST_AUTH_METHOD=trust -e POSTGRES_DB=linky -ti --name postgres-test cgr.dev/ORGANIZATION/postgres:latest ``` Be aware that the Docker specific variables will only have an effect if you start the container with an empty data directory; pre-existing databases won't be affected on container startup. You can also run the Chainguard PostgreSQL container image with a custom configuration file. The following example will mount a PostgreSQL configuration file named `my-postgres.conf` to the container. ```shell docker run --rm -v "$PWD/my-postgres.conf":/etc/postgresql/postgresql.conf -e POSTGRES_PASSWORD=password -ti --name postgres-test cgr.dev/ORGANIZATION/postgres:latest -c 'config_file=/etc/postgresql/postgresql.conf' ``` This command also uses the `postgres` server's `-c` flag to set the `config_file` runtime parameter. --- ### Using the Tag History API URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/reference/using-the-tag-history-api.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard Containers has automated nightly builds, which ensures our container images are always fresh including any recent patches and updated software. Even though it is important to keep your base images always updated, there will be situations where you'll want to keep using an older build to make sure nothing will change in your container environment until you feel it's safe to update. For cases like this, it is useful to point your Dockerfile to use a specific **container image digest** as base image. A container image digest is a unique identifier that is generated for each and every image build. Digests always change, even when the contents of the image remain the same. If you have a container environment that was working fine but suddenly breaks with a new build, using a previous container image build version by declaring an image digest instead of a tag is a way to keep things up and running until you're able to assert that a new version of a container environment works as expected with your application. > NOTE: If you are looking for a quick way to learn the tag history of a container image, you may want to consider using the `chainctl images history` command instead of the API. Refer to **[Examine the history of container images](/platform/chainctl-usage/chainctl-images/#examine-the-history-of-container-images)** for more information. ## Obtaining a registry token Before making API calls, you'll need to generate a token within [Chainguard's registry](/chainguard/containers/registry/overview/). ### Public containers The Registry API endpoint for obtaining the token is: ``` https://cgr.dev/token?scope=repository:chainguard/IMAGE_NAME:pull ``` Where `IMAGE_NAME` is the name of the container image that you want to pull the tag history from. It's worth noting that the token is only valid for pulling the history of that specific image. For free-tier container images (tagged as `latest` or `latest-dev`), you can request a registry token anonymously, without providing any pre-existing auth. The following command will obtain a token for the **Python** container image and register a variable called `auth_header` with the resulting value, which you can use in a subsequent command to obtain the tag history: ```shell auth_header="Authorization: Bearer $(curl 'https://cgr.dev/token?scope=repository:chainguard/python:pull' \ | jq -r .token)" ``` ### Private containers For private images, you need a registry token scoped to the `cgr.dev` audience. After you authenticate with `chainctl auth login`, the most direct way to get one is `chainctl auth token`: ```shell auth_header="Authorization: Bearer $(chainctl auth token --audience cgr.dev)" ``` The `--audience cgr.dev` flag is required. Without it, `chainctl auth token` issues a token for a different audience and the Tag History API rejects it with a `403` response. If you have already set up Docker authentication with [`chainctl auth configure-docker`](/chainguard/containers/registry/authenticating/), you can instead read the token from the Docker credential helper: ```shell auth_header="Authorization: Bearer $(echo 'cgr.dev' | docker-credential-cgr get | jq -r .Secret)" ``` You can also use the [`crane` tool](https://github.com/google/go-containerregistry/tree/main/cmd/crane): ```shell auth_header="$(crane auth token -H cgr.dev/ORGANIZATION_NAME/IMAGE_NAME)" ``` Replace `ORGANIZATION_NAME` and `IMAGE_NAME` as required. For example, if your organization is `foo.com` and you're interested in the `chainguard-base` image, you will use the following command: ```shell auth_header="$(crane auth token -H cgr.dev/foo.com/chainguard-base)" ``` You should now be ready to call the API, either manually or programmatically. ## Calling the API Make sure your authorization header is set, by running the following command: ```shell echo $auth_header ``` You should receive `Authorization: Bearer` followed by a long string (a [JWT](https://jwt.io/introduction)) as output. You can now run a `curl` query to this endpoint, following the below format. ```shell https://cgr.dev/v2/ORGANIZATION_NAME/IMAGE_NAME/_chainguard/history/IMAGE_TAG ``` Where: - For private images `ORGANIZATION_NAME` is the name of your organization, for example: `foo.com`. - For public images `ORGANIZATION_NAME` is always `chainguard`. - `IMAGE_NAME` is the name of the image, for example: `chainguard-base` or `python`. - `IMAGE_TAG` is the tag that you want to pull history from. For example, this is how you can fetch the tag history of **foo.com's** **chainguard-base:latest** Chainguard image using `curl` on the command line: ```shell curl -H "$auth_header" \ https://cgr.dev/v2/foo.com/chainguard-base/_chainguard/history/latest | jq ``` Or for a free-tier container image such as **python:latest**: ```shell curl -H "$auth_header" \ https://cgr.dev/v2/chainguard/python/_chainguard/history/latest | jq ``` You should get output like the following: ``` { "history": [ { "updateTimestamp": "2023-05-12T13:46:10.555Z", "digest": "sha256:81c334de6dd4583897f9e8d0691cbb75ad41613474360740824d8a7fa6a8fecb" }, { "updateTimestamp": "2023-05-12T20:50:19.702Z", "digest": "sha256:a8724b7a80cae14263a3b55f7acb5d195fcbb24afbc8067aa5198aa2a9131cde" }, ... ] } ``` ## Using the start and end parameters In some cases it may be helpful to specify digests created in a given time period rather than querying the entire history of a tag. For this, you can use the `start` and `end` parameters. These optional parameters can be added to requests to the Tag History API and should be specified in the `ISO 8601` format. To illustrate how to query digests of a container image created in the last week, first create a local shell variable named `timestamp`. On Ubuntu, you would create the `timestamp` variable as follows: ```shell timestamp=$(date -d "-1 week" +%Y-%m-%dT%H:%M:%SZ) ``` And on Wolfi, you would create it like this: ```shell timestamp=$(date -d @$(( $(date +%s ) - 604800 )) +%Y-%m-%dT%H:%M:%SZ) ``` Then to query digests of the **python:latest** Chainguard Container created in the last week you would run a command like the following: ```shell curl -s -H "Authorization: Bearer $tok" \ "https://cgr.dev/v2/chainguard/python/_chainguard/history/latest?start=${timestamp}" | jq ``` To query digests of the **python:latest** Chainguard Container created before 2024, first create a new `timestamp` variable like this: ```shell timestamp="2024-01-01T00:00:00Z" ``` Then run the query like this: ```shell curl -s -H "Authorization: Bearer $tok" \ "https://cgr.dev/v2/chainguard/python/_chainguard/history/latest?end=${timestamp}" | jq ``` Both of these examples filter the `curl` command's output through [`jq`](https://jqlang.github.io/jq/), a useful tool for processing JSON on the command line. ## Page limit Please note that the Tag History API will return a maximum of 1000 records on a single request. For tags with many digests, since the oldest digests are ordered first, it may be necessary to specify the timestamp of the desired digests - for this, the `start` and `end` parameters may be used as specified above. ## Using container digests within a Dockerfile Setting up your Dockerfile to use an older build is a matter of modifying your `FROM` line to use a container image digest instead of a tag. For instance, let's say you want to make sure you keep using the current latest build of the Python image. In a previous section of this page we obtained the tag history of the Python image, and the most recent build digest is listed as `sha256:81c334de6dd4583897f9e8d0691cbb75ad41613474360740824d8a7fa6a8fecb`. With that information, you can edit your Dockerfile and replace: ``` FROM cgr.dev/chainguard/python:latest ``` With: ``` FROM cgr.dev/chainguard/python@sha256:81c334de6dd4583897f9e8d0691cbb75ad41613474360740824d8a7fa6a8fecb ``` And your container image will then be locked into that specific build of the `python:latest` image variant. --- ### Debugging distroless container images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/troubleshooting/debugging-distroless-images.md Last Modified: September 28, 2026 Tags: Chainguard Containers Because distroless images are minimal and don't include a package manager or a shell, debugging issues that occur at runtime may require a distinctive approach. In this article, we'll discuss a few different strategies to debug distroless images. ## 1. Using development container image variants Before moving a workload to a distroless runtime image, it is important to make sure that it runs without issues in a similar but less restrictive environment, which allows for easier debugging. It is also possible to make a temporary base image change from a distroless image to a fully featured image that offers more debugging capabilities. The development variants of [Chainguard Containers](/chainguard/containers/) are designed to replicate the same packages of their distroless version, but with additional software that helps in developing, building, and debugging applications in different language ecosystems. These are sometimes referred to as `-dev` variants since they are tagged with `:latest-dev`. > **Note**: If you're debugging a workload that crashes because it expects packages from its previous upstream image, a *full* variant may help. Available for a number of our most popular Containers and tagged `-full`, these variants map their upstream equivalent. Refer to [Full container variants](/chainguard/containers/concepts/container-variants/#full-container-variants). For example, the following table shows a comparison between the development variants of the PHP image, and which packages are included with each variant: | | latest | latest-dev | latest-fpm | |--------------------------|--------|------------|------------| | `wolfi-baselayout` | X | X | X | | `ca-certificates-bundle` | X | X | X | | `php` | X | X | X | | `apk-tools` | | X | | | `bash` | | X | | | `busybox` | | X | | | `git` | | X | | | `composer` | | X | | | `php-fpm` | | | X | You can find similar detailed package information for all [Chainguard Containers](https://images.chainguard.dev) in their respective image details pages under the SBOM section. Once you have changed your Dockerfile base image to use a development variant, you can overwrite the entry point command to get a shell on the container: ```shell docker run -it --entrypoint /bin/sh cgr.dev/chainguard/php:latest-dev ``` Having a package manager and the ability to log into the image to debug any issues is very important at development time, but becomes unnecessary (and less safe) when talking about production environments. That's why we recommend using a distroless variant for production workloads. ### Chainguard Containers in production Although the development image variants have similar security features as their distroless versions, such as complete SBOMs and signatures, they feature additional software that is typically not necessary in production environments. The general recommendation is to use the development variants to build the application and then copy all application artifacts into a distroless image, which will result in a final container image that has a minimal attack surface and won't allow package installations or logins. That being said, it's worth noting that the `-dev` variants of Chainguard Containers are still **more secure** than many popular container images based on fully-featured operating systems such as Debian and Ubuntu, because they carry less software, follow a more frequent patch cadence, and offer attestations for what is included. ### Language ecosystem guides The following guides show how to use these development images in combination with their distroless variants in order to build a final image that is also distroless, but contains everything the application needs to run: - [Getting started with the Python Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/python/) - [Getting started with the Ruby Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/ruby/) - [Getting started with the Go Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/go/) - [Getting started with the Node Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/node/) - [Getting started with the PHP Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/php/) Check also the guide on [Creating Wolfi container images with Dockerfiles](/open-source/wolfi/wolfi-with-dockerfiles/) for guidance on how to build a custom image that can be used for development and debugging. ## 2. Using ephemeral debug containers Another method to debug distroless images running in production is to use ephemeral debug containers, a special type of container that is temporarily attached to an existing Pod to troubleshoot and inspect running services. Suppose you have a Kubernetes cluster and one of the containers is having issues. Perhaps it doesn't seem to be able to connect to other services in the cluster, or expected processes aren't running. If the container has a shell, you'd be able to use `kubectl exec` to run debugging commands from inside the container to help diagnose the issue. With a minimal image, this isn't possible, but we can achieve something very similar using `kubectl debug` to launch an ephemeral container. Let's review an example. We'll start by running a Chainguard NGINX image on a Kubernetes cluster: ```shell kubectl run nginx --image=cgr.dev/chainguard/nginx:latest ``` Which should result in: ```output pod/nginx created ``` We can try to start a shell in this pod: ```shell kubectl exec -it nginx -- sh ``` But the following error occurs: ```output error: Internal error occurred: error executing command in container: failed to exec in container: failed to start exec "67bd5164394b1170ac1846bd77ea0b826332365970fbde3b3201bb9abec9b72c": OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown ``` Which is a long way of telling us there is no shell in the image. What we can do instead is use an ephemeral container. An ephemeral container will connect to the namespaces of an existing container, effectively allowing you to sideload debugging tools. Let's try that now: ```shell kubectl debug -it nginx --image=cgr.dev/chainguard/wolfi-base --target=nginx ``` You should get output similar to: ``` Targeting container "nginx". If you don't see processes from this container it may be because the container runtime doesn't support this feature. Defaulting debug container name to debugger-87792. If you don't see a command prompt, try pressing enter. nginx:/# ``` Now we can try running some debugging commands. Let's start by seeing what's running: ``` nginx:/# ps aux PID USER TIME COMMAND 1 65532 0:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf -e /dev/stderr 10 65532 0:00 nginx: worker process 11 65532 0:00 nginx: worker process 12 65532 0:00 nginx: worker process 13 65532 0:00 nginx: worker process 14 65532 0:00 nginx: worker process 15 65532 0:00 nginx: worker process 16 65532 0:00 nginx: worker process 23 root 0:00 /bin/sh -l 32 root 0:00 ps aux ``` And the open ports: ``` nginx:/# netstat -lntu Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN ``` To facilitate accessing processes running in other containers without having to specify a target, you may enable [process namespace sharing](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) within your Pod setup. It's also worth noting that the filesystem may not be accessible due to default user permissions. ### Troubleshooting volume mounts Kubernetes starts the ephemeral container without mounting the same volumes in the target container. As a result, directories like `/var/log`, which are backed by a volume in the original container, will appear empty or missing in the ephemeral container. Kubernetes does not currently offer a built-in way to automatically replicate volume mounts from the main container to the ephemeral debug container. There is ongoing discussion in the Kubernetes community regarding this limitation, including proposals for features like `--with-volume-mounts[=]` which would allow mounting the same volumes in ephemeral containers. This feature is not yet available. To work around this issue, you can manually create a copy of the pod definition with a new debug container that replicates the necessary volume mounts. Proxy the Kubernetes API: ``` kubectl proxy ``` Patch the pod to add an ephemeral container: ``` curl http://localhost:8001/api/v1/namespaces/default/pods//ephemeralcontainers \ -X PATCH \ -H 'Content-Type: application/strategic-merge-patch+json' \ -d ' { "spec": { "ephemeralContainers": [ { "name": "debugger", "command": ["sh"], "image": "cgr.dev/chainguard/wolfi-base", "targetContainerName": "", "stdin": true, "tty": true, "volumeMounts": [ { "mountPath": "/var/log", "name": "varlog", "readOnly": true } ] } ] } }' ``` - Replace `` and `` with your actual values. - Be sure to match the `volumeMounts` spec to those in your target container. #### Pods enforcing runAsNonRoot If the pod enforces `runAsNonRoot: true`, the ephemeral container will fail to start with a `CreateContainerConfigError` because `wolfi-base` runs as root by default. In this case, add a `securityContext` to the patch that matches the target container’s policy: ``` curl http://localhost:8001/api/v1/namespaces/default/pods//ephemeralcontainers \ -X PATCH \ -H ‘Content-Type: application/strategic-merge-patch+json’ \ -d ‘ { "spec": { "ephemeralContainers": [ { "name": "debugger", "command": ["sh"], "image": "cgr.dev/chainguard/wolfi-base", "targetContainerName": "", "stdin": true, "tty": true, "securityContext": { "runAsUser": 1001, "runAsNonRoot": true, "allowPrivilegeEscalation": false, "capabilities": { "drop": ["ALL"] } }, "volumeMounts": [ { "mountPath": "/var/log", "name": "varlog", "readOnly": true } ] } ] } }’ ``` Set `runAsUser` to match the UID of the target container’s process. You can find this in the Pod spec’s `securityContext.runAsUser`, or by running `ps aux` in the ephemeral container before the `securityContext` constraint takes effect. #### Accessing the target container’s filesystem without volumeMounts As an alternative to replicating `volumeMounts`, you can access the target container’s filesystem directly via `/proc/1/root/` in the ephemeral container. Because `targetContainerName` puts the ephemeral container in the same PID namespace as the target, PID 1 is the target container’s main process, and `/proc/1/root/` traverses its full mount namespace — including Kubernetes-backed volumes such as `emptyDir`, `configMap`, `secret`, and persistent volume claims. For example, to read a log file from the target container: ```shell cat /proc/1/root/var/log/app/app.log ``` This requires the ephemeral container to run as the same UID as the target process, which is why the `securityContext` fix above is a prerequisite. If security policies in your environment block `/proc//root` access (for example, strict seccomp or AppArmor profiles), use the explicit `volumeMounts` approach instead. Attach to the debug container: ``` kubectl attach -c debugger -ti ``` In the ephemeral container shell, you should now see the expected contents within `/var/log` or other paths you’ve mounted. For more strategies on how to debug production distroless containers, check the [Kubernetes documentation on debugging running Pods](https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/). ## Resources to learn more - [Minimal Container Images: Towards a More Secure Future](https://www.chainguard.dev/unchained/minimal-container-images-towards-a-more-secure-future) - Chainguard Blog - [Why distroless](/chainguard/containers/overview/) - Chainguard Container Documentation - [Ephemeral Containers](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/) - Official Kubernetes Documentation - [Introducing Ephemeral Containers](https://opensource.googleblog.com/2022/01/Introducing%20Ephemeral%20Containers.html) - Google Open Source Blog - Talk: [Running a Go Debugger in Kubernetes](https://www.youtube.com/watch?v=V3SrFyMxmq4&t=2691s) - Cloud Native Rejekts EU 23 --- ### Getting started with the Node Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/node.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [Node container image](https://images.chainguard.dev/directory/image/node/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-node) provides a secure runtime for Node.js applications. This distroless image includes Node.js and npm while maintaining a minimal attack surface for production deployments. In this guide, we'll set up a demo application and create a Dockerfile to build and execute the demo using the Node Chainguard Containers as base. This tutorial requires Docker, Node, and Npm to be installed on your local machine.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Step 1: Setting up a demo application We'll start by creating a small Node application to serve as a demo. This application uses a mock server to test GET and POST requests, and is based on the [Docker Node example](https://docs.docker.com/language/nodejs/build-images/). First, create a directory for your app. In this guide we'll use `wolfi-node`: ```shell mkdir ~/wolfi-node && cd ~/wolfi-node ``` Run the following command to create a new `package.json` file. ```shell npm init -y ``` Exit the `init` process. You do not need to fill out the metadata as this is a demo. Install `npm`: ```shell npm install ``` Next, install the application dependencies. We'll need `ronin-server` and `ronin-mocks`, which are used together to create a "mock" server that saves JSON data in memory and returns it in subsequent GET requests to the same endpoint. ```shell npm install ronin-server ronin-mocks ``` Using your code editor of choice, create a new file named `server.js` for your application. Here we'll use `nano`: ```shell nano server.js ``` Copy the following code to your `server.js` file: ```js const ronin = require('ronin-server') const mocks = require('ronin-mocks') const server = ronin.server() server.use('/', mocks.server(server.Router(), false, true)) server.start() ``` Save the file when you're done. Then, run the server with: ```shell node server.js ``` This command will start the application and wait for connections on port `8000`. The mocking server will save any JSON data submitted by a POST request From a new terminal window, run the following command, which will make a POST request to your application sending a JSON payload: ```shell curl --request POST \ --url http://localhost:8000/test \ --header 'content-type: application/json' \ --data '{"msg": "testing" }' ``` When the connection is successful, you should get output like this on the terminal that is running the application: ```shell 2023-02-07T15:48:54:2450 INFO: POST /test ``` You can now test that the content was saved by running a GET request to the same endpoint: ```shell curl http://localhost:8000/test ``` You'll get output similar to this: ```shell {"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing","id":"f427f835-3e93-43ad-91c8-d150dffba0f9","createDate":"2023-02-07T14:48:54.256Z"}]} ``` The demo application is now ready. In the next step, you'll create a Dockerfile to run your app. Before moving along, make sure to stop the server running on your terminal by typing `CTRL+C` (or `CMD+C` for macOS users). ## Step 2: Creating the Dockerfile In your code editor of choice, create a new Dockerfile: ```shell nano Dockerfile ``` The following Dockerfile will: 1. Start a new image based on the `cgr.dev/chainguard/node:latest` container image; 2. Set the work dir to `/app` inside the container; 3. Copy application files from the current directory to the `/app` location in the container; 4. Run `npm install` to install production-only dependencies; 5. Set up additional arguments to the default entrypoint (`node`), specifying which script to run. Copy this content to your own `Dockerfile`: ```Dockerfile FROM cgr.dev/chainguard/node ENV NODE_ENV=production WORKDIR /app COPY --chown=node:node ["package.json", "package-lock.json", "server.js", "./"] RUN npm install --omit=dev CMD [ "server.js" ] ``` Save the file when you're finished. You can now build the container with: ```shell docker build . --pull -t wolfi-node-server ``` Once the build is finished, run the container with: ```shell docker run --rm -it -p 8000:8000 wolfi-node-server ``` And it should work the same way as before: the terminal will be blocked with the application waiting for connections on port `8000`. The difference is that this is running inside the container, so we set up a port redirect to receive requests on `localhost:8000`. ```shell curl --request POST \ --url http://localhost:8000/test \ --header 'content-type: application/json' \ --data '{"msg": "testing node wolfi image" }' ``` You can now query the same endpoint to receive the data that was stored in memory when you run the previous command: ```shell curl http://localhost:8000/test ``` ```shell {"code":"success","meta":{"total":1,"count":1},"payload":[{"msg":"testing node wolfi image","id":"6011f987-b9f8-4442-8253-d54166df5966","createDate":"2023-02-07T15:57:23.520Z"}]} ``` ## Advanced usage Chainguard offers [slim variants](/chainguard/containers/concepts/container-variants/#slim-container-variants) of its Node.js container image. These have a smaller footprint than our standard Node.js images and are useful for building images with a minimal attack surface in multi-stage builds. Check out our [Node migration guide](/chainguard/containers/migration/migration-guides/node/#using-slim-images) to learn more.

If your project requires a more specific set of packages that aren't included within the general-purpose Node Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Guardener Container Image Suggestions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/guardener/github/image-suggestions.md Last Modified: October 8, 2026 Tags: GitHub, Chainguard Containers, Migration Guardener reviews pull requests that add or change container image references and recommends the Chainguard container image that replaces each upstream image. Where it can, Guardener includes the replacement as a one-click suggested change.

Note: Guardener is in beta. Available to organizations that have installed and linked the Chainguard App.

Guardener reads only the lines a pull request adds or changes, so it catches upstream images as they're introduced but doesn't review the images a repository already uses. To replace those images, turn on [Container Image Migration](/chainguard/guardener/github/image-migration/), which opens a pull request for the whole repository. To list replacements for every image without changing any files, run [`chainctl images discover`](/chainguard/containers/migration/migration-tools/images-discover/). ## Prerequisites Before you enable Image Suggestions, make sure that: - The Chainguard App is installed and your GitHub organization is linked to your Chainguard organization, as described in [Getting started](/chainguard/guardener/github/getting-started/). The link is needed on public repositories too, because Guardener checks each replacement against your Chainguard organization. Without a link, the pull request check reports that container image recommendations are unavailable. - To get Image Suggestions on private repositories, your organization's [repository visibility scope](/chainguard/guardener/github/app-connections/#repository-visibility-scope) is `ALL`. With the default `PUBLIC` scope, Guardener skips private repositories without leaving a comment or a check run. ## Enable Image Suggestions Add a `.chainguard/images.yaml` file to your repository: ```yaml enabled: true ``` The file turns the feature on. To opt one repository out of an [organization-level default](/chainguard/guardener/github/configuration/#organization-level-configuration-with-the-github-repository), set `enabled: false` in that repository's file. Guardener reads `.chainguard/images.yaml` from the pull request's base branch, not from the pull request. A pull request that adds the file doesn't get Image Suggestions itself. Pull requests opened after it merges do. ## Choose FIPS or standard images Images your organization already has rank ahead of images it doesn't. Within each group, Guardener ranks the FIPS variant first when Chainguard publishes one. To rank standard images first, set `prefer_fips: false`: ```yaml enabled: true prefer_fips: false ``` ## What Guardener reviews Guardener reads the files a pull request changes and keeps only the image references on added or changed lines. | Files | What Guardener reads | Suggested change | | --- | --- | --- | | Dockerfiles: `Dockerfile`, `Dockerfile.*`, `*.dockerfile`, and `Containerfile` | Every `FROM` instruction, in every build stage | Yes | | YAML and JSON, such as Kubernetes manifests, Compose files, and Helm values | Every `image` value, and Helm-style `image` mappings split into `registry`, `repository`, and `tag` keys | Yes | | Terraform (`*.tf`, `*.tfvars`), shell scripts (`*.sh`), and Makefiles | Lines about an image, such as an `image` argument or a `docker run` command | No; reported only | A few details apply to specific formats: - In a Dockerfile, Guardener substitutes `ARG` defaults into `FROM` lines. It skips `FROM scratch`, `FROM` lines that name an earlier build stage, and references that depend on an `ARG` with no default. - Helm values are read as written, not rendered. A reference computed inside a chart template isn't seen. An `image` mapping with an empty tag uses the chart's `appVersion`. - Images used only in `COPY --from` or `RUN --mount` instructions aren't read. Guardener skips files under `node_modules`, `vendor`, `.terraform`, and `testdata` directories. ## How Guardener responds Guardener skips draft pull requests and reviews them once they're marked ready for review. For each changed reference that has a Chainguard replacement, Guardener leaves a review comment naming the replacement it selected. Guardener ranks replacements the same way as `chainctl images discover` and recommends the first one. The review is non-blocking. Guardener doesn't request changes, and its `actions-recommender-images` check run, which is separate from the Hardened Actions check, completes as successful or neutral, never as failed. The check run summarizes every changed reference, including the ones Guardener couldn't recommend a replacement for. A comment includes a one-click suggested change when all of the following are true: - The file is a Dockerfile, YAML file, or JSON file. - The reference is one literal value on its own line. It isn't assembled from a Dockerfile `ARG` or a split Helm `image` mapping, and no other image shares the line. - Your Chainguard organization already has the replacement image, with a matching tag. - The replacement keeps a version. Guardener doesn't suggest the `latest` tag for a reference with a tag that Chainguard doesn't maintain, or for a reference pinned by digest without a tag. - In a Dockerfile, the build stage and any stage built on it don't run commands with `RUN`, a shell-form `CMD` or `ENTRYPOINT`, or an `ONBUILD` instruction, and don't set `CMD` without `ENTRYPOINT`. Chainguard container images without a `-dev` suffix usually have no shell or package manager, and many set `ENTRYPOINT` to their runtime. Otherwise, the comment names the replacement and says why it has no suggested change, so you can make the change yourself or add the image to your organization first. Adding an image isn't always self-service: how you do it depends on your subscription, and the steps are in [The container isn't in your organization's catalog](/chainguard/containers/troubleshooting/container-version-troubleshooting/#the-container-isnt-in-your-organizations-catalog). A suggested change swaps one image reference for another. Guardener doesn't restructure a Dockerfile: it doesn't rename packages, split a build into stages, or change the user or entry point. Review and test the change as you would any other. To convert a whole Dockerfile, use [Dockerfile migration](/chainguard/guardener/dockerfile-migration/). ## Limits To keep reviews fast on large pull requests, Guardener stops reading at the following limits. Anything past a limit isn't reviewed, and the check run reports that the review stopped early. | Limit | Value | | --- | --- | | Changed files read per pull request | 300 | | Image references per pull request, counted once per file, line, and image | 100 | | Image reference occurrences per pull request | 1,000 | Guardener also skips any single file larger than 1 MiB, and the check run lists it under the files it couldn't analyze. ## Configuration reference | Key | Type | Default | Description | | --- | --- | --- | --- | | `enabled` | boolean | `true` when the file exists | Turns Image Suggestions on or off for the repository. | | `prefer_fips` | boolean | `true` | Ranks FIPS variants before standard variants. Images your organization already has still rank first. | | `migrate` | mapping | None | Settings for [Container Image Migration](/chainguard/guardener/github/image-migration/#configuration-reference). | ## Next steps - **[Container Image Migration](/chainguard/guardener/github/image-migration/)** — Replace the images a repository already uses through one migration pull request. - **[`chainctl images discover`](/chainguard/containers/migration/migration-tools/images-discover/)** — Find replacements for every image a repository already uses. - **[Configuration](/chainguard/guardener/github/configuration/)** — Set Image Suggestions once for your whole organization. - **[Dockerfile migration](/chainguard/guardener/dockerfile-migration/)** — Convert a Dockerfile to use Chainguard container images. --- ### Guardener Container Image Migration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/guardener/github/image-migration.md Last Modified: October 8, 2026 Tags: GitHub, Chainguard Containers, Migration Guardener scans the default branch of a repository for container image references and opens a pull request that replaces them with Chainguard container images your organization already has. As your repository and your organization's images change, Guardener keeps that pull request up to date.

Note: Guardener is in beta. Available to organizations that have installed and linked the Chainguard App.

Container Image Migration and [Container Image Suggestions](/chainguard/guardener/github/image-suggestions/) work together. Image Suggestions reviews the images that each new pull request adds, and migration replaces the images the repository already uses. Each has its own setting in `.chainguard/images.yaml`, so you can use either one or both. A migration pull request changes image references only. Guardener doesn't build your images, run your tests, or convert your Dockerfiles, so review and test the pull request as you would any other change. ## Prerequisites Before you enable Container Image Migration, make sure that: - The Chainguard App is installed with access to the repository, and your GitHub organization is linked to your Chainguard organization, as described in [Getting started](/chainguard/guardener/github/getting-started/). You don't need to change the [repository visibility scope](/chainguard/guardener/github/app-connections/#repository-visibility-scope): unlike Image Suggestions, migration runs on private repositories even with the default `PUBLIC` scope. - Your Chainguard organization has the replacement images. Guardener doesn't add images for you. Adding an image isn't always self-service: how you do it depends on your subscription, and the steps are in [The container isn't in your organization's catalog](/chainguard/containers/troubleshooting/container-version-troubleshooting/#the-container-isnt-in-your-organizations-catalog). ## Enable Container Image Migration Add a `migrate` block to the repository's `.chainguard/images.yaml` file, and set `enabled` inside it to `true`: ```yaml enabled: true migrate: enabled: true ``` The top-level `enabled` key controls Image Suggestions, and the `migrate.enabled` key controls migration. Image Suggestions is on whenever the file exists. To get only migration pull requests, set the top-level `enabled` to `false`: ```yaml enabled: false migrate: enabled: true ``` Commit the file to the repository's default branch. Guardener reads `.chainguard/images.yaml` only from the default branch, and the push that adds or changes the file starts the first migration. To set migration once for every repository in your organization, commit the file to your organization's `.github` repository instead, as described in [Configuration](/chainguard/guardener/github/configuration/#organization-level-configuration-with-the-github-repository). A repository's own `.chainguard/images.yaml` replaces the organization file entirely, so a repository file without a `migrate` block turns migration off for that repository. ## When migration runs Guardener runs a migration for a repository at the following times: - After a push to the default branch that adds, changes, or removes `.chainguard/images.yaml` in that repository. - On a schedule, about once every `migrate.period`. The default and minimum period is 24 hours. - On demand, when you run `chainctl guardener github migrate create`, as described in [Run an on-demand migration](#run-an-on-demand-migration). Each run scans the whole default branch again. A scheduled run therefore picks up images that your organization added since the last run, as well as new image references in the repository. Changes to the organization-level file in your `.github` repository take effect at each repository's next scheduled run. ## Review the migration pull request Guardener keeps one image migration pull request per repository, separate from the [Hardened Actions](/chainguard/guardener/github/actions-security/) migration pull request. The pull request is titled **Migrate container images to Chainguard equivalents**, and its description lists the following: - **Changed files**: every file that the pull request edits. - **Not migrated**: each reference that Guardener found and left unchanged, with the reason. Each run plans its changes against the latest commit on the default branch. If the migration pull request is open, Guardener updates it only when the planned changes differ from the ones it already has. After you merge or close the pull request, the next run opens a new one if there's anything left to change. Guardener never merges the pull request itself. Closing the pull request doesn't turn migration off. To stop migration for a repository, set `migrate.enabled: false`. To make your own changes to the pull request, add the `skip:guardener/images` label or assign the pull request to someone first. Guardener then stops updating it. Don't push to the pull request's branch, under `guardener/images/`, without first adding the label or an assignee, because Guardener force-pushes the branch on each update. When nothing is left to change, Guardener doesn't open a pull request, and it doesn't close one that's already open. If you make the same changes yourself, close the open migration pull request. ## What migration changes Migration reads Dockerfiles, YAML files, and JSON files across the whole repository. It skips files under `node_modules`, `vendor`, `.terraform`, and `testdata` directories. | Files | What Guardener changes | | --- | --- | | Dockerfiles: `Dockerfile`, `Dockerfile.*`, `*.dockerfile`, and `Containerfile` | The image in each `FROM` instruction, in every build stage. | | YAML and JSON, such as Kubernetes manifests, Compose files, and Helm values | Each literal `image` value. Guardener doesn't read Helm chart templates. | | Terraform (`*.tf`, `*.tfvars`), shell scripts (`*.sh`), and Makefiles | Nothing. Guardener doesn't read these files. To find the images they use, run [`chainctl images discover`](/chainguard/containers/migration/migration-tools/images-discover/). | Guardener chooses each replacement the same way as Image Suggestions. When Chainguard publishes a Federal Information Processing Standards (FIPS) variant of an image, Guardener prefers it unless you set `prefer_fips: false`. Guardener keeps the reference's tag when Chainguard maintains it. Otherwise, it uses the newest maintained version on the same minor version line or, failing that, in the same major version. If the version changes, check its release notes before you merge. When a reference has both a tag and a digest, the replacement is also pinned by digest. Guardener leaves a reference unchanged and lists it under **Not migrated** in the following cases: - Your Chainguard organization doesn't have the replacement image, or doesn't have a matching tag. After you add the image, the next run migrates the reference. - Chainguard doesn't publish an equivalent image. - The reference isn't one literal value on its own line. For example, it's assembled from a Dockerfile `ARG`, it's split across Helm `registry`, `repository`, and `tag` keys, or another image shares its line. - The Dockerfile build stage, or a stage built on it, runs commands with `RUN`, a shell-form `CMD` or `ENTRYPOINT`, or an `ONBUILD` instruction. Chainguard container images without a `-dev` suffix usually have no shell or package manager, so swapping only the image could break the build. To convert the whole Dockerfile, use [Dockerfile migration](/chainguard/guardener/dockerfile-migration/). - The Dockerfile build stage sets `CMD` without `ENTRYPOINT`. Many Chainguard container images set `ENTRYPOINT` to their runtime, so the command could change. - The reference has a tag that Chainguard doesn't maintain, and no maintained version is close enough to replace it, so the only replacement would be the `latest` tag. - The reference is pinned by digest without a tag, so there's no version to carry over. ## Exclude files and images To keep Guardener from changing specific files or images, add a `migrate.ignore` block: ```yaml migrate: enabled: true ignore: files: - "Dockerfile.dev" - "examples/*" images: - "busybox:*" ``` Both lists take glob patterns. A `*` matches any characters except `/`, and `**` doesn't match across directories. - `ignore.files` matches a file's path from the repository root, or its file name. In this example, `examples/*` matches `examples/Dockerfile` but not `examples/app/Dockerfile`. - `ignore.images` matches the image reference as written in the file, with Dockerfile `ARG` defaults filled in. In this example, `busybox:*` matches `busybox:1.36`, but it doesn't match `docker.io/library/busybox:1.36` or `busybox` with no tag. Ignored files and images don't appear in the migration pull request. ## Run an on-demand migration Rather than waiting for the next scheduled run, you can start a migration right away with `chainctl`. For example, you might do this after you add an image to your organization. An on-demand run performs the same migration as a scheduled run and uses the same configuration. It doesn't change the schedule. Before you start, make sure that: - You have `chainctl` 0.2.375 or later. Earlier versions migrate only GitHub Actions. To update, run `chainctl update`. - The repository has `migrate.enabled: true` in its own `.chainguard/images.yaml` file or in the organization-level file. - You hold the `guardener.images.migrate` capability on the Chainguard organization that's linked to the repository's GitHub organization. The built-in `viewer`, `editor`, and `owner` roles include it, as do `guardener.user` and `guardener.admin`. For more information, refer to the [Built-in roles and capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/). Run `chainctl guardener github migrate create` with the repository: ```shell chainctl guardener github migrate create / ``` The command runs every migration that the repository opts in to: Container Image Migration through `.chainguard/images.yaml` and Hardened Actions migration through `.chainguard/actions.yaml`. Each opens or updates its own pull request. By default, the command waits up to 10 minutes for the migrations to finish and then prints one result for each: ``` Repository: https://github.com// Triggered by: you@example.com (user) Actions: migration is not enabled here or in repository configuration Images: https://github.com///pull/43 Status: completed ``` In this example, the repository opts in to Container Image Migration only. The `Images:` line reports one of the following results: | Result | Meaning | | --- | --- | | A pull request URL | Guardener opened or updated the migration pull request. | | `no changes needed` | Guardener found nothing it could change. This includes references whose replacement your organization doesn't have yet. To see the replacement for every reference, run [`chainctl images discover`](/chainguard/containers/migration/migration-tools/images-discover/). | | `migration is not enabled here or in repository configuration` | The repository doesn't have `migrate.enabled: true`. | | `failed` | Guardener couldn't finish the migration. The message in parentheses says why, such as a missing capability or a [scan limit](#limits). | When any migration fails, the last line reads `Status: completed with failures`, and the command exits with an error. To return without waiting, or to check a migration later, refer to [Check a migration operation](/chainguard/guardener/github/actions-security/#check-a-migration-operation). ## Limits Because migration reads the whole repository, it has the following limits. When a repository exceeds one, Guardener doesn't open or update the migration pull request, and an on-demand run reports `Images: failed` with the limit it reached. To bring the repository under the limit, exclude directories or files with [`migrate.ignore.files`](#exclude-files-and-images). | Limit | Value | | --- | --- | | Image references, counted once per file, line, and image | 100 | | Image reference occurrences | 1,000 | | Files in Helm chart `templates` directories, which Guardener checks so it can skip them | 300 | | Total size of the files read | 128 MiB | Guardener also skips any file larger than 1 MiB and lists it under **Not migrated**. ## Configuration reference The following keys in `.chainguard/images.yaml` control migration: | Key | Type | Default | Description | | --- | --- | --- | --- | | `migrate.enabled` | boolean | `false` | Turns migration pull requests on for the repository. | | `migrate.period` | duration | `24h` | How often Guardener runs a scheduled migration, written as a Go duration such as `168h` for one week. A period shorter than 24 hours, or a value that isn't a duration, uses 24 hours. | | `migrate.ignore.files` | list of glob patterns | None | Files that migration doesn't read. | | `migrate.ignore.images` | list of glob patterns | None | Image references that migration leaves unchanged. | | `prefer_fips` | boolean | `true` | Ranks FIPS variants before standard variants, for both migration and Image Suggestions. | The top-level `enabled` key controls Image Suggestions only. For more information, refer to the [Image Suggestions configuration reference](/chainguard/guardener/github/image-suggestions/#configuration-reference). ## Full example The following `.chainguard/images.yaml` file turns on Image Suggestions and weekly migration pull requests. It prefers standard images to FIPS variants, and it leaves a development Dockerfile and every `busybox:` reference unchanged: ```yaml enabled: true prefer_fips: false migrate: enabled: true period: "168h" ignore: files: - "Dockerfile.dev" images: - "busybox:*" ``` Commit the file to the repository's default branch. The push that adds or changes it starts a migration. ## Next steps - **[Container Image Suggestions](/chainguard/guardener/github/image-suggestions/)** — Get suggestions on the images each new pull request adds. - **[`chainctl images discover`](/chainguard/containers/migration/migration-tools/images-discover/)** — Find replacements for every image a repository uses, including the ones migration can't change. - **[Dockerfile migration](/chainguard/guardener/dockerfile-migration/)** — Convert a whole Dockerfile to use Chainguard container images. --- ### Run checks before you push with Chainguard Workspaces URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/run-checks.md Last Modified: October 13, 2026 Tags: Chainguard Checks, chainctl, Procedural Waiting for a pull request's checks to find a mistake is slow, especially for a coding agent that has to push, wait, and read the results before it can try again. With [Chainguard Workspaces](/chainguard/workspaces/overview/), you can run your repository's checks against your working tree, uncommitted changes included, before you push. This page explains how, and how to find mistakes in your configuration without running anything. To run checks on a pushed commit without a pull request, or to read results and your checks' history from your terminal, refer to [Inspect and run checks with chainctl](/chainguard/checks/inspect-and-run-checks/). ## Run checks in a Workspaces session Every Workspaces session has a `run-ci` command that runs the checks your repository defines against the working tree in `/work`. To run every check, run `run-ci` with no arguments in the session: ```shell run-ci ``` The command prints each step's output as it runs, followed by a result line. For a repository with `vet` and `test` checks, the output looks similar to the following: ``` run-ci: checks vet, test (advisory: the gate runs the verdict on push) ── vet[0]: go vet ./... ── vet[0]: exit 0 (9.281s) ── test[0]: go test ./... ok github.com/acme/app 0.004s ── test[0]: exit 0 (3.843s) PASS in 13.131s ``` Each `──` line names a check and the number of the step within it, counting from `0`, followed by the step's command or its exit code and duration. The first line is a reminder that the pull request's run, not `run-ci`, decides what GitHub shows. When a check declares a different environment from the session's, `run-ci` also prints a note about it. To run a single check, pass its name: ```shell run-ci test ``` The `run-ci` command exits with one of the following codes, so scripts and agents can act on the result: | Exit code | Meaning | | --------- | ------- | | `0` | Every check passed. | | `1` | A check failed. The last line names the check and the number of the step that failed. | | `2` | The checks couldn't run. The last line says why. | ### How run-ci differs from a pull request run {#how-run-ci-differs-from-a-pull-request-run} The `run-ci` command reads your checks from the session's working tree, so it uses your latest edits to the configuration too. It differs from a pull request run in the following ways: - It runs the checks that have commands, in the order they appear in your configuration, and stops at the first check that fails, including an advisory check. - It skips checks that need something only a pull request run provides: a [workload identity](/chainguard/checks/configuration/#credentials), `services` in the check's environment, or `inputs`. It prints a line for each, such as `run-ci: skipped deploy: declares identity gcp-prod, which only CI provides`. Those checks run only on the pull request, so a pass from `run-ci` doesn't cover them. If you name a skipped check, as in `run-ci deploy`, or if it skips every check, `run-ci` exits with `2`. - It runs checks as the session's user, not as each check's `user`. Sessions run as `root` unless their environment comes from a check with a different `user`, such as a session you open with `chainctl develop --check`. In a `root` session, every check runs as `root`. A session that runs as another user runs only the checks with that `user`, and skips the rest the same way. - It ignores `needs`, `when` conditions, `trigger`, and `timeout`, so it also runs checks that a pull request would skip. A check with `foreach` runs once for each variation, and each one appears in the output as `/`, such as `test/api`. - It runs each check in the session as it is, not in a fresh VM per check. Packages you installed in the session are available, and a check's own package list and network access don't apply. - It uses the session's size and CPU architecture, not each check's `resources`. A session runs on x86-64 unless its resource class ends in `-arm64`, as described in [Resources](/chainguard/workspaces/session-environment/#resources). On pull requests, a class without a suffix usually runs on arm64. Because of these differences, a pass from `run-ci` predicts that the pull request's checks will pass, but only the pull request's run in fresh VMs decides what GitHub shows. The `run-ci` command works while something is attached to the session, such as your terminal. It also works after you detach, if you keep the session running with a **Detach — keep running** option or `--keep`, as described in [Keep a session running while you're away](/chainguard/workspaces/manage-sessions/#keep-a-session-running-while-youre-away). Running checks doesn't keep a session from parking, so if the session parks while `run-ci` runs, the run stops and `run-ci` exits with `2`. ## Run checks in a session from your laptop {#run-checks-in-a-session-from-your-laptop} To run a session's checks without attaching to it, use `chainctl develop check` on your laptop with the session's name or short ID. If the session is parked, `chainctl` resumes it first. Replace `$SESSION` with the session's name or short ID, and `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl develop check $SESSION --parent $ORGANIZATION ``` To run a single check, add `--check` with its name: ```shell chainctl develop check $SESSION --check test --parent $ORGANIZATION ``` The command uses the same runner as `run-ci` and streams each step's output. It skips the same checks as `run-ci`, and prints a line for each, such as `Skipped deploy: declares identity gcp-prod, which only CI provides`. It exits with `0` when the checks pass, or `1` when a check fails or the checks can't run, including when you name a skipped check with `--check`. To print the result as JSON, add `--output json`. The JSON lists the checks it skipped under `skipped`. If you detached from the session and kept it running, `chainctl develop check` might fail with a message that says `the guest is held by another controller`. Run the command again, or attach to the session and run `run-ci` in it. ## Validate your configuration offline {#validate-your-configuration-offline} To find mistakes in your configuration without running anything, run `chainctl checks validate` anywhere in your repository's checkout: ```shell chainctl checks validate ``` With no argument, the command finds your repository's root. You can also pass the path of a repository or of a configuration file. For a valid configuration, the command prints each workflow it found: ``` workflow ci (.chainguard/ci.yaml) ok: the document lowers ``` For a configuration with problems, it prints each problem with its file, line, and column, and exits with a non-zero status. For example, a string `cmd` with a pipe in it produces an error that suggests using a shell: ``` .chainguard/ci.yaml:6:10: check t: cmd "go test ./... | tee out": unquoted shell metacharacter '|': a string `cmd:` is safe-split with no shell behind it (no pipes, redirects, globs, or expansion) — quote the character, or write `cmd: [sh, -c, "..."]` to opt into a shell explicitly ``` Because `validate` needs no network access or Chainguard account, you can run it in a git pre-commit hook. To see how Chainguard Checks interprets your configuration, run `chainctl checks explain`. It prints each check with its environment, trigger, and commands, plus its conditions, network access, and timeout when they're set: ```shell chainctl checks explain ``` Run `explain` when a check doesn't behave as you expect, such as running when you thought a condition would skip it. For a pull request, Chainguard Checks reads only the files directly in your repository's root `.chainguard/` directory and the files under `.chainguard/ci/`. In the directory layout, `validate` and `explain` read the same files. In the single-file layout, they also read `.chainguard/` directories below the root, such as `testdata/.chainguard/`, and folders inside `.chainguard/`. As a result, they can list a workflow that pull requests never run, or fail on a file that pull requests ignore. To avoid this, move those files out of `.chainguard/` directories, or use the [directory layout](/chainguard/checks/configuration/#file-layouts). --- ### Manage Chainguard Workspaces session credentials URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/credentials.md Last Modified: October 13, 2026 Tags: Chainguard Workspaces, Security, Configuration A Chainguard Workspaces session holds no long-lived credentials: its VM stores no API keys, tokens, or registry logins. Instead, the session reaches each service through a local hostname, and the credential is added to the request outside the VM. This page explains what a session can reach by default, how to change it, and [which credentials a session can't supply](#credentials-a-session-cant-supply). A check in Chainguard Checks can get short-lived credentials through a workload identity, which a session can't use. Refer to [Workload identities and trust](/chainguard/checks/identity-and-trust/). ## How credentials reach a session {#how-credentials-reach-a-session} Each service a session can reach has a hostname inside the session, such as `http://github/` or `http://claude/`. Chainguard configures the session to use these hostnames. For example, `git` in the session rewrites `https://github.com/` URLs to `http://github/`, and Claude Code sends its requests to `http://claude/`. When a request reaches one of these hostnames, `chainctl` on your laptop or Chainguard adds the credential outside the VM. Each credential has a *source*: | Source | Where the credential comes from | | ------ | ------------------------------- | | `laptop` | Your laptop. For `claude` and `gcp`, `chainctl` sends the credential to the session's host, outside the VM, when you attach. For `github` and `cgr.dev`, `chainctl` answers each request from your laptop, so the credential never leaves your machine. | | `org` | Your Chainguard organization, such as the Chainguard App's access to your GitHub repositories. | | `seat` | Your Chainguard identity, which your laptop delegates to the session when you attach. Only the `chainguard` service uses this source. | | `off` | Nothing. The session can't use the service. | A credential that `chainctl` sends to the session's host stays there until the session parks or the credential expires, so it keeps working after you close your terminal. Refer to [Use Claude Code in a session](/chainguard/workspaces/coding-agents/) for how long Claude's credential lasts, and to [Use your Google Cloud credentials in a session](#use-your-google-cloud-credentials-in-a-session) for Google Cloud's. Requests that `chainctl` answers from your laptop work only while your laptop serves the session, which it does while you're attached. If your terminal served one of them while you were attached, it keeps serving after you detach and keep the session running, until you press `Ctrl+C`. If nothing serves a session, these requests fail with an error that says no laptop is attached. ## What a session can reach by default {#what-a-session-can-reach-by-default} A new session can reach the following services without asking you: | Service | Default source | What it covers | | ------- | -------------- | -------------- | | `claude` | `laptop` | The credential Claude Code on your laptop uses, for the Claude API's messages and models endpoints. | | `github` | `org` | `git` fetch and push for the session's repository, and for the repositories of your checkout's other github.com remotes where the Chainguard App is installed. To use the others, add them as remotes in the session, as described in [Choose the session's repository](/chainguard/workspaces/manage-sessions/#choose-the-sessions-repository). | | `cgr.dev` | `org` | Pulls images and lists tags from `cgr.dev` as the Chainguard platform, not as you, so it can't pull or list your organization's private images. | | `chainguard` | `seat` | `chainctl` and the Chainguard Model Context Protocol (MCP) servers in the session act as you, limited to read, Chainguard Checks, and MCP capabilities in the session's organization, within the permissions you hold. Refer to [Your Chainguard identity in a session](#your-chainguard-identity-in-a-session). | The GitHub API isn't covered by default. The `gh` CLI is installed in every session, but it has no credential unless you connect your GitHub account before you create the session and your organization's policy allows it, as described in [Let sessions act on GitHub as you](/chainguard/workspaces/github-as-you/). ## Approve requests from a session When a session asks for something beyond its defaults, such as fetching a private repository that isn't one of your checkout's remotes, `chainctl` shows a prompt on your attached terminal. The prompt says what the session wants and which credential it would use. The keys you can press depend on the request: | Request | Keys | | ------- | ---- | | Fetch a repository | `y` approves the request and remembers your approval for the next session on the same repository. `n` declines it. | | Push to a repository | `p` approves the push for this session only. `n` declines it. | Pressing `Enter` declines, and so does leaving the prompt unanswered for 60 seconds. To protect you from approving a request by accident, `chainctl` ignores keys for a moment after a prompt appears, and treats a paste as a decline. If no terminal is attached, a session can't ask you, so it refuses the request. A terminal that keeps serving after you detach also refuses new requests. To allow access ahead of time, use `--with`. ## Allow access ahead of time {#allow-access-ahead-of-time} The `--with` flag adds access to a session when you create it or return to it. You can repeat the flag or separate entries with commas, and entries add to what the session already has. `chainctl` records each change on the session and remembers it on your laptop for the next session on the same organization and repository. The `--with` flag accepts the following entries: | Entry | What it does | | ----- | ------------ | | `github:/` | Allows fetching another repository. | | `github:/+push` | Allows fetching and pushing to another repository. | | `github:/*` | Allows fetching every repository of a GitHub account or organization. | | `github:*` | Allows fetching any GitHub repository. | | `claude`, `chainguard` | Turns a default service back on. | | `cgr.dev` | Pulls from `cgr.dev` as you, with your laptop's `cgr.dev` login. | | `gcp` | Lets the session call Google Cloud APIs as you, with your laptop's Google Cloud application default credentials. You can add it to a new or existing session, `all` doesn't include it, and `chainctl` doesn't remember it for the next session. Refer to [Use your Google Cloud credentials in a session](#use-your-google-cloud-credentials-in-a-session). | | `all` | Restores the defaults and allows fetching any GitHub repository. | | `none` | Turns off `claude`, `cgr.dev`, `chainguard`, and `gcp`, and allows only the session's own repository. It can't be combined with other entries. | For example, this command lets the session push to a second repository, `acme/docs`, in addition to its own: ```shell chainctl develop --with github:acme/docs+push --parent $ORGANIZATION ``` Wildcard entries allow fetching only. To push to a repository, name it with `+push`. Under your organization's default GitHub policy, the Chainguard App doesn't serve wildcard entries, so they give access to public repositories only. To fetch a private repository, name it. The `github` entries have no effect on a session that acts on GitHub as you, as described in [Controls that don't apply to a session that acts as you](/chainguard/workspaces/github-as-you/#controls-that-dont-apply-to-a-session-that-acts-as-you). ## Use your Google Cloud credentials in a session {#use-your-google-cloud-credentials-in-a-session} To let `gcloud` and the Google Cloud client libraries in a session act as you, run `gcloud auth application-default login` on your laptop. Then add `--with gcp` when you create a session or return to one. For example, this command turns on `gcp` for an existing session: ```shell chainctl develop app/quiet-harbor --with gcp --parent $ORGANIZATION ``` While you're attached, you can also turn `gcp` on or off from the session menu: press `Ctrl+]`, choose **Credentials**, choose `gcp`, and then choose **Set the source**. The change can take up to 30 seconds to reach the session. If `chainctl` says that the session's guest `was booted without the wiring that lets it use your Google credentials`, or the **Credentials** screen has no `gcp`, the session was created before it could use your Google credentials, so it can't use them. Delete it and create a new one with `chainctl develop --new --with gcp`. The session's VM holds only a placeholder credential. Each time you attach, `chainctl` sends your Google Cloud credential to Chainguard, which keeps it outside the VM, adds it to the session's requests to Google APIs, and discards it when the session parks. Usually, `chainctl` sends a short-lived access token and renews it while you're attached. If you attach to a session you're already keeping running, however little of that time is left, `chainctl` sends your refresh token instead, and access lasts until the session parks. A session created with `--keep always` always gets the refresh token. If your application default credentials have no refresh token, such as a service account's, `chainctl` sends an access token either way. `chainctl` tells you which one it sent. If you detach with one of the session menu's **Detach — keep running** options, whatever duration you choose, `chainctl` sends your refresh token before it detaches, so access lasts until the session parks. If you exit the shell instead, `chainctl` can't send the refresh token, and access ends within an hour. The message `chainctl` prints when you detach says which applies. While the session has your credential, code in it can use your Google Cloud access, and anything it creates with that access, such as a service account key, outlives the session. If the session's environment has an `egress` list, include `sts.googleapis.com` and the Google APIs the session calls. Every session sets `GOOGLE_APPLICATION_CREDENTIALS` and `CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE` to its placeholder credential, whether or not `gcp` is on. While `gcp` is off, `gcloud` and the Google Cloud client libraries fail with an error that says Google credentials are unavailable. To sign in to Google Cloud in the session another way, such as with `gcloud auth login`, unset both variables first. ## Credentials a session can't supply {#credentials-a-session-cant-supply} A session gets credentials only for Claude, GitHub, Chainguard's `cgr.dev` and `apk.cgr.dev` registries, your Chainguard identity, and, when you turn on `gcp`, Google APIs on `*.googleapis.com`. The `--with` flag takes only the entries listed in [Allow access ahead of time](#allow-access-ahead-of-time), so you can't add another service. A session has no credential for services such as the following: - Amazon Web Services (AWS) and Microsoft Azure - Private npm, PyPI, and Maven registries - Container registries other than `cgr.dev`, such as `ghcr.io` and Google Artifact Registry on `*.pkg.dev` - Chainguard Libraries. A check in Chainguard Checks can download JavaScript and Python libraries through a [workload identity](/chainguard/checks/identity-and-trust/). If you paste one of these credentials into a session, or sign in to a tool there, such as with `aws configure` or `npm login`, the credential stays on the session's disk or in its memory. When the session parks, Chainguard saves the credential in the session's snapshot with everything else, and keeps it until you delete the session or the session expires. Any program in the session, including a coding agent, can read the credential and send it anywhere the session can reach. Do work that needs these credentials on your laptop instead. ## Review and revoke a session's credentials {#review-and-revoke-a-sessions-credentials} To see what a session can reach, run `chainctl develop creds` with the session's name or short ID: ```shell chainctl develop creds app/quiet-harbor --parent $ORGANIZATION ``` The output lists each service with its source and any repositories bound to it, what your laptop remembers approving, whether a laptop is serving the session right now, and the most recent changes to the session's credentials. To change a session's credentials, pass one or more of the following flags to `chainctl develop creds`: | Flag | What it does | | ---- | ------------ | | `--revoke ` | Takes access back from the session and from your laptop's remembered approvals. `` is `claude`, `cgr.dev`, `chainguard`, `github:/`, or `github:/+push`, which takes back only the push. | | `--source =` | Changes where a credential comes from. `` is `laptop`, `org`, or `off`. The `claude` and `gcp` services take only `laptop` or `off`, and `chainguard` takes only `off`. To turn `chainguard` back on, use `--with chainguard`. | | `--forget` | Removes everything your laptop remembers for the session's repository, without changing the session. | Changes to `github` have no effect on a session that acts on GitHub as you, as described in [Controls that don't apply to a session that acts as you](/chainguard/workspaces/github-as-you/#controls-that-dont-apply-to-a-session-that-acts-as-you). For example, to let a session pull your organization's private images from `cgr.dev` as you, switch the `cgr.dev` credential to your laptop: ```shell chainctl develop creds app/quiet-harbor --source cgr.dev=laptop --parent $ORGANIZATION ``` Your laptop must have a `cgr.dev` login in its Docker credential helper, which you can set up as described in [Configure a Docker credential helper](/platform/chainctl-usage/how-to-install-chainctl/#configure-a-docker-credential-helper). The session can pull with it, but not push. Your laptop keeps remembered approvals in `develop/approvals.json` in the `chainctl` configuration directory, readable only by you. The file stores service names, sources, and scopes, never credentials, and Chainguard's servers can't write to it. ### Change credentials from the session menu While you're attached, you can change a session's credentials without leaving it. Press `Ctrl+]` to open the session menu, choose **Credentials**, and then choose a credential. Its screen shows where the credential comes from, what it covers and what it doesn't, and what your laptop remembers for it. The screen offers the following actions: | Action | What it does | | ------ | ------------ | | **Allow more…** | Adds access. It takes the same entries as `--with`, such as `github:/+push` or `cgr.dev`. | | **Revoke…** | Takes access back from the session and from your laptop's remembered approvals, as `chainctl develop creds --revoke` does. For `github`, you choose a repository, or only its push. | | **Set the source** | Changes where the credential comes from. For `claude` and `gcp`, you choose your laptop or no credential. For `github` and `cgr.dev`, you choose your laptop, your organization, or no credential. For `chainguard`, you choose your delegated Chainguard identity, which you then approve with `y`, or no credential. | | **Widen…** | Asks for more capabilities or groups for `chainguard`. Type them as `CAP[,CAP...] [in GROUP[,GROUP...]]`, and then press `y` to approve the wider scope, or `d` to approve only the default scope. | The `claude` screen also offers **Use Vertex AI…** or **Use Anthropic…**, as described in [Switch a session to another provider](/chainguard/workspaces/coding-agents/#switch-a-session-to-another-provider). For a session that acts on GitHub as you, the `github` screen shows your GitHub account and offers no actions. If the list has no `gcp`, the session can't use your Google Cloud credentials, as described in [Use your Google Cloud credentials in a session](#use-your-google-cloud-credentials-in-a-session). When you attach, `chainctl` prints a line for each credential the session gets without asking you. A line that ends in `(turn off: ~. → Credentials)` or `(change: ~. → Credentials)` points to this screen, which you can open by pressing `Ctrl+]`, or by typing `~.` where it works, as described in [Detach from a session](/chainguard/workspaces/manage-sessions/#detach-from-a-session). If the attach has no session menu, the line names the `chainctl develop creds` command to run instead. ## Set your organization's GitHub policy {#set-your-organizations-github-policy} Your organization's policy controls which repositories the Chainguard App serves to sessions. Any session owner can read it: ```shell chainctl develop policy describe --parent $ORGANIZATION ``` To change the policy, you need permission to list every user's sessions in the organization, which the `owner` role includes. A Starter organization has no role that includes it, so its policy stays at the defaults. Pass one or more of the following flags to `chainctl develop policy set`: | Flag | Values | What it controls | | ---- | ------ | ---------------- | | `--org-github` | `bound` (default), `session`, or `off` | With `bound`, the app serves the session's repository and any other repository the session's owner approved, with a token scoped to that repository and operation. With `session`, the app serves only the session's repository, and other repositories go to the session owner's laptop. With `off`, the app serves nothing. | | `--org-github-push-beyond-session` | `true` (default) or `false` | Whether the app can push to repositories other than the session's own, when the session's owner approved it. | | `--user-github` | `true` (default) or `false` | Whether sessions can act on GitHub as their owner. A session acts as its owner only if the owner connected a GitHub account before creating it. With `false`, new sessions reach GitHub through their `github` source instead, and sessions that already act as their owner have their GitHub requests refused. | Sessions pick up a policy change at their next request. The `--org-github` and `--org-github-push-beyond-session` settings don't apply to sessions that act on GitHub as their owner. ## Let sessions act on GitHub as you {#let-sessions-act-on-github-as-you} If you connect your GitHub account, the sessions you create afterward act on GitHub as you through the Chainguard Develop GitHub App, instead of through their `github` source. Such a session doesn't ask you to approve GitHub requests, and the `github` entries and sources on this page don't apply to it. Refer to [Let sessions act on GitHub as you](/chainguard/workspaces/github-as-you/). ## Your Chainguard identity in a session {#your-chainguard-identity-in-a-session} `chainctl` and the Chainguard MCP servers in a session act as you through a delegated Chainguard identity, so they work without a login in the session: - The first time you attach to a session in an organization, `chainctl` creates an identity named `chainctl-develop` there. It acts for you only through grants that your laptop issues. - While you're attached to the session's shell, `chainctl` on your laptop issues a grant that lasts about an hour, and renews it before it expires. After you detach, the identity keeps working until the last grant expires, for up to an hour. A terminal that keeps serving after you detach doesn't renew it. - Your laptop needs a live `chainctl` login to issue grants. Don't run `chainctl auth login` in the session. - By default, a grant carries MCP tool calls, the read-only capabilities you hold in the session's organization, and the capabilities to list, run, rerun, and cancel Chainguard Checks runs. It never carries a capability you don't hold. To ask for more capabilities or groups, open the session menu, choose **Credentials**, choose `chainguard`, and then choose **Widen…**. The identity reaches the Chainguard API, `chainctl checks`, and the `cg-apk` and `cg-versions` MCP servers that every session includes. Whatever a grant carries, the session can't change anything through the Chainguard API except Chainguard Checks runs, and it can only list packages on `apk.cgr.dev`. Other MCP servers get no credential, and pulls from `cgr.dev` use the session's `cgr.dev` credential. Most `chainctl checks` commands, such as `status`, `logs`, and `rerun`, work in the session as you, within your own permissions, and a pull request records a rerun as your request. In a Starter organization, the `limited_owner` role can't list Chainguard Checks runs, so `chainctl checks` commands that look up a run, including `status`, `logs`, and `rerun`, don't work for that role, in a session or on your laptop. The `chainctl checks top`, `chainctl checks trends`, and `chainctl checks accept-terms` commands work only on your laptop. To turn off a session's Chainguard identity, run `chainctl develop creds` with `--revoke chainguard` or `--source chainguard=off`, pass `--with none`, or turn it off on the **Credentials** screen. To turn it back on, use `--with chainguard`. To stop it for every session at once, delete the `chainctl-develop` identity from your organization. Tokens that sessions already hold keep working until they expire. --- ### Getting started with skill hardening URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/agent-skills/skill-hardening.md Last Modified: October 9, 2026 Tags: Agent Skills, Getting Started, chainctl Use `chainctl skills harden` to upload an agent skill, submit it for server-side hardening, and download the result with a report of the changes and scanner findings. You can submit a local directory or harden an artifact you have already pushed to your organization's uploads registry. This guide builds on [Getting started with the Chainguard Skills Registry](/chainguard/agent-skills/skills-registry/), covering hardening and job tracking before you install the skill.

Note: Chainguard Agent Skills is in beta.

## Prerequisites You need: - An [installed and authenticated `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) that includes `skills harden` and `skills status`. - An organization with a [Skills entitlement and accepted Skills terms](#set-up-your-organization). - Permission to upload skills, submit and read hardening jobs, and pull the results. ### Set up your organization In the commands below, replace `your-organization` with your organization's name or UIDP (its unique identifier). If onboarding is not already complete, an organization administrator with `skills.entitlements.create` and `terms.accept` permissions runs: ```shell chainctl skills entitlements create --parent your-organization chainctl skills accept-terms --group your-organization ``` Review and accept the terms in the interactive prompt. To check whether the organization already has an entitlement, run: ```shell chainctl skills entitlements list --parent your-organization ``` An entitlement enables access to the service. Its presence does not indicate that a skill has been uploaded or hardened. ### Check your chainctl version Check that your `chainctl` version includes the commands: ```shell chainctl skills harden --help chainctl skills status --help ``` The `harden` help should show `--folder`, `--digest`, `--wait`, and `--timeout`. If it shows only the generic `skills` help, [update `chainctl`](/platform/chainctl-usage/chainctl-version-update/). ## Create and validate an example skill Create a small skill that produces a release checklist: ```shell mkdir harden-demo cat > harden-demo/SKILL.md <<'EOF' --- name: harden-demo description: Write a short release checklist when the user asks to prepare a software release. license: Apache-2.0 --- Ask the user for the project name and intended release version if either is missing. Return a checklist covering tests, release notes, and the release owner. Use only information provided by the user. Mark missing details as "to confirm". EOF chainctl skills validate ./harden-demo ``` The directory name must match the frontmatter `name`. Keep the directory within the 10 MB limit. Validation checks the skill's structure locally; the hardening pipeline performs the subsequent review and evaluation. ## Harden a local directory From the directory containing `harden-demo`, run: ```shell chainctl skills harden ./harden-demo --group your-organization --wait --timeout 30m ``` The command packages and uploads the directory, submits a hardening job, prints its ID, and waits for completion. `--folder ./harden-demo` is equivalent to the positional directory argument. A separate `skills push` is unnecessary for this workflow. The examples use the production registries. `chainctl` uploads to `uploads.cgr.dev` by default in production; no registry environment variable is required. On success, the command prints the hardened artifact's reference and digest and downloads it into `./hardened/harden-demo/`. The download includes `SKILL.md`, any supporting files, and `HARDENING.md` with the hardening report and findings. The hardened artifact is published under the submitting user's namespace: ```text skills.cgr.dev//users//harden-demo@sha256: ``` The service generates the user namespace. Save the exact reference returned by the command, including `users//` and the digest, for subsequent pulls or installs. ## Submit now and check later Omit `--wait` to return after submission. Use `-o id` to capture the job ID: ```shell JOB_ID=$(chainctl skills harden ./harden-demo --group your-organization -o id) chainctl skills status --group your-organization --id "$JOB_ID" ``` To resume waiting and download the result: ```shell chainctl skills status --group your-organization --id "$JOB_ID" --wait --timeout 30m ``` `--id` takes the 64-character job ID, without a `sha256:` prefix or an operation path. Repeating a submission with the same organization, user, skill name, and content digest returns the same job. Use `status` when you only need to check progress. `--timeout` requires `--wait` and bounds the waiting command, including its other work. A timeout or Ctrl-C stops the local command; it does not cancel the server-side job. Resume with the saved ID. A pipeline failure exits nonzero and prints its failure reason. ## Harden an existing upload You can also push a tagged artifact first and then explicitly request hardening: ```shell chainctl skills push ./harden-demo --group your-organization --tag v1.0.0 chainctl skills list --group your-organization --source uploads chainctl skills harden uploads.cgr.dev/your-organization/harden-demo:v1.0.0 \ --group your-organization --wait --timeout 30m ``` `push` stores the original artifact in `uploads.cgr.dev`. Use `harden` to submit it for hardening and `status` to track the job. A successful push does not mean that a hardened result is available in `skills.cgr.dev`. The upload must be in the same organization passed to `--group`. This command accepts a reference at `//`; use the organization root when pushing an artifact you intend to harden this way. To select the exact artifact from the push output, use its digest instead: ```shell chainctl skills harden --group your-organization --name harden-demo \ --digest 'sha256:<64-hex-digest-from-push>' --wait --timeout 30m ``` `--digest` requires `--name` and takes only the digest, not a tag or full registry reference. Choose one input form per invocation: a path, `--folder`, an uploads reference, or `--digest` with `--name`. ## Browse results in user folders ### Why do I only see a `users` folder? By default, `chainctl skills list` reads the hardened registry (`--source skills`) and shows only the immediate folders and skills at the selected level. For example: ```shell chainctl skills list --group your-organization ``` ```output SOURCE | TYPE | NAME | TAGS | UPDATED ----------------|--------|-------|------|--------- skills.cgr.dev | folder | users | -- | -- ``` `users` is a registry folder containing namespaces for users who submit skills for hardening. A row with `TYPE` set to `folder` has no skill tag or update time, so those columns show `--`. The folder row alone does not tell you whether a hardening job has completed or whether your skill is inside it. Hardened results are organized like this: ```text skills.cgr.dev// └── users/ └── / └── harden-demo ``` Each submitting user has a separate namespace, so different users can harden a skill with the same name without sharing a repository path. ### Expand all user folders Add `--recursive` (or `-r`) to include skills from every nested folder in the listing: ```shell chainctl skills list --group your-organization --source skills --recursive ``` The `NAME` column shows the full path relative to the organization, such as `users//harden-demo`. Repeating the default non-recursive command continues to show only the top-level folder. The `TAGS` column lists all tags for each skill, including version tags and generated hardening tags. Skills without tags are also listed, with `--` in that column. The CLI does not require a `latest` tag. MCP skill discovery uses `latest`, so its results can differ from the CLI listing. To browse one level at a time, append the registry folder path to `--group`: ```shell chainctl skills list --group your-organization/users chainctl skills list --group "your-organization/users/" ``` Replace `` with a folder name returned by the first command. These paths select registry folders; they do not refer to directories on your machine. ### Find a skill after pushing it If `push` returned an `uploads.cgr.dev` reference, select the uploads source to browse that registry: ```shell chainctl skills list --group your-organization --source uploads --recursive ``` To browse uploads and hardened results in one listing: ```shell chainctl skills list --group your-organization --source all --recursive ``` Use the `SOURCE` column to distinguish the registries: | Source option | Registry | Contents | | --- | --- | --- | | `--source skills` (default) | `skills.cgr.dev` | Hardened skills, including skills in user namespaces. | | `--source uploads` | `uploads.cgr.dev` | Original uploads. | | `--source all` | Both registries | Skills from either source, labeled by registry. | If you have only run `push`, follow [Harden an existing upload](#harden-an-existing-upload). If you already submitted a hardening job, use `chainctl skills status --group your-organization --id "$JOB_ID"` to check it. An entitlement listing or a `users` folder row is not a job status check. ### Find a skill that is missing from the listing First, check the source and folder: use `--source uploads` for an uploaded original, `--source skills` for a hardened result, and `--recursive` to include nested skills. Older `chainctl` builds display a `LATEST TAG` column and omit skills without a `latest` tag. This can hide version-tagged uploads and completed hardening results. [Update `chainctl`](/platform/chainctl-usage/chainctl-version-update/) to use the listing with all tags. You can also inspect and pull these artifacts by their exact references. For an upload, inspect the full reference from the `push` output: ```shell chainctl skills describe uploads.cgr.dev/your-organization/harden-demo:v1.0.0 ``` For a hardening job, use `status` and save the returned hardened reference as described in [Review and install the result](#review-and-install-the-result). Use that reference for `describe`, `pull`, or `install`; an empty listing does not mean the job failed. ## Review and install the result Read `hardened/harden-demo/HARDENING.md` for the change summary and findings, including findings that remain open. Review the resulting instructions before using the skill. A successful hardening job can still have remaining findings. Copy the full hardened reference printed by `harden` or `status`: ```shell export HARDENED_REF='' chainctl skills describe "$HARDENED_REF" chainctl skills verify "$HARDENED_REF" chainctl skills install --verify "$HARDENED_REF" ``` `verify` checks that Chainguard signed the hardened result for your organization. With `--verify`, `install` checks the signature again on the exact digest it fetches and installs nothing unless the skill is `verified`. For details, refer to [Verifying Chainguard Agent Skills signatures](/chainguard/agent-skills/verifying-skills/). `install` detects your local agents and reports where it placed the skill. Use the installed name and location shown by the command to load it in your agent, then ask it to prepare a release checklist for a test project. If `./hardened/harden-demo/` already contains a previous download, `--wait` will not overwrite it. Check `status` without `--wait`, then pull the returned reference into a new directory: ```shell chainctl skills pull --verify "$HARDENED_REF" ./harden-demo-reviewed ``` --- ### Using Dependabot with Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/updating-containers/dependabot.md Last Modified: October 15, 2026 Tags: Chainguard Containers [Dependabot](https://docs.github.com/en/code-security/dependabot) is GitHub's dependency update tool. It reads the container image references in your Dockerfiles and Kubernetes manifests, checks the registry for newer tags, and opens pull requests to update them. This guide explains how to configure Dependabot to authenticate to your organization's private registry at `cgr.dev` so it can keep your references to Chainguard Containers current. ## Choosing between Dependabot and Digestabot Dependabot and [Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/) solve different halves of the same problem, and many teams run both: * **Dependabot** opens a pull request when a newer *tag* is available — for example, moving a reference from `go:1.22` to `go:1.26`. Use it to move between version streams. * **Digestabot** opens a pull request when the *digest* behind a fixed tag changes. Chainguard rebuilds container images daily, so the digest behind a tag such as `latest` or `3.14` changes often. Use Digestabot to pick up those rebuilds. Dependabot acts on the tag string in your reference. If you pin to a mutable tag and never change that string, Dependabot has nothing to update, and Digestabot is the better fit. ## Prerequisites To follow this guide, you need: * A GitHub repository containing at least one reference to a Chainguard container image. * Access to a Chainguard organization, with permission to create pull tokens. * `chainctl` installed on your local machine. Refer to [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you haven't set this up. ## Create a pull token Dependabot authenticates to private container registries with a static username and password. It doesn't support [Chainguard assumable identities](/platform/administration/assumable-ids/assumable-ids/), so you need to create a [pull token](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token). Create one with `chainctl`: ```shell chainctl auth configure-docker --pull-token --ttl 2160h ``` This command responds with output such as the following: ```shell To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" ``` The username has the form `/`. Record both values; you'll store them as secrets in the next section. The `--ttl` flag sets how long the token stays valid. The example uses `2160h`, or 90 days. The default is `720h` (30 days) and the maximum is `8760h` (one year). Dependabot's authentication fails once the token expires, so choose a lifetime you can commit to rotating, and set a reminder to replace the token before it lapses. One pull token can serve every repository in your GitHub organization. Storing it as an organization-level secret, rather than creating a token for each repository, leaves you with a single credential to rotate. ## Store the credentials as Dependabot secrets GitHub keeps Dependabot secrets in a separate store from Actions secrets. A token added to Actions secrets is invisible to Dependabot, and the update job fails to authenticate without a clear explanation. To add the secrets to a single repository: 1. In your repository on GitHub, go to **Settings > Secrets and variables > Dependabot**. 2. Select **New repository secret**. 3. Name the secret `CHAINGUARD_PULL_TOKEN_USERNAME` and enter the username from the previous section. 4. Select **Add secret**. 5. Repeat these steps to create `CHAINGUARD_PULL_TOKEN_PASSWORD` with the password value. To share one token across every repository in your GitHub organization, add the secrets at **Settings > Secrets and variables > Dependabot** in the organization's settings instead, and grant access to the repositories that need them. ## Configure Dependabot Create a file named `.github/dependabot.yml` at the root of your repository with the following content: ```yaml version: 2 registries: chainguard: type: docker-registry url: cgr.dev username: ${{secrets.CHAINGUARD_PULL_TOKEN_USERNAME}} password: ${{secrets.CHAINGUARD_PULL_TOKEN_PASSWORD}} replaces-base: true updates: - package-ecosystem: "docker" directory: "/" registries: - chainguard schedule: interval: "daily" ``` This configuration defines a registry named `chainguard`, points it at `cgr.dev`, and authenticates with the secrets you created. The `updates` section tells Dependabot to check the Dockerfiles in the repository root once a day, using that registry. Adjust `directory` to match where your manifests live, and `interval` to match how often you want pull requests. Refer to GitHub's [Dependabot options reference](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference) for the full set of options. Commit this file to your repository's default branch. ### Understanding `replaces-base` Setting `replaces-base: true` tells Dependabot to resolve container image references against `cgr.dev` instead of Docker Hub, the default registry for the Docker ecosystem. This setting doesn't affect fully qualified references such as `cgr.dev/example.com/go:1.22`, which Dependabot matches by hostname either way. It changes how Dependabot resolves *unqualified* references such as `FROM python:3.13`, which it sends to `cgr.dev` rather than Docker Hub. Keep `replaces-base: true` when every container image in the repository comes from Chainguard. If the repository also pulls images from other registries, omit the setting so Dependabot resolves each reference against the registry that hosts it: ```yaml registries: chainguard: type: docker-registry url: cgr.dev username: ${{secrets.CHAINGUARD_PULL_TOKEN_USERNAME}} password: ${{secrets.CHAINGUARD_PULL_TOKEN_PASSWORD}} ``` ## Verify the configuration Dependabot runs on the schedule you set, but you can trigger a run immediately to confirm that authentication works: 1. In your repository on GitHub, go to **Insights > Dependency graph > Dependabot**. 2. Find the entry for the `docker` ecosystem. 3. Select **Check for updates**. Select **Last checked** to open the job log. A successful run lists the tags Dependabot found for each image, then opens a pull request for any reference it can update, with a title such as `Bump example.com/go from 1.22 to 1.26`. ## Update digest-pinned references Chainguard recommends pinning image references to a [digest](/chainguard/containers/troubleshooting/inspecting-containers/) while keeping the tag as a version hint, in the form `cgr.dev/example.com/go:1.22@sha256:...`. Dependabot updates both parts of a reference that's already in this form, as described in [Unique tags](/chainguard/containers/reference/unique-tags/). Two limits are worth knowing before you rely on this: * Dependabot updates a digest that's already present, but it won't add one to a reference that has only a tag. Pin the digest yourself the first time. Adding digests automatically is an [open feature request](https://github.com/dependabot/dependabot-core/issues/14065). * When a reference carries both a tag and a digest, Dependabot [doesn't supersede an open pull request](https://github.com/dependabot/dependabot-core/issues/7387) as newer digests are published. Because Chainguard rebuilds container images daily, use Digestabot for references pinned to a fixed tag. ## Limitations * **Assumable identities aren't supported.** Dependabot's OIDC authentication covers a fixed set of registries that doesn't include Chainguard, so a pull token is the only option. Refer to GitHub's documentation on [configuring access to private registries](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/manage-your-dependency-security/configure-access-to-private-registries) for the current list. * **GitHub-hosted Dependabot only.** Chainguard tests this configuration against Dependabot as hosted by GitHub. Self-hosted and third-party runners may handle credentials differently. ## Troubleshooting ### Authentication fails with `private_source_authentication_failure` This error means Dependabot reached `cgr.dev` but couldn't authenticate. Check the following, in order: 1. Confirm the secrets are stored under **Dependabot**, not **Actions**. This is the most common cause. 2. Confirm the pull token is still valid. Run `chainctl auth pull-token list` to see the tokens in your organization and when they expire. 3. Confirm the username is the complete `/` string, including the slash. 4. Test the credentials outside of Dependabot with `docker login cgr.dev --username --password `, followed by a `docker pull` of one of the images in your repository. ### No pull requests appear If the job log shows a successful run but no pull requests, check the following: * Dependabot acts on the tag string. A reference pinned to a mutable tag such as `latest` produces no pull requests, because the tag never changes. Use Digestabot for those references. * Dependabot opens at most five pull requests per ecosystem by default. Raise the `open-pull-requests-limit` option if existing pull requests are holding the queue. * Unqualified references such as `FROM python:3.13` reach `cgr.dev` only when `replaces-base: true` is set. Otherwise, fully qualify the reference. ## Learn more * [Using Renovate with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/renovate/) covers the equivalent setup for teams outside GitHub, or teams that want short-lived credentials through an assumable identity. * [Stay secure: Strategies and tooling for updating container images](https://www.chainguard.dev/unchained/stay-secure-strategies-and-tooling-for-updating-container-images), on the Chainguard blog, compares the wider range of update tools. * [Authenticating to the Chainguard registry](/chainguard/containers/registry/authenticating/) documents pull tokens and the other authentication options in full. --- ### Manage build pinning for Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/policies-and-security/build-pinning.md Last Modified: October 13, 2026 Tags: Chainguard Libraries, Build pinning Chainguard Libraries can serve a package version as either a Chainguard-built artifact or an [upstream artifact that is scanned and proxied through Chainguard](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). Chainguard-built artifacts may have different checksums for the same version of the upstream artifact. If your lockfile records an upstream checksum and Chainguard later builds that package, your package manager can fail with integrity errors upon dependency resolution. Build pinning keeps library artifacts stable when Chainguard publishes a new build of a package version you previously pulled from the scanned upstream fallback. Chainguard-built artifacts are always the default and take priority when available. Pinning only affects the exact package version already pinned. When enabled, Chainguard remembers which upstream versions your organization pulled and continues serving those specific versions even if a new Chainguard build is available, until you remove the pin. Moving to a different version of the package is unaffected; since that version was never pinned, it resolves fresh and Chainguard’s build is served by default. This feature is enabled by default. > Note: Build pinning does not override Chainguard policy or malware controls. If a pinned upstream package is blocked by a policy or by malware scanning, the request will return an error. ## Prerequisites Before getting started, you need: * An [entitlement to Chainguard Libraries with upstream fallback enabled](/chainguard/libraries/introduction/access/#manage-library-entitlements) * [`chainctl` installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/) * A working configuration using Chainguard Libraries - a package manager configured to pull from Chainguard Libraries directly or a repository manager that proxies Chainguard Libraries ## Pin builds Your package versions are automatically pinned after you run an install for your project. Use this workflow when adopting build pinning: 1. Run an install for your project. 1. Confirm that pins appear: Run `chainctl libraries cache list`. 1. Commit the resulting lockfile if it changed. 1. When you are ready to adopt newer Chainguard builds, remove pins for the affected packages. 1. Regenerate or update the lockfile and run your normal build and test commands. ## View pinned builds To list pins for your organization: ```bash chainctl libraries cache list ``` The output includes the package, version, serving tier, whether the pin is active, and when the artifact was observed. ## Remove pins After a zap, affected package versions resolve again, prioritizing Chainguard builds for any package versions that may have previously been pulled from Chainguard’s upstream fallback. Any new Chainguard-built artifacts may result in changes in a package version’s checksums. The zap does not override malware or policy blocks. ### Preview the change Use `--dry-run` to preview which pins would be removed: ```bash chainctl libraries cache zap --dry-run ``` ### Remove all pins You can remove all pins per ecosystem. For example, to remove all pins for JavaScript: ```bash chainctl libraries cache zap --ecosystem javascript ``` After removing pins, regenerate or update your lockfile. ### Remove specific package pins To remove a specific package pin: ```bash chainctl libraries cache zap --package ``` To narrow the operation to one package version, include the `--version` flag: `--version `. To skip the confirmation prompt in scripts or automation, include the `--yes` flag. After removing a pin, regenerate or update your lockfile. ## Opt out of build pins To stop recording and enforcing pins for an ecosystem, run the following command: ```bash chainctl libraries cache opt-out --ecosystem javascript ``` ## Adopt a Chainguard build after removing a pin To move a package from an upstream-sourced artifact to a Chainguard-built one, you must force a fetch after the zap. Note that if Chainguard has not rebuilt a package, the following steps will re-pin to upstream again. First, force a fresh install:

Java (Maven):

mvn dependency:purge-local-repository -DmanualInclude="<groupId>:<artifactId>:<version>"
mvn install

Java (Gradle):

./gradlew build --refresh-dependencies
Next, confirm that the package now resolves to a Chainguard build: ```bash chainctl libraries cache list --package --live ``` After confirming the change, commit the updated lockfile and run your normal install again. ## Troubleshooting and FAQ ### Does build-pinning apply under the Chainguard-only policy? No. You must have upstream fallback enabled in order for build pinning to take effect. ### What happens to my pins when I switch policies? Going from `CHAINGUARD_AND_UPSTREAM` (upstream fallback enabled) to `CHAINGUARD` (fallback disabled) stops pins from being served, but the pin records aren’t deleted. They persist until the cache is manually zapped. Switching back to `CHAINGUARD_AND_UPSTREAM` later can resurrect old pins unexpectedly. --- ### Guardener Commit Verification URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/guardener/github/commit-verification.md Last Modified: September 28, 2026 Tags: GitHub, Security The Commit Verification feature verifies that every commit in a pull request is cryptographically signed by an authorized signer, according to a policy you control. This ensures that changes to your codebase come from identities you trust, and it supports both keyless (Sigstore) signatures and static keys such as GPG. Keyless commit signatures are verified using [gitsign](https://github.com/sigstore/gitsign) and [Sigstore](/open-source/sigstore/). Static key authorities (such as GPG) are verified directly against the key material you supply, without gitsign. ## Enable Commit Verification Add a `.chainguard/source.yaml` file to your repository that defines one or more **authorities**. A commit is accepted if it satisfies **any** of the listed authorities. ```yaml spec: authorities: - keyless: url: https://fulcio.sigstore.dev identities: - subjectRegExp: .+@example.com$ issuer: https://accounts.google.com ctlog: url: https://rekor.sigstore.dev ``` The example above accepts commits signed with keyless Sigstore signatures where the signer's identity is an `@example.com` address authenticated through Google. ## Keyless (Sigstore) authorities A `keyless` authority verifies short-lived certificates issued by a Sigstore certificate authority (Fulcio) and logged in a transparency log (Rekor). Use `identities` to constrain which signer identities are trusted. The fields in the example above are: - `keyless.url` — the Fulcio instance that issued the signing certificate. - `keyless.identities` — one or more identity constraints. Each entry matches the certificate's subject and issuer. - `subjectRegExp` — a regular expression the signer's subject (for example, an email address) must match. Use `subject` for an exact match instead. - `issuer` — the OIDC issuer that authenticated the signer (for example, `https://accounts.google.com`). Use `issuerRegExp` for a pattern match. - `ctlog.url` — the transparency log (Rekor) instance used to verify the signature was logged. ## Static key authorities A `key` authority verifies signatures made with a static key, such as GPG. This is useful for accepting commits signed by well-known keys — for example, GitHub's own `web-flow` key used when merging in the web UI: ```yaml spec: authorities: - key: kms: https://github.com/web-flow.gpg ``` ## Combining authorities Because a commit is accepted if it satisfies any authority, you can allow several trusted sources at once. The following policy accepts commits signed either by an `@example.com` Sigstore identity or by GitHub's `web-flow` key: ```yaml spec: authorities: - keyless: url: https://fulcio.sigstore.dev identities: - subjectRegExp: .+@example.com$ issuer: https://accounts.google.com ctlog: url: https://rekor.sigstore.dev - key: kms: https://github.com/web-flow.gpg ``` ## Configuration reference | Field | Purpose | | ------------------------------------------------------- | -------------------------------------------------------------------------- | | `spec.authorities` | List of authorities. A commit is accepted if it satisfies any one of them. | | `spec.authorities[].keyless.url` | Fulcio instance that issued the signing certificate. | | `spec.authorities[].keyless.identities[].subjectRegExp` | Regular expression the signer's subject must match. | | `spec.authorities[].keyless.identities[].issuer` | OIDC issuer that authenticated the signer. | | `spec.authorities[].ctlog.url` | Transparency log (Rekor) instance used to verify the signature. | | `spec.authorities[].key.kms` | Reference to a static key (for example, GPG) used to verify signatures. | ## Next steps - **[Hardened Actions](/chainguard/guardener/github/actions-security/)** — Recommend and migrate GitHub Actions to hardened, SHA-pinned equivalents. - **[Configuration](/chainguard/guardener/github/configuration/)** — Review the shared `.chainguard/` configuration model. - Learn more about the signing technology behind this feature in the [Sigstore documentation](/open-source/sigstore/). --- ### Browsing Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/introduction/browse.md Last Modified: September 28, 2026 Tags: Chainguard Libraries Chainguard Libraries is a malware-free catalog of Java, JavaScript, and Python artifacts that have passed through multiple layers of security controls, including malware and greyware scanning, building from source, cooldowns, and additional policies. In the Chainguard Console, you can browse available libraries and versions, inspect package details, and evaluate dependencies before pulling them into your environment. ## Access libraries in the Chainguard Console Log in to the Chainguard Console at [https://console.chainguard.dev/](https://console.chainguard.dev/). In the left-hand navigation under **Libraries**, expand **Ecosystems** to find links for browsing Chainguard's [**Java**](/chainguard/libraries/java/overview/), [**JavaScript**](/chainguard/libraries/javascript/overview/), and [**Python**](/chainguard/libraries/python/overview/) libraries. ### Browse the libraries list When you open a specific ecosystem, you'll see a search input box and a list of libraries. Click any row to open the [library detail page](#library-page). The list includes the following columns: * **Name**: The full library name, excluding any version identifiers. * Python library names are simple strings, such as `setuptools` or `Flask-Admin`. * Java library names are the concatenation of the Maven coordinate values `groupId` and `artifactId`, separated by `:`. Examples are `org.springframework:spring-core` or `org.eclipse.jetty:jetty-http`. * **Latest version**: The latest released and available version of the library and the total number of available versions. * **Updated**: The most recent date when any version of this library was built and published by Chainguard, or cached from the upstream fallback (when upstream fallback is enabled). At the bottom of the page, see a total count of available libraries. #### Upstream fallback details The upstream fallback is available for JavaScript, Python, and Java Libraries. The upstream fallback can be enabled or disabled using the `chainctl libraries entitlements` commands, per ecosystem. Currently, the Chainguard Console can be used to enable, disable, and view the status of the upstream fallback for JavaScript only. At the top of the JavaScript page in the Console, you can see whether [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) is enabled. Learn more in the [Libraries overview documentation](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). When fallback is configured for your organization, you will see all JavaScript packages -- including those built by Chainguard and those that are mirrored from upstream npm -- in the Console. For a given package, you can see whether it is being served from Chainguard's rebuilt artifacts or proxied from upstream npm. For Java and Python, you cannot currently view upstream vs. Chainguard-built packages via the Chainguard Console. ### Search the libraries list Use the **Search** text input at the top of the libraries list to narrow down the list and to locate a specific library. Click into a row to view a [specific library page](#library-page). ### View remediated libraries [CVE remediation](/chainguard/libraries/policies-and-security/cve-remediation/) is available for a subset of Chainguard Libraries for Java (available in beta) and Python. You can view remediated libraries in the Chainguard Console. In the Java and Python libraries directories, click the **Remediated** tab to view a list of remediated libraries. Click into a library to see which versions have remediated CVEs. While viewing the list of remediated versions for a library, click into a version to view more details: which CVEs were remediated, the date that the version was patched, and links to additional resources. Learn more about browsing remediations in [CVE remediation for Chainguard Libraries](/chainguard/libraries/policies-and-security/cve-remediation/#about-cve-remediation). ### View malware information For the JavaScript and Python ecosystems, click the **Malware** tab to learn how many packages have been blocked by Chainguard due to [malware or greyware detection](/chainguard/libraries/introduction/overview/#malware-and-greyware-detection). The list at the bottom of the page displays automatically blocked packages, including the package name, blocked version(s), the date it was blocked on, its MAL ID if available, and signals detected. #### Malware and greyware signals detected Some signals describe confirmed malicious behavior, while others describe greyware or supply-chain risk indicators that may justify blocking even when there is no public malware advisory. Next to each blocked package, Chainguard provides the reason it was blocked when there is no public malware advisory. The detected signals fall under the following categories: * **Credential and data theft**: The package harvests secrets (for example, GitHub tokens or SSH keys) and sends sensitive data out of the environment. * Signals: Accesses credentials, Data exfiltration, Contains an exposed secret * **Backdoors and malicious behavior**: The package obfuscates malicious behavior, including backdoor setup, remote access, staged payloads, and more. The code may contact known malicious infrastructure to fetch or run additional payloads. * Signals: Suspicious execution technique, Obfuscated or hidden payload, Cryptomining, Spreads to other packages (worm), Embedded binary in source, AI/LLM attack content, Suspicious network activity, Contacts known-malicious infrastructure, Linked to known malware * **Install-time script execution**: The package automatically executes malicious behavior through pre-install and post-install lifecycle scripts. * Signals: Malicious install script, Declares install scripts * **Typosquatting and impersonation**: The package is named or brand-engineered to deceive users into downloading the wrong package. * Signals: Typosquatting or impersonation, Dependency confusion * **Compromised or untrusted releases**: The package exhibits signs that a release cannot be trusted. * Signals: Signs of maintainer account compromise, No trusted provenance or signed release, Published with a legacy token (not OIDC), Release integrity anomaly, Anomalous release change, Anomalous package contents, Weak build-pipeline security, Untrusted dependency source ## Library page To access a library page, click on the row for a specific library in the search results or the [initial library list](#initial-display). On a library's page, use the search bar at the top to search for specific versions. The list of library versions includes the following columns: * **Version** - the version of the library. Library versions are strings. Depending on the ecosystem and library they can follow naming patterns and other restrictions that allow ordering by version. * **Size** - the size of the library. * The displayed size reflects the primary file(s) only: `.jar`/`.pom` for Java, and `.whl`/`.tar.gz` for Python. It is not an aggregation of all files under a given version. * **Built** - The date when this version was built and published by Chainguard. Click on the column titles to change the **sort** order of the list. ## View repository configuration in the Chainguard Console The Chainguard Console provides visibility into your repository configuration and the packages being served. When the upstream fallback is configured for your organization, you will see all packages including those built by Chainguard and those that are mirrored from upstream npm. ## Other resources * [Chainguard Console](/platform/console/): Learn about using the Chainguard Console to browse container images. * [Libraries overview](/chainguard/libraries/introduction/overview/): Learn about criteria for building a library, upstream fallback and policy controls, and more. --- ### Using the Chainguard API to manage Custom Assembly resources URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/custom-assembly-api-demo.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural, Custom Assembly Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/) is a tool that allows customers to create customized containers with extra packages added. This enables customers to reduce their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs. You can use the Chainguard API to further customize your Custom Assembly builds and retrieve information about them. This tutorial highlights a demo application (which can be found in [Chainguard Academy's Demo Applications repository](https://github.com/chainguard-dev/edu-images-demos/tree/main)) which, when run, updates a Custom Assembly container's configuration based on a provided YAML file. > **Note**: This tutorial highlights using the Chainguard API to interact with Custom Assembly resources. However, you can also interact with Custom Assembly using [`chainctl`, Chainguard's command-line interface tool](/chainguard/containers/custom-assembly/custom-assembly-chainctl/), as well as [the Chainguard Console](/chainguard/containers/custom-assembly/custom-assembly-console/). ## Prerequisites In order to follow along with this guide, you will need the following: * Access to a Custom Assembly container image. If your organization doesn't yet have access to Custom Assembly, reach out to your account team to start the process. * The demo application used in this guide is written in Go, so you will need Go installed on your local machine. Refer to the [official Go documentation](https://go.dev/doc/install) for instructions on downloading and installing Go. * You will also need [`chainctl` installed](/platform/chainctl-usage/how-to-install-chainctl/) on your local machine to create a Chainguard token and authenticate to the Chainguard API. ## Downloading the demo application This step involves downloading the demo application code to your local machine. To ensure that the application files don't remain on your system, navigate to a temporary directory like `/tmp/`: ```shell cd /tmp/ ``` Your system will automatically delete the `/tmp/` directory's contents the next time it shuts down or reboots. The code that comprises this demo application is hosted in a public GitHub repository managed by Chainguard. Pull down the example application files from GitHub with the following command: ```shell git clone --sparse https://github.com/chainguard-dev/edu-images-demos.git ``` Because this guide's demo application code is stored in a repository with other examples, we don't need to pull down every file from this repository. For this reason, this command includes the `--sparse` option. This initializes a sparse-checkout file, causing the working directory to contain only the files in the root of the repository until the sparse-checkout configuration is modified. Navigate into this new directory and list its contents to confirm this: ```shell cd edu-images-demos/ && ls ``` For now, this directory only contains the repository's `LICENSE` and `README` files: ``` LICENSE README.md ``` To retrieve the files you need for this tutorial's sample application, run the following `git` command: ```shell git sparse-checkout set custom-assembly-go ``` This modifies the sparse-checkout configuration initialized in the previous `git clone` command so that the checkout only consists of the repo's `custom-assembly-go` directory. Navigate into this new directory: ```shell cd custom-assembly-go/ ``` From here, you can run the application and use it to update the packages built into a Custom Assembly container. First though, let's go through the `main.go` file where the executable application code is written In order to understand how it works. ## Understanding the demo application Before outlining how to run the demo application to update a Custom Assembly container image, it's important that you have a general understanding of how the application works. This section will only provide a general overview of the application. We encourage you to read through the complete application, as it includes comments that explain what each portion of the code does. ### Imports The demo application uses the following packages: ```main.go import ( "context" "flag" "fmt" "log" "os" "strings" "time" . . . ) ``` The application also uses the Chainguard SDK to interact with the Chainguard API and demonstrates proper patterns for authentication, error handling, and resource management. For this reason, the `import` section also brings in a number of protos from [`github.com/chainguard-dev/sdk`](https://github.com/chainguard-dev/sdk), the GitHub repository that stores Chainguard's public SDK for integrating with the Chainguard platform: ```main.go import ( . . . "chainguard.dev/sdk/auth" "chainguard.dev/sdk/proto/platform" commonv1 "chainguard.dev/sdk/proto/platform/common/v1" "chainguard.dev/sdk/proto/platform/iam/v1" registryv1 "chainguard.dev/sdk/proto/platform/registry/v1" "gopkg.in/yaml.v2" ) ``` Note that the `imports` block also contains `gopkg.in/yaml.v2`, the import path for the `yaml` package. This will allow the application to decode YAML values. ### Constants Immediately below the imports, the application creates a few constants used throughout the code: ```main.go const ( defaultAPIURL = "https://console-api.enforce.dev" tokenEnvVariable = "TOK" defaultGroupName = "ORGANIZATION" demoRepoName = "CUSTOM-IMAGE-NAME" buildConfigFile = "build.yaml" ) ``` * `defaultAPIURL`: This points to the Chainguard API's URL, which the application will reach in order to interact with the API. * `tokenEnvVariable`: In order to use the Chainguard API, you must authenticate to prove that you have access to the resources you want to interact with. This constant defines an environment variable `TOK` that the application will expect to be present in order to function. This variable must hold a Chainguard authentication token; the next section describes how to create this environment variable. * `defaultGroupName`: This constant points to the Chainguard organization whose Custom Assembly resources you would like to manage. * `demoRepoName`: This constant points to the name of the Custom Assembly container image that you'd like to update with this demo application. * `buildConfigFile`: This last constant points to the `build.yaml` file, which you'll use to configure the Custom Assembly container image. ### Functions Following the list of constants, the application declares a series of functions: * `listRepositories`: This function lists repositories in a group with optional name filtering. * `listBuildReports`: Lists build reports for a repository. Build reports provide information about image builds, including status, timestamps, and digests of the resulting images. This function shows how to query build reports for a specific repository. * `printBuildReports`: This helper function displays build reports in a user-friendly format, showing the start time, result status, and image digest for each report. * `applyCustomization`: This function demonstrates the pattern for updating a repository with a custom overlay that defines the packages to include in the image. The overlay is applied to the repository, which triggers a new build. * `createClient`: This function creates a new Chainguard API client using a token from the environment. * `confirmAction`: This utility function handles user confirmation for potentially destructive actions. These functions come together in the `main()` function, which performs five main steps: 1. Create a Chainguard API client and authenticate 2. List repositories with optional filtering 3. List existing build reports for the repository 4. Apply image customizations using a build.yaml file 5. List and monitor new build reports To accomplish all this, the application's functions perform the following API calls: * [ListBuildReports](/platform/api/spec/#tag/registry/GET/registry/v1/buildreports) * [ListRepos](/platform/api/spec/#tag/registry/GET/registry/v1/repos) * [UpdateRepo](/platform/api/spec/#tag/registry/PUT/registry/v1/repos/{id}) * [Groups_List](/platform/api/spec/#tag/groups/GET/iam/v1/groups) For a deeper understanding of what each function does and how the application works overall, we encourage you to closely review the `main.go` file before running it. You may also benefit from reviewing our [OpenAPI specification reference document](/platform/api/spec/). Once you feel you have a grasp on how the demo application works, move on to the next section which outlines how to run it. ## Running the demo application Before you can run the demo application, there are a few steps you need to take in order for it to work properly. First, run the following `go` commands: ```shell go mod init github.com/chainguard-dev/sdk && go mod tidy ``` The `go mod init` command will initialize a new `go.mod` file in the current directory. Including the `github.com/chainguard-dev/sdk` URL tells Go to use that as the module path. The `go mod tidy` command ensures that the new `go.mod` file matches the source code in the module. As mentioned previously, you must authenticate before you can interact with the Chainguard API. For this reason, this demo application expects an environment variable named `TOK` to be present when it's run. Create this environment variable with the following command: ```shell export TOK=$(chainctl auth token) ``` Following that, open up `main.go` with your preferred text editor. This example uses `nano`: ```shell nano main.go ``` From there, edit the following lines: ``` // Group and repository settings defaultGroupName = "ORGANIZATION" demoRepoName = "CUSTOM-IMAGE-NAME" ``` Replace `ORGANIZATION` with the name of your organization's repository within the Chainguard registry. This usually takes the form of a domain name, such as `example.com`. Additionally, replace `CUSTOM-IMAGE-NAME` with the name of the Chainguard Container you want to customize. For example, you may want to customize an `nginx` or `python` container image. Save and close the `main.go` file. If you used `nano`, you can do so by pressing `CTRL+X`, `Y`, and then `ENTER`. Next, open up the `build.yaml` file: ```shell nano build.yaml ``` This file will have the following content: ``` contents: packages: - wolfi-base - go ``` Here, replace `wolfi-base` and `go` with whatever packages you'd like to be included in the customized container image. Note that you can only add packages that your organization already has access to, based on the Chainguard Containers your organization has access to. Refer to the [Custom Assembly overview](/chainguard/containers/custom-assembly/overview/#limitations) for more details on the limitations of what packages you can add to a Custom Assembly image. Save and close the `build.yaml` file. Finally, you can run the application to apply the configuration listed in the `build.yaml` file to your organization's Custom Assembly image: ```shell go run main.go ``` The application will start by listing the information outlined previously, including the specified organization's repositories and build reports for the chosen Custom Assembly image: ``` Group: example.com (ID: 45a0cEXAMPLE977f050c5fb9aEXAMPLEed764595) All repositories in example.com: - custom-assembly - nginx - curl Repository: custom-assembly (ID: 45a0cEXAMPLE977f050c5fb9aEXAMPLEed764595/c375EXAMPLEb500c) Build Reports for custom-node repository: . . . ``` It will then prompt you to confirm that you want to apply the customization configuration listed in the `build.yaml` file: ``` About to apply customization using configuration file: build.yaml Are you sure you want to update repository custom-node? (y/n): y ``` Enter `y` to confirm. Then, if everything was configured correctly, the application output will show successful build reports: ``` . . . - Started: Mon, 28 Apr 2025 00:28:44 UTC, Result: Success, Digest: . . . ``` ### Troubleshooting Although the demo application has been tested to ensure that it works properly, there are several pitfalls one may encounter when they attempt to run it. For example, you may run into an error like the following: ``` Failed to list groups: rpc error: code = Internal desc = stream terminated by RST_STREAM with error code: PROTOCOL_ERROR ``` This may indicate that there is an issue with your Chainguard authentication token. To resolve this, try recreating the environment variable that holds the token: ```shell export TOK=$(chainctl auth token) ``` You may also encounter errors like the following: ``` cannot find package "chainguard.dev/sdk/auth" . . . ``` This may indicate that the Chainguard SDK wasn't imported correctly. Be sure that you run the following commands to set this up: ```shell go mod init github.com/chainguard-dev/sdk && go mod tidy ``` Again, the `main.go` file contains many comments that explain each portion of the code. If you encounter any errors, we encourage you to review the file closely to better understand how the application works and what might be going wrong. ## Learn more The example application highlighted in this guide is intended to show how you can manage Custom Assembly resources with the Chainguard API. To learn more about Custom Assembly, you can refer to the [Custom Assembly overview](/chainguard/containers/custom-assembly/overview/). Be aware that it's also possible to edit a Custom Assembly container's configuration using `chainctl`. Check out our [documentation on the subject](/chainguard/containers/custom-assembly/custom-assembly-chainctl/) for more information. --- ### FedRAMP technical considerations and risk factors URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/security-and-compliance/fedramp-considerations.md Last Modified: September 28, 2026 Tags: Chainguard Containers, FIPS Many frequently asked questions revolve around how organizations are meant to stay on top of the changing landscape for FedRAMP, PMOS, Revisions, and Certificates. This article outlines various considerations and risk factors that organizations should keep in mind when working to become and stay FedRAMP authorized. ## Important considerations for PMO revision trends There are a number of things one should keep in mind when analyzing revision trends from the FedRAMP Program Management Office (PMO) — which oversees the development of the FedRAMP program — and the changes in [FIPS 140-3](https://csrc.nist.gov/projects/fips-140-3-transition-effort). The following are of particular importance: * FedRAMP authorization has tended to only include external customer communication under scope. Now, though, the FedRAMP PMO is expecting both external and internal communications in scope. * Customers will be now be required to provide an authorization boundary diagram with an associated network diagram that shows all FIPS encrypted flows: * Anything that isn’t encrypted needs to be explicitly highlighted. * Auditors will ask that these diagrams and flows not just show this but, in some cases, to prove this during the audit with observed testing scenarios. * Notable Revision 5 Changes ([Rev 5 - appendix queue](https://web.archive.org/web/20250214120456/https://www.fedramp.gov/blog/2023-05-30-rev-5-baselines-have-been-approved-and-released/): * Customers must list every client and server communication in the infrastructure and which FIPS module is being used. * DoD Security Technical Implementation Guides (STIGs) can be required, although CIS Level 2 benchmarks are accepted if a STIG does not exist, marking a change from Revision 4 which only required CIS Level 1 benchmarks. * In order to get FIPS validated, libraries need to be assessed by one of thirteen authorized labs: * National Institute of Standards and Technology ([NIST](https://www.nist.gov/)) labs are overwhelmed, as there is a large influx of 140-2 demand but limited supply for approval on certification. While FIPS 140-3 is not immediately on the horizon for 2025, it will become the law of the land in 2026. As organizations begin analyzing their requirements and architecture constraints this year, it will be crucial to review and plan for the upcoming changes that are outlined in this section. ## Ongoing FIPS maintenance risks (and how Chainguard can help) An organization's upfront FIPS configuration is important, but the real difficulties often come later on. While organizations frequently pass an audit with their initial configuration, there is a high level of risk associated with ongoing maintenance. This section highlights some of the risks associated with ongoing FIPS maintenance and how Chainguard can resolve them. ### Initial configuration vs continued maintenance risk The need to maintain FIPS across updates and versions with the dynamically changing nature of applications built on open source software within containers places a heavy burden on engineers. Likewise, the need to continuously validate cryptographic operations across various base images can pose significant risk to applications breaking or products going down across version updates and new releases. Additionally, certificates often expire and it is difficult to keep track of this at scale and update FIPS modules accordingly, presenting a heavy workload for compliance teams. Critical vulnerabilities often present themselves in the critical path for the organizations requiring a detailed Plan of Action and Milestones (POAM) and remediation efforts beyond the initial FIPS configuration. With Chainguard, there is a single partner to hold accountable, as we offer enterprise SLAS that can take the place of expensive and costly operations to do this manually. This includes 7 days for Critical, and 14 days for High, Medium, Low. This allows the initial CVE count to remain close to 0 during auditing events, but also accounts for any future CVEs that might show up within the ATO boundary. ### FIPS expertise and resource challenges Typically, an organization must oversee several key resources with advanced expertise to manage the ongoing configuration and maintenance of a FIPS or FedRAMP Authorization to Operate (ATO). However, this is not always translated across developers, application owners, and security teams. This problem scales with more images and boundaries under consideration. For a fixed fee per image, Chainguard will solve this problem by being directly responsible for the build and maintenance of all FIPS images in use, leveraging in-house expertise across a broad set of domains ### Quality testing for immediate use of Chainguard-provided FIPS images One of the hardest things to consider when moving an images program to leverage a CMVP-certified FIPS module is to accommodate all of the differences across programming languages and image types. To solve this, Chainguard provides not only the initial configuration, but the ongoing quality and functional testing across programming languages and application specific requirements for FIPS, to ensure that organizations only have to worry about running their services on these images. This includes a broad test suite across version updates while balancing the trade-offs between CVE fixes and limiting breaking changes to the running applications. ### Application-specific open source software While FIPS modules can be easier to implement within the base layer of an image, application-specific projects within the open source community are entirely maintained by the respective community maintainers and project owners. This means that FIPS considerations vary dramatically across these projects. Building these application images on top of existing FIPS-base images presents significant risk and additional cost associated with ensuring it is compliant through FTE efforts. Furthermore, many open source projects, such as Cassandra, cannot even support FIPS validation in their current state until they change the architecture and source code to be able to support this. Knowing which projects can support FIPS and which cannot is very useful in analyzing images within scope. Since Chainguard’s catalog consists of 100s of FIPS validated images, we have done the heavy lifting of this analysis, and continue to do so for new requests from our customers. ## Revision 5 key requirements solved with Chainguard container images Using Chainguard Containers eliminates the manual toil of container security hassles. Hardened and low CVE images save you time and money by making sure you always meet the security standards needed for government work. The following table highlights the features of Chainguard Containers as mapped to FedRAMP Revision 5's baselines:
Features Rev 5 control
Continuous vulnerability management and automation
  • Almost no vulnerabilities: We start with images that have very few to no known vulnerabilities, helping to pass security checks easily.
  • Automatic fixes: Our system automatically finds and fixes vulnerabilities, providing a 7 day SLA on critical severity CVEs, 14 for High/Medium/Low.
  • Security advisories: We keep our users updated with the latest security advice, helping them stay informed about any risks.
  • RA-5 Vulnerability Scanning
  • CA-7 Continuous Monitoring
  • SI-2 Flaw Remediation
Secure by default
  • Minimal images: built to be safe from the start. We include only what is required to run the application.
  • Strong encryption: We use FIPS validated cryptography providers OpenSSL and Bouncy Castle, which are what the government requires to keep data safe.
  • Hardened to container security best practices with an associated STIG.
  • SA-10 Developer Configuration Management
  • SA-11 Developer Security Testing and Evaluation
Asset management best practices
  • Build time SBOMs for each image: For every image we build, we provide a detailed list with all components inside, making it easy to understand and check.
  • OCI standard images: Our images are built following OCI specifications, ensuring they work well in any standard environment.
  • Signed with Sigstore: Our images, attestations, and SBOMs are signed using Sigstore, proving they haven't been tampered with from the time we built them.
  • SC-8 Protection of Information in Transit
  • SC-28 Protection of Information at Rest

Additionally, Chainguard helps support CM-6 configuration settings requirements. Chainguard announced the release of a STIG for the General Purpose Operating System (GPOS) SRG which specifies security requirements for general purpose operating systems running in a network. The goal for this STIG is that it will help customers confidently and securely integrate Chainguard Containers into their workflows. Please refer to our [STIGs overview](/chainguard/containers/security-and-compliance/stigs/#how-stigs-can-be-used-to-harden-container-images) for more information. ## Kernel-independent FIPS container images Cryptographic protection relies on the secure implementation of a trusted algorithm and a random bit generator that cannot be reasonably predicted at any greater accuracy than random chance. To certify these implementations, NIST operates a cryptographic certification program called the Cryptographic Module Validation Program ([CMVP](https://csrc.nist.gov/projects/cryptographic-module-validation-program)). CMVP validates that implementation is compliant with the relevant standards: * For algorithm implementation, CMVP requires strict compliance with [FIPS standards](https://csrc.nist.gov/publications/fips). Thus FIPS modules must sit inside a self-verified cryptographic boundary. * For random bit generators, CMVP requires strict compliance with [SP 800-90B](https://csrc.nist.gov/pubs/sp/800/90/b/final) recommendations and permits entropy sources to sit either inside or outside the FIPS cryptographic boundary. Traditionally, to meet these compliance requirements, containers would access an SP 800-90B compliant entropy source provided by a certified kernel, as in this diagram:
Diagram labeled 'FIPS Module + SP 800-90B Entropy Source'. The diagram shows two boxes labeled 'Container Image' and 'Host OS Linux Kernel.' The 'Container Image' box holds two boxes within it labeled 'Application' and 'FIPS Module,' with 'FIPS Module' surrounded by a rectangle labeled 'FIPS Cryptographic Boundary.' The 'Host OS Linux Kernel' box contains a box labeled 'Kernel FIPS Module' and a cylinder labeled 'SP 800-90B Entropy Source,' both of which are surrounded by a rectangle labeled 'FIPS Cryptographic Boundary.' An arrow points from the 'Application' to the 'FIPS Module' and from the 'FIPS Module' to the 'Entropy Source.'

This architecture drives significant friction for vendors delivering FIPS compliant workloads for modern cloud-native applications. First, very few versions of the Linux Kernel are certified. Second, Linux Kernel certification timelines are often long and arduous. Ultimately, this results in a very limited choice of certified runtimes and compatible underlying hardware for developers to build on. In practice, it means that a certified kernel from a given vendor might be over 5 years old. Outdated kernels typically lack support and optimizations for the latest generation of hardware, and are often incompatible with the latest cloud instance types. It also means when sticking to the same vendor, the application runtimes are equally as out of date and vulnerable. Chainguard's solution has the FIPS module and the SP 800-90B entropy source co-located in the container image userspace. This eliminates the need for a certified Linux kernel for the majority of workloads and streamlines engineering effort for workload deployments. This is why Chainguard FIPS images now ship with a certified userspace SP 800-90B entropy source, as in this design:
Diagram labeled Kernel Independent FIPS Container Images. The diagram contains a box labeled 'New Chainguard FIPS Container Image' which contains three objects: a box labeled 'Application', a box labeled 'FIPS Module' (which is surrounded by a rectangle labeled 'FIPS Cryptographic Boundary') and a cylinder labeled 'SP 800-90B Entropy Source.' An arrow points from 'Application' to 'FIPS Module,' and another arrow points from 'FIPS Module' to the 'Entropy Source' cylinder.

This means that the entropy source is now independent of the hardware or cloud environment. Essentially, you can have FIPS on any host OS, kernel, and hardware. You can even have FIPS on managed cloud Kubernetes platforms like GKE, EKS, and AKS. Note that this solution has been tested by two NIST labs and [certified with its own Entropy Source Validation (ESV)](https://csrc.nist.gov/projects/cryptographic-module-validation-program/entropy-validations/certificate/191). For the current list of Chainguard's FIPS modules and entropy sources, along with their CMVP and ESV certificates, refer to the [Chainguard FIPS commitment](https://www.chainguard.dev/legal/fips-commitment). Additionally, check out our blog post on [Kernel-Independent FIPS Containers](https://www.chainguard.dev/unchained/kernel-independent-fips-images). ## Conclusion While FIPS 140-2 has become relatively familiar to organizations, it still presents a set of complex challenges for those trying to achieve FedRAMP authorization. With the broad adoption of open source development on container images across development teams beginning to accelerate, the problem of configuring and maintaining FIPS modules within a broad suite of images becomes nearly intractable. Skill gaps and resource shortages further exacerbate the ability to keep up with this demand; and this problem will only get worse when 140-3 becomes the law of the land in 2026. The Rev-5 changes signal a continued trend in the market: as PMOs get better at understanding the state of modernized applications running on containers, and how the architectures interact with the services running, requirements are only becoming more granular. As a result, further documentation and proof will be required by organizations who are seeking to implement FIPS within their container environments. This puts pressure on organizations to fill skill gaps, increase the spend going towards FedRAMP programs, and de-risk ongoing assessments to maintain compliance. Chainguard offers an off-the-shelf solution for customers to run their applications on preconfigured and continuously maintained FIPS validated images so that they do not have to incur these costs or associated risks. As a result, Chainguard has helped customers achieve Moderate and High impact levels with very low engineering lift. With this comes the peace of mind knowing that you have a single partner responsible for building and supplying the key images needed to be successful in FedRAMP endeavours. --- ### Getting started with the WordPress Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/web-and-data-services/wordpress.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [WordPress container image](https://images.chainguard.dev/directory/image/wordpress/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-wordpress) is a drop-in replacement for the official [WordPress FPM-Alpine image](https://hub.docker.com/_/wordpress). It includes a [distroless](/chainguard/containers/concepts/getting-started-distroless/) variant for production use that removes shells, package managers, and other unnecessary components. The image ships with the latest PHP and WordPress versions and all required PHP extensions. This guide covers three ways to use the WordPress Chainguard Container to build and run WordPress projects.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Preparation This guide requires Docker. Download and install it from the [official Docker website](https://docs.docker.com/get-docker/) if you don't have it. The images in this guide require a free Chainguard account. Sign up at [chainguard.dev](https://www.chainguard.dev/) if you don't have one, then authenticate: ```shell chainctl auth login ```

Note: chainctl auth login supports several login flows, including device-code login for machines without a browser and assumable identities for CI. If the default browser flow doesn't work in your environment, see Authentication options.

If you encounter credential errors when pulling images, pull them individually before running `docker compose`: ```shell docker pull cgr.dev/chainguard/wordpress:latest-dev docker pull cgr.dev/chainguard/wordpress:latest docker pull cgr.dev/chainguard/mariadb docker pull cgr.dev/chainguard/nginx ``` ### Clone the demos repository Clone the demos repository to your local machine: ```shell git clone git@github.com:chainguard-dev/edu-images-demos.git ``` Navigate to the `wordpress` demo directory: ```shell cd edu-images-demos/php/wordpress ``` You'll find three directories, one for each example in this guide. ## Example 1: Testing the container image with a fresh WordPress install The `latest-dev` variant of the Chainguard WordPress Container lets you run a fresh WordPress installation and explore the setup wizard. Changes you make won't persist after you stop the environment — the next example shows how to persist customizations using a volume. The files for this example are in the `01-preview` directory. Open `docker-compose.yml` in your editor to follow along. This example includes a `Dockerfile` that copies WordPress source files into the document root and sets ownership at build time: ```Dockerfile FROM cgr.dev/chainguard/wordpress:latest-dev USER root RUN cp -r /usr/src/wordpress/. /var/www/html/ && \ cp /var/www/html/wp-config-docker.php /var/www/html/wp-config.php && \ chown -R php:php /var/www/html USER php ``` The `docker-compose.yml` for this example: ```yaml services: app: build: . image: wordpress-preview restart: unless-stopped environment: WORDPRESS_DB_HOST: mariadb WORDPRESS_DB_USER: $WORDPRESS_DB_USER WORDPRESS_DB_PASSWORD: $WORDPRESS_DB_PASSWORD WORDPRESS_DB_NAME: $WORDPRESS_DB_NAME volumes: - document-root:/var/www/html nginx: image: cgr.dev/chainguard/nginx restart: unless-stopped ports: - 8000:8080 volumes: - document-root:/var/www/html - ./nginx.conf:/etc/nginx/nginx.conf mariadb: image: cgr.dev/chainguard/mariadb restart: unless-stopped environment: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 MARIADB_USER: $WORDPRESS_DB_USER MARIADB_PASSWORD: $WORDPRESS_DB_PASSWORD MARIADB_DATABASE: $WORDPRESS_DB_NAME ports: - 3306:3306 volumes: document-root: ``` This Docker Compose file defines three services: `app`, `nginx`, and `mariadb`: - The `app` service builds a local image using the included `Dockerfile`, which copies WordPress source files from `/usr/src/wordpress` into the document root and renames `wp-config-docker.php` to `wp-config.php`. That config file reads database credentials from environment variables at runtime. The `document-root` volume is shared between `app` and `nginx`. - The `nginx` service uses the [Chainguard nginx Container](https://images.chainguard.dev/directory/image/nginx/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-wordpress), configured to serve the WordPress application on port 8000. - The `mariadb` service uses the [Chainguard MariaDB Container](https://images.chainguard.dev/directory/image/mariadb/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-wordpress), configured with the environment variables needed to create the WordPress database. The environment variables are stored in a `.env` file in the same directory. To view them, run: ```bash cat .env ``` ```ini WORDPRESS_DB_HOST=mariadb WORDPRESS_DB_USER=wp-user WORDPRESS_DB_PASSWORD=wp-password WORDPRESS_DB_NAME=wordpress ``` You can change these values as needed. The `.env` file is hidden and won't appear in most file explorers, but you can open it in any terminal text editor. Build the image and start the services: ```bash docker compose up --build ``` Open `http://localhost:8000` in your browser to reach the WordPress installation page. Follow the on-screen instructions to complete setup. Any customizations will be lost when you stop the environment. To stop the services, press `CTRL+C` in the terminal, then run: ```bash docker compose down ``` This removes the containers and networks. The next example shows how to mount a volume so that customizations like themes and plugins persist. ## Example 2: Customizing a new WordPress installation To keep customizations — themes, plugins, and other changes — between container rebuilds, you need a volume with the correct permissions. This requires a user inside the container with the same UID as your host user. This example uses a custom Dockerfile to add a `wordpress` user with a configurable UID (defaulting to `1000`, the typical UID for a non-root user on Linux) and sets ownership of the document root accordingly. Navigate to `02-customizing` to follow along. Here's the Dockerfile: ```Dockerfile FROM cgr.dev/chainguard/wordpress:latest-dev ARG UID=1000 USER root RUN cp -r /usr/src/wordpress/. /var/www/html/ && \ cp /var/www/html/wp-config-docker.php /var/www/html/wp-config.php && \ cp -r /usr/src/wordpress/wp-content /usr/src/wp-content-default RUN addgroup wordpress && adduser -SD -u "$UID" -s /bin/bash wordpress wordpress RUN chown -R wordpress:wordpress /var/www/html /usr/src/wp-content-default COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh USER wordpress ENTRYPOINT ["/docker-entrypoint.sh"] ``` In addition to creating the `wordpress` user, the Dockerfile copies WordPress source files into the document root at build time and saves a copy of the default `wp-content` directory to `/usr/src/wp-content-default`. A custom entrypoint script uses that copy to populate the host-mounted `wp-content` directory on first run if it doesn't yet contain a `themes` subdirectory: ```bash #!/bin/sh # Populate wp-content from defaults on first run (when themes directory is absent) if [ ! -d /var/www/html/wp-content/themes ]; then cp -r /usr/src/wp-content-default/. /var/www/html/wp-content/ fi exec php-fpm ``` The `docker-compose.yml` file references the custom Dockerfile and accepts a UID build argument: ```yaml services: app: image: wordpress-local-dev build: context: . dockerfile: Dockerfile args: UID: 1000 user: wordpress restart: unless-stopped environment: WORDPRESS_DB_HOST: mariadb WORDPRESS_DB_USER: $WORDPRESS_DB_USER WORDPRESS_DB_PASSWORD: $WORDPRESS_DB_PASSWORD WORDPRESS_DB_NAME: $WORDPRESS_DB_NAME volumes: - ./wp-content:/var/www/html/wp-content - document-root:/var/www/html nginx: image: cgr.dev/chainguard/nginx restart: unless-stopped ports: - 8000:8080 volumes: - document-root:/var/www/html - ./nginx.conf:/etc/nginx/nginx.conf mariadb: image: cgr.dev/chainguard/mariadb restart: unless-stopped environment: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 MARIADB_USER: $WORDPRESS_DB_USER MARIADB_PASSWORD: $WORDPRESS_DB_PASSWORD MARIADB_DATABASE: $WORDPRESS_DB_NAME ports: - 3306:3306 volumes: document-root: ``` Only the `app` service differs from the previous example. The `build` section references the custom Dockerfile and sets `UID` to `1000` by default; pass your own UID at build time to match your host user. The bind mount at `./wp-content` persists the WordPress content directory to the host. Build the image, passing your UID as a build argument: ```shell docker compose build --build-arg UID=$(id -u) app ``` Once the build completes, start the services: ```shell docker compose up ``` Open `http://localhost:8000` in your browser to access the WordPress installation. In a separate terminal, check that the `wp-content` directory was populated with the default WordPress themes and plugins: ```shell ❯ ls -la wp-content total 24 drwxrwxr-x 4 erika erika 4096 Jul 18 21:16 . drwxrwxr-x 3 erika erika 4096 Jul 18 21:15 .. -rw-rw-r-- 1 erika erika 14 Jul 18 21:05 .gitignore -rw-r--r-- 1 erika 65533 28 Jan 1 1970 index.php drwxr-xr-x 2 erika 65533 4096 Jul 18 21:16 plugins drwxr-xr-x 16 erika 65533 4096 Jul 18 21:16 themes ``` The matching UID between the container's `wordpress` user and your host user is what allows files written by the container to be owned by your host account. Any themes and plugins you install now persist between container rebuilds. To stop the services, press `CTRL+C` in the terminal, then run: ```bash docker compose down ``` The next example shows how to build a distroless WordPress image for production. ## Example 3: Using the distroless variant of the WordPress container image This example uses a multi-stage Docker build to produce a distroless image with a smaller attack surface. The distroless image includes only the dependencies needed to run WordPress, without a shell or package manager. The key difference from the previous examples is that all WordPress files — including any customizations in `wp-content` — are baked into the image at build time rather than populated at runtime. This makes the image self-contained: it doesn't rely on init steps or host volumes. Adding custom content at build time increases the final image size, but prevents filesystem changes once the container is running. To demonstrate custom content, this example includes the [Cue](https://wordpress.org/themes/cue/) blogging theme and the [Imsanity](https://wordpress.org/plugins/imsanity/) image-resizing plugin. Navigate to `03-distroless` to follow along. Here's the Dockerfile: ```Dockerfile FROM cgr.dev/chainguard/wordpress:latest-dev AS builder #copy wp-content folder COPY ./wp-content /usr/src/wordpress/wp-content USER root #copy WordPress source to document root and set up config RUN cp -r /usr/src/wordpress/. /var/www/html/ && \ cp /var/www/html/wp-config-docker.php /var/www/html/wp-config.php FROM cgr.dev/chainguard/wordpress:latest COPY --from=builder --chown=php:php /var/www/html /var/www/html ``` The `COPY` instruction places your local `wp-content` into `/usr/src/wordpress/wp-content` before the `RUN` step merges it with the rest of the WordPress source. `USER root` is required because the WordPress source directory in this image is only readable by root. The `RUN` command copies everything to `/var/www/html` and renames `wp-config-docker.php` to `wp-config.php`, which reads database credentials from environment variables at runtime. The final stage copies the populated document root into the distroless image with `php:php` ownership. The `docker-compose.yml` file references the custom Dockerfile: ```yaml services: app: image: wordpress-local-distroless build: context: . dockerfile: Dockerfile restart: unless-stopped environment: WORDPRESS_DB_HOST: mariadb WORDPRESS_DB_USER: $WORDPRESS_DB_USER WORDPRESS_DB_PASSWORD: $WORDPRESS_DB_PASSWORD WORDPRESS_DB_NAME: $WORDPRESS_DB_NAME WORDPRESS_CONFIG_EXTRA: | # Disable plugin and theme update and installation define( 'DISALLOW_FILE_MODS', true ); # Disable automatic updates define( 'AUTOMATIC_UPDATER_DISABLED', true ); volumes: - document-root:/var/www/html nginx: image: cgr.dev/chainguard/nginx restart: unless-stopped ports: - 8000:8080 volumes: - document-root:/var/www/html - ./nginx.conf:/etc/nginx/nginx.conf mariadb: image: cgr.dev/chainguard/mariadb restart: unless-stopped environment: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1 MARIADB_USER: $WORDPRESS_DB_USER MARIADB_PASSWORD: $WORDPRESS_DB_PASSWORD MARIADB_DATABASE: $WORDPRESS_DB_NAME ports: - 3306:3306 volumes: document-root: ``` Build and start the environment: ```shell docker compose up --build ``` This WordPress setup behaves like the previous examples, but the image is self-contained — it requires no host volumes and allows no new package installations or shell access. The `WORDPRESS_CONFIG_EXTRA` environment variable disables theme and plugin installation and automatic updates, preventing filesystem changes inside the running container. To stop the services, press `CTRL+C` in the terminal, then run: ```bash docker compose down ``` To keep your WordPress installation up to date, pin the image to a digest and use [Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/), a GitHub Action that opens a pull request whenever the digest behind the tag you reference changes. Chainguard rebuilds its container images daily, so this picks up each new WordPress build. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose WordPress Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Choosing a base container for your compiled programs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/choosing-a-base-container.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Cheatsheet When selecting the right base image for your application, there are a variety of factors to take into consideration. For starters, it is critical that your application has all of the dependencies it needs to run. The ideal base image will contain the essential packages you need, while leaving out the ones you don’t. However, in practice, you will need to build upon your container images so they meet your specific needs, making it all the more important that you have a strong foundation. In this guide, we will explore a variety of Chainguard's container images which are suitable for different compiled applications. We will take a look at their availability and use-case differences so you can move closer to settling on the best base image for your specific needs. ## Available containers ### wolfi-base The [`wolfi-base`](https://images.chainguard.dev/directory/image/wolfi-base/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) Chainguard Container is a minimal container image based on the [Wolfi *un-distro*](https://github.com/wolfi-dev/), a community-oriented Linux distribution created by Chainguard to facilitate image builds. The `wolfi-base` image contains `busybox` and `apk-tools` so that you can build your own packages for a custom image. It also supports `glibc`.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
The following packages are included in the `wolfi-base:latest` Chainguard Containers: - `apk-tools` - `busybox` - `ca-certificates-bundle` - `chainguard-baselayout` - `glibc` - `glibc-locale-posix` - `ld-linux` ### chainguard-base *Paid Container* \ In addition to the functionality of the `wolfi-base` Chainguard Container, [`chainguard-base`](https://images.chainguard.dev/directory/image/chainguard-base/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) reports as being a Chainguard Container, which scanners use to determine what security feeds to reference for vulnerabilities. Additionally, the `chainguard-base` container image provides access to vulnerability remediation SLAs to ensure your containers are always up-to-date with the latest releases and patches. The following packages are included in the `chainguard-base:latest` Chainguard Container: - `apk-tools` - `busybox` - `ca-certificates-bundle` - `chainguard-baselayout` - `glibc` - `glibc-locale-posix` - `ld-linux` You can find the complete inventory of packages for the `chainguard-base` Chainguard Container at [its listing on Chainguard's registry](https://images.chainguard.dev/directory/image/chainguard-base/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). ### static The Chainguard [`static`](https://images.chainguard.dev/directory/image/static/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) base image is a Wolfi-based image available in one variant with the `:latest` tag. The `static` image is extremely minimal and is not intended to be run directly. It is used to host stand-alone, static binaries, like those produced by compilers such as `gcc`, `go`, and `rust`. It does not contain any programs you can run out-of-the-box. You must add your own static binaries to the image, for example using a Dockerfile multi-stage build. The following packages are included in the `static:latest` Chainguard Container: - `ca-certificates-bundle` - `chainguard-baselayout` - `glibc-locale-posix` - `tzdata` - `wolfi-baselayout` You can find more information about the `static` Chainguard Container at [its listing on the Chainguard's registry](https://images.chainguard.dev/directory/image/static/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). ### glibc-dynamic The [`glibc-dynamic`](https://images.chainguard.dev/directory/image/glibc-dynamic/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) Chainguard Container is best suited for when you need to host dynamically linked binaries that depend on the C standard library. Like the `static` image, `glibc-dynamic` is intended to be used as a base image only, and you must add your own binaries to the image. The `glibc-dynamic` image is freely available in two variants: `:latest` and `:latest-dev`. The `:latest-dev` image adds additional packages which are not present in `:latest` to help facilitate application development. It is suggested to use the `:latest` image for production-facing purposes because of its smaller footprint. The following packages are included in the `glibc-dynamic:latest` Chainguard Container: - `ca-certificates-bundle` - `chainguard-baselayout` - `glibc` - `glibc-locale-posix` - `ld-linux` - `libgcc` - `libstdc++` - `wolfi-baselayout` You can find more information about the `glibc-dynamic` Chainguard Container at [its listing on Chainguard's registry](https://images.chainguard.dev/directory/image/glibc-dynamic/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). ### cc-dynamic The [`cc-dynamic`](https://images.chainguard.dev/directory/image/cc-dynamic/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) Chainguard Container is deprecated. It is suggested that you use the `glibc-dynamic` image instead, as it is designed to replace `cc-dynamic`. You can find more information about the `cc-dynamic` image, such as its packages and licensing information, on [Chainguard's registry](https://images.chainguard.dev/directory/image/cc-dynamic/advisories?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). ### gcc-glibc The [`gcc-glibc`](https://images.chainguard.dev/directory/image/gcc-glibc/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) Chainguard Container is best suited for building C applications which depend on `glibc`. There are two freely available variants of this image, `:latest` and `:latest-dev`. `:latest-dev` is a developer variant of the image which adds additional packages such as `bash` to facilitate the development process. In comparison to the `static` and `glibc-dynamic` Chainguard Containers, `gcc-glibc` is intended to be used to develop programs based on the C standard library, instead of simply hosting binaries. Because of this, it contains additional packages such as `make`, `busybox`, as well as `gcc` to compile programs. The following packages are included in the `gcc-glibc:latest` Chainguard Container: - `binutils` - `build-base` - `busybox` - `ca-certificates-bundle` - `gcc` - `glibc` You can find the complete inventory of packages for the `gcc-glibc` Chainguard Container at [its listing on Chainguard's registry](https://images.chainguard.dev/directory/image/gcc-glibc/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). ### glibc-openssl *Paid Container* \ The [`glibc-openssl`](https://images.chainguard.dev/directory/image/glibc-openssl/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs) Chainguard Container is designed for C applications which depend on OpenSSL. It contains the `openssl` and `openssl-provider-legacy` packages to support this use-case. It comes in two variants,`latest` and `latest-dev`. As in the aforementioned images, `latest` is designed for deployment, while `latest-dev` contains additional packages to assist in program development such as a shell and package manager. The following packages are included in the `glibc-openssl:latest` Chainguard Container: - `ca-certificates-bundle` - `chainguard-baselayout` - `glibc` - `glibc-locale-posix` - `ld-linux` - `openssl` - `openssl-provider-legacy` You can find the complete inventory of packages for the `glibc-openssl` Chainguard Container at [its listing on Chainguard's registry](https://images.chainguard.dev/directory/image/glibc-openssl/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). ## What about `musl`? At the time of this writing, none of Chainguard's container images come packaged with `musl`. Chainguard builds `glibc`-based container images because `glibc` is commonly used, which makes it easier for most developers to start consuming Chainguard Containers in their environments. Additionally, `glibc` is widely tested, making it a dependable choice for a C standard library implementation. As `glibc` is a well-established option, choosing to use `glibc` ensures more applications will be compatible with new images. Though `musl` is sometimes chosen because of its minimal footprint, Chainguard’s distroless approach based on [Wolfi](https://www.chainguard.dev/unchained/introducing-wolfi-the-first-linux-un-distro) often results in a container image of comparable (or smaller) size than official `musl` based images. For more information, please refer to our [glibc vs. musl](/chainguard/containers/concepts/glibc-vs-musl/) article. ## Next steps Understanding the differences between various Chainguard Containers allows you to make informed decisions about what images to choose for your compiled applications. You can check out our complete suite of Chainguard Containers on [Chainguard's registry](https://images.chainguard.dev/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-compiled-programs-compiled-programs). To learn more about using Chainguard Containers, head to the [Chainguard Academy](/chainguard/containers/), where you can find documentation to help you start incorporating them into your workflow. Interested in learning more about adopting Chainguard Containers for your organization? [Let’s get in touch!](https://www.chainguard.dev/contact) --- ### How to set up pull through from Chainguard's registry to Google Artifact Registry URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/artifact-registry-pull-through.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Registry Organizations can use Chainguard Containers along with third-party software repositories in order to integrate with current workflows as the single source of truth for software artifacts. In this situation, you can set up a proxy repository to function as a mirror of [Chainguard's registry](/chainguard/containers/registry/overview/). This mirror can then serve as a pull through cache for your Chainguard Containers. This tutorial outlines how to set up a remote repository with [Google Artifact Registry](https://cloud.google.com/artifact-registry/docs/repositories/remote-overview). It will walk you through how to set up an Artifact Registry Repository you can use as a pull through cache for Chainguard's Free containers or Production containers originating from a private Chainguard repository. ## Prerequisites In order to complete this tutorial, you will need the following: * Docker installed on your local machine. Follow the [official installation instructions](https://docs.docker.com/engine/install/) to set this up. * Administrative privileges over a Google Cloud Platform project. This project will also need to have the [Artifact Registry API](https://cloud.google.com/artifact-registry/docs/reference/rest) enabled. * If you plan to set up an Artifact Registry repository to serve as a pull through cache for Production containers, then you will also need to have privileges to create a pull token from Chainguard. * Additionally, you'll need `chainctl` installed to create the pull token. If you haven't already installed this, follow the [installation guide](/platform/chainctl-usage/how-to-install-chainctl/). ## Setting up Google Artifact Registry as a pull through for free containers Chainguard's Free container images are free to use, publicly available, and always represent versions tagged as `:latest`. To set up a remote repository in Google Artifact Registry from which you can pull Chainguard Free container images, log in to the [Google Cloud Console](https://console.cloud.google.com) and choose your project. Once there, navigate to the [Artifact Registry section](https://console.cloud.google.com/artifacts), click on **Repositories** in the left-hand navigation menu, and click on the **Create Repository** button near the top of the page. On the **Create Repository** page, enter the following details for your new remote repository: * **Name** — This is used to refer to your repository. You can choose whatever name you like here, but this guide's examples will use the name `chainguard-pull-through`. * **Format** — For the purposes of this guide, this must be set to **Docker**. * **Mode** — Set this to **Remote**. * **Remote repository source** — Choose **Custom** then enter `https://cgr.dev/` in the **Custom repository** field. Following that, choose the Location, Encryption and Cleanup policy options for your repository. This guide's examples will use the location `us-central1`, but you can choose the location that best suits your needs. Finally, click the **Create** button to create the repository. ### Testing pull through of a free container image By default, the Artifact Registry repository requires authentication. Log in with a valid Google Artifact Registry: ```sh gcloud auth configure-docker us-central1-docker.pkg.dev ``` Be sure to change `us-central1` to reflect the location of your Artifact Registry repository. Also, after running this command you may be prompted to log in to your Google Cloud account. After running the command, you will be able to pull a Free container through Google Artifact Registry. The following example pulls the `go` container: ```sh docker pull us-central1-docker.pkg.dev//chainguard-pull-through/chainguard/go:latest ``` This command first specifies the location of the Artifact Registry repository we just created (`us-central1-docker.pkg.dev//chainguard-pull-through/`). It then follows that with the name of the Free containers and the remote repository we want to pull it from (`chainguard/go:latest`). If you run into issues with this command, be sure that it contains the correct Google Artifact Registry URL for your repository, including the location and project ID. ## Setting up Google Artifact Registry as a pull through for production containers Chainguard's Production container images are enterprise-ready container images that come with patch SLAs and features such as [Federal Information Processing Standard](/platform/fips/fips-images/) (FIPS) readiness. The process for setting up a Google Artifact Registry repository that you can use as a pull through cache for Chainguard Production container images is similar to the one outlined previously for Free containers, but with a few extra steps. To get started, you will need to create [a pull token](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) for your organization's registry. Pull tokens are longer-lived tokens that can be used to pull container images from other environments that don't support OIDC, such as some CI environments, Kubernetes clusters, or with registry mirroring tools like Google Artifact Registry. First log in with `chainctl`: ```sh chainctl auth login ``` Then configure a pull token: ```sh chainctl auth configure-docker --pull-token ``` This command will prompt you to select an organization. Be sure to select the organization whose Production container images you want to pull through the Artifact Registry repository. This will create a pull token and print a `docker login` command that can be run in a CI environment to log in with the token. This command includes both `--username` and `--password` arguments. Note down the `username` value, as you will need it shortly. Then run the following command to create an environment variable named `$PASSWORD` set to the pull token password generated by the previous command: ```sh export PASSWORD="" ``` Now that you've set up a pull token, you can configure a repository for pulling through Production container images. You can edit the existing repository and all your users will have access to the private images. Alternatively, you could create a new `chainguard-private` repository exactly as before but with restricted access, though restricting access to repositories in Google Artifact Registry is beyond the scope of this guide. First, you will need to store the pull token password as a Google Secret Manager secret. This is because Google Artifact Registry does not support storing passwords directly in the repository configuration. To do this, first run the following command: ```sh gcloud secrets create chainguard-pull-token ``` This command creates an empty secret. Next, you can update the secret with the pull token password using the environment variable you set previously: ```sh echo -n $PASSWORD | gcloud secrets versions add chainguard-pull-token --data-file=- ``` If you haven't already done so, this command will ask if you want to enable the [Secret Manager API](https://cloud.google.com/secret-manager/docs/configuring-secret-manager?hl=en). Press `y` and then `ENTER` to enable the API and allow the command to finish. Alternatively, you can also provide the secret using the Google Cloud Console in the [Secret Manager](https://console.cloud.google.com/security/secret-manager) section. To do this, select **Create Secret**, provide a name for the secret, and enter the pull token password in the **Secret value** field. You also have the option to choose a replication policy, rotation policy, expiration policy, notification policy and more for the secret. Back in the Google Artifact Registry, click on the repository you want to configure for pulling through Production containers and then click on the **Edit** button to edit the repository configuration. In the **Remote repository source** section of the configuration screen, choose **Authenticated**. Enter the pull token `username` value in the **Username** field. In the **Password** field, select the secret you created in Google Secret Manager. Click the **Save** button to apply the changes. ### Testing pull through of a production container: As with testing pull through of a Free container, you'll first need to authenticate to the Artifact Registry: ```sh gcloud auth configure-docker us-central1-docker.pkg.dev ``` Be sure to change `us-central1` to reflect the location of your Artifact Registry repository. After running the command, you will be able to pull any Production container images that your organization has access to through Google Artifact Registry. For example, the following command will pull the `chainguard-base` Container if your organization has access to it: ```sh docker pull us-central1-docker.pkg.dev//chainguard-pull-through//chainguard-base:latest ``` Be sure the `docker pull` command you run includes the name of your Chainguard organization's registry. ## Debugging pull through from Chainguard's registry to Google Artifact Registry If you run into issues when trying to pull Containers from Chainguard's registry to Google Artifact Registry, please ensure the following requirements are met: * Ensure that all Containers [network requirements](/chainguard/containers/registry/network-requirements/) are met. * When configuring a remote Google Artifact Registry repository, ensure that the **URL** field is set to `https://cgr.dev/`. This field **must not** contain additional components. * You can troubleshoot by running `docker login` from another node (using the Google Artifact Registry pull token credentials) and try pulling an image from `cgr.dev/chainguard/` or `cgr.dev//`. * It could be that your Google Artifact Registry repository was misconfigured. In this case, create and configure a new Google Artifact Registry repository to test with. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by checking out our [Containers documentation](/chainguard/containers/overview/). If you'd like to learn more about Google Artifact Registry, we encourage you to refer to the [official Google Artifact Registry documentation](https://cloud.google.com/artifact-registry/docs/overview). --- ### Migrate a Python application to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/migration-guides/python.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Migration Chainguard's Python containers provide a migration path to significantly reduce vulnerabilities in Python applications while maintaining full compatibility with existing workloads. This guide explains how to migrate your containerized Python applications to benefit from Chainguard's enhanced security posture and daily updates. Chainguard's container images are built on [Wolfi](/open-source/wolfi/), a distroless Linux distribution designed for security and a reduced attack surface. These images are smaller and have few or no CVEs. Our container images for Python are built nightly for extra freshness, so they're always up-to-date with the latest remediations.
What is Distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi OS? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Because Chainguard's container images aim to be minimal, adapting your containerized application requires that you consider some additional factors, which the following sections describe. ## Chainguard Containers for Python overview We distribute two versions of our [Python container image](https://images.chainguard.dev/directory/image/python/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-python): a development image that includes shells such as ash/bash and package managers such as pip and a standard image that removes these tools for increased security. Our public standard images are tagged as `latest`, while our public development images are tagged as `latest-dev`. ## Differences from the Docker Official Image When migrating your Python application, keep in mind these differences between the [Chainguard Container for Python](https://images.chainguard.dev/directory/image/python/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-python) and the [official Docker image](https://hub.docker.com/_/python). - The entrypoint for the Chainguard Container for Python is `/usr/bin/python`. When running either the `latest` or `latest-dev` versions of the image interactively, you'll be working in the Python interpreter. When using `CMD` in your Dockerfiles, the container passes the provided commands to `python` by default. If you change the path to include binaries from a virtual environment, you should manually set the entrypoint. Otherwise, your Dockerfile continues to use the included system Python as the entrypoint, and you won't have access to installed packages in the virtual environment. - Chainguard's container images for Python run as the `nonroot` user by default. If you need elevated permissions, such as to add packages with `apk`, run the image as `--user root`. You should not use the root user in a production scenario. - The `/home` and `/home/nonroot` directories are owned by the nonroot user. - The `python:latest` Chainguard Container intended for production does not include a `sh`, `ash`, or `bash`. Refer to the [Debugging distroless](/chainguard/containers/troubleshooting/debugging-distroless-images/) guide for advice on resolving issues without the use of these shells. - The `python:latest` Chainguard Container does not contain package managers such as `pip` or `apk`. See the sections below for guidance on multi-stage builds (recommended) or building your own images on Wolfi (advanced usage). - Chainguard's container images for Python aim to be lightweight, and you may find that specific packages or dependencies are not included by default. The [image details reference](https://images.chainguard.dev/directory/image/python/specifications?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-python) provides specific information on packages, features, and default environment variables for the image. ## Migrating a Python application When migrating most containerized Python applications, we recommend building a virtual environment with any needed Python packages using our provided development images, then copying over the virtual environment to our stripped-down standard image. Chainguard Academy hosts [detailed instructions for a multi-stage build for a CLI-based Python script](/chainguard/containers/getting-started/languages-and-runtimes/python/). The below Dockerfile provides an example of such a multi-stage build for a simple Flask application. You can view a version of this Dockerfile with included sample Flask application and `requirements.txt` in [this repository](https://github.com/chainguard-dev/cg-images-python-migration/tree/python-only), and the original unmigrated application in the [v0 branch](https://github.com/chainguard-dev/cg-images-python-migration/tree/v0). A more complex setup with reverse proxy orchestrated with Docker Compose is provided in the next section. ```Dockerfile # syntax=docker/dockerfile:1 FROM cgr.dev/chainguard/python:latest-dev AS dev WORKDIR /flask-app RUN python -m venv --without-pip venv ENV PATH="/flask-app/venv/bin":$PATH COPY requirements.txt requirements.txt RUN pip --python /flask-app/venv/bin/python install -r requirements.txt FROM cgr.dev/chainguard/python:latest WORKDIR /flask-app COPY app.py app.py COPY --from=dev /flask-app/venv /flask-app/venv ENV PATH="/flask-app/venv/bin:$PATH" EXPOSE 8000 ENTRYPOINT ["python", "-m", "gunicorn", "-b", "0.0.0.0:8000", "app:app"] ``` When running an application containerized with the above Dockerfile, the application should be visible on `0.0.0.0:8000`. As you can see, the primary difference in this Flask application compared to the pre-migration application is the use of a multistage build. In the initial stage, we copy our requirements into the development version of the Python Chainguard Image, initialize a virtual environment, and install needed packages with pip. In the second stage, we copy the virtual environment from the development image, copy the application from the host, set exposed port metadata, and run the application with the [Gunicorn](https://gunicorn.org/) WSGI server. We create the virtual environment with `--without-pip` and install into it with `pip --python`, which runs the development image's own pip against the virtual environment. Because the second stage copies that environment into the production image, anything installed there ships to production. Leaving pip out keeps the production image limited to the packages your application imports. By default, the entrypoint for the Python Chainguard Container is `/usr/bin/python` rather than `bash`. However, if you shadow the included system `python` with the virtual environment `python` on the path as we do above, you should set the entrypoint explicitly. Otherwise, you won't have access to the packages included in your virtual environment. We recommend that you pin dependencies to specific versions in your own application. The example Flask application script linked above also enables debug mode, which should be turned off in a production scenario. You may wish to include the following environmental variables in your Dockerfile. The first prevents the buffering of output, meaning that all messages printed to standard output are immediately printed rather than being held in a cache. The second prevents the creation of cached bytecode, which can marginally reduce image size. ```Dockerfile ENV PYTHONUNBUFFERED 1 ENV PYTHONDONTWRITEBYTECODE 1 ``` ## Serving an application with nginx and Docker Compose We provide an [nginx Chainguard Container](https://images.chainguard.dev/directory/image/nginx/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-python), also built on Wolfi and rebuilt daily, that can be used as a secure and performant reverse proxy to serve your application. You can view an [example orchestration of a Flask application and nginx using Chainguard Containers](https://github.com/chainguard-dev/cg-images-python-migration/tree/compose-flask-nginx) at the linked repository. The `compose.yml` file is provided as a reference below. ```Dockerfile services: flask-app: build: context: flask-app restart: always ports: - 8000:8000 networks: - backnet - frontnet nginx: build: nginx restart: always ports: - 80:80 depends_on: - flask-app networks: - frontnet networks: backnet: frontnet: ``` The backnet and frontnet networks are provided in anticipation of other backend services such as a database container. View the [sample repository branch](https://github.com/chainguard-dev/cg-images-python-migration/tree/compose-flask-nginx) for a full orchestration example with nginx configuration. ## Advanced usage If your project image requires a set of packages that cannot be installed with pip using the multi-stage approach above, you can consider building your application on the [Wolfi base image](https://images.chainguard.dev/directory/image/wolfi-base/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-migration-migrating-python) and installing additional Python and non-Python packages as APKs. ## Additional resources You may wish to refer to the [Python microservice example](/chainguard/containers/migration/porting-apps-to-chainguard/#updating-the-python-microservice) in the [porting a sample application guide](/chainguard/containers/migration/porting-apps-to-chainguard/) as an additional useful reference while migrating your application. Debugging distroless containers can be a challenge given their lack of interactive tools such as shells. If you're having difficulty debugging issues with your multi-stage build, you may find the [Debugging distroless](/chainguard/containers/troubleshooting/debugging-distroless-images/) guide a useful resource. The following blog posts and videos may also assist with migrating your Python application: - [Blog post: Securely Containerize a Python Application with Chainguard Containers](https://dev.to/chainguard/securely-containerize-a-python-application-with-chainguard-images-bn8) - [Video: How to containerize a Python application with a multi-stage build using Chainguard Containers](https://www.youtube.com/watch?v=2D0JULd4E5A) --- ### How to port a sample application to Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/migration/porting-apps-to-chainguard.md Last Modified: October 8, 2026 Tags: Chainguard Containers ## Porting key points * Chainguard's distroless Containers have no shell or package manager by default. This is great for security, but sometimes you need these things, especially in builder images. For those cases we have `-dev` variants (such as `cgr.dev/chainguard/python:latest-dev`) which do include a shell and package manager. * Chainguard's container images typically don't run as root, so a `USER root` statement may be required before installing software. This should be a temporary escalation only; after completing any root-level operations, you should create and switch to a dedicated non-root user (for example, using `addgroup` and `adduser`) or use the image's built-in non-root user. Leaving the container running as root defeats the security purpose of using minimal images. * The `-dev` variants and `wolfi-base` / `chainguard-base` use BusyBox by default, so you need to port any `groupadd` or `useradd` commands to `addgroup` and `adduser`. * The [Free tier](/chainguard/containers/concepts/container-categories/#free-containers) of Containers provides `:latest` and `:latest-dev` versions. Our paid Production Containers offer tags for major and minor versions. * We use apk tooling, so `apt install` commands become `apk add`. * Chainguard's container images are based on `glibc` and our packages cannot be mixed with Alpine packages. * In some cases, the entrypoint in Chainguard Containers can be different from equivalent container images based on other distros, which can lead to unexpected behavior. You should always check the image's specific documentation to understand how the entrypoint works. * When needed, Chainguard recommends using a Base Container like `chainguard-base` or a `-dev` variant to install an application's OS-level dependencies. * Although `-dev` variants are still more secure than most popular container images based on other distros, for increased security on production environments we recommend combining them with a distroless variant in a multi-stage build. ## The sample application The application in question is [identidock](https://github.com/using-docker/identidock). This application was written for the book [Using Docker](https://learning.oreilly.com/library/view/using-docker/9781491915752/) about ten years ago, which shows that we can still migrate software of this age to a new container while realizing the benefits of a no-to-low CVE count. The application itself creates [identicons](https://en.wikipedia.org/wiki/Identicon) for a user name, similar to what [GitHub generates for users with no avatar](https://github.blog/2013-08-14-identicons/). It was designed at the time to demonstrate a "microservices" approach, and as such it's made up of 3 services: * The main identidock service, which takes the requests and talks to the [dnmonster](https://github.com/amouat/dnmonster) service and the redis cache * A NodeJS application which creates the identicons * Redis which is used as a simple cache The following diagram shows how the services fit together. The user only talks to the identidock service. The identidock service first checks the cache to see if it has already created an identicon for the input and, if not, requests a new identicon from the dnmonster service. The identicon is then returned to the user and saved to the cache if required. ![Diagram of Identidock architecture](arch.png) The book walked through using various orchestrators to deploy the application, some of which have since fallen out of usage (anyone remember [fleet](https://github.com/coreos/fleet)?). For the sake of this tutorial, we'll use Docker Compose, which is arguably the simplest surviving orchestrator covered in the book. The first task was to get the 10-year-old application building and running again. As it was a simple example application, this was thankfully straightforward and mainly required bumping versions of dependencies and a couple of cases of replacing unmaintained libraries. For a larger project, this may well have been a major effort. The original code can be found on the [using-docker GitHub repository](https://github.com/using-docker/identidock) and the updated working version (prior to moving to Chainguard Containers) can be found on the [v1 branch](https://github.com/chainguard-dev/identidock-cg/tree/v1) of the [repository for this tutorial](https://github.com/chainguard-dev/identidock-cg/). In order to follow along with the tutorial, please clone the code and switch to the `v1` branch: ```bash git clone https://github.com/chainguard-dev/identidock-cg.git cd identidock-cg git switch v1 ``` ## Updating the Node.js microservice To begin, we'll update the heart of the application – the dnmonster service. dnmonster is based on [monsterid.js](https://github.com/KevinGaudin/monsterid.js/) by Kevin Gaudin. The dnmonster container hosts an API which returns an [identicon](https://en.wikipedia.org/wiki/Identicon) based on the input it's given. ```bash docker run -d -p 8080:8080 amouat/dnmonster curl --output ./monster.png 'localhost:8080/monster/wolfi?size=100' ``` In this example, we give dnmonster the input "wolfi", for which it produces the following image: ![Simple "monster" art](monster.png "Monster generated for wolfi input") The version of the code on the v1 branch already contains a few updates from the original code, as well as bumping versions, the codebase was moved from the old and sporadically maintained [restify](https://github.com/restify/node-restify) module to the more modern [express](https://expressjs.com/) module. The Dockerfile for this version of the dnmonster service can be found in the dnmonster folder: ```Dockerfile FROM node RUN apt-get update && apt-get install -yy --no-install-recommends \ libcairo2-dev libjpeg62-turbo-dev libpango1.0-dev libgif-dev \ librsvg2-dev build-essential g++ #Create non-root user RUN groupadd -r dnmonster && useradd -r -g dnmonster dnmonster RUN install -d -o dnmonster -g dnmonster /home/dnmonster RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY package.json /usr/src/app/ RUN npm install COPY ./src /usr/src/app RUN chown -R dnmonster:dnmonster /usr/src/app USER dnmonster EXPOSE 8080 CMD [ "npm", "start" ] ``` The container image can be built with: ```bash cd dnmonster docker build --pull -t dnmonster . ``` Looking at this image: ```bash docker images dnmonster REPOSITORY TAG IMAGE ID CREATED SIZE dnmonster latest 3337171ebb44 4 minutes ago 1.79GB ``` We can also run the [Grype scanning tool](https://github.com/anchore/grype) to investigate if there are any known vulnerabilities present in the image: ``` grype docker:dnmonster ✔ Loaded image dnmonster:latest ✔ Parsed image sha256:17ad85081f0b4151b30556e2750ceab3222495cfc32caec2bf6e7be3647de78e ✔ Cataloged contents 5c969fe3503a9f5e6c83117d0efa79af70380eac2abe66a756eae00351972352 ├── ✔ Packages [788 packages] ├── ✔ File digests [20,143 files] ├── ✔ File metadata [20,143 locations] └── ✔ Executables [1,343 executables] ✔ Scanned for vulnerabilities [600 vulnerability matches] ├── by severity: 7 critical, 103 high, 290 medium, 49 low, 500 negligible (195 unknown) └── by status: 7 fixed, 1137 not-fixed, 544 ignored ... ``` This tells us that (at the time of writing) the image is 1.79 GB in size and has **hundreds of known vulnerabilities**. The first step in moving to Chainguard Containers is to try switching the container image name in to check if anything breaks. In this case, we’ll begin with the development variant of the Node image. Change the first line of the Dockerfile from: ```Dockerfile FROM node ``` To: ```Dockerfile FROM cgr.dev/chainguard/node:latest-dev ``` Unlike the `cgr.dev/chainguard/node:latest` image, the `:latest-dev` version includes a shell and package manager, which we need for some of the build steps. In general, it's better to use the more minimal `:latest` version where possible in order to keep the size down and reduce the tooling available to attackers. Often the `:latest-dev` container image can be used as a build step in a multi-stage, with a more minimal image such as `:latest` used in the final production image. If you try building this image, you'll find that it breaks in several places. The container image needs to install various libraries so that it can compile the [`node-canvas`](https://github.com/Automattic/node-canvas) dependency, and this looks a bit different in Debian than it does in [Wolfi](https://github.com/wolfi-dev/) (the OS powering Chainguard Containers). In Wolfi, we first need to switch to the root user to install software and we use `apk add` instead of `apt-get`. We then need to figure out the Wolfi equivalents of the various Debian packages, which may not always have a one-to-one correspondence. There are tools to help here – you can consult our [migration guides](/chainguard/containers/migration/compatibility/debian-compatibility/) and use apk tools (like `apk search libjpeg`), but searching the [Wolfi GitHub](https://github.com/wolfi-dev/os) repository for package names often provides you with what you’re looking for. Make these changes by replacing the `RUN apt-get …` line with the following `RUN apk update` and adding a `USER root` line. The start of the Dockerfile should look like this: ```Dockerfile FROM cgr.dev/chainguard/node:latest-dev USER root RUN apk update && apk add \ cairo-dev libjpeg-turbo-dev pango-dev giflib-dev python3 make gcc\ librsvg-dev glib-dev harfbuzz-dev fribidi-dev expat-dev libxft-dev\ libfontconfig1 ``` The next change we need to make is to the `RUN groupadd …` line. Chainguard's container images use BusyBox by default, which means `groupadd` needs to become `addgroup`. Rewrite the line so that it looks like this: ```Dockerfile RUN addgroup dnmonster && adduser -D -G dnmonster dnmonster ``` Finally, the default entrypoint for the Chainguard container image is `/usr/bin/node`. If we leave the `CMD` as it is, node interprets it as an argument, which isn't what we want. The Docker official image uses an entrypoint script to interpret commands, but this isn't available in the `cgr.dev/chainguard/node:latest-dev` image. The fix is to change the `CMD` command to `ENTRYPOINT`, which overrides the `/usr/bin/node` command: ```Dockerfile ENTRYPOINT [ "npm", "start" ] ``` Once you've made all these changes, you should have a Dockerfile that looks like: ```Dockerfile FROM cgr.dev/chainguard/node:latest-dev USER root RUN apk update && apk add \ cairo-dev libjpeg-turbo-dev pango-dev giflib-dev python3 make gcc\ librsvg-dev glib-dev harfbuzz-dev fribidi-dev expat-dev libxft-dev\ libfontconfig1 #Create non-root user RUN addgroup dnmonster && adduser -D -G dnmonster dnmonster RUN install -d -o dnmonster -g dnmonster /home/dnmonster RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY package.json /usr/src/app/ RUN npm install COPY ./src /usr/src/app RUN chown -R dnmonster:dnmonster /usr/src/app USER dnmonster EXPOSE 8080 ENTRYPOINT [ "npm", "start" ] ``` At this point, we have a version of dnmonster that works and is equivalent to the previous version. We can build this image: ```bash docker build --pull -t dnmonster-cg . ... ``` And investigate it again: ```bash docker images dnmonster-cg REPOSITORY TAG IMAGE ID CREATED SIZE dnmonster-cg latest 5b785d38a022 About a minute ago 1.55GB grype docker:dnmonster-cg ✔ Vulnerability DB [updated] ✔ Loaded image dnmonster-cg:latest ✔ Parsed image sha256:4795459b2f4a28bb721b19b798b4f78d6affb6b2a43096f8752576e6ce3fcd2c ✔ Cataloged contents aa53bb57b8e994e65de7f3e19da6b1945a86005fe76961383166248aad56f6fe ├── ✔ Packages [599 packages] ├── ✔ File digests [14,204 files] ├── ✔ File metadata [14,204 locations] └── ✔ Executables [733 executables] ✔ Scanned for vulnerabilities [2 vulnerability matches] ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible (2 unknown) └── by status: 0 fixed, 2 not-fixed, 0 ignored NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY git 2.48.1-r0 apk CVE-2024-52005 Unknown python-3.13 3.13.1-r5 apk CVE-2025-0938 Unknown ``` So the image is a bit smaller at 1.55GB, but more importantly, we've vastly reduced the number of vulnerabilities. There are now only 2 known vulnerabilities. But we can still do more. In particular, although 1.55GB is smaller than the previous version, it's still a large image. To get the size down, we can use a multi-stage build where the built assets are copied into a minimal Production Container, which doesn't include build tooling or dependencies required during development. Ideally, we would use the `cgr.dev/chainguard/node:latest` image for this, but we also need to install the dependencies for `node-canvas`, which means we need an image with apk tools. Normally it’s recommended to use a `:latest-dev` image for this, but in node's case, the `:latest-dev` image is relatively large due to the inclusion of build tooling, such as C compilers, that can be required by Node modules. Instead, we're going to use the `wolfi-base` image and install `nodejs` as a package. To do this, replace the Dockerfile with the following: ```Dockerfile FROM cgr.dev/chainguard/node:latest-dev AS build USER root RUN apk update && apk add \ cairo-dev libjpeg-turbo-dev pango-dev giflib-dev python3 make gcc \ librsvg-dev glib-dev harfbuzz-dev fribidi-dev expat-dev libxft-dev \ libfontconfig1 #Create non-root user RUN addgroup dnmonster && adduser -D -G dnmonster dnmonster RUN install -d -o dnmonster -g dnmonster /home/dnmonster RUN mkdir -p /usr/src/app WORKDIR /usr/src/app COPY package.json /usr/src/app/ RUN npm install COPY ./src /usr/src/app RUN chown -R dnmonster:dnmonster /usr/src/app USER dnmonster EXPOSE 8080 ENTRYPOINT [ "npm", "start" ] FROM cgr.dev/chainguard/wolfi-base RUN apk update && apk add nodejs \ cairo-dev libjpeg-turbo-dev pango-dev giflib-dev \ librsvg-dev glib-dev harfbuzz-dev fribidi-dev expat-dev libxft-dev WORKDIR /app COPY --from=build /usr/src/app /app EXPOSE 8080 ENTRYPOINT [ "node", "server.js" ] ``` We've added an `as build` statement to the first `FROM` line and added a second build that starts on the line `FROM cgr.dev/chainguard/wolfi-base`. The second build installs the required dependencies (including Node.js) before copying the build artifacts from the first image. We also changed the entrypoint to execute Node directly, as the container image no longer contains npm. Build and investigate the image: ```bash docker build --pull -t dnmonster-multi . … docker images dnmonster-multi REPOSITORY TAG IMAGE ID CREATED SIZE dnmonster-multi latest a2efea945fb9 2 minutes ago 620MB grype dnmonster-multi ✔ Loaded image dnmonster-multi:latest ✔ Parsed image sha256:08c8f2b6f0bc8b55a5962abfb9daaee9030fc37c19875592ea53f896f29a4c60 ✔ Cataloged contents 37c6abd0a832ad48e4a604a392d6ebbea1a7483ddedf94b7762186de9e991cff ├── ✔ Packages [343 packages] ├── ✔ File digests [8,887 files] ├── ✔ File metadata [8,887 locations] └── ✔ Executables [601 executables] ✔ Scanned for vulnerabilities [0 vulnerability matches] ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible └── by status: 0 fixed, 0 not-fixed, 0 ignored No vulnerabilities found ``` This results in a container image that is now 620MB in size and has 0 CVEs. We're most of the way now, but there are still a couple of finishing touches to make. The first one is to remove the dnmonster user. The wolfi-base image already defines a `nonroot` user, so we can make the build a little less complicated by using that user directly. The second one is to add in a process manager. We have node running as the root process (PID 1) in the container, which isn't ideal as it doesn't handle some of the responsibilities that come with running as PID 1, such as forwarding signals to subprocesses. You can see this most clearly when you try to stop the image – it takes several seconds as the process doesn't respond to the SIGTERM signal sent by Docker and has to be hard killed with SIGKILL. To fix this, we can add [`tini`](https://github.com/krallin/tini), a small init for containers. The `tini` binary runs as PID 1, launches npm as a subprocess, and takes care of PID 1 responsibilities. Now, the final Dockerfile looks like this: ```Dockerfile FROM cgr.dev/chainguard/node:latest-dev AS build USER root RUN apk update && apk add \ cairo-dev libjpeg-turbo-dev pango-dev giflib-dev python3 make gcc \ librsvg-dev glib-dev harfbuzz-dev fribidi-dev expat-dev libxft-dev \ libfontconfig1 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ENV NODE_ENV production COPY package.json /usr/src/app/ RUN npm install COPY ./src /usr/src/app FROM cgr.dev/chainguard/wolfi-base RUN apk update && apk add tini nodejs \ cairo-dev libjpeg-turbo-dev pango-dev giflib-dev \ librsvg-dev glib-dev harfbuzz-dev fribidi-dev expat-dev libxft-dev WORKDIR /app COPY --from=build /usr/src/app /app EXPOSE 8080 ENTRYPOINT ["tini", "--" ] CMD [ "node", "server.js" ] ``` This version is also available in the [main branch of the repository](https://github.com/chainguard-dev/identidock-cg/blob/main/dnmonster/Dockerfile). Build it: ```bash docker build --pull -t dnmonster-final . … ``` And run it to prove it still works: ```bash docker run -d -p 8080:8080 dnmonster-final ... curl --output ./monster.png 'localhost:8080/monster/wolfi?size=100' ``` > **Note:** If you receive a "port is already allocated" error, be sure to clean up the previous container. Check what containers are running with `docker container ls` and remove it with `docker rm -f `. There are still more tweaks that could be made. Bret Fisher has some [excellent resources on building Node.js containers in this GitHub repo](https://github.com/BretFisher/nodejs-rocks-in-docker). But for the purposes of this example app, we've made excellent progress. ## Updating the Python microservice The next service we update is Identidock, the main entrypoint for the application. Identidock is responsible for looking up requests in the cache and falling-back to calling the dnmonster service if they're not present. Again, the version of the code on the v1 branch already contains a few updates from the original code, but in this case all that was needed was to bump various libraries to newer versions. The Dockerfile for the v1 version can be found in the identidock folder and looks like: ```Dockerfile FROM python RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi RUN pip install Flask==3.1.0 uWSGI requests==2.32.3 redis==5.2.1 WORKDIR /app USER uwsgi COPY app /app COPY cmd.sh / EXPOSE 9090 9191 CMD ["/cmd.sh"] ``` The image can be built with the following, assuming the current directory is the root of repo: ```bash cd identidock docker build --pull -t identidock . … ``` Inspect the container image: ```bash docker images identidock REPOSITORY TAG IMAGE ID CREATED SIZE identidock latest a718358590ff 11 seconds ago 1.51GB ``` Scan for vulnerabilities: ```bash grype docker:identidock ✔ Loaded image identidock:latest ✔ Parsed image sha256:0b4ac715984206f1e9134aa48a8efeba88e7badc3969d6f8c79cca98b47df676 ✔ Cataloged contents 5ebeaac39f8f941d41629fdb1e13c39bc73558caadfe7699963b4b3a6c55a222 ├── ✔ Packages [452 packages] ├── ✔ File digests [20,129 files] ├── ✔ File metadata [20,129 locations] └── ✔ Executables [1,428 executables] ✔ Scanned for vulnerabilities [621 vulnerability matches] ├── by severity: 7 critical, 100 high, 296 medium, 47 low, 515 negligible (200 unknown) └── by status: 2 fixed, 1163 not-fixed, 544 ignored ``` At the time of writing, this container image is 1.51GB with hundreds of vulnerabilities (7 critical) according to Grype. Again as a first step, we try switching directly to the Chainguard Container. To do this, edit the Dockerfile so the first line reads: ```Dockerfile FROM cgr.dev/chainguard/python:latest-dev ``` Before building the image we need to also update `groupadd` syntax to use the `addgroup` format. As Chainguard's container images don't run as root by default for security reasons, we also need to change to the root user for this command to work. Replace the `RUN groupadd` line with the lines: ```Dockerfile USER root RUN addgroup uwsgi && adduser -D -G uwsgi uwsgi ``` The image now builds, but there are issues due to differences in the image entrypoint. If you run the container image, you get a confusing error message such as: ```bash `File "/cmd.sh", line 4` if [ "$ENV" = 'DEV' ]; then ^^^^^^ SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='? ``` This is caused by the Chainguard Containers using `/usr/bin/python` as the entrypoint, which means the `cmd.sh` entrypoint script is interpreted by Python instead of the shell. Fixing this can be as easy as changing the entrypoint, but let's take a look at the script first. This is the file `cmd.sh` in the identidock directory: ```bash #!/bin/bash set -e if [ "$ENV" = 'DEV' ]; then echo "Running Development Server" exec python "/app/identidock.py" elif [ "$ENV" = 'UNIT' ]; then echo "Running Unit Tests" exec python "tests.py" else echo "Running Production Server" exec uwsgi --http 0.0.0.0:9090 --wsgi-file /app/identidock.py \ --callable app --stats 0.0.0.0:9191 fi ``` This script decides how to run the application depending on how the `ENV` environment variable is set. The idea here is to allow us to use the same image in development, testing, and production. This approach is no longer recommended as it leads to development tooling being present in the production environment. Even though the development tooling isn't run in production, it is still bloating the image and is potentially exploitable by attackers. Therefore, we use a different approach and break the Dockerfile into separate development and production images. Let’s skip to the final Dockerfile for our image and walk through the changes made. These changes address multiple issues, beyond just having multiple images, and are based on the [Chainguard Academy guide to Python images](/chainguard/containers/getting-started/languages-and-runtimes/python/). Replace the Dockerfile with this one (this is also available on the ["main" branch](https://github.com/chainguard-dev/identidock-cg/blob/main/identidock/Dockerfile)): ```Dockerfile FROM cgr.dev/chainguard/python:latest-dev AS dev ENV LANG=C.UTF-8 ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 ENV PATH="/app/venv/bin:$PATH" WORKDIR /app RUN python -m venv --without-pip /app/venv COPY requirements.txt ./ RUN pip --python /app/venv/bin/python install --no-cache-dir -r requirements.txt COPY app /app EXPOSE 5000 ENTRYPOINT ["python"] CMD ["identidock.py"] FROM cgr.dev/chainguard/python WORKDIR /app ENV PYTHONUNBUFFERED=1 ENV PATH="/app/venv/bin:$PATH" COPY app/identidock.py ./ COPY --from=dev /app/venv /app/venv EXPOSE 9090 ENTRYPOINT [ "gunicorn", "-b", "0.0.0.0:9090", "identidock:app" ] ``` And add the file `requirements.txt` to the current directory (`identidock`) with the following contents: ``` Flask==3.1.0 requests==2.32.3 redis==5.2.1 gunicorn==23.0.0 ``` The first thing to notice is that we have a multistage build now. If you want the Development container image rather than the production one, you can specify it during docker build: ```bash docker build --pull --target dev -t identidock:dev . ``` Otherwise, you get the standard variant only. There are several more environment variables defined. These prevent the creation of Python bytecode and buffering of output. The installation of pip modules has moved to the `requirements.txt` file. The main thinking here is that we don't need to update the Dockerfile each time a dependency is updated or changed. The development server runs on port 5000, while the production server runs on port 9090. We could edit this so they both run on the same port, but this approach reduces the chance of accidentally running the development server in production. The development server is started directly from the entrypoint, so we are no longer dependent on an entrypoint script, simplifying our architecture. To get a minimal, clean production install, we are using a Python virtual environment ([venv](https://docs.python.org/3/library/venv.html)) in the development image to isolate all dependencies, which are then copied over to the production image. The `--without-pip` flag keeps pip out of that environment, so the production image carries only the packages the application imports. Installing into the environment then takes `pip --python /app/venv/bin/python`, which runs the development image's system pip against the environment. If you work inside the development container, use that same `--python` flag to add a package, because a bare `pip install` targets the system interpreter instead. Finally, the production image has been changed to use [gunicorn](https://gunicorn.org/) as [uwsgi has entered "maintenance mode"](https://github.com/unbit/uwsgi). Build the final image: ```bash docker build --pull -t identidock-cg . ``` And take a look at it: ```bash docker images identidock-cg REPOSITORY TAG IMAGE ID CREATED SIZE identidock-cg latest 1b5689af14a1 14 seconds ago 122MB ``` Run a scan with grype: ```bash grype docker:identidock-cg ✔ Loaded image identidock-cg:latest ✔ Parsed image sha256:c79295258a2b67f2a0eda49c41a2d791888df6cc7b3bdea694d810ec5d2916d8 ✔ Cataloged contents 6c8262ac152209ef3e36cad9c49c0d3e3f16d7cbfeafad9abb569c7690c68c2e ├── ✔ Packages [45 packages] ├── ✔ File digests [1,667 files] ├── ✔ File metadata [1,667 locations] └── ✔ Executables [135 executables] ✔ Scanned for vulnerabilities [1 vulnerability matches] ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible (1 unknown) └── by status: 0 fixed, 1 not-fixed, 0 ignored NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY python-3.13 3.13.1-r5 apk CVE-2025-0938 Unknown ``` The result of all these changes is that the production image is only 122 MB (down from 1.51GB, so an enormous saving of over a GB) and has 1 CVE (down from hundreds). This is a huge improvement! Further information on using Chainguard Containers with Python can be found in our [Getting started guide](https://edu.chainguard.dev/chainguard/containers/getting-started/languages-and-runtimes/python/). ## Replacing the Redis container and updating the Docker Compose file Updating Redis is straightforward. We're not making any changes to the application image, so all we need to do is directly update the reference to `redis:7` in the Docker Compose file to `cgr.dev/chainguard/redis`. The new container image requires no extra configuration, but we go from a 195 MB image with 129 vulnerabilities to a 33 MB image with 0 CVEs (again according to Grype). To update Compose, in the top level directory of the repo, replace the content of the `docker-compose.yml` file with the following: ```yaml name: identidock services: frontend: build: identidock ports: - "9090:9090" dnmonster: build: dnmonster redis: image: cgr.dev/chainguard/redis ``` If you now run `docker compose up --build`, you should have a working application that can be reached on port 9090: ![Screenshot of running application](app.png "Screenshot of running application") There are some differences between this version and the original. The environment variable used for switching between image variants has been removed and the ports have changed to reflect the default port used in gunicorn. This Compose file doesn't contain support for a development workflow currently – ideally we would be able to quickly iterate on our code without building a new image. The original file used volumes to achieve this, but this isn't something we want to do with the production image. One solution is to have a separate development Compose file, which builds the development image and uses a volume to mount code at runtime for immediate feedback. New versions of Docker also support [Compose Watch](https://docs.docker.com/compose/file-watch/) which can be a more efficient and granular solution than volume mounts. Refer to [What is Docker Compose Watch and what problem does it solve?](https://collabnix.com/what-is-docker-compose-watch-and-what-problem-does-it-solve/) for an introductory tutorial on using Compose Watch. ## Conclusion Porting our application to Chainguard Containers was relatively straightforward. There were some gotchas around differences to other images, such as different entrypoint settings and names for packages. The largest part of the puzzle was moving from single image builds to multistage builds that take advantage of the minimal Chainguard runtime images for Python and NodeJS. Once all this was done, we ended up with a much smaller set of images and with a drastically reduced number of CVEs. ### Cleaning up To clean up the resources used in this guide, first stop any containers started by Compose: ```bash docker-compose down ``` Then check `docker ps` to see if any containers are still running and stop them with `docker stop `. You can then remove any container images you've built: ```bash docker rmi -f dnmonster dnmonster-cg dnmonster-multi identidock identidock:dev identidock-cg ``` --- ### Unique tags for Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/reference/unique-tags.md Last Modified: October 8, 2026 Tags: Chainguard Containers Chainguard's unique tags feature provides unique timestamped tags for every container image build, addressing enterprise requirements for precise version tracking and automated deployment workflows. Many organizations rely on distinct tags to trigger automated deployments and maintain audit trails, making traditional floating tags like 'latest' unsuitable for production use. To help with cases like this, Chainguard offers Unique Tags for private registries. Unique Tags are ideal for organizations that require a strict tag per release or update of their images. They benefit teams looking for precise tracking and management of container images. Unique Tags are an opt-in feature that is only available for private registries. If your organization is interested in using Unique Tags, [contact support](https://support.chainguard.dev/) and we'll enable this feature for you. Refer to [Get support](/get-started/get-support/) for the portal's prerequisites. This guide provides an overview of what these Unique Tags are, the kinds of problems they aim to solve, and how you can access them in the Chainguard Console. ## Chainguard's unique tags Unique Tags are only available for private registries, as Chainguard's public registry only has the `:latest` or, in some cases, the `:latest-dev` tags available. Unique Tags feature an opt-in feature, which allows customers the flexibility to enable it based on their specific operational and security requirements. Chainguard's Unique Tags end in a timestamp, such as `20240229`, which indicates the date when the Container was built. Because Chainguard's container images are rebuilt whenever there is a change to an included package, the timestamp ensures that the specific tag always represents that specific container image build and not another. One benefit of using this timestamp scheme with Unique Tags is that it can help users to quickly identify when a given version of an container image was built. It also helps to make them human-readable, as opposed to the long, unpronounceable strings that make up container image digests. Unique Tags also allow for individual image repositories within a registry to be included or excluded as needed. For example, if you have an application that requires a specific tagging scheme to be compatible with an existing Helm chart, you can enable Unique Tags for your registry, but exclude that specific repository so that its container images only receive the standard tags. This granular level of control ensures that organizations can implement unique tagging in a way that best suits their organization's specific needs. It offers a tailored approach to image management, allowing for precise and efficient tracking of image versions and builds across different environments. Additionally, the Unique Tags feature is integrated with Chainguard's [Tag History API](/chainguard/containers/reference/using-the-tag-history-api/) and [event notifications](/platform/administration/cloudevents/events-reference/). These integrations allow you to track changes over time. ## How do I find unique tags? If your organization has Unique Tags enabled, the Chainguard Console shows them automatically. There's no setting to turn on. After signing in to the Chainguard Console, select **Images** in the left-hand navigation menu, then select the **Organization** tab. This tab lists the Chainguard Containers your organization can access. Select any image to open its **Tags** tab. The **Tag** column lists the Unique Tags available for that image. These tags include a timestamp in the format `YYYYMMDDHHMM`, and may include a prefix to help identify and parse the tag name programmatically. For example, a container image version might have a tag similar to `:openjdk-17-202412120223`. This means that this particular version of the container image was last updated on December 12, 2024, at 2:23 AM. You can use this version's **Pull URL** (`cgr.dev/$ORGANIZATION/jdk-fips:openjdk-17-202412120223`) to download this container image, and you can be confident that this Pull URL always refers to the same container image. ## Unique vs immutable tags: are unique tags right for me? By design, container image tags are mutable and can change over time. Although Unique Tags are meant to serve as a solution for teams whose internal workflows require unique tags, for true image immutability we recommend pinning images by digest (`{repo}:{tag}@{digest}`) to ensure immutability and reproducibility whenever possible, rather than Unique Tags. Some things to be aware of before opting into Unique Tags: - It applies across the entire organization, to every image. While Unique Tags may be appropriate for one use case, it may not be appropriate for all the use cases in your organization. - It appends a timestamp to all tags, like `1.2.3-20260218175623` - You no longer receive updates for non-unique tags, like `1.2.3`. It's an either/or situation. - It arguably makes the tags harder to browse in the Chainguard UI and the UI of other registry solutions. - It can cause performance issues in certain scenarios, as the response when listing tags is much larger. - We don't enforce immutability at the registry level. So while we won't update unique tags, there is nothing on a technical level that prevents them from being pushed to. Once tags are mirrored to an internal registry, unless that registry supports tag immutability, the tags can be overwritten. For all of these reasons, digests are a stronger mechanism for ensuring immutability than unique tags for almost everyone. Check out the ["Pulling by digest" section](/chainguard/containers/using-and-deploying/using-containers/#pulling-by-digest) of our guide on How to Use Chainguard Containers for more information. [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/) covers how to retrieve a digest and pin a reference to it. Additionally, you may find our three-part blog series on Chainguard's image tagging philosophy to be of interest. - [Part 1](https://www.chainguard.dev/unchained/chainguards-image-tagging-philosophy-enabling-high-velocity-updates-pt-1-of-3?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) - [Part 2](https://www.chainguard.dev/unchained/chainguards-image-tagging-philosophy-enabling-high-velocity-updates-pt-2-of-3) - [Part 3](https://www.chainguard.dev/unchained/chainguards-image-tagging-philosophy-enabling-high-velocity-updates-pt-3-of-3) Many dependency automation tools like [Dependabot](https://docs.github.com/en/code-security/dependabot/ecosystems-supported-by-dependabot/supported-ecosystems-and-repositories#docker) and [Renovatebot](https://docs.renovatebot.com/docker/#digest-pinning) support digest pinning with tag hints. For example, if an image is of the form `cgr.dev/example.com/image:1.2@sha256:...`, these tools attempt to update image digests based on the `1.2` tag.

Note: OCI and Docker runtimes ignore the tag in this format, so there’s no guarantee that the digest ever belonged to the tag. Tools use the tag as a version hint, but runtimes don’t enforce it: the digest takes priority.

--- ### Getting started with the PHP Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/php.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's PHP container images provide secure foundations for PHP applications with minimal vulnerabilities compared to traditional PHP images. These images come in multiple variants: the `latest-fpm` variant for serving web applications via FastCGI, the `latest` variant for CLI applications, and development variants that include additional tools for building and debugging PHP workloads. In this guide, we'll set up a demo and demonstrate how you can use Chainguard Containers to develop, build, and run PHP applications. This tutorial requires Docker to be installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/).
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## 1. Setting up a (CLI) demo application We'll start by getting the demo application ready. This CLI app generates random names based on a list of nouns and adjectives. To exemplify usage with Composer, the app has a single dependency on [minicli](https://github.com/minicli/minicli), a minimalist CLI framework for PHP. Start by cloning the demos repository to your local machine: ```shell git clone git@github.com:chainguard-dev/edu-images-demos.git ``` Locate the `namegen` demo and cd into its directory: ```shell cd edu-images-demos/php/namegen ``` You can use the `php:latest-dev` image variant with a volume in order to install application dependencies with Composer. We'll use the `root` user to be able to write to the volume mounted in the container: ```shell docker run --rm -v ${PWD}:/app --entrypoint composer --user root \ cgr.dev/chainguard/php:latest-dev \ install --no-progress --no-dev --prefer-dist ``` You'll get output like this, indicating that the package `minicli/minicli` was installed: ```shell Installing dependencies from lock file Verifying lock file contents can be installed on current platform. Package operations: 1 install, 0 updates, 0 removals - Downloading minicli/minicli (4.2.0) - Installing minicli/minicli (4.2.0): Extracting archive Generating autoload files 1 package you are using is looking for funding. Use the `composer fund` command to find out more! ``` Next, make sure permissions are set correctly on the generated files. On Linux systems run the following: ```shell sudo chown -R ${USER}:${USER} . ``` On macOS systems, run this: ```shell sudo chown -R ${USER} . ``` The application should now be ready to be executed. For transparency, here is the code that will be executed, which you'll find in the `namegen` script: ```php #!/usr/bin/php registerCommand('get', function () use ($app) { $animals = [ 'turtle', 'seagull', 'octopus', 'shark', 'whale', 'dolphin', 'walrus', 'penguin', 'seahorse']; $adjectives = [ 'ludicrous', 'mischievous', 'graceful', 'fortuitous', 'charming', 'ravishing', 'gregarious']; $app->getPrinter()->info($adjectives[array_rand($adjectives)] . '-' . $animals[array_rand($animals)]); }); $app->runCommand($argv); ``` You can now execute the app to test it out. Run the following command: ```shell docker run --rm -v ${PWD}:/work \ cgr.dev/chainguard/php:latest \ /work/namegen get ``` The command should output a random name combination: ```shell ludicrous-walrus ``` In the next step, you'll build the application in a multi-stage Dockerfile. ## 2. Building a distroless container for the application We'll now build a distroless container for the application. To be able to install dependencies with Composer, our build will consist of **two** stages. First, we'll build the application using the development variant, a Wolfi-based image that includes Composer and other useful tools for development. Then, we'll create a separate stage for the final container. The resulting container will be based on the distroless PHP Wolfi image, which means it doesn't come with Composer or even a shell. For reference, here is the content of the included `Dockerfile`: ```Dockerfile FROM cgr.dev/chainguard/php:latest-dev AS builder USER root COPY . /app RUN chown -R php /app USER php RUN cd /app && \ composer install --no-progress --no-dev --prefer-dist FROM cgr.dev/chainguard/php:latest COPY --from=builder /app /app ENTRYPOINT [ "php", "/app/namegen" ] ``` This Dockerfile will: 1. Start a new build stage based on the `php:latest-dev` container image and call it `builder`; 2. Copy files from the current directory to the `/app` location in the container; 3. Enter the `/app` directory and run `composer install` to install any dependencies; 4. Start a new build stage based on the `php:latest` image; 5. Copy the application from the `builder` stage; 6. Set up the application as entry point for this container. You can now build the container with: ```shell docker build . --pull -t php-namegen ``` You'll get output similar to this: ```shell [+] Building 0.1s (12/12) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 322B 0.0s => [internal] load metadata for cgr.dev/chainguard/php:latest-dev 0.0s => [internal] load metadata for cgr.dev/chainguard/php:latest 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load build context 0.0s => => transferring context: 4.86kB 0.0s => [builder 1/4] FROM cgr.dev/chainguard/php:latest-dev 0.0s => [stage-1 1/2] FROM cgr.dev/chainguard/php:latest 0.0s => CACHED [builder 2/4] COPY . /app 0.0s => CACHED [builder 3/4] RUN chown -R php /app 0.0s => CACHED [builder 4/4] RUN cd /app && composer install --no-progress --no-dev --prefer-dist 0.0s => CACHED [stage-1 2/2] COPY --from=builder /app /app 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:e617d7afd472d4a78d82060eaacd3a1c33310d6a267f6aaf9aa34b44e3ef8e5c 0.0s => => naming to docker.io/library/php-namegen 0.0s ``` Once the build is finished, run the container with: ```shell docker run --rm php-namegen get ``` And you should get output similar to what you got before, with a random name combination. ``` fortuitous-octopus ``` If you inspect the container image with a `docker image inspect php-namegen`, you'll notice that it has only **two** layers, thanks to the use of a multi-staging Docker build. ```shell docker image inspect php-namegen ``` ```shell ... "RootFS": { "Type": "layers", "Layers": [ "sha256:52cf795862535c5f22dac055428527508088becebbe00293457693a5f8fa1df2", "sha256:95a8dc6d81c92158ac032e5167768a04e45f25b3bf4009c5698673c19d36d5c2" ] }, "Metadata": { "LastTagTime": "2024-11-15T12:52:18.412117879+01:00" } } ] ``` In such cases, the last `FROM` section from the Dockerfile is the one that composes the final image. That's why in our case it only adds one layer on top of the base `php:latest` image, containing the `COPY` command we use to copy the application from the build stage to the final container. It's worth highlighting that nothing is carried from one stage to the other unless you copy it. That facilitates creating a slim final image with only what's necessary to execute the application. ## 3. Working with the PHP-FPM image variant The `latest-fpm` image variant is suitable for running PHP applications over FastCGI, to be served by a web server such as Nginx. In this section, we'll run a Docker Compose setup using the `latest-fpm` image variant and the Chainguard Nginx image. The `namegen-api` demo is a variation of the previous demo, but it serves the random name generation over HTTP. The application responds with a JSON payload containing the animal and adjective combination, and accepts an optional `animal` parameter to specify the animal for the final suggested name. Start by accessing the `namegen-api` demo folder. This should be at the same level as the previous `namegen` demo in the `edu-images-demos` repository. If your terminal is still open on the previous demo, you can navigate to the `namegen-api` folder with: ```shell cd ../namegen-api ``` The `index.php` file contains the following code: ```php $chosenAnimal, 'adjective' => $chosenAdjective]); ``` To serve this application over HTTP, we'll use the `latest-fpm` image variant in a Docker Compose setup. The following `docker-compose.yml` is included within the `namegen-api` folder: ```yaml services: app: image: cgr.dev/chainguard/php:latest-fpm restart: unless-stopped working_dir: /app volumes: - ./:/app nginx: image: cgr.dev/chainguard/nginx restart: unless-stopped ports: - 8000:80 volumes: - ./:/app - ./nginx.conf:/etc/nginx/nginx.conf ``` This Docker Compose setup defines two services: `app` and `nginx`. The `app` service uses the `latest-fpm` image variant and mounts the current directory to the `/app` directory in the container. The `nginx` service uses the Chainguard Nginx container image and also mounts the current directory to the `/app` directory in the container, setting it as the document root with a custom configuration. A second volume replaces the default Nginx configuration with our custom one, included as `nginx.conf` in the same directory: ``` events { worker_connections 1024; } http { server { listen 80; index index.php index.html; root /app/public; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } } } ``` To run this setup, execute: ```shell docker-compose up ``` This command will start the services defined in the `docker-compose.yml` file. You can access the application at `http://localhost:8000` in your browser, or you can make a curl request to it: ```shell curl http://localhost:8000 ``` You should get a JSON response with a random name combination: ``` {"animal":"octopus","adjective":"ludicrous"} ``` You can also try passing an `animal` parameter to the URL: ```shell curl 'http://localhost:8000?animal=cat' ``` ``` {"animal":"cat","adjective":"mischievous"} ``` To stop the services, you can press `Ctrl+C` in the terminal where you ran `docker-compose up`. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose PHP Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### Chainguard Libraries for Go overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/go/overview.md Last Modified: October 14, 2026 Tags: Chainguard Libraries, Go, Overview Chainguard Libraries for Go provides enhanced security for the Go module ecosystem through an authenticated, secure-by-default Go module proxy. The proxy applies organization-defined policies, malware and greyware protections, and cooldown controls to Go dependencies before they reach development and build environments. Go modules support cloud-native infrastructure, high-volume services, fintech, networking, and security applications. Chainguard Libraries for Go helps reduce the risks of consuming modules from public sources, including malicious modules, typosquats, and vulnerabilities in versions your applications still need to use.

Note: Chainguard Libraries for Go is in beta. Contact your Chainguard account team to enable it for your organization.

## Runtime requirements Go modules retrieved from Chainguard have the same module paths, APIs, and Go toolchain requirements as their corresponding upstream projects unless a remediated release explicitly documents otherwise. Your application’s existing go.mod remains the source of truth for module requirements. ## Technical details You must use the username and password [retrieved with chainctl](/chainguard/libraries/introduction/access/) to access the Chainguard Libraries for Go repository. The URL for the repository is: `https://libraries.cgr.dev/go/`. When a Go command requests a module, Chainguard resolves the request through the sources enabled for your organization: - Chainguard-remediated module versions - Eligible versions from the upstream Go module proxy, when upstream fallback is enabled The `/go` endpoint is the endpoint you configure in `GOPROXY`. It uses the standard Go module proxy paths for version lists, metadata, `go.mod` files, and module source archives. Go builds from source on the customer side. Unlike ecosystems that distribute prebuilt language artifacts, Chainguard Libraries for Go distributes module source archives. A CVE-remediated module is published as a distinct Go version rather than replacing the upstream bytes for the same version. ## Go checksum verification Chainguard-remediated versions are not recorded in the public Go checksum database because they are not available from public upstream origins. Configure Go to skip public checksum verification for modules retrieved through Chainguard: ```bash go env -w GONOSUMDB='*' ``` You can alternatively disable checksum database verification globally: ```bash go env -w GOSUMDB=off ``` The Go module proxy remains authenticated over TLS, and `go.sum` continues to pin the module bytes after the first successful fetch. Do not use `GOPRIVATE` for this configuration. `GOPRIVATE` also sets `GONOPROXY`, which causes Go to bypass Chainguard. `GONOSUMCHECK` is not a current Go configuration variable and should not be used. ## Upstream fallback [Upstream fallback](/chainguard/libraries/introduction/#upstream-fallback-and-controls) is enabled by default for Chainguard Libraries for Go. The Go endpoint serves eligible versions from the upstream Go module proxy under Chainguard security controls. Upstream fallback does not make Chainguard Libraries a complete mirror of the upstream registry. A module or version may be unavailable because it is inside the cooldown window or is blocked by Chainguard or your organization’s policies. ### View packages blocked due to malware Chainguard’s source code and maintainer behavior scanning identifies and blocks malicious and greyware packages in Chainguard Libraries via the Chainguard Repository. You can get a list of blocked packages via chainctl or the API. Refer to the [Libraries Overview page](/chainguard/libraries/introduction/overview/#malware-and-greyware-detection) for more information. --- ### Chainguard Libraries for .NET overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/dotnet/overview.md Last Modified: October 15, 2026 Tags: Chainguard Libraries, .NET, NuGet Chainguard Libraries for .NET provides a secure source for your open source NuGet dependencies. [The NuGet Gallery](https://www.nuget.org/) is the primary public registry for .NET packages, but packages from public registries can expose applications and build pipelines to malicious or compromised code. Chainguard addresses these supply chain risks through the [Chainguard Repository](/chainguard/chainguard-repository/overview/) by applying malware and greyware scanning, organization-defined policies, and additional security controls before packages are made available. The Chainguard Repository provides a single endpoint for package retrieval. In combination with third-party software repository managers, you can use Chainguard Libraries for .NET as a secure source of truth for your .NET development and build processes.

Note: Chainguard Libraries for .NET is in beta. Contact your Chainguard account team to enable it for your organization.

## Runtime requirements The runtime requirements for .NET artifacts available from Chainguard Libraries for .NET are identical to the requirements of the original upstream project. ## Technical details You must use the username and password [retrieved with chainctl](/chainguard/libraries/introduction/access/) to access the Chainguard Libraries for .NET repository. The URL for the repository is: `https://libraries.cgr.dev/dotnet/`. ## View packages blocked due to malware Chainguard’s source code and maintainer behavior scanning identifies and blocks malicious and greyware packages in Chainguard Libraries via the [Chainguard Repository](/chainguard/chainguard-repository/overview/). You can get a list of blocked packages via `chainctl` or the API. Refer to the [Libraries Overview page](/chainguard/libraries/introduction/overview/#malware-and-greyware-detection) for more information. ## Upstream fallback [Upstream fallback](/chainguard/libraries/introduction/#upstream-fallback-and-controls) is enabled by default for Chainguard Libraries for .NET. The .NET endpoint serves eligible versions from upstream under Chainguard security controls. Upstream fallback does not make Chainguard Libraries a complete mirror of the upstream registry. An artifact or version may be unavailable because it is inside the cooldown window or is blocked by Chainguard or your organization’s policies. --- ### Global configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/go/global-configuration.md Last Modified: September 29, 2026 Tags: Chainguard Libraries, Go Go module consumption in a large organization is typically managed by a repository manager. The repository manager acts as a single point of access for developers and development tools to retrieve Go modules. Configure Artifactory with a single remote repository pointed at `https://libraries.cgr.dev/go/`. Chainguard Repository handles upstream fallback and policy enforcement; Artifactory handles local caching, access control, and the endpoint used by your Go toolchains. ## JFrog Artifactory JFrog Artifactory supports Go repositories for proxying and hosting Go modules, and virtual repositories for combining sources into one endpoint. Refer to the [Go repositories documentation for Artifactory](https://docs.jfrog.com/artifactory/docs/go-repositories) for more information. If an existing Artifactory remote repository points directly to a public Go module proxy, disable or remove it from the virtual repository that your builds use. Artifactory resolves virtual repositories according to their configured order, so a misconfiguration can cause an unprotected module to be served without an obvious build failure. ### Prerequisites Before you begin, you need: - An [entitlement to Chainguard Libraries for Go](/chainguard/libraries/go/migration/#create-an-entitlement) - Administrator access to your repository manager instance - A [pull token for Chainguard Libraries for Go](/chainguard/libraries/go/migration/#create-a-pull-token) ### Initial configuration Use the following steps to add Chainguard Libraries for Go as an Artifactory remote repository: 1. Sign in to Artifactory as a user with administrator privileges. 1. Click **Administration** in the top navigation bar. 1. In the left navigation, click **Repositories**. 1. Click **Create a Repository**, then choose **Remote**. 1. Configure the remote repository with the following values: - **Package type**: Go - **Repository key**: `go-chainguard` - **URL**: `https://libraries.cgr.dev/go/` - **Username**: the username returned by `chainctl` - **Password**: the password returned by `chainctl` 1. Open the **Advanced configuration** tab and configure the following: - In the **Network** section, confirm that **Lenient Host Authentication** is unchecked. This prevents Artifactory from forwarding credentials across a redirect. - Optionally select **Enable Cookie Management**. JFrog recommends this for remote repositories that use redirects. - In the **Others** section, select **Bypass HEAD Requests** so that Artifactory retrieves package files with GET requests instead of probing with HEAD requests first. - In the **Others** section, uncheck **Block Mismatching Mime Types**. - Select **Disable URL Normalization** so that Artifactory does not rewrite a pre-signed redirect URL. 1. Click **Create Remote Repository**. Chainguard Libraries serves module metadata and source archives through the standard Go module proxy protocol. Some module files may be served through redirects to pre-signed storage URLs. These settings prevent Artifactory from rewriting the redirect, forwarding credentials to another host, or caching a redirect response instead of the module file. ### Create a virtual repository Create a virtual repository to give developers and CI systems a single Go module endpoint: 1. Click **Create a Repository**, then choose **Virtual**. 1. Configure the virtual repository with the following values: - **Package type**: Go - **Repository Key**: `go-all` - **Repositories**: add `go-chainguard` 1. Click **Create Virtual Repository**. For the recommended configuration, `go-chainguard` should be the only external upstream in `go-all`. ### Validate the remote repository Validate the Artifactory remote before configuring developer workstations or CI/CD systems. The Artifactory **Test** button is not a reliable indicator of a working Chainguard configuration; a remote can pass the test but still be misconfigured for package retrieval. Use a module and version that are available through your organization's Chainguard Libraries for Go entitlement. The following example uses `rsc.io/quote` version `v1.5.2`: Fetch the module archive directly from Chainguard and compute its checksum: ```bash curl -fsS -L \\ -u "${CHAINGUARD_GO_IDENTITY_ID}:${CHAINGUARD_GO_TOKEN}" \\ "https://libraries.cgr.dev/go/rsc.io/quote/@v/v1.5.2.zip" \\ | sha256sum ``` Fetch the same archive through the Artifactory remote repository and compute its checksum: ```bash curl -fsS -L \\ -u "${ARTIFACTORY_USERNAME}:${ARTIFACTORY_TOKEN}" \\ "https:///artifactory/go-chainguard/rsc.io/quote/@v/v1.5.2.zip" \\ | sha256sum ``` Replace `artifactory-host` with your Artifactory hostname. Replace `ARTIFACTORY_USERNAME` and `ARTIFACTORY_TOKEN` with credentials that can read from Artifactory. Fetch the archive through the virtual repository that developers and CI systems will use: ```bash curl -fsS -L \\ -u "${ARTIFACTORY_USERNAME}:${ARTIFACTORY_TOKEN}" \\ "https:///artifactory/go-all/rsc.io/quote/@v/v1.5.2.zip" \\ | sha256sum ``` The three checksums must match. Do not proceed to production rollout if the Artifactory checksum differs from the direct Chainguard checksum or if either Artifactory request fails. If validation fails, review the following settings: - URL: confirm that the remote repository URL is exactly https://libraries.cgr.dev/go/. - Credentials: regenerate the pull token with `chainctl auth pull-token create --repository=go` and update the Artifactory remote repository credentials. Expired tokens can fail without a clear error in the Artifactory user interface. - Advanced configuration: confirm that the redirect and request settings from the initial configuration are applied. - Cached files: if the remote repository was previously configured with incorrect settings, delete the affected cached module files from the `go-chainguard` cache and repeat the validation. - Virtual repository order: confirm that no public Go proxy appears ahead of `go-chainguard` or is still included in `go-all`. ### Configure build tools Use the URL of the `go-all` virtual repository in Go configuration. In Artifactory, select the `go-all` repository and use **Set Me Up** or **Generate Settings** to obtain the URL and authentication instructions for your Artifactory version. The URL commonly has the following form: `https://exampleorg.jfrog.io/artifactory/go-all`. See [Configuring your build tool](/chainguard/libraries/go/build-configuration/#repository-manager) for more information. --- ### Global configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/dotnet/global-configuration.md Last Modified: September 30, 2026 Tags: Chainguard Libraries, .NET .NET package consumption in a large organization is typically managed by a repository manager. The repository manager acts as a single point of access for developers and development tools to retrieve NuGet packages. This page describes how to configure [JFrog Artifactory](https://jfrog.com/artifactory/) and [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) to proxy Chainguard Libraries for .NET. The instructions use Chainguard Repository's built-in upstream fallback, which is the recommended configuration. With upstream fallback enabled, configure Artifactory with a single remote repository that points to the Chainguard NuGet v3 service index: `https://libraries.cgr.dev/dotnet/v3/index.json`. Chainguard Repository handles upstream fallback and policy enforcement. Artifactory handles local caching, access control, and the endpoint used by your NuGet clients. Do not add a direct `nuget.org` remote to the virtual repository used by your builds unless you intentionally want to manage fallback yourself. ## Prerequisites Before you begin, you need: - An [entitlement to Chainguard Libraries for .NET](/chainguard/libraries/dotnet/migration/#create-an-entitlement) - Administrator access to your repository manager instance - A [pull token for Chainguard Libraries for .NET](/chainguard/libraries/dotnet/migration/#create-a-pull-token) ## JFrog Artifactory JFrog Artifactory supports NuGet repositories for proxying packages and virtual repositories for combining repositories behind a single endpoint. The following configuration uses the recommended upstream-fallback approach. A remote repository that points directly to nuget.org can bypass Chainguard Repository protections. Because Artifactory resolves packages through a virtual repository, a misplaced or incorrectly ordered remote can result in an unprotected package being served without an obvious build error. ### Initial configuration Use the following steps to add Chainguard Libraries for .NET as an Artifactory remote repository: 1. Sign in to Artifactory as a user with administrator privileges. 1. Click **Administration** in the top navigation bar. 1. In the left navigation, click **Repositories**. 1. Click **Create a Repository**, then choose **Remote**. 1. Configure the remote repository with the following values: - **Package type**: NuGet - **Repository key**: `dotnet-chainguard` - **URL**: `https://libraries.cgr.dev/dotnet/v3/index.json` - **Username**: the username returned by `chainctl` - **Password**: the password returned by `chainctl` 1. Open the **Advanced configuration** tab and configure the following: - In the **Network** section, confirm that **Lenient Host Authentication** is unchecked. This prevents Artifactory from forwarding credentials across a redirect. - Optionally select **Enable Cookie Management**. JFrog recommends this for remote repositories that use redirects. - In the **Others** section, select **Bypass HEAD Requests** so that Artifactory retrieves package files with GET requests instead of probing with HEAD requests first. - In the **Others** section, uncheck **Block Mismatching Mime Types**. - Select **Disable URL Normalization** so that Artifactory does not rewrite a pre-signed redirect URL. 1. Click **Create Remote Repository**. These settings are required because Chainguard Libraries stores artifacts behind redirects to pre-signed URLs. Without them, Artifactory may rewrite the redirect, forward credentials to the redirected host, or cache the redirect response instead of the NuGet package. ### Create a virtual repository Create a virtual repository to give NuGet clients a single access point: 1. Click **Create a Repository**, then choose **Virtual**. 1. Configure the virtual repository with the following values: - **Package type**: NuGet - **Repository Key**: `dotnet-all` - **Repositories**: add `dotnet-chainguard` 1. Click **Create Virtual Repository**. For the recommended configuration, `dotnet-chainguard` should be the only external upstream in `dotnet-all`. You can also add approved internal NuGet repositories to the virtual repository according to your organization's access-control requirements. ### Validate the remote repository Validate the Artifactory remote before configuring developer workstations or CI/CD systems. The Artifactory **Test** button is not a reliable indicator of a working Chainguard configuration; a remote can pass the test but still be misconfigured for package retrieval. - Confirm that the remote repository URL is `https://libraries.cgr.dev/dotnet/v3/index.json`. - Confirm that the remote uses the current .NET pull-token username and password. Confirm that the token is not expired and was copied correctly. - Confirm that the [Advanced configuration settings](#initial-configuration) are applied. - Clear any previously cached test content from `dotnet-chainguard` if the repository was configured with different settings. - Create or use a test .NET project that references a package available from Chainguard Libraries. - Configure the test project to use the Artifactory virtual repository, then clear the NuGet HTTP cache: ```bash dotnet nuget locals all --clear ``` - Force a network restore: ```bash dotnet restore --force --no-cache --verbosity normal ``` - Inspect `obj/project.assets.json`. The `sources` entry must contain the Artifactory `dotnet-all` URL, and must not contain `nuget.org`. - In Artifactory, confirm that the package is present in the `dotnet-chainguard` remote cache and that the request is available through the `dotnet-all` virtual repository. ### Configure build tools After validation, configure each workstation and build server that restores .NET dependencies, including CI/CD infrastructure, to use the Artifactory virtual repository. In Artifactory, select the `dotnet-all` virtual repository and use **Set Me Up** or **Generate Settings** to obtain the URL and authentication instructions for your Artifactory version. The URL commonly has the following form: `https:///artifactory/api/nuget/dotnet-all` See [Configuring your build tool](/chainguard/libraries/dotnet/build-configuration/#repository-manager) for more information. ## Sonatype Nexus Repository [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) allows for merging multiple remote repositories as a repository group. The following instructions are based on the [Nexus documentation for NuGet](https://help.sonatype.com/en/nuget-repositories.html). The recommended approach is to rely on Chainguard Repository's [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls), configuring a single proxy repository pointed at the Chainguard NuGet v3 service index rather than adding a separate nuget.org proxy. A proxy that points directly to nuget.org can bypass Chainguard Repository protections. ### Initial configuration The following steps create a proxy repository for Chainguard Libraries for .NET and a repository group that exposes it to your build tools. First, log in to Sonatype Nexus as a user with administrator privileges and access the **Server administration** and configuration section with the gear icon in the top navigation bar. Next, configure a proxy repository for the Chainguard Libraries for .NET repository: 1. Select **Repository - Repositories** in the left hand navigation. 1. Click **Create repository**. 1. Select the **nuget (proxy)** recipe. 1. Configure the following: - **Name**: `dotnet-chainguard` - **Protocol Version**: `NuGet V3` - **Proxy - Remote storage**: Add the URL `https://libraries.cgr.dev/dotnet/v3/index.json`. - **HTTP - Authentication**: Select `Username` as the Authentication type, and provide the [username and password values as retrieved with chainctl](/chainguard/libraries/introduction/access/). 1. Click **Create repository**. Create a repository group, or add to an existing repository group: 1. Select **Repository - Repositories** in the left hand navigation. 1. Click **Create repository**. 1. Select the **nuget (group)** recipe. 1. Configure the following: - **Name**: `dotnet-all` - Under **Group - Member repositories**, move the new repository `dotnet-chainguard` to the right to include it in the group. Position `dotnet-chainguard` at the top of the list using the arrow controls. In a typical configuration, the Chainguard repository is placed first so that packages are retrieved through Chainguard when available. If you are manually managing fallback, you can configure an additional nuget.org proxy repository and add it to the group after `dotnet-chainguard`. ### Build tool access NuGet v3 clients access a Nexus group through its service index, which commonly has the form `https://repo.example.com/repository/dotnet-all/index.json` (with `repo.example.com` replaced with the hostname of your repository manager). See [Configuring your build tool](/chainguard/libraries/dotnet/build-configuration/#repository-manager) for information on setting the package source and authenticating to your repository manager. --- ### Configure Go build tools URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/go/build-configuration.md Last Modified: October 14, 2026 Tags: Chainguard Libraries, Go Chainguard Libraries for Go implements the standard [GOPROXY protocol](https://go.dev/ref/mod#goproxy-protocol). To use it, configure Go to retrieve modules through the Chainguard Go module proxy. This page is a reference for configuring Go projects and CI/CD environments. It covers authentication, proxy configuration, cache clearing, and verification.. Apply these changes on every workstation and build server that downloads Go modules, including CI/CD systems such as GitHub Actions, Jenkins, and TeamCity. The Chainguard Go module proxy endpoint, `https://libraries.cgr.dev/go`, is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for Go. It serves requested versions from upstream [under Chainguard security controls](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). This guide outlines build tool configuration. If you are looking for something else, refer to the following guides depending on your goals: | If you want to... | Use this page | | --- | --- | | Understand what Chainguard Libraries for Go is and how it works | [Go overview](/chainguard/libraries/go/overview/) | | Set up organization-wide access through a repository manager | [Global configuration](/chainguard/libraries/go/global-configuration/) | | Look up how to configure Go for use with Chainguard Libraries | This page | | Migrate an existing project step by step | [Go migration guide](/chainguard/libraries/go/migration/) | If a package or version is blocked by a policy or malware scan, your build tool returns an error. Refer to the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/) for more details. ## Direct access Pulling artifacts directly from Chainguard Libraries for Go requires authentication with username and password for a pull token as detailed in the [Libraries Access documentation](/chainguard/libraries/introduction/access/). Direct access configures Go to use Chainguard Libraries without a repository manager: ```bash go env -w GOPROXY=https://libraries.cgr.dev/go go env -w GONOSUMDB='*' ``` You can use `GOSUMDB=off` instead of `GONOSUMDB=*`: ```bash go env -w GOSUMDB=off ``` Do not add `,direct`, `https://proxy.golang.org`, or another public proxy to `GOPROXY`. Use the Chainguard endpoint as the only entry so that module requests remain within your organization’s configured controls. Direct access requires per-project and per-workstation configuration. For organizations with multiple teams, proxying through an artifact manager may be a more suitable approach. Learn more in the [global configuration documentation](/chainguard/libraries/go/global-configuration/). ### Authentication Go can read HTTP Basic authentication credentials from a `.netrc` file. For local development, add an entry for the Chainguard host: ```bash cat >> ~/.netrc </go go env -w GONOSUMDB='*' ``` Check the effective configuration: ```bash go env GOPROXY GONOSUMDB GOSUMDB ``` ## Verify the configuration Use a clean module cache when testing the configuration for the first time. This prevents previously cached modules from hiding proxy or authentication problems: ```bash go clean -modcache go mod download go test ./... go mod verify ``` Inspect `GOPROXY` and your build logs to confirm that module requests are going through Chainguard. ## Minimal example project Use the following commands to test access in a temporary directory: ```bash workdir=$(mktemp -d) cd "$workdir" go mod init example.com/proxy-check go get rsc.io/quote@v1.5.2 go mod verify ``` A successful `go mod verify` confirms that the downloaded module content matches the checksums recorded in `go.sum`. --- ### Configure .NET build tools URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/dotnet/build-configuration.md Last Modified: September 29, 2026 Tags: Chainguard Libraries, .NET Chainguard Libraries for .NET works with the standard NuGet client through a package-source configuration change. This page is a reference for configuration, authentication, and cache clearing. Apply these changes on every workstation and build server that builds and restores .NET dependencies, including CI/CD infrastructure. The `https://libraries.cgr.dev/dotnet` endpoint is also the [Chainguard Repository](/chainguard/chainguard-repository/overview/) endpoint for .NET. It serves requested versions from upstream [under Chainguard security controls](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls). This guide outlines build tool configuration. If you are looking for something else, refer to the following guides depending on your goals: | If you want to... | Use this page | | --- | --- | | Understand what Chainguard Libraries for .NET is and how it works | [.NET overview](/chainguard/libraries/dotnet/overview/) | | Set up organization-wide access through a repository manager | [Global configuration](/chainguard/libraries/dotnet/global-configuration/) | | Look up how to configure .NET for use with Chainguard Libraries | This page | | Migrate an existing project step by step | [.NET migration guide](/chainguard/libraries/dotnet/migration/) | If a package or version is blocked by a policy or malware scan, your build tool returns an error. Refer to the [Error messages documentation](/chainguard/libraries/troubleshooting/errors/) for more details. ## Library access approaches ### Direct access For direct access, add your current session’s Chainguard credentials to your NuGet configuration file: ```bash chainctl auth configure nuget ``` To configure NuGet with a long-lived pull token instead: ```bash chainctl auth configure nuget --pull-token ``` #### Manually configure direct access For direct access, add your Chainguard credentials to your NuGet configuration file. If you do not have a configuration yet, create a project-level NuGet configuration file: ```bash dotnet new nugetconfig ``` Add the following configuration, making sure to substitute the username and password returned by `chainctl auth pull-token`: ```xml ``` ### Repository manager If your organization uses a repository manager, configure it to proxy the Chainguard .NET endpoint. Then configure NuGet to use the repository manager URL, for example: ```xml ``` Use the repository manager’s credentials in the NuGet configuration. Do not use the Chainguard pull token to authenticate directly to a third-party repository manager. ## Applying configuration ### Clear caches If you suspect stale or corrupted package data, clear the NuGet HTTP cache: ```bash dotnet nuget locals http-cache --clear ``` To force a network fetch and bypass both the global packages cache and the HTTP cache, run: ```bash dotnet restore --force --no-cache --verbosity normal ``` When Chainguard is configured correctly, the restore output should include requests to `https://libraries.cgr.dev/...`. ### Restore and build Restore dependencies and build the project: ```bash dotnet restore dotnet build dotnet run ``` An existing project does not require changes to `Program.cs`, dependency declarations, or application code. The package-source configuration controls where NuGet retrieves the existing dependencies. Verify that Chainguard was used: ```bash dotnet nuget list source ``` The output should return a Chainguard source. ## Minimal example project To test the configuration with a small application, create a console project and add a package: ```bash dotnet new console -o MyConsoleApp cd MyConsoleApp dotnet add package Newtonsoft.Json ``` For this sample project only, update `Program.cs` so the application uses the package: ```Program.cs using System; using Newtonsoft.Json; namespace MyConsoleApp { class Program { static void Main(string[] args) { var account = new { Name = "Alice", Joined = DateTime.Now }; string json = JsonConvert.SerializeObject(account, Formatting.Indented); Console.WriteLine(json); } } } ``` Restore and run the sample: ```bash dotnet restore dotnet run ``` ### Verify the package source Inspect the project assets file to confirm that the restore used Chainguard Libraries: ```bash dotnet nuget list source ``` The output should include the Chainguard endpoint: ```output Registered Sources: 1. chainguard [Enabled] https://libraries.cgr.dev/dotnet/v3/index.json ``` If the project uses a repository manager, the assets file should instead show the repository manager URL. --- ### Let Chainguard Workspaces sessions act on GitHub as you URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/github-as-you.md Last Modified: October 13, 2026 Tags: Chainguard Workspaces, Security, Procedural By default, a Chainguard Workspaces session reaches GitHub through the Chainguard App with access to specific repositories, as described in [Manage session credentials](/chainguard/workspaces/credentials/). If you connect your GitHub account, the sessions you create afterward act on GitHub as you instead, unless your organization's `user_github` policy bans it. They act through the Chainguard Develop GitHub App (`chainguard-develop`), which is separate from the Chainguard App that runs Chainguard Checks. A session that acts as you can do the following: - Fetch and push with `git` for any repository you can access where the Chainguard Develop app is installed. - Use the `gh` CLI and the GitHub API, within the app's permissions and your own access. This page explains how to connect your account, where to install the app, and how to limit what these sessions can do. ## Connect your GitHub account Your organization's policy allows this by default, so an organization owner doesn't need to turn anything on. To have the sessions you create act on GitHub as you, connect your GitHub account once. If a GitHub organization you work in requires SAML single sign-on, sign in to it in your browser first. `chainctl` opens your browser so you can authorize the connection: ```shell chainctl develop github login ``` `chainctl develop init` offers the same step when you sign up, as described in [Get started with Chainguard Workspaces](/chainguard/workspaces/getting-started/). The connection applies to the sessions you create in every organization that doesn't ban it, and renews itself until you disconnect it. No GitHub token is written to your laptop. If your organization allows it and you haven't connected an account, or your connection expired or was revoked, `chainctl develop` prints a one-line reminder when it creates a session, and creates the session anyway. To disconnect, run `chainctl develop github logout`. Each session keeps the GitHub identity it was created with, so connecting your account doesn't change the sessions you already have. After you disconnect, a session that acts as you refuses `git` and `gh` requests until you connect again, and `chainctl` offers to connect your account when you attach to it. ## Install the Chainguard Develop app {#install-the-chainguard-develop-app} A session that acts as you reaches only the repositories where the Chainguard Develop app is installed. Install it from the [Chainguard Develop installation page](https://github.com/apps/chainguard-develop/installations/new) on the following accounts: - Every GitHub organization or account that owns a private repository your sessions fetch. Sessions read public repositories without the app. - Every organization or account that owns a repository your sessions push to. - Your own GitHub account, for your own repositories and forks. An organization owner installs the app on an organization, and a member can request it. The app asks for read and write access to contents, pull requests, and workflows, and read-only access to checks, commit statuses, and actions. On a terminal, `chainctl develop github login` and `chainctl develop init` check which accounts the checkout in your current directory needs, and walk you through each installation that's missing. To check again later, run `chainctl develop github status` in your checkout. It shows your GitHub account, the app it's connected through, whether the connection works, whether your organization's policy allows sessions to act as you, and which accounts still need the app. If the app can't reach a repository that a session needs, `chainctl develop` or the failed `git` command says why and where to fix it. Refer to [A session that acts as you can't reach a repository](/chainguard/workspaces/troubleshooting/#a-session-that-acts-as-you-cant-reach-a-repository). ## What a session that acts as you can do A session that acts as you can do anything your GitHub account can, within the Chainguard Develop app's permissions, and GitHub attributes everything it does to you: - It can approve other people's pull requests, and its approvals count toward required reviews and code owner reviews as yours would. GitHub doesn't let it approve pull requests you opened. - It can merge any pull request you can merge, including with auto-merge or a merge queue. - It can push changes to files in `.github/workflows`. - It can't create or update check runs or commit statuses, or rerun GitHub Actions workflows, because the app can only read them. To rerun Chainguard Checks from a session, use `chainctl checks rerun`, as described in [Your Chainguard identity in a session](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session). ## Protect your branches {#protect-your-branches} Because an agent in a session that acts as you can approve and merge as you, protect the branches it shouldn't change on its own with a branch protection rule or ruleset. Require pull request reviews, and turn on **Require approval of the most recent reviewable push**, so that someone other than the account that pushed last must approve each pull request. ## Controls that don't apply to a session that acts as you {#controls-that-dont-apply-to-a-session-that-acts-as-you} For a session that acts as you, `chainctl` doesn't ask you to approve GitHub requests, and the session's `github` source and bindings aren't used. The following have no effect on its GitHub access: - `github:` entries of `--with`, and `--with all` - The GitHub part of `--with none`, which still turns off `claude`, `cgr.dev`, `chainguard`, and `gcp` - `chainctl develop creds --revoke github:...` and `--source github=...` - Your organization's `--org-github` and `--org-github-push-beyond-session` settings `chainctl develop creds` shows that the session acts as you on GitHub, and tells you when a change to its `github` credential has no effect. To keep a new session off your GitHub account, run `chainctl develop github logout` before you create the session, or have an organization owner ban sessions from acting as their owner. ## Ban sessions from acting as their owner To ban sessions in an organization from acting as their owner, run the following command with the permissions described in [Set your organization's GitHub policy](/chainguard/workspaces/credentials/#set-your-organizations-github-policy): ```shell chainctl develop policy set --user-github=false --parent $ORGANIZATION ``` Replace `$ORGANIZATION` with the name of your Chainguard organization. New sessions in the organization then reach GitHub through their `github` source, as sessions do by default. Sessions that already act as their owner refuse GitHub requests while the ban is on. To lift the ban, set `--user-github=true`. Sessions created during the ban keep reaching GitHub through their `github` source after you lift it. --- ### Inspect and run checks with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/inspect-and-run-checks.md Last Modified: October 15, 2026 Tags: Chainguard Checks, chainctl, Procedural The `chainctl checks` commands read your checks' results from your terminal, so you or a coding agent can find out why a check failed without opening GitHub. This page explains how to inspect a pull request's runs, how to run checks on a pushed commit without opening a pull request, and how to find the checks that take the most time or fail most often. For every command and flag, refer to the [`chainctl checks` command reference](/chainguard/checks/chainctl-checks-reference/). These commands need a role in your Chainguard organization, as described in [The Details link or a chainctl checks command says you don't have access](/chainguard/checks/troubleshooting/#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access). To pass your organization's name to `--parent`, rather than its ID, you also need a role that can see the organization, such as `viewer`. ## Inspect pull request runs {#inspect-pull-request-runs} To check a pull request's latest run, pass the pull request to `chainctl checks status`. Replace `$OWNER/$REPO#$NUMBER` with the pull request, such as `acme/app#123`, or with its URL, and capitalize the owner and repository the same way GitHub does. Replace `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl checks status "$OWNER/$REPO#$NUMBER" --parent $ORGANIZATION ``` The command shows the pull request's newest run and each check's result. It exits with `0` while the run is in progress, so to wait until the run finishes, add `--watch`. After you push a commit or start a rerun, the command shows the previous run until Chainguard Checks creates the new one. For its exit codes and JSON output, refer to [chainctl checks status](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-status). To see why a pull request's checks failed, run `chainctl checks failures`: ```shell chainctl checks failures "$OWNER/$REPO#$NUMBER" --parent $ORGANIZATION ``` For each failed check, the command shows the step that failed, with its exit code and command, and the end of the failing output, quoted from the check's log. To show one check, add `--check` with its name. To also list the checks that didn't run, and why, add `--not-run`. The output stays within a size limit that `--budget` sets in bytes: the command shows as many failed checks in full as fit, then one line for each of the rest. To print a check's log, pass the pull request and the check's name to `chainctl checks logs`: ```shell chainctl checks logs "$OWNER/$REPO#$NUMBER" --check test --parent $ORGANIZATION ``` To keep reading a running check's output until it finishes, add `--follow`. To show only a long log's error lines, with three lines around each, add `--errors -C 3`. For other ways to read part of a log, refer to [chainctl checks logs](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-logs). To see the programs that each step of a failed check started, and which one most likely failed, run `chainctl checks trace` with `--failed`: ```shell chainctl checks trace "$OWNER/$REPO#$NUMBER" --check test --failed --parent $ORGANIZATION ``` These commands also work in a Workspaces session, where `chainctl` acts as you. During the beta, they can't find a run by its commit, so pass the pull request, as described in [Name a run](/chainguard/checks/chainctl-checks-reference/#name-a-run). For a run after merging, pass the run's ID instead, such as `chainctl checks status $RUN_ID --parent $ORGANIZATION`. The ID is the part after `/runs/` in the **Details** link of the **Chainguard Checks (postsubmit)** summary check. ## Run checks on a pushed commit To run your checks in fresh VMs without opening a pull request, push your commit and run `chainctl checks run` in your checkout. Unlike `run-ci` in a [Workspaces session](/chainguard/checks/run-checks/), it runs only what you pushed, not uncommitted changes. Replace `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl checks run . --watch --parent $ORGANIZATION ``` The command runs the commit checked out in the current directory. Chainguard Checks reads the commit from GitHub, in the repository of the checkout's `origin` remote, so push the commit first. In a checkout of a fork, add `--git-remote upstream` to read it from the repository you forked instead. The repository's GitHub account must be linked to your Chainguard organization, as described in [Getting started](/get-started/checks-and-workspaces/set-up/#sign-up-with-chainctl-develop-init). The command prints the run's name, followed by its checks. With `--watch`, it waits until the run finishes, and exits with `1` if a blocking check doesn't pass. To run only one check and the checks it `needs`, add `--check` with its name. To include a check that runs only on request, name it with `--check`, or add `--request` with its name, as a `ci/run:` label does on a pull request. For every flag, refer to the [`chainctl checks run` reference](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-run). A run you start this way differs from a pull request run in the following ways: - It's for your information only. It doesn't appear on GitHub and never counts as a pull request's or commit's result, but it uses your organization's Checks allowance like any other run. - It runs only checks that run on pull requests, so it skips checks whose `trigger` is only `postsubmit`. - Its `when.changed` conditions compare the commit with where it branched from the remote's default branch, or with its parent if the commit is on that branch. To compare it with another commit, add `--base` with that commit. - It has no pull request, trigger, or labels. If a check's `when` expression reads any `context` field, the command names the check and doesn't start a run. To run other checks, select one with `--check`. - A check with a workload `identity` fails, because only pull request runs and runs after merging get identities. In a Starter organization, don't use `chainctl checks run`. Your role can start runs, which use your allowance, but can't read their results, so open a pull request instead. ### Read a run you started To read a run you started, pass its ID, or the start of it, to `chainctl checks status` or the other commands that read runs. The ID is the part after `/runs/` in the run's name. Replace `$RUN_ID` with the ID: ```shell chainctl checks status $RUN_ID --watch --parent $ORGANIZATION ``` Don't pass `.` or the commit's SHA instead. Because the run never counts as the commit's result, `status` reports that the platform created no run for the commit. To find the ID of a run you started earlier, run `chainctl checks list --parent $ORGANIZATION`. To stop a run before it finishes, pass its ID to `chainctl checks cancel`. ## See check history {#see-check-history} To find the checks that take the most time or fail most often, use `chainctl checks top`, `chainctl checks trends`, and `chainctl checks stats`. To rank a repository's checks by their total runtime over the past week, run `chainctl checks top`. Replace `$OWNER/$REPO` with the repository, such as `acme/app`, and `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl checks top --repo $OWNER/$REPO --since 7d --parent $ORGANIZATION ``` To rank the checks by failure rate instead, add `--by failures`. For the other rankings, the time windows you can read, and how the command counts failures, refer to [chainctl checks top](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-top). To see how long a check takes, hour by hour, pass its name to `chainctl checks trends`: ```shell chainctl checks trends --repo $OWNER/$REPO --check test --parent $ORGANIZATION ``` For each hour, the command shows the check's median, 95th percentile, and 99th percentile durations. Only the 10 checks with the most total runtime in the window have trends. For more, refer to [chainctl checks trends](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-trends). To count the outcomes of your organization's recent runs, run `chainctl checks stats`: ```shell chainctl checks stats --parent $ORGANIZATION ``` The command prints a table of each repository's runs by outcome, and a table of each check's results by outcome, with the check's failure percentage and median duration. For what each column counts, refer to [chainctl checks stats](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-stats). --- ### Migrating a Go project to Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/go/migration.md Last Modified: October 14, 2026 Tags: Chainguard Libraries, Go Chainguard Libraries for Go provides an authenticated Go module proxy that applies Chainguard security controls to Go dependencies. Because Chainguard Libraries uses the standard [GOPROXY protocol](https://go.dev/ref/mod#goproxy-protocol), migrating an existing project does not require application-code changes or a new dependency declaration format. You configure the Go toolchain to use Chainguard, refresh any cached modules, and verify the build. This guide covers two common setups: - **Direct access**: Your Go toolchain connects directly to `libraries.cgr.dev`. This is useful for an evaluation, a small team, or an isolated CI job. - **Repository manager**: Your Go toolchain connects to a repository manager that proxies Chainguard Libraries. This is recommended for organization-wide use. ## Prerequisites Before you begin, you need: - An existing Go project with a `go.mod file`. A `go.sum` file is recommended. - A supported Go toolchain. Go 1.21 or later is recommended for the current Go Libraries beta. - [chainctl installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/). - An [entitlement to Chainguard Libraries for Go](#create-an-entitlement) - A [pull token for Chainguard Libraries for Go](#create-a-pull-token) ### Create an entitlement To create an entitlement to Chainguard Libraries for Go, run: ```bash chainctl libraries entitlements create --ecosystems=GO ``` You do not need to manually enable upstream fallback for Go; the entitlement for Go automatically includes Chainguard protections for upstream artifacts. You can also configure a cooldown policy after you create the entitlement. For example, to create and enforce a policy for a 14-day cooldown: ```bash chainctl libraries policy create --name=go-cooldown-14d --cooldown-days=14 chainctl libraries policy enable go-cooldown-14d --ecosystem=GO --mode=ENFORCE ``` It can take up to 30 minutes for configured policies to take effect. Learn more about cooldown and other policies in the [Libraries policies documentation](/chainguard/chainguard-repository/library-policies/). ### Create a pull token Create a pull token for the Go repository: ```bash chainctl auth pull-token --repository=go --ttl=8670h ``` The command returns a username and password. Use these values to authenticate Go to Chainguard Libraries. > **Note**: Do not commit the credentials to source control. Keep the configuration file out of the repository when it contains literal credentials, or generate it at build time from secrets stored in your CI/CD system. For shared environments, use a dedicated machine identity or service account instead of a personal token when available. For local development, you can store the credentials in `~/.netrc`: ```bash cat >> ~/.netrc <

Set Chainguard as the only Go module proxy:

go env -w GOPROXY=https://libraries.cgr.dev/go
go env -w GONOSUMDB='*'

You can use GOSUMDB=off instead of GONOSUMDB=*:

go env -w GOSUMDB=off

Do not add direct, https://proxy.golang.org, or another public proxy to GOPROXY. A sole Chainguard entry ensures that module requests remain within your organization’s configured controls.

Do not set GOPRIVATE. It also sets GONOPROXY and can bypass Chainguard. Do not set GONOSUMCHECK; it is not a current Go configuration variable.

Check the effective configuration:

go env GOPROXY GONOSUMDB GOSUMDB
## Step 3: Verify authentication and proxy access You can test the endpoint directly with the credentials exported by chainctl: ```bash curl -fsS -u "${CHAINGUARD_GO_IDENTITY_ID}:${CHAINGUARD_GO_TOKEN}" \ https://libraries.cgr.dev/go/rsc.io/quote/@v/v1.5.2.info ``` A successful response contains JSON similar to: ```bash { "Version": "v1.5.2", "Time": "2021-05-05T15:00:00Z" } ``` The exact timestamp may differ. If the request returns 401, check the token, `.netrc` hostname, and token expiration. If it returns 404, confirm that your organization has upstream fallback enabled and that the module version is available. ## Step 4: Pull modules and verify the build Use a clean module cache when first testing the migration so that existing cached modules do not hide proxy configuration problems: ```bash go clean -modcache go mod download go test ./... go mod verify ``` For a minimal smoke test in a temporary directory: ```bash workdir=$(mktemp -d) cd "$workdir" go mod init example.com/proxy-check go get rsc.io/quote@v1.5.2 go mod verify ``` After the build succeeds, inspect `go env GOPROXY` and your build logs to confirm that module requests are going through Chainguard. Keep the generated `go.sum` file with the project if it is part of your existing dependency-management workflow. --- ### Migrating a .NET project to Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/dotnet/migration.md Last Modified: September 29, 2026 Tags: Chainguard Libraries, .NET Chainguard Libraries for .NET provides a registry of NuGet packages that have been scanned for malware and are subject to configurable policies. Switching an existing project requires only a registry configuration change – no changes to your application code or dependency versions. This guide walks through migrating an existing .NET project to Chainguard Libraries, covering the two most common setups: - **Direct access**: Your build tool connects directly to libraries.cgr.dev. This option is faster for initial evaluation and smaller-scale setups. - **Repository manager**: Your build tool connects to a repository manager, which proxies requests to Chainguard Libraries. This option is recommended for teams and organizations. ## Prerequisites Before getting started, you need: - A .NET project with a .csproj file - The .NET SDK - If you are using macOS, install the .NET SDK with Homebrew: `brew install --cask dotnet-sdk` - `chainctl` installed and authenticated - An [entitlement to Chainguard Libraries for .NET](#create-an-entitlement) - A [pull token for Chainguard Libraries for .NET](#create-a-pull-token) ### Create an entitlement To create an entitlement to Chainguard Libraries for .NET, run: ```bash chainctl libraries entitlements create --ecosystems=DOTNET ``` You do not need to manually enable upstream fallback for .NET; the entitlement for .NET automatically includes Chainguard protections for upstream artifacts. You can also configure a cooldown policy after you create the entitlement. For example, to create and enforce a policy for a 14-day cooldown: ```bash chainctl libraries policy create --name=dotnet-cooldown-14d --cooldown-days=14 chainctl libraries policy enable dotnet-cooldown-14d --ecosystem=DOTNET --mode=ENFORCE ``` It can take up to 30 minutes for configured policies to take effect. Learn more about cooldown and other policies in the [Libraries policies documentation](/chainguard/chainguard-repository/library-policies/). ### Create a pull token Create a pull token for the .NET repository: ```bash chainctl auth pull-token --repository=dotnet --ttl=8670h ``` The command returns a username and password. Use these values to authenticate NuGet to Chainguard Libraries. > **Note**: Do not commit the credentials to source control. Keep `nuget.config` out of the repository when it contains literal credentials, or generate it at build time from secrets stored in your CI/CD system. For shared environments, use a dedicated machine identity or service account instead of a personal token when available. ## Step 1: Configure authentication and registry Choose the setup that matches your organization. With direct access, your build tool connects to libraries.cgr.dev. With a repository manager, your build tool connects to a proxy that you configure to fetch from Chainguard Libraries.

NuGet reads package sources from nuget.config files at the project, solution, user, and machine levels. Before changing the configuration, list the sources currently in use:

dotnet nuget list source

Edit or create a file named nuget.config in the project directory, alongside the .csproj file. Replace its contents with the following configuration, substituting the username and password with the values of your pull token:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="chainguard" value="https://libraries.cgr.dev/dotnet/v3/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <chainguard>
      <add key="Username" value="REPLACE WITH USERNAME FROM pull-token" />
      <add key="ClearTextPassword" value="REPLACE WITH PASSWORD FROM pull-token" />
    </chainguard>
  </packageSourceCredentials>
</configuration>

The <clear /> element removes previously configured package sources, including nuget.org. This prevents NuGet from silently falling back to nuget.org.

Confirm the active sources:

dotnet nuget list source

The expected output shows Chainguard as enabled:

Registered Sources:
  1.  chainguard [Enabled]
      https://libraries.cgr.dev/dotnet/v3/index.json
## Step 2: Clear caches, restore, and build Because packages have previously been served from upstream, you must clear the NuGet HTTP cache and restore: ```bash dotnet nuget locals http-cache --clear dotnet restore --force --no-cache --verbosity normal ``` The output should include requests to the Chainguard endpoint. If you configured access through a repository manager, the output shows your repository manager URL instead. If the URL or credentials are incorrect, `restore` fails instead of using a cached package. Common errors include `NU1301`, `NU1101`, and `HTTP 401` responses. After restoring, build your project: ```bash dotnet build ``` ## Step 3: Verify your libraries Inspect the project assets file to confirm that your project is using Chainguard Libraries: ```bash dotnet nuget list source ``` The expected output includes the Chainguard endpoint: ```output "sources": { "https://libraries.cgr.dev/dotnet/v3/index.json": {} ``` If you configured access through a repository manager, the `sources` entry shows your repository manager URL instead of `libraries.cgr.dev`, and must not contain `nuget.org`. --- ### Verifying Chainguard Agent Skills signatures URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/agent-skills/verifying-skills.md Last Modified: October 9, 2026 Tags: Agent Skills, Procedural, Security, Cosign, chainctl Chainguard signs every skill it publishes to `skills.cgr.dev`. The signature lets you confirm that a skill came from Chainguard's publishing pipeline and that nobody changed it after Chainguard signed it. Chainguard signs each published skill as the SKILLS identity of the organization that owns it, and attaches the signature to the skill's digest as a [Sigstore](https://www.sigstore.dev/) bundle. The signature covers the skill's manifest digest, which covers everything in the artifact: the hardened skill files, the `HARDENING.md` report, and the skill's metadata. Chainguard signs only hardened, published skills. It doesn't sign the skills in your organization's uploads registry (`uploads.cgr.dev`). `chainctl skills validate` doesn't check signatures. It checks that a local skill directory is formatted correctly, and a passing result says nothing about who published a skill. To check that Chainguard signed a skill, use `chainctl skills verify`, as described in this guide.

Note: Chainguard Agent Skills is in beta.

## Prerequisites To follow this guide, you need: * `chainctl` **v0.2.376** or later. Check your version with `chainctl version`. Refer to [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't have it yet. * To verify a skill from your own organization, the `groups.list` capability on that organization, such as with the built-in `owner`, `editor`, `viewer`, `limited_owner`, or `console_viewer` role. For details, refer to the [capabilities reference](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/). Skills in the `public` and `chainguard` organizations need no role. * To verify with Cosign instead of `chainctl`, [Cosign](/open-source/sigstore/cosign/how-to-install-cosign/) **v3.1.1** or later. Earlier versions can't verify the transparency log entries Chainguard uses to sign skills. ## Verify a skill with `chainctl` Pass a skill reference to the `verify` subcommand: ```shell chainctl skills verify skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ``` ```output FIELD | VALUE -----------|-------------------------------------------------------------------------------------- Status | verified Digest | sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f Identity | https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a Issuer | https://issuer.enforce.dev Log Index | 141100935 ``` `verify` accepts a reference by tag or by digest. It resolves a tag to a digest once, verifies that digest, and prints it, so you can pin the exact version you verified. To verify a pinned digest and get the result as JSON, add `-o json`: ```shell chainctl skills verify skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine@sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f -o json ``` ```output {"status":"verified","digest":"sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f","issuer":"https://issuer.enforce.dev","identity":"https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a","logIndex":141100935} ``` For a skill in your organization's private registry, pass the hardened reference that `chainctl skills harden` or `chainctl skills status` returned: ```shell chainctl skills verify "$HARDENED_REF" ``` The output has the same fields. The `Identity` is your organization's SKILLS identity rather than the `public` one, as described in [Chainguard's skill signing identities](#chainguards-skill-signing-identities). ### What `verify` checks A skill is `verified` only when a Sigstore bundle attached to its digest passes all of these checks: * The signing certificate chains to the public-good Sigstore trusted root. * `https://issuer.enforce.dev` issued the certificate to exactly the SKILLS identity of the organization that owns the skill. `chainctl` accepts no other identity, and you can't override the identity with a flag or setting. * The bundle has a verified transparency log entry and a verified timestamp. * The bundle signs the exact digest that `verify` checks. `chainctl` has the signing identities of the `public` and `chainguard` organizations built in. For any other organization, it looks up the organization's SKILLS identity with your credentials, which is why you need the `groups.list` capability on that organization. ### Results and exit codes `verify` reports one of four results in the `Status` field, or the `status` key of the JSON output: | Status | Meaning | Exit code | | ----- | ----- | ----- | | `verified` | A bundle signed by the owning organization's SKILLS identity passed every check. | `0` | | `unsigned` | The digest has no Sigstore bundle. | `1` | | `failed` | The digest has a bundle, but none of its bundles passed. The `Reason` field explains why. Skills in `uploads.cgr.dev` always fail, because Chainguard doesn't sign uploads. | `1` | | `skipped` | The skill isn't on the production registry, `skills.cgr.dev`, so `chainctl` can't verify it. `verify` prints a warning. | `0` | An error that stops verification from running, such as a skill that doesn't exist, also exits with a nonzero code. ## Verify before you pull or install Add `--verify` to `chainctl skills pull` or `chainctl skills install` to check the signature on the exact digest that `chainctl` fetches before it writes anything to disk: ```shell chainctl skills install --verify skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ``` `chainctl` prints the verification result before it installs the skill. The following example shows the output on a machine where Claude Code is installed: ```output Verified sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f: signed by https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a (log index 141100935) Recorded skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine@sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f in skills-lock.json Installing github.com/github/awesome-copilot/game-engine AGENT | LOCATION | MODE -------------|---------------------------------------------------------------------|------------------------------------------------------------------------------------- Claude Code | .claude/skills/public-github.com-github-awesome-copilot-game-engine | symlink → ../../.agents/skills/public-github.com-github-awesome-copilot-game-engine ``` `pull` works the same way: ```shell chainctl skills pull --verify skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine:latest ./game-engine ``` ```output Verified sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f: signed by https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a (log index 141100935) Skill written to: /home/linky/game-engine ``` If the skill isn't `verified`, the command exits with a nonzero code and writes no files or symlinks. `--verify` is stricter than `verify`: a `skipped` result fails too. With `-o json`, `pull` and `install` include the result that `chainctl skills verify -o json` prints in a `verification` field of their output. `chainctl` reads its registry setting from configuration files and environment variables, including a `chainctl/config.yaml` in the current working directory. A project you clone could point `chainctl` at a registry other than `skills.cgr.dev`. Treating `skipped` as a failure keeps such a project from silently turning `--verify` off. If you deliberately work with a staging or development registry, add `--allow-unverifiable-host` along with `--verify` to accept a skill from that registry unverified, with a warning. `--allow-unverifiable-host` has no effect on skills from `skills.cgr.dev`, and `chainctl` rejects it without `--verify`. ## Chainguard's skill signing identities Chainguard signs each organization's skills as that organization's SKILLS identity. The certificate issuer is always `https://issuer.enforce.dev`, and the certificate identity is the issuer URL followed by the identity's UIDP: | Organization | Certificate identity | | ----- | ----- | | `public` | `https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a` | | `chainguard` | `https://issuer.enforce.dev/720909c9f5279097d847ad02a2f24ba8f59de36a/c09dd88a8a73ea84` | To find the SKILLS identity of your own organization, read its account associations: ```shell chainctl iam account-associations describe your-organization --chainguard -o json \ | jq -r '.[0].chainguard.service_bindings.SKILLS' ``` The command prints the UIDP of your organization's SKILLS identity. For the `chainguard` organization, it prints the UIDP from the preceding table: ```output 720909c9f5279097d847ad02a2f24ba8f59de36a/c09dd88a8a73ea84 ``` Prefix the UIDP with `https://issuer.enforce.dev/` to get the certificate identity. Reading account associations needs the `account_associations.list` capability on the organization. ## Verify a skill with Cosign Chainguard recommends `chainctl skills verify` for checking a skill's signature, but you can also check it yourself with Cosign, using the signing identities from the previous section. ### Public registry Verifying a skill from the public registry needs no credentials: ```shell cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity=https://issuer.enforce.dev/85490af34ebd49d9ea1fe9370c4ef1169930fc5f/c478d8a95ab1938a \ skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine@sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f ``` ```output Verification for skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine@sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates [{"critical":{"identity":{"docker-reference":"skills.cgr.dev/public/github.com/github/awesome-copilot/game-engine@sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f"},"image":{"docker-manifest-digest":"sha256:ed8ffbbd8ccf13b3230ebc64e924799824a91676d0444896b28810f906a4072f"},"type":"https://sigstore.dev/cosign/sign/v1"},"optional":{}}] ``` Get the digest of the version you want from `chainctl skills describe` or `chainctl skills verify`. Cosign also accepts a tag, but verifying by digest makes sure you check the exact version you go on to use. ### Private registry Cosign needs credentials to pull from your organization's registry. Configure the Chainguard Docker credential helper, which Cosign also uses: ```shell chainctl auth configure-docker ``` Then run `cosign verify`, replacing `` with the UIDP of your organization's SKILLS identity from the previous section: ```shell cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity=https://issuer.enforce.dev/ \ "$HARDENED_REF" ``` `$HARDENED_REF` is the full hardened reference, including its digest, that `chainctl skills harden` or `chainctl skills status` returned.

Note: Earlier instructions for verifying skills used --new-bundle-format=false and a --certificate-identity-regexp that matched Google Cloud service accounts. Those flags check an older signature format that Chainguard no longer relies on. Use the SKILLS identities on this page instead.

## Learn more * [Getting started with the Chainguard Skills Registry](/chainguard/agent-skills/skills-registry/) * [Getting started with the Chainguard Agent Skills public registry](/chainguard/agent-skills/public-registry/) * [Getting started with skill hardening](/chainguard/agent-skills/skill-hardening/) * [`chainctl skills verify` reference](/platform/chainctl/chainctl-docs/chainctl_skills_verify/) * [An introduction to Cosign](/open-source/sigstore/cosign/an-introduction-to-cosign/) * [Verifying signatures in air-gapped environments](/open-source/sigstore/cosign/verifying-in-air-gapped-environments/) --- ### Migrate from GitHub Actions to Chainguard Checks URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/migrate-from-github-actions.md Last Modified: October 13, 2026 Tags: Chainguard Checks, Migration, GitHub If your repository runs its pull request checks with GitHub Actions, you can move them to Chainguard Checks one workflow at a time. Chainguard Checks and GitHub Actions can run on the same pull requests, so you can compare their results before you remove a workflow. You can convert workflows by hand, or start from a draft that the `chainctl checks migrate` command writes for you. If you'd rather keep your workflows on GitHub Actions, [Chainguard Actions](/chainguard/actions/overview/) replaces the actions they use with hardened equivalents, and Guardener's [Hardened Actions](/chainguard/guardener/github/actions-security/) feature can migrate them for you. ## Draft a configuration with chainctl The `chainctl checks migrate --dry` command drafts a configuration from your GitHub Actions workflows without running anything. Run it from the root of a local clone of your repository whose `origin` remote is on github.com: ```shell chainctl checks migrate --dry ``` The command reads each workflow that runs on `pull_request` and prints a draft `.chainguard/ci/migrated.yaml` file, followed by a list of what it carried over, flagged, and ignored. To write the draft to its file, run the following commands: ```shell mkdir -p .chainguard/ci chainctl checks migrate --dry -o json | jq -r .document > .chainguard/ci/migrated.yaml ``` The draft has one check for each job it can carry over, and a separate check for each combination of a static matrix of up to 8 combinations. A job whose matrix uses `include` or an expression, or has more than 8 combinations, isn't carried over. The list after the draft flags it, so convert it by hand. The draft's checks appear on GitHub as `presubmit / migrated / `. The draft uses the [directory layout](/chainguard/checks/configuration/#file-layouts), and a repository can't use both layouts. If your repository already has a `.chainguard/ci.yaml` file, move its checks into a file in `.chainguard/ci/`, and its environment to `.chainguard/ci/env/default.yaml`, before you add the draft. Review the draft before you commit it: - Add the packages each check needs. The draft lists packages for setup actions, such as `actions/setup-go`, but not for tools that the GitHub Actions runner image includes, such as `git` and `make`. - Add the hosts each check needs under `network.egress`. The draft gives checks no network access. To find the hosts, refer to [Find the hosts a check needs](#find-the-hosts-a-check-needs). - For each check that needs x86-64, set `resources` to a class that pins it, such as the built-in `medium-amd64`. The draft ignores `runs-on`, so its checks usually run on arm64. - Read each `# migrator:` comment. Each one marks something the draft couldn't carry over, such as a step that uses a third-party action, a step with a `${{ }}` expression, a service container, or a `paths` filter. Convert each one by hand. Then run `chainctl checks validate` to check the result. The rest of this page explains how to convert what the draft leaves out, including workflows that run on other events, such as `push`. ## How the models differ A GitHub Actions job starts on a general-purpose runner image, checks out your code, and installs its tools with setup actions before running its commands. In Chainguard Checks, a check starts in a minimal VM with your code already in `/work`, installs the packages you list, and runs its commands. The following table summarizes the differences: | Area | GitHub Actions | Chainguard Checks | | ---- | -------------- | ----------------- | | Actions | Steps can use actions with `uses:`. | There are no actions. List each tool's package and run it directly. You don't need `actions/checkout`, because `/work` already holds your repository's tracked files and history at the commit under test, but not its submodules or Git LFS content. | | Preinstalled tools | The runner image includes many tools. | The VM includes BusyBox, GNU tar, and the packages you list. List every tool a check uses, including `git`, `bash`, and `make`. | | Network | Jobs can reach the internet. | A check has no network access unless it lists hosts under `network.egress`, such as your package registry. | | Shell | A `run` step uses `bash -e`, or `bash -eo pipefail` with `shell: bash`. | A `script` runs with BusyBox `sh -e`, or with `bash -e` if you list the `bash` package and set `shell: bash`. Neither sets `pipefail`, so for a step that had `shell: bash`, start its script with `set -o pipefail`. The draft keeps `pipefail` for these steps. Refer to [The script key](/chainguard/checks/configuration/#the-script-key). | | Caches | Jobs save and restore caches with actions, such as `actions/cache`. | A check starts with no cache. If the `trust` section of `.chainguard/ci/identities.yaml` on your default branch defines a cache pool, a Go check can reuse build output from earlier runs. | | Run length | A job can run for up to 6 hours. | A whole run can take up to 50 minutes, and checks still running at that point fail. Split long jobs into several checks, which run in parallel. | | Secrets | Jobs read secrets from the `secrets` context, and get a `GITHUB_TOKEN`. | There's no `secrets` context. During the beta, a check gets no GitHub token, so it can't fetch your private GitHub repositories, such as private Go modules. A check that must authenticate to Google Cloud or Chainguard can use a workload identity, as described in [Credentials](/chainguard/checks/configuration/#credentials). | ## Key mapping The following table maps common GitHub Actions keys and actions to their Chainguard Checks equivalents: | GitHub Actions | Chainguard Checks | | -------------- | ----------------- | | `on: pull_request` | The default. A check runs on pull requests unless you set `trigger`. | | `on: push` to the default branch | `trigger: postsubmit` | | `on: pull_request: paths:` | `when: changed:`. A `**` matches any number of directories only as a whole path segment, so write `**.go` as `**/*.go`. A pattern that starts with `!` has no equivalent. Refer to [Run when files change](/chainguard/checks/configuration/#run-when-files-change). | | `on: pull_request: paths-ignore:` | No equivalent. List the paths that should run the check under `when: changed:` instead. | | `if:` | `when: expr:` with a CEL expression | | `workflow_dispatch`, or a job that a label starts | `when: {requested: true}`, run with a `ci/run:` label | | `continue-on-error: true`, or a job that isn't required | `advisory: true` | | `jobs.` | `checks.` | | `runs-on` | `resources`. A built-in class without a suffix usually runs on arm64, while `ubuntu-latest` is x86-64, so use an `-amd64` class, such as `medium-amd64`, for a job that needs x86-64. If your repository defines its own classes in `.chainguard/ci/resources.yaml`, the built-in classes aren't available, and only a class with a `platform` pins the architecture. Refer to [Resources](/chainguard/checks/configuration/#resources). | | `steps[*].run` | `steps[*].cmd` or `steps[*].script`. A step takes only `id`, `name`, `cmd`, `script`, and `shell`, so step-level `env`, `if`, and `working-directory` are refused. | | `steps[*].uses` | Not supported. List the tool's package and run it. | | `actions/checkout` with `repository` | `inputs` with `git`, for a public repository on github.com. Refer to [Check out another repository](/chainguard/checks/variations-services-caches/#check-out-another-repository). | | `actions/checkout` with `submodules` or `lfs` | No equivalent. Submodule directories are empty, and Git LFS files hold their pointer files. | | `actions/upload-artifact` and `actions/download-artifact` | `outputs` on the check that writes the files, and `inputs` with `from` on a check in the same file that reads them. Refer to [Pass files between checks](/chainguard/checks/variations-services-caches/#pass-files-between-checks). A run keeps no files that you can download. | | `actions/cache`, or the caching in `actions/setup-go` | For the Go build cache, `environment.caches: {gobuild: {}}`, with `user: build` on the check and a cache pool in the `trust` section of `.chainguard/ci/identities.yaml` on your default branch. Refer to [Caches](/chainguard/checks/variations-services-caches/#caches). Other cache types have no effect during the beta. | | `jobs..services` | `environment.services`. A service runs in the check's VM from packages that you list, not in a container. Refer to [Services](/chainguard/checks/variations-services-caches/#services). | | `env` | `vars` | | `needs` | `needs`, for checks in the same file | | `timeout-minutes` | `timeout`, as a duration such as `15m` | | `strategy.matrix` | `foreach`. Chainguard Checks doesn't expand a matrix, so write one entry for each combination, and replace each `${{ matrix. }}` with a variable that the entry sets in `vars`, such as `$GOARCH` in a `script`. An entry can't change the check's packages, so for a matrix of tool versions, write a separate check with its own environment for each version, as the draft does. Refer to [Run a check once per variation](/chainguard/checks/variations-services-caches/#run-a-check-once-per-variation). | | `strategy.max-parallel` | `parallelism` | | `defaults.run.working-directory` | `workdir` | | `$GITHUB_STEP_SUMMARY` | Not set, so a command that writes to it fails. Instead, print Markdown between a `::sandbox-summary::` line and an `::end-sandbox-summary::` line, and the check's output on GitHub shows it before the log. Refer to [Add a Markdown summary](/chainguard/checks/configuration/#add-a-markdown-summary). | | `on: schedule` | Not supported. | | `on: merge_group` | Not supported. Chainguard Checks doesn't run on merge queues, so don't require **Chainguard Checks (presubmit)** on a branch that uses one. Refer to [Require checks before merging](/chainguard/checks/github/#require-checks-before-merging). | Refer to the [configuration reference](/chainguard/checks/configuration/) for each Chainguard Checks key. ## Example The following GitHub Actions workflow runs Go tests on pull requests that change Go files: ```yaml {title=".github/workflows/test.yaml"} name: test on: pull_request: paths: ["**/*.go", "go.mod", "go.sum"] jobs: test: runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: "1.24" - run: go test ./... env: CGO_ENABLED: "0" ``` The equivalent `.chainguard/ci.yaml` file installs Go 1.24 as a package instead of using `actions/setup-go` and drops `actions/checkout`: ```yaml {title=".chainguard/ci.yaml"} version: 1 environment: packages: [go-1.24] checks: test: cmd: go test ./... vars: CGO_ENABLED: "0" timeout: 15m when: changed: ["**/*.go", "go.mod", "go.sum"] network: egress: [proxy.golang.org, sum.golang.org] ``` The `network` key lets the check download modules. The workflow doesn't list these hosts, because a GitHub Actions job can reach any host. To find the hosts a check needs, refer to [Find the hosts a check needs](#find-the-hosts-a-check-needs). By default, `actions/setup-go` caches downloaded modules and build output between runs. This file uses no cache, so each run downloads its modules and builds from scratch. To reuse Go build output between runs, add the `gobuild` cache, as described in [Caches](/chainguard/checks/variations-services-caches/#caches). During the beta, Chainguard Checks doesn't cache modules, so keep the `network` hosts. With the cache, `go test` still runs every test instead of reusing earlier results. ## Find the hosts a check needs The run page that a check's **Details** link opens lists each host the check tried to reach, and suggests an `egress` list built from the hosts it connected to. The suggestion includes only connections that the check's `egress` allowed, so to see every host a check uses, let it reach any host for one run: 1. In a pull request whose code you trust, set the check's `network.egress` to `["*"]`. With `*`, the check's commands can reach any public host. 2. When the check finishes, open its **Network activity** on the run page, as described in [A check can't reach a host](/chainguard/checks/troubleshooting/#a-check-cant-reach-a-host). 3. Review the hosts listed after **Suggested allowlist (review before applying)**, replace `*` with them, and push the change. The suggestion covers only that run. It leaves out connections to an IP address that the check didn't look up by name, and hosts that the check reaches only on other paths, such as retries, fallback mirrors, or code that the run didn't exercise. To open the run page, you need a role in your Chainguard organization, as described in [The Details link or a chainctl checks command says you don't have access](/chainguard/checks/troubleshooting/#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access). Without one, list the hosts you expect, run the check, and look in its log for connection errors. ## Switch over To move a workflow to Chainguard Checks without a gap in coverage, follow these steps: 1. In a pull request, add the equivalent checks to `.chainguard/ci.yaml`, or add the `.chainguard/ci/migrated.yaml` file that you drafted, and keep the GitHub Actions workflow. 2. Compare the results of both on that pull request and the ones after it. 3. When the results match, add **Chainguard Checks (presubmit)** as a required status check, as described in [Require checks before merging](/chainguard/checks/github/#require-checks-before-merging). 4. Remove the GitHub Actions workflow, and remove its jobs from your required status checks. --- ### Use Claude Code in a Chainguard Workspaces session URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/coding-agents.md Last Modified: October 15, 2026 Tags: Chainguard Workspaces, AI, Procedural A Chainguard Workspaces session is a place for a coding agent to work with less risk to your laptop. The agent runs in the session's VM, which holds no long-lived credentials, and it reaches Claude with your own Claude credential, which stays outside the VM. This page explains how to run Claude Code in a session. Claude Code is the only coding agent that sessions supply a model credential for. You can install other coding agents in a session, but sessions don't send them a credential. ## Before you begin Claude Code in a session uses the Claude credential from your laptop, so set it up there first: 1. Update `chainctl` to the [minimum version](/get-started/checks-and-workspaces/set-up/#install-or-update-chainctl) or later, as described in [Updating chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl). Earlier versions don't send Claude's credential to a session. 2. Install Claude Code on your laptop, as described in the [Claude Code documentation](https://code.claude.com/docs/en/overview). 3. Run `claude` on your laptop and log in. When you attach to a session, `chainctl` sends the session's host the first credential it finds on your laptop, in this order: a `chainctl-credential-claude` credential helper on your `PATH`, the `ANTHROPIC_API_KEY` environment variable, an `apiKeyHelper` from your Claude Code settings, the `CLAUDE_CODE_OAUTH_TOKEN` environment variable, and your stored Claude login. Apart from the credential helper, this is the order Claude Code uses on your laptop. Both an API key and a Claude subscription login work. Claude Code must also be installed in the session. The default environment includes it. If your session uses your repository's `.chainguard/develop.yaml` or `.chainguard/ci.yaml` file, add `claude` to that file's packages, as described in [Configure a session's environment](/chainguard/workspaces/session-environment/). ## Start Claude Code Open a session, then run `claude` in it: ```shell claude ``` Claude Code starts without asking you to log in. When you attach, `chainctl` sends your Claude credential to the session's host, outside the VM. Claude Code's requests go to `http://claude/` in the session, and the host sends them on with your credential. The host passes on only requests to the Claude API's messages and models endpoints, not its account or organization endpoints. For a stored Claude login, `chainctl` sends only the short-lived access token, never the refresh token. If Claude Code in the session reports that the token sent to the session expired, attach to the session again so that `chainctl` sends a fresh one. If `chainctl` says that the Claude login on your laptop expired, run `claude` on your laptop once to refresh it. To keep a long task from stopping when a login expires, use a token from `claude setup-token`, as described in [Keep Claude Code working while you're away](#keep-claude-code-working-while-youre-away). For other errors, refer to [Claude Code reports an error in the session](/chainguard/workspaces/troubleshooting/#claude-code-reports-an-error-in-the-session). ## Use Claude through Vertex AI {#use-claude-through-vertex-ai} To use Vertex AI, set your Google Cloud project in `chainctl`. You don't need to change your Claude Code settings. Replace `$GCP_PROJECT` with your Google Cloud project ID: ```shell chainctl config set develop.vertex.project $GCP_PROJECT ``` New sessions then use Vertex AI in that project, as described in [How a session chooses its provider](#how-a-session-chooses-its-provider). The region defaults to `global`. To use another region, set `develop.vertex.region` the same way. With Vertex AI, `chainctl` sends the session's host a Google credential from your laptop's application default credentials, which `gcloud auth application-default login` sets up. It sends the credential the same way it sends the `gcp` credential, as described in [Use your Google Cloud credentials in a session](/chainguard/workspaces/credentials/#use-your-google-cloud-credentials-in-a-session). Claude Code in the session holds no Google credential. ### How a session chooses its provider When you create a session, `chainctl` chooses how Claude Code in it reaches Claude, in this order: 1. If Claude Code on your laptop is set to use Vertex AI with `CLAUDE_CODE_USE_VERTEX`, the session uses Vertex AI with the project in `ANTHROPIC_VERTEX_PROJECT_ID` and the region in `CLOUD_ML_REGION`. 2. If you set `develop.vertex.project` in your `chainctl` configuration, the session uses Vertex AI in that project. 3. If Claude Code on your laptop has a Claude login or an API key, the session uses the Anthropic API. 4. If your Google Cloud application default credentials name a project, the session uses Vertex AI in that project. If none of these applies, the session uses the Anthropic API, and Claude Code works once you log in to Claude on your laptop and attach again. `chainctl` reads `CLAUDE_CODE_USE_VERTEX` and the other Claude Code variables from the `env` of your Claude Code settings, then from your environment. If Claude Code uses Vertex AI but names no project, `chainctl` uses `develop.vertex.project`, then the project your Google Cloud credentials name, and refuses to create the session if it finds none. When a new session uses Vertex AI, `chainctl` says so and names where the project came from. ### Switch a session to another provider {#switch-a-session-to-another-provider} A session keeps its provider when it parks and resumes. To switch a running session between the Anthropic API and Vertex AI: 1. Press `Ctrl+]` to open the session menu, and choose **Credentials**. 2. Choose `claude`, then choose **Use Vertex AI…**, or **Use Anthropic…** on a session that uses Vertex AI. To use Vertex AI, your laptop needs Google Cloud application default credentials. 3. Choose whether the change applies to this session only or to new sessions too. For Vertex AI, check the project and region first. For new sessions, `chainctl` saves the project and region in `develop.vertex.project` and `develop.vertex.region`, or clears them when you switch to the Anthropic API. 4. In the session, exit Claude Code with `/exit`, then run `claude --continue` to resume the conversation with the new provider. Claude Code that's already running keeps the old provider, so its next request fails with a message that tells you to restart it. If `chainctl` tells you to start a new session to use the new provider, the session is too old to switch, so create a new one. ## Configure Claude Code in a session {#configure-claude-code-in-a-session} From your laptop, a session takes only your Claude credential and the Vertex AI settings described in [Use Claude through Vertex AI](#use-claude-through-vertex-ai). The rest of your Claude Code setup stays on your laptop: `chainctl` doesn't copy `~/.claude/settings.json`, `~/.claude/CLAUDE.md`, your MCP servers, plugins, skills, subagents, slash commands, or model choice. Instead, Claude Code in a session uses the following: - The session's managed settings, which include a short brief that describes the session and its tools, including how to run your repository's checks with `run-ci`. - Your repository's own `CLAUDE.md`, `.claude/` directory, and `.mcp.json` file, within the limits described in [MCP servers in a session](#mcp-servers-in-a-session). To give every session the same setup, commit it to your repository's `CLAUDE.md` and `.claude/` directory. This includes skills. For example, to use a hardened skill from [Chainguard Agent Skills](/chainguard/agent-skills/overview/) in every session, run [`chainctl skills install`](/chainguard/agent-skills/public-registry/#install-a-skill) on your laptop from your repository's top-level directory, then commit what it writes: the `.agents/skills/` and `.claude/skills/` entries and the `skills-lock.json` file. ### Tell Claude Code how to work in a session {#tell-claude-code-how-to-work-in-a-session} The session's brief explains `run-ci`, but rules you commit to your repository's `CLAUDE.md` apply in every session. To add rules for sessions, copy the following section into `CLAUDE.md`, or into `AGENTS.md` for other coding agents. Replace `$ORGANIZATION` with the name of your Chainguard organization. Claude Code also reads `CLAUDE.md` on your laptop, so the section starts by saying when its rules apply. ```markdown {title="CLAUDE.md"} ## Chainguard Workspaces sessions Follow these rules when the `run-ci` command exists, which means you're working in a Chainguard Workspaces session. - Before you push, run `run-ci`. It checks the working tree in `/work` and exits with `0` when every check passes, `1` when a check fails, and `2` when the checks couldn't run. It skips checks that only a pull request can run, so list any skipped checks when you report the result. - Put large files, such as build output or virtual environments, under `/var/tmp` or `/work`. The `/tmp` directory is held in memory. - Don't run `chainctl auth login`. `chainctl` already acts as the user in the session. - Don't ask for credentials that the session doesn't supply, such as AWS keys or a private registry token. Tell the user what you need, so they can do that work on their laptop. - After you push, wait for the pull request's checks with `chainctl checks status OWNER/REPO#NUMBER --watch --parent $ORGANIZATION`. - If a check fails, run `chainctl checks failures OWNER/REPO#NUMBER --parent $ORGANIZATION` to see why. For more of a check's log, run `chainctl checks logs OWNER/REPO#NUMBER --check CHECK --errors --parent $ORGANIZATION`. Fix the change and push again. ``` In a Starter organization, the `limited_owner` role can't look up Chainguard Checks runs, so `chainctl checks status`, `chainctl checks logs`, and the other commands that read a run don't work for it, as described in [Your Chainguard identity in a session](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session). ### Choose a model A session doesn't set a model, so Claude Code starts with its default model unless your repository's `.claude/settings.json` sets one. To use another model, start Claude Code with `claude --model`, or run `/model` in Claude Code. With Vertex AI, Claude Code's default model might not be enabled in your Google Cloud project. If Claude Code's requests fail with an error about the model, choose a model that your project has enabled. ### MCP servers in a session {#mcp-servers-in-a-session} Every session configures two Chainguard MCP servers for Claude Code: [`cg-apk`](/platform/mcp-servers/cg-apk/) and [`cg-versions`](/platform/mcp-servers/cg-versions/). They act as you through your Chainguard identity in the session, so they need no browser sign-in. They work only while that identity does, as described in [Your Chainguard identity in a session](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session), and only if you hold the `mcp.tool.call` capability in the session's organization, as the `owner` and `mcp.tool_user` roles do. The `limited_owner` role that `chainctl develop init` gives you doesn't include it, and neither do the `viewer` and `sandbox.user` roles. An MCP server with the same name in your repository's `.mcp.json` replaces either one. Claude Code also loads the `https://` MCP servers in your repository's `.mcp.json` without asking you to approve each one. The session adds no credential to their requests. The session's managed settings allow only MCP servers reached over `https://`, and settings in your repository can't allow others. MCP servers that run as local commands, and servers reached over `http://`, don't start, including ones you add in the session with `claude mcp add`. The [`cg-oci`](/platform/mcp-servers/cg-oci/) MCP server doesn't work in a session, because the session's requests to `cgr.dev` reach only the container registry. ## Choose a permission mode Claude Code in a session starts in its default permission mode, so it asks you before it runs commands or edits files. Because the session is a separate VM that holds no long-lived credentials, you might choose to let Claude Code work without asking. Claude Code normally refuses `--dangerously-skip-permissions` when it runs as `root`, as it does in a session. The session sets `IS_SANDBOX=1` so that Claude Code allows it: ```shell claude --dangerously-skip-permissions ``` Before you let an agent work unattended, consider what the session can still reach. By default, it can reach the open internet and push to its own repository. To narrow that, give the session an `egress` list, as described in [Network access](/chainguard/workspaces/session-environment/#network-access), and review its credentials, as described in [Manage session credentials](/chainguard/workspaces/credentials/). Requests beyond the session's defaults still need your approval on your terminal. If you connected your GitHub account before you created the session, and your organization allows it, the session acts on GitHub as you, without asking you first. It can push, approve other people's pull requests, and merge as you, and neither an `egress` list nor `chainctl develop creds` limits it. Before you let an agent work unattended in such a session, protect your branches, as described in [Protect your branches](/chainguard/workspaces/github-as-you/#protect-your-branches). A session has no credentials for services such as AWS, Azure, or a private package registry. Don't paste one into the session for the agent to use: any agent or command in the session can read it, and it stays in the session's snapshot when the session parks. Do that work on your laptop instead, as described in [Credentials a session can't supply](/chainguard/workspaces/credentials/#credentials-a-session-cant-supply). ## Keep Claude Code working while you're away {#keep-claude-code-working-while-youre-away} To leave Claude Code working in a session, detach and keep the session running, rather than letting it park: 1. Press `Ctrl+]` to open the session menu. 2. Choose **Detach — keep running 4h**, or **Detach — keep running for…** to set a duration of up to 24 hours. The session's host keeps the Claude credential that `chainctl` sent, so Claude Code keeps working after you close the terminal. How long it keeps working without a terminal depends on the credential: | Credential | How long Claude Code keeps working | | ---------- | ---------------------------------- | | An API key, the output of an `apiKeyHelper`, or `CLAUDE_CODE_OAUTH_TOKEN` | Until the session parks. | | Your stored Claude login | Until the access token that `chainctl` sent expires. | | A Google credential, for Vertex AI | Until the access token that `chainctl` sent expires, or until the session parks if `chainctl` sent your refresh token. | When you attach, `chainctl` says which credential it sent and how long the session keeps it. The **Credentials** screen in the session menu shows the same. The terminal you detached from keeps serving the session, through sleep and network interruptions, until the session stops running, you press `Ctrl+C`, or you close the terminal. While it serves, it keeps the session's Claude credential current: it sends new Google access tokens, reruns your `apiKeyHelper`, and sends your stored login again each time Claude Code on your laptop refreshes it. If your stored login is about to expire without being refreshed, `chainctl` tells you to run `claude` on your laptop. For long unattended work with a Claude subscription, run `claude setup-token` on your laptop. In the shell where you run `chainctl develop`, set `CLAUDE_CODE_OAUTH_TOKEN` to the token it prints, and then attach. The session keeps that token until it parks. When the session parks, its host discards the credential. When you return, `chainctl` sends it again, and Claude Code is still running in the session, with its conversation intact. While the terminal serves a detached session, it refuses new approval requests, so allow anything the agent will need ahead of time with `--with`. For a task that runs longer than a day, create a session with `--keep always`. Refer to [Keep a session running while you're away](/chainguard/workspaces/manage-sessions/#keep-a-session-running-while-youre-away). ## Run several agents in parallel {#run-several-agents-in-parallel} You can run several agents in one session, each in its own terminal, or give each agent its own session. ### Run agents in one session To start another agent in a session you're attached to, press `Ctrl+]` to open the session menu, press `c` to start a terminal, and run `claude` in the new terminal. To switch between terminals, press `Ctrl+]`, then the terminal's number. A session runs up to nine terminals. Refer to [Use several terminals in a session](/chainguard/workspaces/manage-sessions/#use-several-terminals-in-a-session) for the other keys and the status row. Open each terminal from the session menu, not by running `chainctl develop` in another terminal window on your laptop. Only one `chainctl` can be attached to a session at a time, so a second one takes over and detaches the first, as described in [Return to a session](/chainguard/workspaces/manage-sessions/#return-to-a-session). The terminals share the session's VM, including its CPUs, memory, and `/work` directory. When two agents work in `/work`, each one's edits and branch switches change the files the other is working on. To keep their work apart, give each agent after the first its own branch in a git worktree outside `/work`. For example, in the new terminal, run: ```shell git -C /work worktree add -b fix-cache /root/fix-cache cd /root/fix-cache claude ``` The `run-ci` command checks only `/work`, so an agent in another worktree can't check its work with it before it pushes. Only one `run-ci` or `chainctl develop check` can run in a session at a time, so one started while another runs fails and tells you to wait for the first to finish. ### Run agents in separate sessions With the [free monthly allowance](/chainguard/workspaces/overview/#availability-and-limits), each user can have one session at a time, so this section applies only if your organization's allowance includes more sessions, such as one arranged with your Chainguard account team. Each session is its own VM with its own copy of your repository, so you can give each agent its own session. To open more sessions on the same repository, pass `--new` and a name for each one: ```shell chainctl develop --new --name fix-auth --parent $ORGANIZATION chainctl develop --new --name fix-cache --parent $ORGANIZATION ``` Run each command in its own terminal. Each agent works on its own branch in its own session, and pushes its work when it's done. ## Check the agent's work before it pushes If your repository has a `.chainguard/ci.yaml` file with checks, Claude Code can run them in the session with `run-ci`, and the session's brief tells it how. You can also ask it to run `run-ci` before it pushes. The `run-ci` command exits with `0` when every check passes, `1` when a check fails, and `2` when the checks couldn't run. To run the same checks from your laptop without attaching, use `chainctl develop check`: ```shell chainctl develop check app/fix-auth --parent $ORGANIZATION ``` If you detached from the session and kept it running, `chainctl develop check` might fail with a message that says `the guest is held by another controller`. Run the command again, or attach to the session and run `run-ci` in it. Both commands run the checks in the session's own environment, against its working tree as it is now. They skip checks that only a pull request can run, and print a line for each, as described in [How run-ci differs from a pull request run](/chainguard/checks/run-checks/#how-run-ci-differs-from-a-pull-request-run). A pass in the session doesn't cover those checks, so have the agent report any skipped checks along with its result. A pass predicts that the pull request's checks will pass, but only [Chainguard Checks](/chainguard/checks/overview/), which runs each check in a fresh VM, decides what GitHub shows. --- ### Configure Chainguard Libraries access in container builds URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/libraries/policies-and-security/build-containers.md Last Modified: September 28, 2026 Tags: Chainguard Libraries, Integration When you build a container that installs from Chainguard Libraries, the build must authenticate to Chainguard Libraries — or to a repository manager that proxies it — without baking credentials into the final image. The package-manager configuration differs by language, but the container-build pattern is the same: pass the credentials into the build stage only for the install step that needs them. For the credentials file, target filename, and file format expected by each package manager, see the ecosystem-specific build configuration page: * [Java build configuration](/chainguard/libraries/java/build-configuration/) * [JavaScript build configuration](/chainguard/libraries/javascript/build-configuration/) * [Python build configuration](/chainguard/libraries/python/build-configuration/) * Some Chainguard Libraries for Python packages currently lack macOS-compatible wheels. When the dependency you need is available only as a Chainguard Linux wheel, local installation and testing against the Chainguard-built package must happen in a container. A working build-time secret mount is therefore part of local development on macOS, not only a CI/CD concern. The repository can be Chainguard Libraries directly or a repository manager that proxies Chainguard Libraries. The secret-mount pattern is the same in either setup. ## Do not bake credentials into image layers Avoid both of the following approaches: * Do not `COPY` a credentials file into the build stage. Deleting the file in a later `RUN` instruction does not remove it from the layer history; it can still be extracted with `docker history` or by inspecting the image layers. * Do not pass credentials through `ARG` or `ENV`. Build arguments and environment variables can be recorded in image metadata and exposed to anyone with access to the image. Also avoid printing the secret or copying it to another persistent path during the `RUN` instruction that consumes it. A secret mount protects the mounted file; it cannot protect a copy that your build writes into a layer. ## Use BuildKit secret mounts Docker BuildKit's `--secret` flag mounts a file for a single `RUN` instruction. The file is available only while that instruction runs, is not written to an image layer, and does not appear in the image history. ```dockerfile # syntax=docker/dockerfile:1 FROM cgr.dev/chainguard/python:latest-dev AS build RUN --mount=type=secret,id=netrc,target=/home/nonroot/.netrc,uid=65532,gid=65532 \ pip install -r requirements.txt ``` Supply the secret when you build the image: ```bash docker build \ --secret id=netrc,src="$HOME/.netrc" \ -t myimage . ``` For a multi-stage build, consume the secret in the build stage and copy only the application artifacts into the final stage: ```dockerfile # syntax=docker/dockerfile:1 FROM cgr.dev/chainguard/python:latest-dev AS build WORKDIR /app COPY requirements.txt . RUN --mount=type=secret,id=netrc,target=/home/nonroot/.netrc,uid=65532,gid=65532 \ python -m venv /app/venv && \ /app/venv/bin/pip install --no-cache-dir -r requirements.txt COPY . . FROM cgr.dev/chainguard/python:latest WORKDIR /app COPY --from=build /app /app ENV PATH="/app/venv/bin:$PATH" CMD ["python", "app.py"] ``` The final stage has no secret mount and does not copy the credentials file. Only the built application and its dependencies are copied from the build stage. The secret can come from a local file, a CI secret store, or another build-time secret source. Do not put it in the Dockerfile or the build context. The `id` value connects the command-line secret to the `RUN` instruction. The `target` value is the path where the package manager expects the credentials file. Both values are package-manager and image specific. ## Account for the nonroot default user Chainguard `-dev` images run as a nonroot user by default. This differs from many Debian- and Alpine-based build images, which run as `root` unless you set another user. BuildKit secret mounts are read-only and are owned by `root` unless you set the mount ownership. If the `RUN` instruction executes as the default nonroot user, the package manager cannot read the file: ```text cat: can't open '/home/nonroot/.netrc': Permission denied ``` For Chainguard's default nonroot user, set both `uid` and `gid` to `65532`: ```dockerfile RUN --mount=type=secret,id=netrc,target=/home/nonroot/.netrc,uid=65532,gid=65532 \ pip install -r requirements.txt ``` The UID/GID is consistent across Chainguard `-dev` images, but the username and home directory are not. Use the target path for the specific image in your `FROM` instruction: | Image | Default user | Home directory | Example secret target | | --- | --- | --- | --- | | `python:latest-dev` | `nonroot` | `/home/nonroot` | `/home/nonroot/.netrc` | | `node:latest-dev` | `node` | `/home/node` | `/home/node/.npmrc` | | `jdk:latest-dev` | `java` | `/home/java` | `/home/java/.m2/settings.xml` | | `maven:latest-dev` | `nonroot` | `/home/nonroot` | `/home/nonroot/.m2/settings.xml` | If a stage switches to `USER root` before installing packages, the mount target and ownership must match the user active in the `RUN` instruction that reads the credentials. In that case, a root-owned mount should use a root-accessible target such as `/root/.netrc`, not the nonroot path and ownership from the table. ## Package-manager reference The mount mechanics are shared across languages. Only the target filename and file format change: | Ecosystem | Typical credentials file | See | | --- | --- | --- | | Python | `.netrc` | [Python build configuration](/chainguard/libraries/python/build-configuration/) | | JavaScript | `.npmrc` | [JavaScript build configuration](/chainguard/libraries/javascript/build-configuration/) | | Java | Maven `settings.xml` or a Gradle init script | [Java build configuration](/chainguard/libraries/java/build-configuration/) | The ecosystem-specific page is also the source of truth for repository URLs, authentication fields, fallback behavior, lockfile handling, and package-manager-specific troubleshooting. ## CI/CD Use the same `--secret` flag in CI/CD. Read the credential from the CI system's secret store and pass it to BuildKit as an environment-backed secret rather than writing it to the workspace. ```yaml - name: Build image run: | docker build \ --secret id=netrc,env=NETRC_CONTENTS \ -t myimage . env: NETRC_CONTENTS: ${{ secrets.CHAINGUARD_PYTHON_NETRC }} ``` The same pattern applies to `.npmrc`, Maven `settings.xml`, and other package-manager configuration files: mount the file only for the install step that needs it, and keep it out of the final runtime stage. --- ### Adding custom certificates with Custom Assembly URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/custom-assembly-certs.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural, Custom Assembly Many enterprise environments use internal certificate authorities (CAs) to issue certificates for internal services. These custom certificates need to be trusted by containers that communicate with the internal services. Custom Assembly allows you to build custom certificates directly into your container images, ensuring they trust your organization's internal services without requiring manual certificate mounting at runtime. > **Note**: If you are looking for a way to embed certificates at build time, refer to our guide on [How to use incert to create container images with built-in custom certificates](/chainguard/containers/custom-assembly/incert-custom-certs/). ## Prerequisites and limitations Before getting started, you'll need the following: * Access to Chainguard's Custom Assembly tool, which is available to any organization with access to Production Chainguard Containers. * Permissions in your Chainguard organization to use Custom Assembly. * Review the [Custom Assembly permissions requirements](https://edu.chainguard.dev/chainguard/containers/custom-assembly/overview/#custom-assembly-permissions-requirements) for more information * [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) installed and configured. * One or more PEM-encoded certificate files that you want to add to your container. * Each certificate must be a PEM-encoded string of an x509v3 certificate. * Private keys must not be passed as a certificate, and will be rejected. * The total size of all inlined certificates must not exceed 50 KB. Please reach out to your account team if there are any issues with this limit. Additionally, be aware of the following limitations when adding custom certificates: * Adding new certificates is currently only available through the API and `chainctl`. * Custom certificates are included in the image's provenance attestation but are not currently listed in the SBOM. They will appear in the apko configuration attestation. ## Using `chainctl` to add custom certificates using Custom Assembly With Custom Assembly, you can add custom certificates to your Chainguard Containers using `chainctl images repos build edit` or `chainctl images repos build apply`. The process is similar to the process for [using Custom Assembly to add packages with `chainctl`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/). ### Interactive usage You can add certificates interactively by running a command like the following: ```shell chainctl images repos build edit --repo $CONTAINER ``` This will open your default text editor with the current configuration. This example includes the `--repo` argument, which points to the name of the image you want to customize. If you omit it, `chainctl` prompts you to select a container image interactively. It also prompts for the organization when you have access to more than one; pass `--parent=` to skip that prompt. In the editor, add one or more `certificates` sections with your custom certificates. Note that each entry must contain exactly one PEM block (`BEGIN CERTIFICATE` to `END CERTIFICATE`): ```yaml contents: packages: - jq - git - curl certificates: additional: - name: internal-ca content: | -----BEGIN CERTIFICATE----- BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX ... (certificate content continues) -----END CERTIFICATE----- - name: partner-ca content: | Some descriptive text about this certificate's purpose -----BEGIN CERTIFICATE----- MIIDZTCCAk2gAwIBAgIJALT1VH+nSlnTMA0GCSqGSIb3DQEBCwUAMEYxCzAJBgNV ... (certificate content continues) -----END CERTIFICATE----- ``` Note that each certificate entry requires: * `name`: A descriptive name for the certificate (used for the filename). * `content`: The certificate in PEM format, including the `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` markers. Optionally, you can also include descriptive text before the certificate block to document its purpose. Save your changes and close the editor. `chainctl` will display the changes and prompt for confirmation before applying them. This adds (concatenates) the provided inline certificates to the default truststore of the image in `/etc/ssl/certs/ca-certificates.crt`. It also writes them individually to `/usr/local/share/ca-certificates`, making them available for workflows that involve manually running `update-ca-certificates`. Alternatively, you can use the `--with-certificates` flag to pre-populate the `certificates.additional` section from a selected `.pem` file. Here is an example invocation that uses a `.pem` file named `certificates.pem`: ```shell chainctl images repos build edit --with-certificates certificate.pem --repo $CONTAINER ``` As with the previous example, this will open up the configuration in your default editor. After saving and closing the editor, `chainctl` will prompt you to confirm the changes before applying them. ### Non-interactive usage To add certificates without any interactivity, you can use the `apply` subcommand. This requires you to supply a YAML configuration file listing the certificates, like the example created with this command: ```shell cat > cert.yaml < -----END CERTIFICATE----- EOF ``` Then include this file in the `apply` command by adding the `-f` argument: ```shell chainctl image repos build apply --repo $CONTAINER -f cert.yaml --yes ``` This command will again ask you to confirm that you want to apply the new configuration. To make this example completely declarative, this example includes `--yes` to automatically confirm the changes: ```output Image configuration changes: Legend: + to add, ~ to change, - to remove certificates.additional (+1, ~0, -0, final: 1): + Name: ca1 Fingerprint: CA:2F:ED:41:E8:A2:C9:D0:25:A2:8E:E5:0C:95:9B:E2 15:10:F4:1E:72:B7:31:AA:B0:51:CD:AC:E9:F2:0C:51 SANs: (none) Issuer: O=Internet Widgits Pty Ltd,ST=Some-State,C=AU Expires: 2036-03-08 19:10:43 UTC PublicKey: RSA 2048-bit (ee878ed5) Serial: 53:99:5e:47:8c:27:39:60:7b:fb:2b:17:77:ae:a2:01:03:1c:93:31 ExtKeyUsage: (none) KeyUsage: (none) BasicConstraints: CA:TRUE Subject: O=Internet Widgits Pty Ltd,ST=Some-State,C=AU Plan: 1 to add, 0 to change, 0 to remove ``` The non-interactive approach is particularly useful for CI/CD pipelines and automation. You can also perform bulk operations across multiple repos. Refer to [Making bulk changes across repos](/chainguard/containers/custom-assembly/custom-assembly-chainctl/#making-bulk-changes-across-repos) to learn more. ## Verifying that certificates were added Before following the steps below, ensure you have [`crane` installed](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md#installation). You can verify that your certificates are present in the system trust bundle by using `crane` to export the image filesystem, extract the system CA bundle from the archive, and write it to a local file for inspection: ```shell crane export cgr.dev/my-org/my-custom-image:latest - | tar -xOf - etc/ssl/certs/ca-certificates.crt > ca-certificates.crt ``` After running this command, inspect the copied file locally to confirm that your certificate is present. ## Chainguard-managed certificate bundles Customers have the ability to add Chainguard-managed certificate bundles for certain regulated environments to their Custom Assembly images. These certificates are bundled into discrete packages that you can apply to customized images in bulk. You can add them to a customized image the same way you would add any other package with Custom Assembly, including with [`chainctl`](/chainguard/containers/custom-assembly/custom-assembly-chainctl/) or in the [Chainguard Console](/chainguard/containers/custom-assembly/custom-assembly-console/). The following example YAML configuration shows two certificate bundle packages added to an image: ```yaml contents: packages: - ca-certificates-aws-rds-global - ca-certificates-dod-wcf . . . ``` As of this writing, Chainguard offers the following certificate bundle packages for DoD and AWS environments: * `ca-certificates-aws-rds-global` — Any commercial AWS Region * `ca-certificates-aws-rds-govcloud-global` — AWS US GovCloud * `ca-certificates-dod-eca` — US DoD External Certificate Authority (ECA) PKI certificates * `ca-certificates-dod-wcf` — US DoD Web Content Filtering (WCF) PKI certificates * `ca-certificates-mozilla-eol-20251003` — [Obsolete CA certificates](https://wiki.mozilla.org/CA/Root_CA_Lifecycles) removed by Mozilla in 2026 ## Custom Assembly and Java truststores In addition to the system truststore, Custom Assembly’s custom certificates update Java truststores automatically. As outlined previously, when you define `certificates.additional` in a Custom Assembly config, the certificates are appended to the system bundle at `/etc/ssl/certs/ca-certificates.crt`. The same certificates are also added to the Java truststore at `/etc/ssl/certs/java/cacerts`, if that file exists in the image (for example, because the image includes Chainguard’s Java truststore package or a JDK/JRE that exposes it there). There is no extra configuration flag for Java: the build looks for a Java truststore in that location and, if found, appends the custom certificates to it in the same way it does for the OS store. This works even if you add Java to a non-Java base image with Custom Assembly; once the Java truststore file is present, it gets updated. ## Troubleshooting ### Certificate validation errors If you receive validation errors when adding certificates: * Verify that your certificate file contains only valid PEM-encoded certificate data * Check that there is no private key material in the file * Ensure the certificate has not expired * Verify that the total size of all certificates added is under 50KB ### Applications not trusting custom certificates If applications within your container are not trusting your custom certificates: * Verify the certificate was added successfully by checking `/etc/ssl/certs/ca-certificates.crt` * Check that the certificate file exists in `/usr/local/share/ca-certificates/` * Ensure your application is configured to use the system truststore ## Alternative: Using `incert` for certificate injection For scenarios where you need to add certificates to an existing image without using Custom Assembly, you can use [`incert`](/chainguard/containers/custom-assembly/incert-custom-certs/), an open source tool from Chainguard. However, we recommend using Custom Assembly over `incert` whenever possible, as this approach provides: * Automatic rebuilds when the base image is updated * Integration with Chainguard's security patching lifecycle * Provenance attestation for audit and compliance * No need to maintain your own build pipeline --- ### Requesting new Chainguard resources URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/reference/request-resources.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Chainguard Console The Chainguard Console includes the Requests section where customers can submit and track requests for resources that Chainguard doesn't currently offer. This improves transparency around which technologies Chainguard is working to build and helps minimize duplicate build requests. This guide provides an overview of how to submit a request for a new resource to Chainguard, as well as the limitations on what resources can be built.

Note: The Requests section is in beta.

## Prerequisites In order to submit requests for new resources in the Chainguard Console, you must be part of a [verified organization](/platform/administration/iam-organizations/verified-orgs/). Users with access to only Chainguard's Free tier of container images will not be able to submit requests. ## The requests section To begin, navigate to the [**Requests** section of the Chainguard Console](https://console.chainguard.dev/org/chainguard/requests/active). This section includes three tabs: * **My requests**, showing any requests you've upvoted or initiated, as well as any initiated by other members of your organization * **Active builds**, which includes any resources the Chainguard team is in the process of building * **Community requests**, showing all the resource requests not currently being built Each of these tabs contains a table with the following columns: * A column at the far left where you can upvote requests by clicking the upward-pointing arrow. * Click the arrow again to remove an upvote. * The **Name** of the resource requested. * The request's **Status**. This can be one of the following: * **Future** * **In progress** * **Paused** * **Reviewing** * **Won't build** * If the original requester included them, **Project Details** that describe the resource being requested Additionally, the **Community requests** tab includes a **Demand** column, showing what demand percentile the request falls into. Also, the **Active builds** tab includes an **Estimated delivery** column that shows when Chainguard expects the resource to be available. ## Requesting new resources Within each tab in the **Requests** section of the console is a button labeled **New request**. Click this button to open a modal window where you can enter the details of your resource request. This window contains several fields. The following are required for you to submit a request: * **Type** — Specify the type of resource you're requesting, either an **Image**, a **Package**, or a **Helm chart**. * **Name** — Enter the existing public name of the resource you're requesting. * **Open Source project repository** — Include a link to a public repository containing the source code for the open source project you're requesting. * **Include FIPS-validated variant?** — Select either **Yes** or **No**. * Additionally, you must confirm that none of Chainguard's existing artifacts or requests match your request. There are also a few optional fields you can fill in: * **Project Dockerfile** — Include a URL to a Dockerfile for the application that includes a build definition; if the project does not include a Dockerfile, provide as much build information as possible. * **Dependencies** — Specify any other resources the artifact you're requesting needs to function. * **Deployment method** — Specify how you plan to use this resource; for example, if you're requesting an image, you can specify that you plan to run it using Helm, Cloud Run, Argo, Docker, etc. Although these fields aren't required, filling them in provides Chainguard with more context for your request and can help speed up the approval process. After filling out the form, click **Request image** to submit your request. Your request will then appear in both the **My requests** and **Community requests** tabs, and other customers will be able to upvote it. The Chainguard team will then review the request and prioritize it based on demand, as determined by the number of upvotes the request has received from users. ## Limitations There are a few limitations you should consider before submitting a new request: * Chainguard will not build resources based on proprietary code. * Note that some projects distributed under open source licenses have strict terms that prevent Chainguard from building artifacts based on them. * If a project is no longer receiving updates or releases, Chainguard typically won't build it since there aren't reliable security fixes upstream. * There are cases where Chainguard cannot fulfill a FIPS request and be FIPS compliant. In such cases the standard variant can often still be built but the FIPS variant will get marked as **Won't build**. Finally, be aware that requesting that Chainguard build a software artifact does not mean it will automatically be accessible to your organization. Once the resource is built, you can reach out to our sales team to add it to your organization; alternatively, if your organization has [Catalog pricing](/chainguard/containers/reference/pricing/) enabled, you can add it yourself after it's built. Note that the **Requests** section is for resources Chainguard doesn't build at all. If Chainguard already builds the container image and you need a version that isn't listed, or the image isn't in your organization's catalog yet, refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) instead. --- ### How to sync images from Chainguard's registry to Harbor URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/harbor.md Last Modified: September 28, 2026 Tags: Chainguard Containers [Harbor](https://goharbor.io) is an open-source artifact registry. It's designed to securely store, manage, and distribute OCI artifacts, including container images and Helm charts by enforcing policies like vulnerability scanning, image signing, and role-based access control. Harbor delivers enterprise-grade compliance, performance, and interoperability across platforms like Kubernetes and Docker, all accessible via a web UI or RESTful API. This tutorial outlines how to sync images from Chainguard's registry to a Harbor instance. It describes two approaches: 1. A [proxy cache](https://goharbor.io/docs/2.1.0/administration/configure-proxy-cache/), which configures a Harbor project as a pull through cache. 2. A [replication rule](https://goharbor.io/docs/2.1.0/administration/configuring-replication/create-replication-rules/), which copies images to a Harbor project. ## Prerequisites You need the following in order to complete this tutorial: * Administrative privileges over a Harbor instance. Refer to the [official Harbor documentation](https://goharbor.io/docs/2.13.0/) to learn how to set this up. * `chainctl` — Chainguard's command-line interface — installed on your local machine. If you don't have `chainctl` installed, refer to our [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) guide to set this up. * Access to an account with permissions to pull Chainguard container images from your organization's repository within the Chainguard registry. This is necessary, as you will create a pull token for Harbor to use to access the registry, and you cannot generate a pull token that grants broader access than your own. ## Create a registry endpoint Before configuring a proxy cache or replication rule, you must create a registry endpoint for the Chainguard registry. If you don't already have one, generate a pull token in your organization: ```shell chainctl auth configure-docker --pull-token ``` This returns username and password credentials: ```Output To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" ``` Take note of these values, as you'll need them shortly. Next, open up the Harbor UI and perform the following steps: 1. Navigate to **Administration > Registries** 2. Click the **+ NEW ENDPOINT** button. 3. Configure the endpoint with these details: * **Provider** — There is no specific integration with the Chainguard registry, so here you must select the generic `Docker Registry` provider type. * **Name** — This is used to refer to your repository. You can choose whatever name you like here, but this guide's examples use the name `cgr.dev`. * **Endpoint URL** — This value **must** be `https://cgr.dev`. * **Access ID** — Enter the username value returned by the `chainctl auth` command you just ran. * **Access Secret** — Here, enter the password value returned by the `chainctl auth` command. 4. Click the **TEST CONNECTION** button to ensure that Harbor can reach `cgr.dev` successfully. 5. Click **OK** to create the endpoint. After creating the endpoint, you can move on to creating a proxy cache. ## Create a proxy cache A [*proxy* cache](https://goharbor.io/docs/2.1.0/administration/configure-proxy-cache/) allows Harbor to proxy and cache images from the Chainguard registry. To configure a cache, perform the following steps in the Harbor UI: 1. Navigate to **Projects**. 2. Click the **+ NEW PROJECT** button. 3. Create the project with these details: * **Project Name** — This can be whatever you like. This guide uses the name `cgr-proxy`. * **Access Level** — Set this as required for your organization. Checking the **Public** box means `docker login` is not required. * **Project quota limits** — leave at `-1` for unlimited or set as required. * **Proxy Cache** — Toggle this on. * **Endpoint** — Choose the endpoint you created in the previous step. * **Bandwidth** — leave at `-1` for unlimited or set as required. 4. Click **OK** Following that, you can pull images from the Harbor project like so: ```shell docker pull $HARBOR_URL/cgr-proxy/$ORGANIZATION/$IMAGE:$TAG ``` Be sure to replace the placeholder values (`$HARBOR_URL`, `$ORGANIZATION`, `$IMAGE`, and `$TAG`) to reflect your own setup. ## Create a replication rule A [*replication rule*](https://goharbor.io/docs/2.1.0/administration/configuring-replication/create-replication-rules/) is an alternative approach to a proxy cache. This section outlines how to configure a replication rule that copies images from the Chainguard registry to a Harbor project. First, create a project: 1. Navigate to **Projects**. 2. Click the **+ NEW PROJECT** button. 3. Create the project with these details: * **Project Name** — This can be whatever you like. This guide uses the name `cgr-mirror`. * **Access Level** — Set this as required for your organization. Checking the **Public** box means `docker login` is not required. * **Project quota limits** — leave at `-1` for unlimited or set as required. * **Proxy Cache** — Leave this toggled off. 4. Click **OK** Then, perform the following steps to create a new replication rule: 1. Navigate to **Administration > Replications**. 2. Click the **+ NEW REPLICATION RULE** button. 3. Create a new rule with these details: * **Name** — This can be whatever you like. This guide uses the name `cgr-mirror`. * **Replication mode** — Must be `Pull-based`. * **Source registry** — Specify the `cgr.dev` endpoint. * **Source resource filter > Name** — (Optional) You can ensure you only select images in your organization by setting this to `/*`. * **Destination > Namespace** — This should be the project you created before. * **Destination > Flattening** — Set to `Flatten All Levels`. This removes the organization name from the path. * **Trigger Mode** — Set to `Scheduled` to run the replication regularly or `Manual` to trigger on an ad hoc basis. * **Bandwidth** — leave at `-1` for unlimited or set as required. * **Override** — Ensure this is enabled so that tags in the destination project are replaced when they change. 4. Click the **Save** button. To trigger the replication manually, select the `cgr-mirror` rule in the table and click the **REPLICATE** button. Then, navigate to **Projects > cgr-mirror** and observe images populating. You should be able to pull images from the project like this: ```shell docker pull $HARBOR_URL/cgr-mirror/$IMAGE:$TAG ``` Again, be sure to replace this command's placeholder values as necessary. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by checking out our [Containers documentation](/chainguard/containers/overview/). Additionally, if you'd like to learn more about Harbor, we encourage you to refer to the [official Harbor documentation](https://goharbor.io/docs). --- ### glibc vs. musl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/glibc-vs-musl.md Last Modified: October 8, 2026 Tags: Chainguard Containers, cheatsheet Over the years, various implementations of the [C standard library](https://en.wikipedia.org/wiki/C_standard_library) — such as the [GNU C library](https://www.gnu.org/software/libc/), [musl](https://musl.libc.org/about.html), [uClibc-ng](https://www.uclibc-ng.org/), and many others — have emerged with different goals and characteristics. These various implementations exist because the C standard library defines the required functionality for operating system services (such as file input/output and memory management) but does not specify implementation details. Among these implementations, the GNU C Library ([glibc](https://www.gnu.org/software/libc/)) and [musl](https://musl.libc.org/about.html) are among the most popular. When developing [Wolfi](https://www.chainguard.dev/supply-chain-security-101/wolfi-overview), the "undistro" on which all of Chainguard's container images are built, Chainguard elected to have it use glibc instead of another implementation like musl. This conceptual article aims to highlight the differences between these two implementations within the context of Chainguard's choice of using glibc over musl as the default implementation for the Wolfi undistro. > **Note**: Several sections of this guide present data about the differences between glibc and musl across various categories. You can recreate some of these examples used to find this data with the Dockerfiles and C program files hosted in the `glibc-vs-musl` directory of the [Chainguard Academy Containers Demos repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/glibc-vs-musl). ## High-level differences between glibc and musl The GNU C Library ([glibc](https://www.gnu.org/software/libc/)), driven by the [GNU Project](https://www.gnu.org/gnu/thegnuproject.en.html), was first released in 1988. glibc aims to provide a consistent interface for developers to help them write software that will work across multiple platforms. Today, glibc has become the default implementation of the C standard library across the majority of Linux distributions, including Ubuntu, Debian, Fedora, and even Chainguard's Wolfi. [musl](https://musl.libc.org/about.html) (pronounced "muscle") was first released in 2011 as an alternative to glibc. The goal of musl is to strive for "simplicity, resource efficiency, attention to correctness, safety, ease of deployment, and support for UTF-8/multilingual text." While not as widely used as glibc, some Linux distributions are [based on musl](https://wiki.musl-libc.org/projects-using-musl), the most notable being Alpine Linux. The following table highlights some of the main differences between glibc and musl. | Criteria | glibc | musl | | ------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------- | | First Release | 1988 | 2011 | | License | GNU Lesser General Public License (LGPL) | MIT License (more permissive) | | Binary Size | Larger Binaries | Smaller Binaries | | Runtime Performance | Optimized for performance | Slower performance | | Build Performance | Slower | Faster | | Compatibility | POSIX Compliant + GNU Extensions | POSIX Compliant | Memory Usage | Efficient, higher memory usage | Potential performance issues with large memory allocations (e.g. Rust) | | Dynamic Linking | Supports lazy binding, unloads libraries | No lazy binding, libraries loaded permanently | | Portability Issues | Fewer portability issues, widely used | Potential issues due to different system call behaviors | | Python Support | Fast build times, supports precompiled wheels | Slower build times, often requires source compilation | | NVIDIA Support | Supported by NVIDIA for CUDA | Not supported by NVIDIA for CUDA | Be aware that binaries are not compatible between Alpine and Wolfi. You **should not** attempt to copy Alpine binaries into a Wolfi-based container image. > **Note**: As mentioned previously, several of the remaining sections in this guide present data about the differences between glibc and musl across various categories. You can recreate some of these examples by following the same procedure of setting creating and testing container images based on the Dockerfiles and program files relevant to the example you're exploring. You can find the appropriate files in the `glibc-vs-musl` directory of the [Chainguard Academy Containers Demos repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/glibc-vs-musl). ## Library and binary size musl is significantly smaller than glibc. A primary reason for this is due to the differing approaches adhering to the Portable Operating System Interface ([POSIX](https://en.wikipedia.org/wiki/POSIX)). POSIX is a family of standards specified by the IEEE Computer Society to ensure consistent application behavior across different systems. musl adheres strictly to POSIX standards without incorporating additional extensions. glibc, while adhering to the POSIX standards, includes additional GNU-specific extensions and features. These extensions provide enhanced functionality and convenience, offering developers comprehensive tools. As an example, glibc provides support for Intel Control Enforcement Technology (CET) when running on compatible hardware, providing control flow security guarantees at runtime — a feature that doesn't exist on musl. However, this extensive functionality results in larger library sizes for glibc, with glibc's [function index](https://www.gnu.org/software/libc/manual/html_node/Function-Index.html) listing over 1700 functions. You might notice the decreased binary size for musl in a simple `hello world` program, whether linked statically or dynamically. As we can observe, since musl is much smaller than glibc, the statically linked binary is much smaller on Alpine. In the case of dynamic linking, the binary size is smaller for musl compared to glibc because of its simplified implementation of the dynamic linker as outlined in [musl project's design philosophy](https://wiki.musl-libc.org/design-concepts). The following table shows the difference in binary size of statically and dynamically linked `hello world` programs: | Distro | Static linking | Dynamic linking | | ------------------------- | -------------- | --------------- | | Alpine (musl) binary size | 132K | 12K | | Wolfi (glibc) binary size | 892K | 16K | The smaller the binary size, the better the system is at debloating. You can find the Dockerfiles used in this setup in the [`binary-bloat` directory of this guide's example's repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/glibc-vs-musl/binary-bloat). ## Portability of applications The *portability* of an application refers to its ability to run on various hardware or software environments without requiring significant modifications. In practice, many developers target glibc specifically rather than a generic POSIX C library, much like writing scripts for bash rather than a POSIX-compliant shell. Consequently, developers can encounter portability issues when moving an application from one libc implementation to another. That said, [Hyrum's Law](https://www.hyrumslaw.com/) reminds us that achieving perfect portability is tough. Even when you design an application to be portable, it might still unintentionally depend on certain quirks of the environment or libc implementation. ## Building from source performance We've compared the build from source performance for individual projects using the [musl-gcc compiler](https://wiki.musl-libc.org/getting-started.html) toolchain used in Alpine and [gcc compiler](https://gcc.gnu.org/) toolchain used in [Chainguard Wolfi](https://github.com/wolfi-dev/os/blob/main/gcc.yaml). We compare the build from source times of both ecosystems. The following table highlights the results of this comparison by highlighting the compilation times between Wolfi (glibc) and musl-gcc. The shorter the build time, the better the system's performance. | Repository | Wolfi compilation time | musl-gcc compilation | Build successful with musl? | | ------------ | ---------------------- | -------------------- | ------------------------------- | | binutils-gdb | 18m 3.11s | *
| No - C++17 features unsupported | | Little-CMS | 29.44s | 24.13s | Yes | | zlib | 11.48s | 9.37s | Yes | | libpcap | 8.19s | 5.61s | Yes | | gmp | 98.91s | 99.38s | Yes | | openssl | 849.08s | 671.92s | Yes | | curl | 92.33s | 79.15s | Yes | | usrsctp | 55.39s | 48.38s | Yes | You can find the Dockerfiles used in this setup in the [`build-comparison` directory of this guide's example's repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/glibc-vs-musl/build-comparison). This table shows that musl-gcc has **a lower compilation time** than gcc on Wolfi for these projects **if it can build the project successfully.** musl-gcc fails to compile binutils-gdb because it conforms to POSIX standards, and binutils-gdb uses certain code features that are not conformant to these standards. ## Python builds A common way to use existing Python packages is through precompiled binary wheels distributed from the Python Package Index ([PyPI](https://pypi.org/)). Python wheels are typically built against glibc; because musl and glibc are different implementations of the C standard library, binaries compiled against glibc may not work correctly or at all on systems using musl. Due to this incompatibility, PyPI defaults to compiling from source on Alpine Linux. This implies you need to **compile all the C source code** required for every Python package. This also means you must determine every system library dependency needed to build the Python package from the source. For example, you have to install the dependencies beforehand, using `apk add ` before you perform `pip install X`. The following table shows PIP install times across Alpine (musl) and Wolfi (glibc). You can find the Dockerfiles used in this setup in the [`python-build-comparison` directory of this guide's example's repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/glibc-vs-musl/python-build-comparison). | Python Package | Alpine (musl) | Wolfi (glibc) | | ------------------ | ------------- | ------------- | | Matplotlib, pandas | 21m 30.196s | 0m 24.3s | | tensorflow | 104m 21.904s | 2m 54.5s | | pwntools | 29m 45.952s | 21.5s | As this table shows, this results in long build times whenever you want to use Python-based applications with musl. Take the example of `pwntools`, a Python package that allows for the construction of exploits in software. When using glibc-based distros, the installation would be in the form `pip3 install pwntools`. To install `pwntools` on a musl-based distro (such as Alpine), the [Dockerfile](https://github.com/chainguard-dev/edu-images-demos/blob/main/glibc-vs-musl/python-build-comparison/alpine/alpine-pwntools/Dockerfile) is much more complicated: ```dockerfile FROM alpine:latest # Prebuilt Alpine packages required to build from source RUN apk add --no-cache musl-dev gcc python3 python3-dev libffi-dev libcap-dev make curl git pkgconfig openssl-dev bash alpine-sdk py3-pip RUN python -m venv my-venv RUN my-venv/bin/python -m pip install --upgrade pip # Build from source cmake for latest version RUN git clone https://github.com/Kitware/CMake.git && cd CMake && ./bootstrap && make && make install ENV PATH=$PATH:/usr/local/bin # Build from source Rust for latest version RUN curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf > setup-rust.sh RUN bash setup-rust.sh -y ENV PATH=$PATH:/root/.cargo/bin # Finally install pwntools RUN pip3 install pwn ``` As this Dockerfile shows, `pwntools` requires a set of other packages. These in turn require the most up-to-date versions of Rust and cmake, which are not available in the default prebuilt packages in Alpine. You would have to build both from source before installing the Python dependencies and, finally, `pwntools`. Such dependencies will have to be identified iteratively through a process of trial and error while building from source. ## Runtime performance Time is critical. One common bottleneck occurs when allocating large chunks of memory repeatedly. We compare this memory allocation performance between Wolfi and the latest Alpine [this benchmark script](https://github.com/chainguard-dev/edu-images-demos/blob/main/glibc-vs-musl/musl-performance-issues/allocations-slowdown.sh). The benchmark uses JSON dumping, a highly memory intensive operation. | Runtime | Alpine (musl) | Wolfi (glibc) | | ---------------------------- | ------------- | ------------- | | Memory Allocations Benchmark | 102.25 sec | 51.01 sec | This table highlights how excessive memory allocations can cause musl (used by Alpine) to perform up to **2x slower** than glibc (used by Wolfi). A memory-intensive application needs to be wary of performance issues when migrating to the musl-alpine ecosystem. Apart from memory allocations, multi-threading has also been problematic for musl, as shown in various [GitHub issues](https://github.com/rust-lang/rust/issues/70108). glibc provides a thread-safe system, while musl is not thread-safe. The POSIX standard only requires stream operations to be atomic; there are no requirements on thread safety, so musl does not provide additional thread-safe features. This means unexpected behavior or race conditions can occur during multiple threads. We used a Rust script (referenced from the [GitHub issue](https://github.com/rust-lang/rust/issues/70108)) to test single-thread and multi-thread performance on Alpine (musl) and Wolfi (glibc). The next table shows performance benchmarks across single-threaded and multi-threaded Rust applications. | Runtime | Alpine (musl) | Wolfi (glibc) | | ----------------------------- | ------------- | ------------- | | Single-thread (avg of 5 runs) | 1735 ms | 1300 ms | | Multi-thread (avg of 5 runs) | 1178 ms | 293 ms | Alpine (musl) has the worse performance out of the two, taking around 4x more time for multi-thread when compared to Wolfi (glibc). As discussed previously, the real source of thread contention is in the `malloc` implementation of musl. Multiple threads may allocate memory at once, or free memory may be allocated to other threads. Therefore, the thread synchronization logic is a bottleneck for performance. ## Conclusion glibc and musl both serve well as C implementations. Our goal for this article is to explain Chainguard's rationale for choosing to use glibc for Wolfi. We believe that's what made the most sense for our project, but you should continue your own research to determine if one C implementation would suit your needs better than another. If you spot anything we've overlooked regarding glibc or musl or have additional insights to contribute, please feel free to raise the issue in [chainguard-dev/edu](https://github.com/chainguard-dev/edu). We welcome further discussion on weaknesses in glibc, such as its larger codebase and complexity compared to musl. Additionally, insights into the intricacies of compiler toolchains for cross-compilation are welcomed, especially when dealing with glibc and musl. ## Additional references For more information, we recommend the following resources: * [Understanding thread stack sizes and how Alpine is different](https://ariadne.space/2021/06/24/understanding-thread-stack-sizes-and.html): Explains why applications built for glibc can encounter issues on musl. * [The tragedy of gethostbyname](https://ariadne.space/2022/03/26/the-tragedy-of-gethostbyname.html): Details why legacy libc DNS APIs are often unreliable across systems. --- ### Getting started with the Python Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/python.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's Python container images provide a more secure foundation for Python applications through distroless design, containing significantly fewer CVEs compared to traditional Python images. These production-ready images are optimized for building and running Python workloads. Two variants of Chainguard Python images are available: a minimal runtime image containing only Python and its standard library, and a `-dev` variant that includes pip and a shell for development purposes. Since most Python applications require third-party packages, the recommended approach is using a [multi-stage Docker build](https://docs.docker.com/build/building/multi-stage/) with the `-dev` image for dependency installation and the minimal image for runtime. This guide covers two examples of Python container images based on Wolfi as a runtime. The first uses the minimal image containing just Python, which has access to the [Python standard library](https://docs.python.org/3/library/). The second demonstrates a multi-stage build.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Example 1 — Minimal Python Chainguard Container In this example, you'll build and run a distroless Python Chainguard Container in a single-stage build process. You'll first make a demonstration app, then build and run it. ### Step 1: Setting up a demo application Start by creating a basic command-line Python application to serve as a demo. This app generates random octopus facts based on a list in a text file, using the `random` module from the Python standard library. First, create a directory for your app. You can use any meaningful name and path; this example uses `octo-facts/`. ```shell mkdir ~/octo-facts/ && cd $_ ``` Create a new file named `main.py` to serve as the application entry point. The following Python script defines a light CLI app that takes in a text file, `facts.txt`, and returns a random line from that file. ```shell cat > main.py <<'EOF' '''Import random module to implement random.choice() function''' import random def random_line(text): '''Opens and reads lines of a UTF-8 encoded file, returning a random line''' with open(text, 'r', encoding='UTF-8') as file: line = file.readlines() return random.choice(line) def main(): '''Prints random line from facts.txt; verify your path''' print(random_line('facts.txt')) if __name__ == "__main__": main() EOF ``` Next, pull down the `facts.txt` file with `curl`. [Inspect the URL](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/octo-facts/facts.txt) before downloading it to ensure it is safe to do so. Make sure you are still in the same directory where your `main.py` script is. ```shell curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/octo-facts/facts.txt ``` At this point, you can run the script and be sure you are satisfied with the functionality. We recommend that you use a Python programming environment. Determine whether your system uses the `python` or `python3` command. ```shell python main.py ``` You should receive the output of a randomized octopus fact. ``` The wolfi octopus was discovered in 1913. ``` The demo application is now ready. In the next step, you’ll create a Dockerfile to run your app. ### Step 2: Creating the Dockerfile For this single-stage build, you'll only need one `FROM` line in your Dockerfile. The resulting container is based on the distroless Python Wolfi container image, which means it doesn’t come with a package manager or even a shell. Begin by creating a Dockerfile. The following Dockerfile: 1. Starts a build stage based on the `python:latest` image; 2. Declares the working directory; 3. Copies the script and the text file that's being read; 4. Sets up the application as entry point for this container. ```shell cat > Dockerfile <<'EOF' FROM cgr.dev/chainguard/python:latest WORKDIR /octo-facts COPY main.py facts.txt ./ ENTRYPOINT [ "python", "/octo-facts/main.py" ] EOF ``` You can now build the container image. If you receive an error, try again with `sudo`. ```shell docker build . --pull -t octo-facts ``` Once the build is finished, run the container. ```shell docker run --rm octo-facts ``` And you should get output similar to what you got before, with a random octopus fact. ``` Octopuses can breathe and see through their skin. ``` You have successfully completed the single-stage Python Chainguard Container. At this point, you can continue to the [multi-stage example](#example-2-multi-stage-build-for-python-chainguard-container) or [advanced usage](#advanced-usage). ## Example 2 — Multi-stage build for Python Chainguard Container In this example, you'll build and run a multi-stage Python Chainguard Container. The build image includes pip and a shell, and the final distroless image leaves out these development tools for production. ### Step 1: Setting up a demo application Start by creating a Python application that takes in an image file and converts it to ANSI escape sequences on the CLI to render an image. To begin, create a directory for your app. You can use any meaningful name and path that resonates with you; this example uses `linky/`. ```shell mkdir ~/linky/ && cd $_ ``` First, write out the requirements for your app in a file named `requirements.txt`. This installs version 0.2.2 of [climage](https://pypi.org/project/climage/), which converts images into ANSI escape sequences: ```shell cat > requirements.txt <<'EOF' climage==0.2.2 EOF ``` Create a file named `linky.py` to hold your Python code. It defines a CLI app that takes in an image file, `linky.png`, and prints a representation of that file to the terminal: ```shell cat > linky.py <<'EOF' '''import climage module to display images on terminal''' from climage import convert def main(): '''Take in PNG and output as ANSI to terminal''' output = convert('linky.png', is_unicode=True) print(output) if __name__ == "__main__": main() EOF ``` Next, pull down the `linky.png` image file with `curl`. [Inspect the URL](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/linky/linky.png) before downloading it to ensure it is safe to do so. Make sure you are still in the same directory where your `linky.py` script is. ```shell curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/linky/linky.png ``` With your demo application ready, you can move on to the container stage. ### Step 2: Creating the Dockerfile To keep the final container distroless while still being able to install dependencies with pip, the build consists of two stages: first, you’ll build the application using the `python:latest-dev` image variant, a Wolfi-based image that includes pip and other useful tools for development. Then, you’ll create a separate stage for the final image. The resulting container is based on the distroless Python Wolfi container image, which means it doesn’t come with pip or even a shell. Begin by creating a Dockerfile. The following Dockerfile: 1. Starts a new build stage based on the `python:latest-dev` container image and calls it `builder`; 2. Creates a new virtual environment to cleanly hold the application's dependencies, using `--without-pip` to keep pip out of the environment and therefore out of the final image; 3. Copies `requirements.txt` from the current directory to the `/linky` location in the container; 4. Runs `pip --python /linky/venv/bin/python install --no-cache-dir -r requirements.txt` to install dependencies, where `--python` points the builder's own pip at the virtual environment; 5. Starts a new build stage based on the `python:latest` image; 6. Copies the dependencies in the virtual environment from the builder stage, and the source code from the current directory; 7. Sets up the application as the entry point for this container. Write this configuration to your own Dockerfile: ```shell cat > Dockerfile <<'EOF' FROM cgr.dev/chainguard/python:latest-dev AS builder ENV LANG=C.UTF-8 ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 ENV PATH="/linky/venv/bin:$PATH" WORKDIR /linky RUN python -m venv --without-pip /linky/venv COPY requirements.txt . RUN pip --python /linky/venv/bin/python install --no-cache-dir -r requirements.txt FROM cgr.dev/chainguard/python:latest WORKDIR /linky ENV PYTHONUNBUFFERED=1 ENV PATH="/venv/bin:$PATH" COPY linky.py linky.png ./ COPY --from=builder /linky/venv /venv ENTRYPOINT [ "python", "/linky/linky.py" ] EOF ``` You can now build the container image. If you receive a permission error, try running under `sudo`. ```shell docker build . --pull -t linky ``` Once the build is finished, run the image with: ```shell docker run --rm linky ``` And you should get output similar to what you got before, with a printed Linky on the command line. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose Python Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### chainctl checks command reference URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/chainctl-checks-reference.md Last Modified: October 15, 2026 Tags: Chainguard Checks, chainctl, Reference The `chainctl checks` commands read your pull requests' results from your terminal, start and rerun checks, summarize your checks' history, and validate your configuration. This page lists each command's arguments, flags, exit codes, and JSON output. For examples of the commands in use, refer to [Inspect and run checks with chainctl](/chainguard/checks/inspect-and-run-checks/). During the beta, the `chainctl checks` and `chainctl develop` commands don't appear in `chainctl --help` or in the [chainctl reference](/platform/chainctl/chainctl-docs/chainctl/). You can still run them, and `--help` on either one shows its full help. For example, run `chainctl checks status --help`. You can also type `chainctl ci` instead of `chainctl checks`. If `chainctl` doesn't recognize a command or flag on this page, [update chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl). The examples on this page use `acme/app#123` for a pull request. Replace `$ORGANIZATION` with the name of your Chainguard organization. ## Commands | Command | What it does | | ------- | ------------ | | [`chainctl checks status`](#chainctl-checks-status) | Shows a run and whether each of its checks passed. | | [`chainctl checks logs`](#chainctl-checks-logs) | Prints a run's check logs, or part of one. | | [`chainctl checks trace`](#chainctl-checks-trace) | Shows the programs that each step of a check started. | | [`chainctl checks events`](#chainctl-checks-events) | Prints the events that one attempt at a check recorded. | | [`chainctl checks describe`](#chainctl-checks-describe) | Shows where a run came from, its status, and each of its checks. | | [`chainctl checks list`](#chainctl-checks-list) | Lists the runs you started with `chainctl checks run`. | | [`chainctl checks run`](#chainctl-checks-run) | Runs a pushed commit's checks without a pull request. | | [`chainctl checks rerun`](#chainctl-checks-rerun) | Reruns a pull request's latest run. | | [`chainctl checks cancel`](#chainctl-checks-cancel) | Cancels a run you started with `chainctl checks run`. | | [`chainctl checks stats`](#chainctl-checks-stats) | Counts the outcomes of your organization's newest runs. | | [`chainctl checks top`](#chainctl-checks-top) | Ranks your organization's checks over a window of time. | | [`chainctl checks trends`](#chainctl-checks-trends) | Shows how long checks take, hour by hour. | | [`chainctl checks validate`](#chainctl-checks-validate) | Finds problems in your configuration without running anything. | | [`chainctl checks explain`](#chainctl-checks-explain) | Prints each check as Chainguard Checks reads it. | | [`chainctl checks migrate`](#chainctl-checks-migrate) | Drafts a configuration from your GitHub Actions workflows. | | [`chainctl checks accept-terms`](#chainctl-checks-accept-terms) | Accepts the terms that Chainguard Checks is offered under. | ## Access and organization Every command on this page except `validate`, `explain`, `migrate --dry`, and `accept-terms` needs the `owner` or `sandbox.user` role in the Chainguard organization linked to the repository's GitHub account. Without one of these roles, the commands fail with `permission denied`. To get a role, refer to [Give teammates access](/get-started/checks-and-workspaces/set-up/#give-teammates-access). If you created a Starter organization with `chainctl develop init`, you have the `limited_owner` role instead. It can start runs with `chainctl checks run`, which use your allowance, but can't read any run, so don't use `chainctl checks run` there. Read the results on GitHub instead, as described in [The Details link or a chainctl checks command says you don't have access](/chainguard/checks/troubleshooting/#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access). The `validate`, `explain`, and `migrate --dry` commands read only your local checkout, so they don't need a Chainguard account or network access. The commands that need a role take `--parent` with the name or ID of your Chainguard organization. To use the name, you also need a role that can see the organization, such as `viewer`. With `sandbox.user` alone, pass the organization's ID. Without `--parent`, `chainctl` uses the `CHAINCTL_SANDBOX_GROUP` environment variable, then the `default.group` value in your `chainctl` configuration. If neither is set, it asks you to choose an organization. ## Name a run {#name-a-run} Most commands that read runs take a `REF` argument that names the run. During the beta, use one of the following forms: | `REF` | Example | What it names | | ----- | ------- | ------------- | | A pull request | `acme/app#123`, or `https://github.com/acme/app/pull/123` | The pull request's newest run. Capitalize the owner and repository the same way GitHub does. | | A run ID | `$RUN_ID` | One run. For a run after merging, use the whole ID after `/runs/` in the **Details** link of **Chainguard Checks (postsubmit)**. For a run you started with `chainctl checks run`, you can use the start of its ID. | The `chainctl checks status -o json` command prints the run's full name in its `run` field. Pass that name to the other commands to keep reading the same run after someone pushes to the pull request. The commands also accept a directory, which stands for the commit checked out there, or a full commit SHA. During the beta, these never find your repositories' runs, so the commands report that the platform created no run, even when checks ran. Without a `REF`, the commands use the current directory, so pass a pull request or a run ID instead. Refer to [chainctl checks status reports no platform verdict](/chainguard/checks/troubleshooting/#chainctl-checks-status-reports-no-platform-verdict). ## Output and exit codes To print JSON for a script or a coding agent, add `-o json`, which is short for `--output json`. Each command's section describes its JSON. On a terminal, commands that print your repository's output remove escape sequences and other control characters from it. To keep them, add `--raw` to a command that takes it. When the output isn't a terminal, such as when you pipe it to a file, the commands always print it as it is. Unless a command's section says otherwise, it exits with `0` when it succeeds and `1` when it fails. ## Inspect runs ### chainctl checks status {#chainctl-checks-status} Shows a run and whether each of its checks passed. ``` chainctl checks status REF [--watch] ``` | Flag | Description | | ---- | ----------- | | `--watch` | Waits until the run finishes, and prints the checks again each time they change. | For example, to wait for a pull request's checks to finish: ```shell chainctl checks status acme/app#123 --watch --parent $ORGANIZATION ``` The output marks a failed advisory check as `(advisory)`, and shows the label that runs each check that runs only on request. The command exits with one of the following codes: | Exit code | Meaning | | --------- | ------- | | `0` | The run passed, or it's still running and you didn't add `--watch`. | | `1` | The run finished and a blocking check didn't pass, or the command failed. | | `2` | Chainguard Checks created no run for the `REF`. | With `-o json`, the command prints one object, the final one with `--watch`. It has the run's `run`, `repository`, `revision`, `pull_request`, `phase`, and `outcome`, and `failed`, which is `true` when the command exits with `1`. Its `checks` list has each check's `workflow`, `check`, `phase`, and `outcome`, plus `exit_code`, `advisory`, and `request_label` when they apply. When there's no run, the object's `status` is `no platform verdict`. ### chainctl checks logs {#chainctl-checks-logs} Prints the combined standard output and standard error of a run's checks, followed by the errors, warnings, and notices each check reported, as described in [Report errors and annotations](/chainguard/checks/configuration/#report-errors-and-annotations). Without `--check`, the command prints every check's log, each after a line that names the check. ``` chainctl checks logs REF [--check CHECK [--workflow WORKFLOW]] [--follow] [--attempt N] [--raw] chainctl checks logs REF --check CHECK (--errors | --grep RE) [-C N] [--max N] [--offset BYTES] chainctl checks logs REF --check CHECK --tail N chainctl checks logs REF --check CHECK --offset BYTES [--limit-bytes N] ``` | Flag | Description | | ---- | ----------- | | `--check` | Prints only this check's log. | | `--workflow` | Names the file of the check, when two files in the directory layout have a check with that name. | | `-f`, `--follow` | Keeps reading a running check's output until it finishes, and waits for a check that hasn't started. Without `--check`, it keeps reading until every check in the run finishes. | | `--attempt` | Reads this attempt of a check that ran more than once, such as `1` for the first. Needs `--check`. | | `--errors` | Shows only error lines, such as `::error` lines and error-level JSON or logfmt lines. If the log has none, the command shows its last 30 lines. | | `--grep` | Shows only the lines that match this RE2 regular expression, reading at most 64 MiB of the log. | | `-C`, `--context` | With `--errors` or `--grep`, shows this many lines before and after each line. | | `--max` | With `--errors` or `--grep`, shows at most this many lines, 10 error lines or 20 matching lines by default. | | `--tail` | Shows the log's last lines, up to this number or 16 KiB. | | `--offset` | Starts `--errors` or `--grep` at this byte of the log, or reads the log from this byte. | | `--limit-bytes` | With `--offset`, reads this many bytes, 64 KiB by default. | | `--raw` | Prints the log as it is, including escape sequences, even on a terminal. | The `--errors`, `--grep`, `--tail`, `--offset`, and `--limit-bytes` flags read part of one check's log, so they need `--check`, and you can't combine them with `--follow`. Use only one of `--errors`, `--grep`, and `--tail` at a time. Each excerpt starts with the byte range it covers. If the command leaves lines out, it says so, and gives the `--offset` to continue from. For example, to show a failed check's error lines with three lines around each one: ```shell chainctl checks logs acme/app#123 --check test --errors -C 3 --parent $ORGANIZATION ``` The `-o json` flag works only with `--errors`, `--grep`, `--tail`, `--offset`, or `--limit-bytes`. The command then prints one JSON object per line: one with `type` set to `excerpt` for each excerpt, with its `check`, `attempt`, `offset`, `end_offset`, and `text`, followed by one with `type` set to `summary`. The summary says how much the command showed, whether it left anything out, and the `next_offset` to continue from. ### chainctl checks trace Shows the programs that each step of a check started, with each program's name, how long it ran, and how it exited. Chainguard Checks records program names, never their arguments. For a failed step, the output names the program that most likely failed it. This guess comes from the recorded programs, so it can be wrong, such as when a shell or build tool ignores a program's exit code. ``` chainctl checks trace REF [--check CHECK [--workflow WORKFLOW]] [--failed] ``` | Flag | Description | | ---- | ----------- | | `--check` | Shows only this check's steps. Without it, the command shows every check that has recorded programs. | | `--workflow` | Names the file of the check, when two files in the directory layout have a check with that name. | | `--failed` | Shows only failed steps, each as the path from the step's command to the program that most likely failed it. | For example: ```shell chainctl checks trace acme/app#123 --check test --failed --parent $ORGANIZATION ``` A trace marked `INCOMPLETE` lost some of its records, or `chainctl` cut it to fit, so parts of it might be missing. With `-o json`, the command prints one JSON object per line for each step, with the step's `workflow`, `check`, `step` number, and its tree of programs in `document`. ### chainctl checks events Prints the events that one attempt at a check recorded, in order: when its setup, each of its steps, and each program that a step started began and ended, and any diagnostics attached to them. ``` chainctl checks events REF --check CHECK [--workflow WORKFLOW] [--attempt N] ``` | Flag | Description | | ---- | ----------- | | `--check` | The check to print events for. Required. | | `--workflow` | Names the file of the check, when two files in the directory layout have a check with that name. | | `--attempt` | Reads this attempt instead of the latest one. | For example: ```shell chainctl checks events acme/app#123 --check test --parent $ORGANIZATION ``` With `-o json`, the command prints the events as the API returns them. ### chainctl checks describe Shows where a run came from, its status, and each of its checks. ``` chainctl checks describe RUN [--steps] [--events] ``` `RUN` is a run ID, as described in [Name a run](#name-a-run). The command doesn't take a pull request, so to describe a pull request's run, pass the `run` field that `chainctl checks status -o json` prints. | Flag | Description | | ---- | ----------- | | `--steps` | Adds each check's steps. | | `--events` | Adds the run's events, and how long each attempt spent queued, waiting for a free slot, booting its VM, setting up, and running its steps. | For example: ```shell chainctl checks describe $RUN_ID --events --parent $ORGANIZATION ``` With `-o json`, the command prints one object with the `run`, its `workflow_runs`, and with `--events`, its `events`, as the API returns them. ### chainctl checks list Lists the runs you started with `chainctl checks run`, newest first. During the beta, it doesn't list pull request runs or runs after merging. ``` chainctl checks list [--repo REPO] [--outcome OUTCOME] [--limit N] ``` | Flag | Description | | ---- | ----------- | | `--repo` | Lists only runs of this repository, such as `acme/app`. | | `--outcome` | Lists only finished runs with this outcome: `passed`, `failed`, `errored`, `dependency_failed`, or `cancelled`. It searches the 500 newest runs. | | `--limit` | Lists at most this many runs, 20 by default. | For example: ```shell chainctl checks list --repo acme/app --outcome failed --parent $ORGANIZATION ``` With `-o json`, the command prints a list with one object for each run, with its `run`, `create_time`, `repository`, `revision`, `phase`, and `outcome`. ## Start and stop runs ### chainctl checks run {#chainctl-checks-run} Runs a commit's checks in Chainguard Checks without a pull request. The commit must be pushed to GitHub, because Chainguard Checks reads its configuration from there through your organization's link to the repository's GitHub account. To run checks against uncommitted changes, use `chainctl develop check` instead, as described in [Run checks in a session from your laptop](/chainguard/checks/run-checks/#run-checks-in-a-session-from-your-laptop). ``` chainctl checks run [DIR] [--check CHECK [--workflow WORKFLOW]] [--request CHECK]... [--git-remote NAME] [--base REV] [--watch] ``` `DIR` is a local checkout, the current directory by default. The command runs the commit checked out there, in the github.com repository of its `origin` remote. | Flag | Description | | ---- | ----------- | | `--check` | Runs only this check and the checks it `needs`, including a check that runs only on request. | | `--workflow` | Names the file of the check, when two files in the directory layout have a check with that name. | | `--request` | Runs this check if it runs only on request, as a `ci/run:` label does on a pull request. Repeat the flag for more checks. | | `--git-remote` | Uses the github.com repository of this remote instead of `origin`, such as `upstream` in a checkout of a fork. | | `--base` | The commit that `when.changed` conditions compare against. By default, it's where the commit branched from the remote's default branch, or the commit's parent if the commit is on that branch. | | `--watch` | Waits until the run finishes, and prints the checks again each time they change. | For example, to run the `test` check and wait for its result: ```shell chainctl checks run . --check test --watch --parent $ORGANIZATION ``` The run doesn't report to GitHub, and it never counts as a pull request's or commit's result. It uses your organization's Checks allowance like any other run. Because the run has no pull request, the command refuses a check whose `when.expr` condition reads the trigger, the pull request, or its labels. The command prints the run's name, followed by its checks. The run's ID is the part of the name after `/runs/`. Pass the ID to `chainctl checks status`, `cancel`, or the other commands that read runs. With `--watch`, the command exits with `1` when a blocking check doesn't pass. With `-o json`, it prints the run as the API returns it, or with `--watch`, the run's final status in the same form as `chainctl checks status -o json`. ### chainctl checks rerun Reruns a pull request's latest run after it finishes, the same way as **Re-run** on GitHub. Refer to [Rerun checks](/chainguard/checks/github/#rerun-checks) for what reruns and how to follow the new run. ``` chainctl checks rerun REF ``` During the beta, `REF` must be a pull request. The command can't rerun runs after merging or runs you started with `chainctl checks run`. To run checks after merging again, push or merge a new commit to your default branch. To repeat a run you started with `chainctl checks run`, run that command again. For example: ```shell chainctl checks rerun acme/app#123 --parent $ORGANIZATION ``` With `-o json`, the command prints the run whose checks it reruns, as the API returns it. ### chainctl checks cancel Cancels a run you started with `chainctl checks run`, stopping its checks that are queued or running. You can't cancel a run that has finished. ``` chainctl checks cancel RUN ``` `RUN` is the run's ID, or the start of it. During the beta, you can't cancel pull request runs or runs after merging with `chainctl`. A new push to a pull request cancels the run for the previous push. For example: ```shell chainctl checks cancel $RUN_ID --parent $ORGANIZATION ``` With `-o json`, the command prints the run as the API returns it. ## Summarize check history The `stats`, `top`, and `trends` commands summarize your organization's checks across runs. For examples, refer to [See check history](/chainguard/checks/inspect-and-run-checks/#see-check-history). The `top` and `trends` output starts with the window it read and how current its data is, because the newest attempts can take time to appear. The first time you run either command, `chainctl` might ask you to sign in again. Without a terminal to sign in at, such as in a script, the command fails and prints the `chainctl auth login` command to run first. ### chainctl checks stats {#chainctl-checks-stats} Counts the outcomes of your organization's newest runs, by repository and by check. ``` chainctl checks stats [--limit N] ``` | Flag | Description | | ---- | ----------- | | `--limit` | Counts at most this many runs, 500 by default and 2,000 at most. | In the output, `OPEN` counts runs or checks that haven't finished, and `RUNNING` is the part of `OPEN` that's running. `FAIL%` is the share of failed results among the results that passed or failed, so checks that are still running, were canceled or skipped, or couldn't finish don't change it. `CONFIGS` counts the different configurations a repository's runs used. With `-o json`, the command prints one object with the number of `runs` it read and a `repositories` list. Each repository has its `name`, `counts`, `configs`, and a `checks` list with each check's `name`, `counts`, and `median_seconds`. Each `counts` object has `total`, `passed`, `failed`, `errored`, `dependency_failed`, `not_instantiated`, `cancelled`, `open`, and `running`. ### chainctl checks top {#chainctl-checks-top} Ranks your organization's checks over a window of time. ``` chainctl checks top [--repo REPO] [--since DURATION | --from TIME --to TIME] [--by count|runtime|failures] [--limit N] ``` | Flag | Description | | ---- | ----------- | | `--by` | Ranks checks by their number of attempts (`count`), their total runtime (`runtime`), or their failure rate (`failures`). The default is `runtime`. Only a check with at least 100 attempts in the window has a failure rate. | | `--repo` | Ranks only checks of this repository, such as `acme/app`. | | `--since` | Reads the window that ends now and lasts this long, such as `7d` or `36h`. The default is `14d`, and the longest is `31d`. | | `--from`, `--to` | Reads the window between these RFC 3339 times, such as `2026-10-01T00:00:00Z`. Use both together, for a window of up to 31 days. | | `--limit` | Shows at most this many checks, up to 20, which is the default. | A check's failure rate is the share of its attempts that failed or couldn't finish, such as by timing out. Attempts include retries, but not attempts whose result Chainguard Checks reused from an earlier run. With `-o json`, the command prints one object with the `window`, when the data was generated (`generatedAt`), the ranking (`by`), and a `checks` list. Each check has its `job_ref`, `name`, `attempts`, `failed`, `errored`, `runtime_seconds`, and `failure_rate`, which is `null` for a check with fewer than 100 attempts. ### chainctl checks trends {#chainctl-checks-trends} Shows the median, 95th percentile, and 99th percentile durations of checks for each hour of a window, for the 10 checks with the most total runtime in that window. Hours with no attempts don't appear. ``` chainctl checks trends [--repo REPO] [--since DURATION | --from TIME --to TIME] [--check CHECK] ``` | Flag | Description | | ---- | ----------- | | `--check` | Shows only this check. A check outside the 10 has no trend. | | `--repo` | Shows only checks of this repository, such as `acme/app`. | | `--since` | Reads the window that ends now and lasts this long, such as `7d` or `36h`. The default is `14d`, and the longest is `31d`. | | `--from`, `--to` | Reads the window between these RFC 3339 times. Use both together, for a window of up to 31 days. | With `-o json`, the command prints one object with the `window`, `generatedAt`, and a `trends` list with one object for each check and hour. Each object has the check's `job_ref`, the `hour`, the number of attempts it measured in `samples`, and the `median_seconds`, `p95_seconds`, and `p99_seconds` durations. ## Set up checks ### chainctl checks validate Finds problems in your configuration without running anything, the way Chainguard Checks reads it for a pull request, with the differences described in [Validate your configuration offline](/chainguard/checks/run-checks/#validate-your-configuration-offline). That section also shows the command's output. ``` chainctl checks validate [PATH] ``` `PATH` is a repository's root directory, a configuration file under its `.chainguard/` directory, or a standalone configuration file. For a file under `.chainguard/`, the command validates its repository's whole configuration. Without `PATH`, the command validates the repository that the current directory is in. For example, to validate another repository's configuration: ```shell chainctl checks validate ../app ``` The command exits with `1` when the configuration has a problem that Chainguard Checks would refuse. Warnings, such as for a key that Chainguard Checks ignores, don't change the exit code. With `-o json`, the command prints one object with the repository's `root`, the problems that kept it from reading the configuration in `diagnostics`, and a `workflows` list. Each workflow has its `workflow` name, its `path`, its `refusals` and `warnings`, problems with `needs` in `graph`, and `ok`. The object's own `ok` is `true` when the whole configuration is valid. ### chainctl checks explain Prints each check as Chainguard Checks reads it for a pull request, with its environment, trigger, steps, and resource class, without running anything. ``` chainctl checks explain [DIR] [--raw] ``` `DIR` is the repository's root directory. Without it, the command reads the repository that the current directory is in. | Flag | Description | | ---- | ----------- | | `--raw` | Prints check names, commands, and other text from your repository as they are, including escape sequences, even on a terminal. | If Chainguard Checks would refuse the configuration, the command prints each problem with its file and position, and exits with `1`. With `-o json`, it prints the resolved configuration, or the problems when it's refused. ### chainctl checks migrate Drafts a `.chainguard/ci/migrated.yaml` file from your repository's GitHub Actions workflows that run on `pull_request`. Refer to [Migrate from GitHub Actions to Chainguard Checks](/chainguard/checks/migrate-from-github-actions/) for how to review the draft. ``` chainctl checks migrate [PATH] --dry ``` `PATH` is a local clone whose `origin` remote is on github.com, the current directory by default. The `--dry` flag drafts the file and prints it, followed by a list of what the draft carried over, flagged, and ignored, without running anything. For example, to write the draft to its file: ```shell mkdir -p .chainguard/ci chainctl checks migrate --dry -o json | jq -r .document > .chainguard/ci/migrated.yaml ``` With `-o json`, the command prints one object with the `repository`, `revision`, the draft's `path`, the number of `jobs` it drafted, the draft itself in `document`, and the `ledger` of what it carried over, flagged, and ignored. ### chainctl checks accept-terms Accepts the terms that Chainguard Checks is offered under for your organization: the [Code Analysis Terms](https://www.chainguard.dev/legal/code-analysis-terms) and the [Master Service and License Agreement](https://www.chainguard.dev/legal/master-service-and-license-agreement). An owner of the organization runs it once. The `chainctl develop init` command also asks you to accept these terms, as described in [Get started with Chainguard Checks](/get-started/checks-and-workspaces/set-up/#sign-up-with-chainctl-develop-init). ``` chainctl checks accept-terms [--parent ORGANIZATION] [--yes] ``` | Flag | Description | | ---- | ----------- | | `--parent` | The organization to accept the terms for. Without it, the command uses the `default.group` value in your `chainctl` configuration, or asks you to choose. | | `--yes` | Accepts the terms without asking, for use without an interactive terminal. By adding it, you agree to the statement in the command's help, which it also prints. | For example: ```shell chainctl checks accept-terms --parent $ORGANIZATION ``` Running the command again after the organization accepts the terms changes nothing. --- ### chainctl develop command reference URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/chainctl-develop-reference.md Last Modified: October 15, 2026 Tags: Chainguard Workspaces, chainctl, Reference The `chainctl develop` command opens and manages Chainguard Workspaces sessions. This page lists each `chainctl develop` command, with its flags, output formats, exit codes, and the roles it needs. For how the commands fit together, refer to [Manage sessions](/chainguard/workspaces/manage-sessions/). During the beta, the `chainctl checks` and `chainctl develop` commands don't appear in `chainctl --help` or in the [chainctl reference](/platform/chainctl/chainctl-docs/chainctl/). You can still run them, and `--help` on either one shows its full help. To read the help for the version you have installed, run `chainctl develop --help`, or add `--help` to any command on this page. You can shorten `chainctl develop` to `chainctl dev`. If `chainctl` doesn't recognize a command or flag on this page, [update chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl). The examples on this page use `$ORGANIZATION` for the name of your Chainguard organization. If you belong to only one organization, you can omit `--parent`. ## Commands | Command | What it does | | ------- | ------------ | | [`chainctl develop`](#chainctl-develop) | Opens a session, or returns to one. | | [`chainctl develop list`](#chainctl-develop-list) | Lists your sessions. | | [`chainctl develop rename`](#chainctl-develop-rename) | Renames a session. | | [`chainctl develop delete`](#chainctl-develop-delete) | Deletes a session. | | [`chainctl develop check`](#chainctl-develop-check) | Runs your repository's checks in a session. | | [`chainctl develop creds`](#chainctl-develop-creds) | Shows or changes what a session can reach. | | [`chainctl develop github`](#chainctl-develop-github) | Connects your GitHub account to the sessions you create. | | [`chainctl develop policy`](#chainctl-develop-policy) | Shows or changes your organization's GitHub policy for sessions. | | [`chainctl develop init`](#chainctl-develop-init) | Signs you up and turns on Chainguard Checks and Chainguard Workspaces. | | [`chainctl develop accept-terms`](#chainctl-develop-accept-terms) | Accepts the terms that Chainguard Workspaces is offered under. | ## Flags for every command Every `chainctl develop` command accepts the following flags: | Flag | Description | | ---- | ----------- | | `--parent` | The organization or folder, by name or ID, that holds the sessions. Defaults to the `CHAINCTL_SANDBOX_GROUP` environment variable, then to the `default.group` value in your `chainctl` configuration. If none of these is set and you belong to one organization, `chainctl` uses it. If you belong to several, it asks which one, or lists them and fails when it can't ask. `chainctl develop init` handles `--parent` differently, as described in [`chainctl develop init`](#chainctl-develop-init). | | `--non-interactive` | Never prompts. Where `chainctl` would ask a question, such as which of several matching sessions you mean, it fails and lists the choices instead. | | `-o`, `--output` | The output format, for the commands listed in [Output formats](#output-formats). | ## Session arguments Commands that take a `SESSION` argument accept any of the following: - The session's name, such as `app/quiet-harbor`, or the name with the repository's GitHub owner in front, such as `acme/app/quiet-harbor`, as `chainctl develop list` shows it. - The session's short ID, such as `3f9a2c1e`. - A directory or a repository URL, if exactly one of your sessions matches it. If several of your sessions match, `chainctl` asks which one you mean, or lists them and fails when it can't ask. Commands find only your own sessions. ## chainctl develop Opens a session and attaches your terminal to its shell. If one of your sessions already matches the argument, `chainctl develop` returns to it instead, resuming it first if it's parked. ``` chainctl develop [DIR|REPO|SESSION] [flags] ``` The argument is a directory, a repository URL, or a session. Without one, `chainctl develop` uses the current directory. Refer to [How chainctl develop chooses a session](/chainguard/workspaces/manage-sessions/#how-chainctl-develop-chooses-a-session) for what each one opens. | Flag | Description | | ---- | ----------- | | `--new` | Opens a new session even if one already matches the directory or repository. | | `--name` | Sets the second part of a new session's name, such as `scratch` in `app/scratch`. Without it, `chainctl` generates one. | | `--env` | Sets a new session's environment: the path to a file, `default`, or `generate`. Refer to [How a session chooses its environment](/chainguard/workspaces/session-environment/#how-a-session-chooses-its-environment). | | `--check` | Gives a new session the environment and network access of a check. From a local directory, the check must be in `.chainguard/ci.yaml`. From a repository URL, it can also be in the `.chainguard/ci/` directory. Refer to [How a session chooses its environment](/chainguard/workspaces/session-environment/#how-a-session-chooses-its-environment). | | `--cache` | For a session opened from a repository URL, mounts the Go build cache that your checks keep for your default branch. Refer to [Start with a warm Go build cache](/chainguard/workspaces/session-environment/#start-with-a-warm-go-build-cache). | | `--with` | Gives a new or existing session access beyond its defaults. Repeat the flag or separate entries with commas. Refer to [Allow access ahead of time](/chainguard/workspaces/credentials/#allow-access-ahead-of-time) for the entries. | | `--keep` | Keeps the session running when you detach, for a duration of up to `24h`, such as `8h`. With `always`, creates a session that keeps running instead of parking. Refer to [Keep a session running while you're away](/chainguard/workspaces/manage-sessions/#keep-a-session-running-while-youre-away). | | `--git-remote` | For a directory, the git remote whose GitHub repository a new session is on, or `-` for no repository. Refer to [Choose the session's repository](/chainguard/workspaces/manage-sessions/#choose-the-sessions-repository). | | `-o json` | Prints the session as JSON instead of attaching to it, and creates the session first if none matches. Refer to [Use sessions from scripts](/chainguard/workspaces/manage-sessions/#use-sessions-from-scripts). | The `--name`, `--env`, `--check`, `--cache`, and `--keep always` flags each open a new session, so you don't need `--new` with them, and you can't combine them with a session name. In a checkout of a repository named `app`, this example opens a new session named `app/scratch` with the default environment: ```shell chainctl develop --env default --name scratch --parent $ORGANIZATION ``` ## chainctl develop list Lists your sessions that haven't expired, most recently used first, with each session's branch, state, and when you last used it. Alias: `ls`. ``` chainctl develop list [--all] [--expired] [-o json|table|wide] ``` | Flag | Description | | ---- | ----------- | | `-a`, `--all` | Lists every user's sessions in the organization, with an `OWNER` column. Needs the `owner` role, as described in [Roles](#roles). | | `--expired` | Also lists your expired sessions. | | `-o wide` | Adds each session's commit, environment, and directory, in the `HEAD`, `ENV`, and `DIRECTORY` columns. | For example output, refer to [List sessions](/chainguard/workspaces/manage-sessions/#list-sessions). ## chainctl develop rename Replaces the second part of a session's name. The repository part stays the same. Alias: `mv`. ``` chainctl develop rename SESSION NAME [-o json] ``` This example renames `app/quiet-harbor` to `app/tests`: ```shell chainctl develop rename app/quiet-harbor tests --parent $ORGANIZATION ``` ## chainctl develop delete Destroys a session's VM and its snapshot, after asking you to confirm. Anything in the session that you didn't push is lost. For an expired session, the command removes it from `chainctl develop list --expired` and frees its name. Alias: `rm`. ``` chainctl develop delete SESSION [--yes] [-o json] ``` | Flag | Description | | ---- | ----------- | | `-y`, `--yes` | Deletes the session without asking you to confirm. | This example deletes `app/scratch` without asking: ```shell chainctl develop delete app/scratch --yes --parent $ORGANIZATION ``` ## chainctl develop check Runs the checks in a session's working tree, in the session's own environment, and streams each step's output to your terminal. If the session is parked, `chainctl` resumes it first. The command uses the same runner as the `run-ci` command in a session. ``` chainctl develop check SESSION [--check CHECK] [-o json] ``` | Flag | Description | | ---- | ----------- | | `--check` | Runs only the named check. Without it, `chainctl` runs every check that has a command, in the order your configuration lists them, and stops at the first failure. | | `-o json` | Prints the result as JSON on standard output, and sends the steps' output to standard error. | This example runs the `test` check in `app/quiet-harbor`: ```shell chainctl develop check app/quiet-harbor --check test --parent $ORGANIZATION ``` For which checks the command skips, and how its result compares with a pull request's, refer to [Run checks in a session from your laptop](/chainguard/checks/run-checks/#run-checks-in-a-session-from-your-laptop). ## chainctl develop creds Shows what a session can reach, or changes it. ``` chainctl develop creds SESSION [--revoke ENTRY]... [--source SERVICE=SOURCE]... [--forget] [-o json|table] ``` Without flags, the command lists each service's source and bindings, what your laptop remembers approving, what serves the session's laptop credentials right now, and the latest changes to the session's credentials. | Flag | Description | | ---- | ----------- | | `--revoke` | Takes an entry back from the session and from your laptop's remembered approvals. Repeatable. | | `--source` | Sets where a service's credential comes from: `laptop`, `org`, or `off`. Repeatable. | | `--forget` | Removes everything your laptop remembers for the session's repository, without changing the session. | To add access, run `chainctl develop SESSION --with ENTRY` instead. Refer to [Review and revoke a session's credentials](/chainguard/workspaces/credentials/#review-and-revoke-a-sessions-credentials) for the entries and sources that each service takes. This example takes back a session's access to `cgr.dev`: ```shell chainctl develop creds app/quiet-harbor --revoke cgr.dev --parent $ORGANIZATION ``` ## chainctl develop github Connects your GitHub account so that the sessions you create act on GitHub as you, through the Chainguard Develop GitHub App. Refer to [Let sessions act on GitHub as you](/chainguard/workspaces/github-as-you/). ``` chainctl develop github login [--port PORT] [-o json] chainctl develop github logout chainctl develop github status [-o json|table] ``` | Command | What it does | | ------- | ------------ | | `login` | Opens your browser to connect your GitHub account, replacing any earlier connection. The `--port` flag sets the local port that your browser returns to after you authorize the connection. | | `logout` | Disconnects your GitHub account. Sessions that act on GitHub as you refuse `git` and `gh` requests until you connect again. | | `status` | Shows your connected GitHub account, the app it's connected through, whether the connection works, whether your organization's policy allows sessions to act as you, and which accounts the checkout in your current directory still needs the app installed on. | ## chainctl develop policy Shows or changes your organization's GitHub policy for sessions. Alias: `policies`. ``` chainctl develop policy describe [-o json|table] chainctl develop policy set [--org-github bound|session|off] [--org-github-push-beyond-session=true|false] [--user-github=true|false] [-o json|table] ``` The `describe` command, with the aliases `desc` and `get`, shows the policy, its revision, and who last changed it. The `set` command changes only the settings you pass, and needs the `owner` role. Refer to [Set your organization's GitHub policy](/chainguard/workspaces/credentials/#set-your-organizations-github-policy) for what each setting controls. This example limits the Chainguard App to each session's own repository: ```shell chainctl develop policy set --org-github session --parent $ORGANIZATION ``` ## chainctl develop init {#chainctl-develop-init} Signs you in, creates a Chainguard Starter organization or picks one you belong to, and turns on Chainguard Checks and Chainguard Workspaces with a free monthly allowance. Run it in an interactive terminal. ``` chainctl develop init [--parent ORGANIZATION] [--github-org ACCOUNT] [--name NAME] [--port PORT] ``` | Flag | Description | | ---- | ----------- | | `--github-org` | The GitHub organization to link, instead of your own GitHub user. | | `--parent` | An existing organization, such as an Enterprise organization, to turn on Checks and Workspaces for. You need the `owner` or `limited_owner` role in it. | | `--name` | Has no effect, because an organization you create is named after your work email domain. You can't combine it with `--parent`. | | `--port` | The local port that your browser returns to after you authorize the GitHub link. | Without `--parent`, the command creates a Starter organization if you don't belong to any organization, and uses your organization on the Starter or Team plan if you have exactly one. Otherwise, it fails and asks you to pass `--parent`. Unlike the other commands, it doesn't fall back to the `default.group` value in your `chainctl` configuration. For what the command asks as it runs, refer to [Sign up with chainctl develop init](/get-started/checks-and-workspaces/set-up/#sign-up-with-chainctl-develop-init). ## chainctl develop accept-terms {#chainctl-develop-accept-terms} Accepts the Secured Platform Terms and the Master Service and License Agreement (MSLA) for an organization. Chainguard Workspaces refuses to open sessions in an organization until it accepts them. Someone with the `owner` or `limited_owner` role runs the command once, and running it again changes nothing. ``` chainctl develop accept-terms [--parent ORGANIZATION] [--yes] ``` | Flag | Description | | ---- | ----------- | | `--yes` | Accepts the terms without asking, such as where no interactive terminal is available. Passing it means you agree to the statement in `chainctl develop accept-terms --help`, which the command also prints. | An organization that already has Workspaces through your Chainguard account team must accept the terms before anyone can open new sessions in it. Running `chainctl develop init --parent $ORGANIZATION` accepts them, turns on Checks, and keeps the allowance your account team arranged. To accept only the Workspaces terms, run `chainctl develop accept-terms` instead: ```shell chainctl develop accept-terms --parent $ORGANIZATION ``` If you accept only the Workspaces terms and your GitHub account isn't linked to the organization yet, link it as described in [Getting started with Guardener](/chainguard/guardener/github/getting-started/#step-2-link-your-chainguard-organization-to-github), which uses the same Chainguard App. ## Output formats Each command accepts only the following output formats: | Command | Formats | JSON output | | ------- | ------- | ----------- | | `chainctl develop` | `json` | The session. `chainctl` doesn't attach. | | `list` | `table` (default), `wide`, `json` | An array of sessions. | | `rename` | `json` | The renamed session. | | `delete` | `json` | The deleted session, with `"deleted": true`. | | `check` | `json` | The result: `passed`, `jobs` (the checks that ran), `failed_job` and `failed_step` for a failure, `skipped`, and `elapsed`. | | `creds` | `table` (default), `json` | `sources`, with each service's `source` and `bindings`; `serving`, what serves the session's laptop credentials; and `trail`, the latest changes. | | `github login` | `json` | The connected account, in the same form as `github status`. | | `github status` | `table` (default), `json` | `login`, `valid`, `user_github`, `installed_accounts`, and `app_slug`. | | `policy describe`, `policy set` | `table` (default), `json` | The policy. | A session in JSON output includes its `name`, such as `app/quiet-harbor`; its `label`, the name with the repository's GitHub owner in front; its `id` and `short_id`; its `state`, `repository`, `branch`, `head`, and `directory`; and timestamps such as `create_time` and `expire_time`. Fields without a value are left out. ## Exit codes | Command | Exit code | | ------- | --------- | | `chainctl develop` | When you exit the session's shell, such as with `exit`, the shell's exit status. Otherwise, `0` when you detach and `1` when the command fails. | | `chainctl develop check` | `0` when every check passes, and `1` when a check fails or the checks can't run. | | Every other command | `0` when the command succeeds, and `1` when it fails. | ## Roles {#roles} What you can do with `chainctl develop` depends on your role in the session's organization: | Commands | Roles | | -------- | ----- | | `chainctl develop`, `list`, `rename`, `delete`, `check`, `creds`, `github`, and `policy describe` | `owner`, `limited_owner`, or `sandbox.user` together with a role that includes `groups.list`, such as `viewer` | | `accept-terms` | `owner` or `limited_owner` | | `list --all` and `policy set` | `owner` | The `limited_owner` role is the one that `chainctl develop init` gives the person who creates a Starter organization. No one in a Starter organization has the `owner` role, so no one there can run `list --all` or `policy set`. On their own, other roles, such as `editor` and `viewer`, can't open or manage sessions. A teammate with `sandbox.user` also needs the `viewer` role, as described in [Give teammates access](/get-started/checks-and-workspaces/set-up/#give-teammates-access). To run `chainctl develop init --parent` on an organization you already belong to, you need the `owner` or `limited_owner` role in it. An organization owner can grant the `sandbox.user` role with a role-binding, as described in [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). In a session, your role also limits what your Chainguard identity can do. The `limited_owner` role doesn't include `mcp.tool.call` or `sandbox.run.list`, so the Chainguard MCP servers and the `chainctl checks` commands that look up a run don't work for it. Refer to [MCP servers in a session](/chainguard/workspaces/coding-agents/#mcp-servers-in-a-session) and [Your Chainguard identity in a session](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session). ## Environment variables and configuration | Name | What it sets | | ---- | ------------ | | `CHAINCTL_SANDBOX_GROUP` | The default for `--parent`. | | `CHAINCTL_DEVELOP_CACHE` | With `true`, new sessions ask for the Go build cache, as `--cache` does. Unlike `--cache`, it doesn't make `chainctl develop` open a new session. Refer to [Start with a warm Go build cache](/chainguard/workspaces/session-environment/#start-with-a-warm-go-build-cache). | | `default.group` | The `chainctl` configuration value that `--parent` falls back to when `CHAINCTL_SANDBOX_GROUP` isn't set. The `CHAINGUARD_DEFAULT_GROUP` environment variable also sets it. | | `develop.vertex.project`, `develop.vertex.region` | The Google Cloud project and region that new sessions use for Claude through Vertex AI. Refer to [Use Claude through Vertex AI](/chainguard/workspaces/coding-agents/#use-claude-through-vertex-ai). | --- ### Troubleshoot Chainguard Checks URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/checks/troubleshooting.md Last Modified: October 15, 2026 Tags: Chainguard Checks, Troubleshooting This page describes common problems with Chainguard Checks and how to fix them. If your problem isn't listed here, ask in the #checks-questions channel in the [Chainguard community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw). ## A pull request gets no checks {#a-pull-request-gets-no-checks} If a pull request shows no **Chainguard Checks (presubmit)** check at all, Chainguard Checks didn't start a run for it. Confirm the following: - The Chainguard App is installed on the GitHub account that owns the repository, with access to the repository, and that account is linked to your Chainguard organization. Running `chainctl develop init` links the account when you sign up. A GitHub account can be linked to only one Chainguard organization at a time. - The repository has a `.chainguard/ci.yaml` file or a `.chainguard/ci/` directory at its root, on the pull request's branch or its base branch. A file named `ci.yml` doesn't count. - The configuration has presubmit checks. Checks with only `trigger: postsubmit` run after merging, not on pull requests. A pull request that changes every check to `trigger: postsubmit` still gets a summary check, which fails with **Run creation failed**, as described in [Change checks in a pull request](/chainguard/checks/github/#change-checks-in-a-pull-request). It gets no checks only if each of the base branch's presubmit checks is limited by `when.changed` to files that no longer exist. - Chainguard Checks is turned on for your organization, either with `chainctl develop init` or by your Chainguard account team. Pull requests opened or updated before then get no checks, so push a new commit to the pull request to start a run. If `chainctl develop init` failed, refer to [chainctl develop init fails](/chainguard/workspaces/troubleshooting/#chainctl-develop-init-fails). - Your organization isn't at its run limit. With the [Starter allowance](/chainguard/checks/overview/#availability-and-limits), your organization can have 10 runs queued or running at a time, including runs after merging. At the limit, Chainguard Checks retries a new run for a few minutes, then gives up, and the pull request shows no summary check. The run doesn't start on its own when a place frees up, so once earlier runs finish, push a new commit, reopen the pull request, or use **Re-run**. - If your organization started a free trial with `chainctl` 0.2.379 or earlier, the trial hasn't ended. That trial lasts 30 days, plus a 7-day grace period, and includes 500 vCPU-hours of Checks. When the grace period ends, Chainguard Checks stops starting runs. To continue, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). ## The summary check says the configuration is invalid {#the-summary-check-says-the-configuration-is-invalid} If **Chainguard Checks (presubmit)** fails with a title that starts with **Invalid config**, Chainguard Checks refused the pull request's configuration and ran nothing. The summary check's output lists each problem with its file and line. To find the same problems before you push, run `chainctl checks validate .` from your repository's directory. The following table lists the most common causes, grouped by the key or file they involve: | Cause | What to do | | ----- | ---------- | | A configuration file doesn't have `version: 1`. | Add `version: 1` to the file. | | A configuration file uses GitHub Actions keys, such as `jobs`, `on`, `runs-on`, or `env`. | Replace them with their equivalents, as described in [Migrate from GitHub Actions](/chainguard/checks/migrate-from-github-actions/). | | A string `cmd` contains unquoted shell syntax, such as a pipe, redirection, `&&`, a variable, or a glob character. | A string `cmd` runs without a shell. Quote those characters to pass them to the command as they are, or use `script` to run them with a shell. Inside double quotes, `$` and backticks are refused too, so put them in single quotes. Refer to [The cmd key](/chainguard/checks/configuration/#the-cmd-key). | | A check's `shell` isn't the exact name of a package in the check's environment. | Set `shell` to a package that the environment lists, such as `python-3.13`. | | A check has a `network` key with no hosts under `egress`. | List the hosts the check needs. To keep a check off the network, leave out `network` entirely. | | A `when` key is empty. | To run the check every time, leave out `when`. Refer to [Conditions](/chainguard/checks/configuration/#conditions). | | `when.changed` lists no patterns. | List at least one pattern, or leave out `changed`. | | `when.requested` is set to something other than `true`. | Set it to `true`, or leave it out. | | A `foreach` entry sets `when: {requested: true}`. | Set `when: {requested: true}` on the check instead. | | A check with `when: {requested: true}` has no `description`. | Add a `description` that says what the check covers and when it's worth running. Refer to [Run on request](/chainguard/checks/configuration/#run-on-request). | | A check with `when: {requested: true}` also sets `changed` or `expr` under `when`. | To run a check when its files change and also on request, use `when.changed` alone. Refer to [Run a check on request](/chainguard/checks/github/#run-a-check-on-request). | | A check with `when: {requested: true}` has a name longer than 43 characters. | Shorten the name. Refer to [Run on request](/chainguard/checks/configuration/#run-on-request). | | A blocking check needs an advisory check, either with `needs` or by taking an input from it with `from`. | Make both checks advisory, or both blocking. Refer to [Advisory checks](/chainguard/checks/configuration/#advisory-checks). | | A check that doesn't run on request needs one that does, with `needs` or `from`. | Make both checks run on request, or neither. Refer to [Run on request](/chainguard/checks/configuration/#run-on-request). | | A check's name has a character that isn't allowed, or doesn't start and end with a letter or digit. | Rename the check. Unless the character is `.` or `/`, the error for a character that isn't allowed says only that the name must fit the 128-character limit, even when the name is short. Refer to [Check names](/chainguard/checks/configuration/#check-names). | | Two checks in the same file have names that differ only in case, such as `Test` and `test`. | Rename one of them. | | A check's full name, such as `ci/test`, is longer than 128 characters. | Shorten the check's name, or the file's. | | A check's environment names a cache under `caches`, but the check doesn't set `user: build`. | Set `user: build` on the check, or give it an environment without the cache. Refer to [Build caches](/chainguard/checks/variations-services-caches/#build-caches). | | A check sets a variable that its cache sets, such as `GOCACHE`. | Remove the variable from the check's `vars`. | | A check's `docs` names a Markdown file that doesn't exist beside the check's file in `.chainguard/ci/`, or that's larger than 1 MiB. | Add or shorten the file. The single-file layout doesn't support `docs`. Refer to [Describe a check](/chainguard/checks/configuration/#describe-a-check). | | `.chainguard/ci/resources.yaml` doesn't define a class named `default`. | Add a `default` class. Refer to [Define your own resource classes](/chainguard/checks/configuration/#define-your-own-resource-classes). | | `resources.yaml` gives a `memory` or `disk` size in a unit other than `Mi` or `Gi`, such as `16GB`. | Write the size in `Mi` or `Gi`, such as `16Gi`. | | A check names a resource class that `resources.yaml` doesn't define. | The file replaces the built-in classes, so a check can't name `medium` or `large-amd64` unless the file defines it. Define the class, or name one that the file defines. | | `.chainguard/` or `.chainguard/ci/` holds a file that the repository's layout doesn't allow, such as a `.yml` file. | Remove or rename the file. Refer to [File layouts](/chainguard/checks/configuration/#file-layouts) for the files each layout allows. | | The repository has a `.chainguard/ci.yaml` file and also has files in `.chainguard/ci/` other than `identities.yaml`. | A repository can't use both layouts. Move the checks from `.chainguard/ci.yaml` into a file in `.chainguard/ci/`, and its environment to `.chainguard/ci/env/default.yaml`. | ## The summary check says the run couldn't be created or failed {#the-summary-check-says-the-run-couldnt-be-created-or-failed} If **Chainguard Checks (presubmit)** fails with **Run creation failed** or **Run failed**, Chainguard Checks read your configuration but didn't run it. The summary check's output gives the reason. The following table lists the most common causes: | Cause | What to do | | ----- | ---------- | | The pull request removes a check, changes a check's conditions or trigger so that it no longer runs for this pull request, or leaves no presubmit checks while the base branch's configuration still has one, even one that wouldn't have run for this pull request. The exception is a check limited by `when.changed` to files that no longer exist. | A change can't remove itself from its own checks. Refer to [Change checks in a pull request](/chainguard/checks/github/#change-checks-in-a-pull-request). | | The pull request changes 3,000 or more files, and the output says so. | Split the change into smaller pull requests, because **Re-run** fails the same way. A commit on your default branch that changes 3,000 or more files also fails in **Chainguard Checks (postsubmit)**. Refer to [Availability and limits](/chainguard/checks/overview/#availability-and-limits). | | A check `needs` another check that doesn't run under the same trigger, such as a presubmit check that needs a check with only `trigger: postsubmit`. | Give both checks the same `trigger`. | | A check's `git` input names a private repository, or a `ref` that doesn't exist in the repository. | Chainguard Checks resolves each `git` input to a commit when it creates the run, without credentials, so it can check out only public GitHub repositories. Refer to [Check out another repository](/chainguard/checks/variations-services-caches/#check-out-another-repository). | | A check's `user` is the name of an account that the VM already has, such as `daemon`, `mail`, or `nobody`, and the output says that the user collides with an existing account. | `chainctl checks validate` accepts these names, but the run fails. Choose another name, such as `build`, as described in [Run as another user](/chainguard/checks/variations-services-caches/#run-as-another-user). | | A check binds `context.github.pull_request.body` to `CHAINGUARD_BASE_REVISION`, `GOOGLE_APPLICATION_CREDENTIALS`, `GITHUB_TOKEN`, or `GOFLAGS`. | Bind the description to a variable with another name. Refer to [Environment variables](/chainguard/checks/configuration/#environment-variables). | | A check binds the pull request's description, and the description is over 65,536 bytes or contains a NUL character. The output says how many bytes the description is, or that it contains NUL. | Shorten the description, then use **Re-run** on the summary check, or push a new commit. Characters outside ASCII take more than one byte each. | | A check asks for a larger resource class than your organization allows. The output says that a job asks for more CPUs and memory, or more scratch disk, than your organization's largest resource class. | Choose a smaller class with `resources`, or lower your class's `cpus`, `memory`, or `disk`. With the Starter allowance, the largest class is `medium`, which has 4 vCPUs and 16 GB of memory. Unless your organization's allowance sets a different limit, a check's VM can have at most 16 vCPUs, 64 GB of memory, and 100 GB of disk. The built-in classes are within these limits, but a class you [define in `resources.yaml`](/chainguard/checks/configuration/#define-your-own-resource-classes) can exceed them. | | Your organization used up this month's compute allowance. The output says that your organization can't start work until the allowance resets on the first day of the month (UTC), and that work already running finishes. | After the reset, use **Re-run** on the summary check, or push a new commit. To continue sooner, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). Refer to [Availability and limits](/chainguard/checks/overview/#availability-and-limits) for how checks use the allowance. | | Your organization hasn't accepted the terms that Chainguard Checks is offered under. The output names the terms and the command that accepts them. | Have an organization owner run `chainctl checks accept-terms --parent $ORGANIZATION`, replacing `$ORGANIZATION` with the name of your Chainguard organization. After about 30 seconds, use **Re-run** on the summary check, or push a new commit. | ## The summary check says checks could not be executed {#the-summary-check-says-checks-could-not-be-executed} If **Chainguard Checks (presubmit)** says that checks could not be executed, or a check's title starts with **Could not be executed**, those checks stopped for a reason other than a command that failed. The summary check's output lists each one's reason under **Errors:**, as described in [Read the summary check](/chainguard/checks/github/#read-the-summary-check). These are the most common reasons: | Reason | What to do | | ------ | ---------- | | `task timed out after`, followed by the check's `timeout` | The check ran longer than its own [timeout](/chainguard/checks/configuration/#timeouts). Raise `timeout`, or split the check into smaller ones. | | `workflow run exceeded the 50-minute limit` | The run reached its time limit before the check finished. Refer to [A check stops after 50 minutes](#a-check-stops-after-50-minutes). | | Includes `workflow identity rejected` | The check didn't get its workload identity, for example because the pull request doesn't reach the identity's ring. Refer to [When a pull request doesn't get an identity](/chainguard/checks/identity-and-trust/#when-a-pull-request-doesnt-get-an-identity). | | `the package set does not resolve`, or `two packages in the set install the same file` | Chainguard Checks couldn't install the check's packages. Fix the names and version constraints in `environment.packages`, or remove one of the two packages that install the same file. | | Any other reason | The check's VM kept failing for a reason outside the check, even after [automatic retries](/chainguard/checks/github/#automatic-retries). Use **Re-run** on the summary check, or push a new commit. | ## The summary check reports a merge conflict {#the-summary-check-reports-a-merge-conflict} Chainguard Checks runs each pull request against the commit GitHub would create by merging it. If the pull request has merge conflicts with its base branch, GitHub can't create that commit, and the summary check fails with **Merge conflict with the base branch**. Resolve the conflicts and push. ## A check can't find a command {#a-check-cant-find-a-command} A check's VM starts with BusyBox, GNU tar, and only the packages you list, so commands such as `git`, `bash`, `make`, and `curl` aren't available unless you list their packages. Add the package to `environment.packages` in your configuration file. If a script file fails with `not found`, its first line, such as `#!/bin/bash`, may name an interpreter that isn't installed. List the interpreter's package. If a `script` uses Bash syntax, also set `shell: bash` and list the `bash` package. ## A check can't reach a host {#a-check-cant-reach-a-host} A check has no network access unless it lists hosts under `network.egress`, so downloads and API calls fail, often with errors that a host couldn't be resolved or a connection was refused. Add each host the check needs to its `egress` list, such as `proxy.golang.org` for Go modules or `registry.npmjs.org` for npm packages. List bare hostnames, without a scheme or port. Refer to [Network access](/chainguard/checks/configuration/#network-access). Some hosts redirect to others. For example, a package registry might serve files from a separate content delivery host. To see every host a check tried to reach, follow these steps: 1. Open the check's **Details** link on the pull request, and sign in with your Chainguard account. 2. In the run page's sidebar, under the check's name, select **Details**. 3. Expand **Network activity**. It's already open if Chainguard Checks refused a connection. 4. Add each host whose **Decision** is `deny` or `destroy` to the check's `egress` list. The table has a row for each hostname the check looked up and each address it connected to. A connection's row shows the IP address and port, followed by the hostnames the check looked up for that address. With `policy: destroy`, the page also names the host that stopped the check. To open the run page, you need a role in your Chainguard organization, as described in [The Details link or a chainctl checks command says you don't have access](#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access). ## A check can't find submodules, Git LFS files, or private repositories {#a-check-cant-find-submodules-git-lfs-files-or-private-repositories} A check's `/work` directory holds your repository's tracked files and history, but not submodules or Git LFS content. Submodule directories are empty, and files that Git LFS tracks hold their pointer files. During the beta, a check also has no GitHub credential, so commands that fetch from a private GitHub repository, such as a download of a private Go module, fail to authenticate. Refer to [Credentials](/chainguard/checks/configuration/#credentials). To add files from another public repository, use a [`git` input](/chainguard/checks/variations-services-caches/#check-out-another-repository). ## A check fails on pull requests but passes in run-ci {#a-check-fails-on-pull-requests-but-passes-in-run-ci} The `run-ci` command in a Workspaces session runs each check in the session's environment, which can differ from the pull request's: - The session might have packages or network access that the check's own environment doesn't list. - Sessions run on x86-64 unless their resource class pins arm64, while pull request checks usually run on arm64 unless their resource class pins x86-64. If a check downloads or runs an x86-64 binary, set its resource class to an `-amd64` variant of a built-in class, such as `medium-amd64`. If you [define your own resource classes](/chainguard/checks/configuration/#define-your-own-resource-classes), set `platform` on the check's class instead, because only `platform` pins the architecture. Refer to [How run-ci differs from a pull request run](/chainguard/checks/run-checks/#how-run-ci-differs-from-a-pull-request-run). To see what failed on the pull request, print the error lines of the check's log from your terminal, with three lines around each. Replace `$OWNER/$REPO#$NUMBER` with the pull request, such as `acme/app#123`, `test` with the check's name, and `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl checks logs "$OWNER/$REPO#$NUMBER" --check test --errors -C 3 --parent $ORGANIZATION ``` For other ways to read part of a long log, and the role these commands need, refer to [Inspect pull request runs](/chainguard/checks/inspect-and-run-checks/#inspect-pull-request-runs). ## A check stops after 50 minutes {#a-check-stops-after-50-minutes} A whole run can take up to 50 minutes from when it starts, and checks still running or waiting to start at that point fail. Split long checks into smaller ones, which run in parallel, up to four at a time with the Starter allowance. You can also move slow work to a [postsubmit check](/chainguard/checks/github/#run-checks-after-merging). ## A check runs out of space in /tmp {#a-check-runs-out-of-space-in-tmp} A check's `/tmp` directory is held in memory and can hold up to a quarter of the VM's memory: about 2 GB with the default class, or 4 GB with `medium`. When it's full, commands that write there fail with `No space left on device`, even though the VM's disk has room. Put large files, such as build output, downloaded archives, or a Python virtual environment, under `/var/tmp` or `/work`, which are on the VM's disk. Many tools write temporary files to the directory that `TMPDIR` names, so you can also set `TMPDIR` to `/var/tmp` in the check's `vars`. To give `/tmp` more room instead, choose a resource class with more memory. Refer to [Resources](/chainguard/checks/configuration/#resources). ## A check doesn't use its cache {#a-check-doesnt-use-its-cache} A check whose environment names a cache under `caches` runs without one, and without an error, in the following cases: - Your default branch doesn't define cache pools yet. This includes the pull request that adds them, because Chainguard Checks reads pools only from your default branch. Refer to [Set up cache pools](/chainguard/checks/variations-services-caches/#set-up-cache-pools). - No pool lists the run's ring, such as ring `0` for a pull request opened by someone outside your subrings. - The cache isn't `gobuild`. During the beta, the other cache types cache nothing. - The run is a rerun from **Re-run** on GitHub or from `chainctl checks rerun`. - The check uses a workload identity, and a ring in its pool can't get the same identity. - The pool is still filling, as described in [How caches fill](/chainguard/checks/variations-services-caches/#how-caches-fill). To see what each check gets, run `chainctl checks explain` and read its `cache selection` section. For each check that names a cache, it shows what the check mounts in each pool, or why it mounts nothing. It can't tell which pool a pull request's author reaches, and it lists the cache types other than `gobuild` as `read-write`, though they cache nothing during the beta. ## A required check never reports {#a-required-check-never-reports} If GitHub waits indefinitely for a required check, you probably required a per-check result, such as **presubmit / ci / test**, instead of the summary check. A check that a condition skips, or that runs only on request, never reports a result. Require only **Chainguard Checks (presubmit)**, as described in [Require checks before merging](/chainguard/checks/github/#require-checks-before-merging). A GitHub merge queue that requires **Chainguard Checks (presubmit)** never gets a result either, because Chainguard Checks doesn't run on merge queues during the beta. The queue waits until its timeout expires, then removes the pull request. Don't require the summary check on a branch that uses a merge queue. ## The requested checks agent doesn't comment on a pull request {#the-requested-checks-agent-doesnt-comment-on-a-pull-request} The [requested checks agent](/chainguard/checks/github/#let-an-agent-request-checks) doesn't add a comment for each push. It keeps one **Requested checks** comment on the pull request, edits it, and names the commit that it answers for. If the comment doesn't appear at all, or names an earlier commit, check the following: - `requested_checks_agent` is set to `comment` or `agent` on the pull request's base branch. The agent reads the setting from the latest commit on the base branch, so the pull request that sets it gets no answer. After it merges, pull requests that are already open get an answer at their next push or edit. - The pull request is open and isn't a draft. Marking a draft ready for review starts the agent. - If the pull request changes files in `.chainguard/`, its configuration is valid. To check, run `chainctl checks validate`. - The pull request has a check that the agent could request: a presubmit check with `when: {requested: true}`, or with `when.changed` patterns that the pull request's changes don't match. The agent skips checks whose `when.expr` is false and checks that already run, and when none are left, it doesn't comment. - The pull request changes fewer than 3,000 files. The agent answers once for each pushed commit and base branch. Editing the pull request's title or description doesn't make it choose again, but pushing a commit or changing the base branch does. When it finds no check worth running, it still comments to say so. ## chainctl doesn't list the checks command {#chainctl-doesnt-list-the-checks-command} During the beta, the `chainctl checks` and `chainctl develop` commands don't appear in `chainctl --help` or in the [chainctl reference](/platform/chainctl/chainctl-docs/chainctl/). You can still run them, and `--help` on either one shows its full help. For every subcommand and flag, refer to the [`chainctl checks` reference](/chainguard/checks/chainctl-checks-reference/) and the [`chainctl develop` reference](/chainguard/workspaces/chainctl-develop-reference/). If `chainctl checks validate .` reports that validating a directory isn't supported, or `chainctl` doesn't recognize a command on this site, your `chainctl` is out of date. Update it with `sudo chainctl update`, or with `brew upgrade chainctl` if you installed it with Homebrew. ## chainctl checks status reports no platform verdict {#chainctl-checks-status-reports-no-platform-verdict} During the beta, `chainctl checks status`, `chainctl checks logs`, and the other commands that inspect runs can't find a run by its commit. When you pass a commit SHA or a directory, or run one of these commands with no argument, which uses the commit checked out in the current directory, they report that the platform created no run for it, even when its checks ran. The `chainctl checks list` command doesn't list pull request runs or runs after merging either. To inspect a pull request's latest run, pass the pull request instead. Replace `$OWNER/$REPO#$NUMBER` with the pull request, such as `acme/app#123`, or with its URL. Replace `$ORGANIZATION` with the name of the Chainguard organization linked to the repository's GitHub account: ```shell chainctl checks status "$OWNER/$REPO#$NUMBER" --parent $ORGANIZATION ``` If the command still reports no platform verdict for a pull request whose checks appear on GitHub, check that you typed the owner and repository exactly as GitHub shows them, including capital letters. With a `chainctl` release older than the [minimum version](/get-started/checks-and-workspaces/set-up/#install-or-update-chainctl), pull requests also report no platform verdict. Update it with `sudo chainctl update`, or with `brew upgrade chainctl` if you installed it with Homebrew. For a run after merging, pass the run's ID instead of a commit. The ID is the part after `/runs/` in the **Details** link of **Chainguard Checks (postsubmit)**. You can also read the results on GitHub, as described in [Run checks after merging](/chainguard/checks/github/#run-checks-after-merging). For a run you started with [`chainctl checks run`](/chainguard/checks/chainctl-checks-reference/#chainctl-checks-run), pass the ID that the command printed, or the start of it, instead of `.` or a commit. To find the ID later, run `chainctl checks list`. These runs never appear on GitHub. ## The Details link or a chainctl checks command says you don't have access {#the-details-link-or-a-chainctl-checks-command-says-you-dont-have-access} The **Details** link on a check opens the run on a page that Chainguard hosts, where you sign in with your Chainguard account. To open the run page, or to read or rerun runs with `chainctl checks` commands such as `status`, `logs`, and `rerun`, you need the `owner` or `sandbox.user` role in the Chainguard organization linked to the repository's GitHub account. Without one of these roles, `chainctl checks` fails with `permission denied`. To pass your organization's name to `--parent`, you also need a role that can see the organization, such as `viewer`. With `sandbox.user` alone, `chainctl` can't find your organization by its name, so pass the organization's ID instead. To get these roles, ask an owner of your organization, as described in [Give teammates access](/get-started/checks-and-workspaces/set-up/#give-teammates-access). During the beta, if you created a Starter organization with `chainctl develop init`, you have the `limited_owner` role instead, which can't open the run page or read runs, and you can't grant the `sandbox.user` role. In that case, do the following: - Read the results on the pull request, as described in [How results appear](/chainguard/checks/github/#how-results-appear), and rerun checks with **Re-run** on GitHub. - Don't use `chainctl checks run`. Your role can start runs, which use your allowance, but can't read their results. - To run checks before you push, use a Workspaces session with `chainctl develop check`, as described in [Run checks in a session from your laptop](/chainguard/checks/run-checks/#run-checks-in-a-session-from-your-laptop). --- ### Troubleshoot Chainguard Workspaces URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/workspaces/troubleshooting.md Last Modified: October 15, 2026 Tags: Chainguard Workspaces, Troubleshooting This page describes common problems with Chainguard Workspaces sessions and how to fix them. If your problem isn't listed here, ask in the #workspaces-questions channel in the [Chainguard community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw). ## chainctl doesn't list the develop command {#chainctl-doesnt-list-the-develop-command} During the beta, the `chainctl checks` and `chainctl develop` commands don't appear in `chainctl --help` or in the [chainctl reference](/platform/chainctl/chainctl-docs/chainctl/). You can still run them, and `--help` on either one shows its full help. For every subcommand and flag, refer to the [`chainctl checks` reference](/chainguard/checks/chainctl-checks-reference/) and the [`chainctl develop` reference](/chainguard/workspaces/chainctl-develop-reference/). If `chainctl` reports that `develop` is an unknown command, or doesn't recognize a flag or subcommand on this site, your `chainctl` is out of date. Update it with `sudo chainctl update`, or with `brew upgrade chainctl` if you installed it with Homebrew. ## chainctl develop init fails {#chainctl-develop-init-fails} If `chainctl develop init` stops with an error, find its message in the following list. Replace `$ORGANIZATION` with the name of your Chainguard organization. - `this identity is not eligible for a self-serve Starter organization`: run `chainctl develop init` again and, in your browser, sign in with a work email address, using a password or Google. Other sign-in methods, such as GitHub, can't create a Starter organization. If you already signed in one of those ways, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). - `unable to create self-serve organization`: most often, your email domain already has a Chainguard organization. Ask one of its owners to invite you. If that organization is on the Starter or Team plan, you can also run `chainctl starter request-access` to send its owners a request. - `you belong to several organizations, or none on the Starter or Team plan`, or `you already belong to an organization`: run the command again with `--parent $ORGANIZATION`, and without `--name`, to choose the organization where you turn on Checks and Workspaces. - `missing: terms.accept`: only the `owner` and `limited_owner` roles can turn on Checks and Workspaces. Ask someone with one of those roles to run `chainctl develop init --parent $ORGANIZATION`. - `creating a Starter organization asks a few questions about you`, or `accepting the required legal agreements needs an interactive terminal`: run the command in an interactive terminal, without `--non-interactive`. - `This organization can't use self-serve Checks and Workspaces`, or `Checks and Workspaces were turned off for this organization`: [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). - `you must own the GitHub account (organization or user)`: link your own GitHub user, or a GitHub organization where you're an owner. ### The GitHub account is linked to another organization {#the-github-account-is-linked-to-another-organization} If `chainctl develop init` fails with a message that says the GitHub account or organization you chose is `already linked` to another Chainguard organization, you can't link it until that link is removed, because a GitHub account can be linked to only one Chainguard organization at a time. In an interactive terminal, `chainctl develop init` names the account and asks for a different one to link. To keep that account linked where it is, or outside an interactive terminal, do one of the following: - Run `chainctl develop init` again and link a different GitHub account. The command uses the organization it already created. - Use the organization that holds the link instead. Have one of its owners run `chainctl develop init --parent $ORGANIZATION`, replacing `$ORGANIZATION` with that organization's name. - If you own the GitHub account, unlink it from the other organization with `chainctl guardener github unlink --github-org $GITHUB_ACCOUNT`, replacing `$GITHUB_ACCOUNT` with the account's name, and then run `chainctl develop init` again. Unlinking stops Guardener for that account in the other organization. For more about links, refer to [Managing Chainguard App connections](/chainguard/guardener/github/app-connections/#moving-a-github-organization-to-a-different-chainguard-organization). ## You don't have permission {#you-dont-have-permission} If a `chainctl develop` command fails with a `PermissionDenied` error that says only `permission denied`, the account you're logged in with doesn't have a role in the organization that allows the command, as described in [Roles](/chainguard/workspaces/chainctl-develop-reference/#roles). Membership alone isn't enough, because the `viewer` and `editor` roles don't include Workspaces. Ask an organization owner to give you access, as described in [Give teammates access](/get-started/checks-and-workspaces/set-up/#give-teammates-access). If you have more than one Chainguard account, run `chainctl auth status` to check which one you're logged in with. The `chainctl develop list --all` and `chainctl develop policy set` commands need the `owner` role. Without it, `chainctl develop policy set` fails with the same error, and `chainctl develop list --all` fails with an error that says `listing another owner's sessions requires the list capability on the instance`. ## Your organization isn't entitled to Chainguard Workspaces {#your-organization-isnt-entitled-to-chainguard-workspaces} If `chainctl develop` fails with a `PermissionDenied` error that says `this organization is not entitled to Chainguard Workspaces`, Checks and Workspaces aren't turned on for the organization. Have someone with the `owner` or `limited_owner` role turn them on with the following command, replacing `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl develop init --parent $ORGANIZATION ``` If an owner turned them on a moment ago, wait a minute and try again. ## Your organization hasn't accepted the terms {#your-organization-hasnt-accepted-the-terms} If `chainctl develop` fails with a `FailedPrecondition` error that says your organization hasn't accepted the terms Chainguard Workspaces is offered under, Chainguard refuses to create sessions in the organization until someone with the `owner` or `limited_owner` role accepts them. The error names the terms. Have someone with one of those roles run the following command, replacing `$ORGANIZATION` with the name of your Chainguard organization: ```shell chainctl develop accept-terms --parent $ORGANIZATION ``` The acceptance can take up to 30 seconds to take effect, so wait briefly before you try again. Sessions that are already running or parked keep working. ## A session won't open on a directory {#a-session-wont-open-on-a-directory} If `chainctl develop` refuses to open a directory, the error says why. The following sections describe the most common causes. ### Several sessions match the directory {#several-sessions-match-the-directory} If a directory has more than one session, `chainctl` asks which one you want, or lists them and fails when it can't ask. Pass the session's name or short ID instead of the directory. ### The repository has no environment {#the-repository-has-no-environment} With `--non-interactive`, a repository that has neither a `.chainguard/develop.yaml` file nor a `.chainguard/ci.yaml` file is an error. Pass `--env default` to use the default environment. ### The directory is too large to generate an environment {#the-directory-is-too-large-to-generate-an-environment} If `chainctl develop --env generate` fails with an error that says the directory is `larger than the 33554432-byte generation limit`, the files that `chainctl` uploads to generate the environment add up to more than 32 MiB. An error that says the upload `holds more than 50000 files` means that it has too many files. Either way, pass `--env default` or an [environment file of your own](/chainguard/workspaces/session-environment/#use-a-separate-environment-file) instead. To learn which files the upload includes, refer to [Generate an environment](/chainguard/workspaces/session-environment/#generate-an-environment). ### The directory is the top of a linked worktree or submodule {#the-directory-is-the-top-of-a-linked-worktree-or-submodule} If `chainctl develop` fails with an error that says `the checkout's .git is a pointer to a git directory elsewhere`, the directory is the top of a linked worktree, which `git worktree add` creates, or of a submodule. Its history is stored outside the directory, so `chainctl` can't sync it. Run `chainctl develop` from the main checkout instead. A session opened there gets each submodule as an empty directory. From a subdirectory of a linked worktree or submodule, `chainctl` syncs only that subdirectory, without the repository's history. ## You reached the session limit {#you-reached-the-session-limit} With the free monthly allowance, each user can have one session at a time, including a parked one, and the organization can have three. A parked session counts toward both limits until you delete it or it expires. At a limit, `chainctl develop` fails with a `ResourceExhausted` error: - If the error says `you are at your limit of 1 active sessions in this organization`, delete a session you no longer need with `chainctl develop delete`, or return to the one you have. - If the error says `this organization is at its active-session capacity (3)`, other users hold the organization's sessions. Only the user who created a session can delete it with `chainctl develop delete`, so ask your teammates to delete the sessions they no longer need. If you have the `owner` role, `chainctl develop list --all` shows every user's sessions with an `OWNER` column. In a Starter organization, no one can list other users' sessions, as described in [Roles](/chainguard/workspaces/chainctl-develop-reference/#roles). ## A session fails to start because of a package {#a-session-fails-to-start-because-of-a-package} If a session fails to start with an error about a package, such as `nothing provides` followed by a package name, the session's environment lists a package that doesn't exist or that your organization can't access. Check the package names in the file the environment comes from, such as `.chainguard/develop.yaml`, `.chainguard/ci.yaml`, or your `--env` file. Package names often include a version, such as `go-1.24` or `python-3.13`. ## A session asks for more resources than your organization allows {#a-session-asks-for-more-resources-than-your-organization-allows} If a session fails with an error that says the environment's guest is `larger than this organization's largest resource class`, the session's environment asks for more vCPUs, memory, or disk than your organization allows. The error names what the environment asks for and your organization's limit. Choose a smaller size in the environment's `resources`, as described in [Resources](/chainguard/workspaces/session-environment/#resources), or [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). ## Changes on your laptop don't appear in the session {#changes-on-your-laptop-dont-appear-in-the-session} This is expected. Your directory syncs into a session only the first time you attach. After that, changes on your laptop don't reach the session, and changes in the session don't reach your laptop. Commit and push from the session, then pull on your laptop, or the other way around. To start a session from your laptop's current tree, open a new one with `chainctl develop --new`. ## Claude Code reports an error in the session {#claude-code-reports-an-error-in-the-session} Most errors mean that the session doesn't have a current Claude credential. For how long a session keeps the credential that `chainctl` sends, refer to [Keep Claude Code working while you're away](/chainguard/workspaces/coding-agents/#keep-claude-code-working-while-youre-away). Claude Code shows the error in the session, and `chainctl` prints messages about the credential on your laptop. To review those messages later, press `Ctrl+]` to open the session menu and choose **Logs**. The following sections describe the most common causes. ### No credential has arrived {#no-credential-has-arrived} If the error says that `none has arrived`, the session has no Claude credential. Claude Code might show only a `503` error. Update `chainctl` to the [minimum version](/get-started/checks-and-workspaces/set-up/#install-or-update-chainctl) or later, run `claude` on your laptop and log in, and then attach to the session with `chainctl develop`. If you use Claude through Vertex AI, run `gcloud auth application-default login` on your laptop instead of logging in to Claude. ### The session's host is too old {#the-sessions-host-is-too-old} If the error says that the session's host `predates laptop credential push`, or `chainctl` says that the session's host was started before it could send model credentials, the host can't take a credential. Attach to the session with a current `chainctl`, press `Ctrl+]`, and choose **Restart the host**. You can also detach, let the session park, and attach again. ### The credential sent to the session expired {#the-credential-sent-to-the-session-expired} If the error says that `the owner's Claude login token sent to this session expired`, the short-lived access token from your stored Claude login ran out. `chainctl` renews it while you're attached, and while a terminal that you detached from with a **Detach — keep running** option stays open. Attach again to send a fresh one. To keep a long unattended task from stopping, use a token from `claude setup-token`, as described in [Keep Claude Code working while you're away](/chainguard/workspaces/coding-agents/#keep-claude-code-working-while-youre-away). If you use Claude through Vertex AI, the error says instead that your Google credential or access token sent to the session for Vertex AI no longer works or expired. Attach again to send a fresh one. ### Claude Code uses a different provider {#claude-code-uses-a-different-provider} If the error says that `claude in this session now uses` Vertex AI or the Anthropic API, the session's Claude provider changed while Claude Code was running. Exit Claude Code and run `claude --continue` to resume the conversation. ### Claude is turned off for the session {#claude-is-turned-off-for-the-session} If the error says that `claude is turned off for this session`, the session's `claude` credential is turned off. Turn it back on with the command that the error names. Replace `$SESSION` with the session's short ID, which the error includes: ```shell chainctl develop creds $SESSION --source claude=laptop ``` ### Your Claude login expired {#your-claude-login-expired} If `chainctl` says that `your Claude login on this laptop` expires soon or has expired, run `claude` on your laptop once to refresh it. `chainctl` sends the refreshed login while you're attached. If you're detached, attach to the session again. ### You aren't logged in to Claude {#you-arent-logged-in-to-claude} If `chainctl` says `claude: nothing to send` when you attach, it found no Claude login or Google credentials on your laptop. Run `claude` on your laptop and log in, or run `gcloud auth application-default login` if you use Claude through Vertex AI. `chainctl` tries again while you're attached. ### Claude Code isn't installed in the session {#claude-code-isnt-installed-in-the-session} If `claude` isn't found in the session, the session's environment doesn't list it. Add `claude` to your environment's packages and open a new session, or open one on the default environment with `--env default`. ## A push or fetch fails in the session {#a-push-or-fetch-fails-in-the-session} If `git push` or `git fetch` fails with a message that the org GitHub App can't serve the repository because it's not installed or not linked, the Chainguard App isn't installed on the GitHub account that owns the repository, or that account isn't linked to your organization. Install the app from the [Chainguard App installation page](https://github.com/apps/chainguard/installations/new), selecting every account you work in. Then allow the repository, replacing `$OWNER` and `$REPO` with its owner and name: ```shell chainctl develop --with github:$OWNER/$REPO --parent $ORGANIZATION ``` If the message says the request isn't allowed for the session yet, that the session owner declined it, or that nobody answered within 60 seconds, the session asked for access beyond its defaults and nobody approved it. Approve it on your attached terminal when the prompt appears, or allow it ahead of time with `--with`. Refer to [Manage session credentials](/chainguard/workspaces/credentials/). Pushing from the session over SSH doesn't work. The session rewrites only `https://github.com/` URLs, so keep your remotes on HTTPS. ### The branch requires signed commits {#the-branch-requires-signed-commits} Commits that you make in a session aren't signed. If a GitHub rule requires signed commits on the branch that you push to, GitHub rejects the push. To get the commits onto that branch, push them to a branch that the rule doesn't cover, sign them on your laptop, and then merge them as you usually do: 1. In the session, create a branch and push it, replacing `$BRANCH` with a name that the rule doesn't cover: ```shell git switch -c $BRANCH git push origin $BRANCH ``` 2. On your laptop, check out the branch, sign each of its commits, and push it again. If the session started from a branch other than `main`, replace `main` with that branch's name: ```shell git fetch origin git switch $BRANCH git rebase --exec 'git commit --amend --no-edit -S' origin/main git push --force-with-lease ``` If a rule requires signed commits on every branch, you can't push commits from a session to the repository. ### Find the details in the forwarding log {#find-the-details-in-the-forwarding-log} `chainctl` writes every GitHub and `cgr.dev` request that it serves from your laptop, and every failure, to `develop-forward.log` in its configuration directory on your laptop. On macOS, that's `~/Library/Application Support/chainctl/`. On Linux, it's `$XDG_CONFIG_HOME/chainctl/`, or `~/.config/chainctl/` if `XDG_CONFIG_HOME` isn't set. The log doesn't record Claude requests, because the session's host sends them itself. ## A session that acts as you can't reach a repository {#a-session-that-acts-as-you-cant-reach-a-repository} If you connected your GitHub account with `chainctl develop github login`, the sessions you create act on GitHub as you through the Chainguard Develop GitHub App, `chainguard-develop`. This app is separate from the Chainguard App. To fetch a private repository or push to any repository, a session needs Chainguard Develop installed on the account that owns the repository. For how to install it, refer to [Install the Chainguard Develop app](/chainguard/workspaces/github-as-you/#install-the-chainguard-develop-app). When you open a session from a repository URL, Chainguard checks that your account can reach the repository. If it can't, `chainctl develop` fails with a `FailedPrecondition` error that names one of the following causes and where to fix it: - Chainguard Develop isn't installed on the account that owns the repository. An owner of that account installs it, and a member can request it, on the [Chainguard Develop installation page](https://github.com/apps/chainguard-develop/installations/new). - The app's installation hasn't granted it read access to repository contents. An owner of the account accepts the app's requested permissions. - The organization requires SAML single sign-on. Sign in at the page that the error names, and then run `chainctl develop github login` again. - Your account can't see the repository through the app. If the app is installed only on selected repositories, an owner of the account adds this one. Otherwise, ask an owner for access. On a terminal, `chainctl` offers to open the page where you make the fix, waits for you to press `Enter`, and then tries to create the session again. A session that you open on a directory isn't checked when you create it. Instead, `git fetch` or `git push` fails in the session with a message that begins `GitHub refused the push` or `GitHub refused the fetch` and names the cause and the fix. The fix takes effect at once, so retry the command after you make it. You don't need a new session. To check from inside a session whether your account reaches a repository, run the following command, replacing `$OWNER` and `$REPO` with the repository's owner and name: ```shell curl -s "http://github/.chainguard/access?repo=$OWNER/$REPO" ``` On your laptop, `chainctl develop github status` shows which accounts the checkout in your current directory still needs the app installed on. ## A session runs out of space or memory {#a-session-runs-out-of-space-or-memory} The `/tmp` directory in a session is held in memory and limited to a quarter of the session's memory, which is 4 GB with the default 16 GB. Writing large files there uses memory that your processes need. Put large files under `/var/tmp` or `/work`, which are on the session's disk, 50 GB by default. To change a session's memory or disk, refer to [Resources](/chainguard/workspaces/session-environment/#resources). ## A session started fresh instead of resuming {#a-session-started-fresh-instead-of-resuming} Rarely, Chainguard can't restore a parked session's snapshot. The session then starts fresh, and `chainctl` says that the parked snapshot couldn't be restored and that uncommitted work was lost. Push your work regularly so that a lost snapshot doesn't cost you much. ## A session has expired {#a-session-has-expired} A parked session expires 5 days after it parks, and an expired session can't be resumed. `chainctl develop list --expired` shows your expired sessions. An expired session keeps its name until you delete it, so to reuse the name, delete the expired session with `chainctl develop delete`. Then open a new session with `chainctl develop`. ## Your organization's monthly compute allowance is used up {#your-organizations-monthly-compute-allowance-is-used-up} If `chainctl develop` fails with a `FailedPrecondition` error that says `this month's compute allowance is used up`, your organization has used its monthly vCPU-hours of Workspaces. Until the allowance resets on the first day of the month (UTC), you can't create a session or resume a parked one, and a session created with `--keep always` parks after you detach instead of staying up. Sessions that are already running aren't stopped. A parked session still expires 5 days after it parks, even if the allowance hasn't reset yet, so push your work regularly. To learn how sessions use the allowance, refer to [Availability and limits](/chainguard/workspaces/overview/#availability-and-limits). To continue before the allowance resets, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). ## Your organization's access to Chainguard Workspaces ended {#your-organizations-access-to-chainguard-workspaces-ended} If `chainctl develop` fails with a `PermissionDenied` error that says `this organization's access to Chainguard Workspaces has ended`, you can't create sessions, attach to them, or keep them running. You can still list, rename, and delete sessions, and use `chainctl develop creds`, `chainctl develop policy`, and `chainctl develop github`, so you can clean up. To continue, [contact Chainguard](https://www.chainguard.dev/contact?utm=docs). This happens, for example, when a free trial that your organization started with `chainctl` 0.2.379 or earlier ends. Such a trial includes 500 vCPU-hours of Workspaces over 30 days, followed by a 7-day grace period during which `chainctl` warns you each time you create a session. Its compute budget doesn't reset each month, and when the budget is used up, `chainctl develop` fails with a message that says `compute budget used up`. ## chainctl can't find your session {#chainctl-cant-find-your-session} `chainctl develop list` shows only your own sessions, unless you have the `owner` role and pass `--all`. If you're logged in to `chainctl` with a different account than the one that created a session, `chainctl` reports that no session matches. In some cases, it says instead that you're logged in as one identity but the session belongs to another. Only a session's owner can attach to it. Run `chainctl auth login` and choose the account that owns the session. --- ### How Chainguard creates container images with low-to-no CVEs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/concepts/zerocve.md Last Modified: October 8, 2026 Tags: Video, Chainguard Containers Chainguard's container images typically report few or no CVEs when scanned, which raises a fair question about whether those findings are being suppressed. They are not. Scanners read Chainguard Containers exactly as they read any other container, and they report whatever they find. The low counts are instead the product of three practices: shipping less software, rebuilding nightly, and publishing security advisories that tell scanners what a given finding means. This article explains each practice, and describes how to verify the results independently. ## Scanners do report CVEs in Chainguard Containers Scanning a current container usually returns a short list of findings, or none at all: ```sh grype cgr.dev/chainguard/jre:latest ``` Scanning an older build of the same container returns considerably more. Any digest works for this comparison, including whichever one your own Dockerfiles pin today. The following example uses a build published in October 2024: ```sh grype cgr.dev/chainguard/jre@sha256:43afe3cb7331f517eff19667939fb84c1e7aed283608e752007cfbbf9b4252d1 ``` ```output NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY . . . openjdk-23-jre 23.0.1-r0 23.0.2-r0 apk GHSA-46mv-5cpj-wjxv Unknown zlib 1.3.1-r4 1.3.2-r0 apk GHSA-h858-mf2m-8jf4 Unknown ``` The key point is that CVE counts on Chainguard Containers are a function of how recently the container was built. Vulnerabilities are disclosed against software that has already been published, so any given build accumulates findings as it ages. The `FIXED-IN` column records the practical consequence — most of what a scanner finds in an old build has a newer package version that resolves it. A pinned digest never changes. That is the purpose of pinning, and also its cost. Pair it with tooling that moves the pin, such as [Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/), and refer to [Considerations for image updates](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/) for guidance on planning the cadence. For current CVE data on a specific container and tag, refer to that container's entry in the [Chainguard Containers directory](https://images.chainguard.dev/directory?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-containers-concepts-zerocve). ## Practice 1: shipping less software Software that isn't in the container cannot carry a vulnerability. Most Chainguard containers are [distroless](/chainguard/containers/concepts/getting-started-distroless/): they hold the application, its runtime dependencies, and little else. Most do not include a shell, a package manager, or the general-purpose utilities that a conventional base image carries in case they are needed. That default is not absolute. Some containers do ship a shell or additional packages, either because the application requires them or for compatibility with common workflows. The standard Node.js container includes `busybox` and `npm` for that reason, whereas the Python container has no shell at all. Chainguard's slim variants pare those compatibility packages back. Shipping fewer packages is what makes the other two practices manageable. A container with a few dozen packages leaves only a few dozen to track, patch, and triage. Two related pages cover the tradeoffs this creates. [Chainguard Container variants](/chainguard/containers/concepts/container-variants/) explains both the development variants, which add a shell and package manager for build stages and debugging, and the slim variants. [Debugging distroless container images](/chainguard/containers/troubleshooting/debugging-distroless-images/) covers working with a container that has no shell. ## Practice 2: rebuilding nightly Chainguard tracks upstream releases and rebuilds its containers nightly, so a patched package reaches a published container without waiting for a release cycle. Keeping software current is unglamorous work, and it accounts for most of the effort involved: the majority of known vulnerabilities in a running system have already been fixed upstream and have not been picked up. The practical consequence is that each fix arrives as a new build. Pulling the same digest for six months means running six-month-old software regardless of how quickly Chainguard patched it, which is why the [container lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/) and update tooling matter as much as the container contents do. ## Practice 3: publishing security advisories The first two practices address most findings. The third handles the remainder: findings that are genuine but already resolved, and findings that were never applicable to begin with. A Chainguard security advisory is a machine-readable record, issued per package and per CVE, that scanners read alongside their own vulnerability data. Each advisory records one of several determinations: the CVE is fixed as of a given package version; the package is not affected, for instance because the vulnerability only reaches Windows code paths and the package is built for Linux; a fix is pending upstream; or a fix is not planned. Scanners that consume these records filter their output accordingly and report more accurate results. Advisories are published in several places: - The [Security Advisories page](https://images.chainguard.dev/security/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-containers-concepts-zerocve) in the Containers directory, for browsing by CVE or container. - [`wolfi-dev/advisories`](https://github.com/wolfi-dev/advisories) on GitHub, where each package's advisories live as YAML. - An [OSV](https://ossf.github.io/osv-schema/) feed, for scanners and automation. [How to use Chainguard Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-to-use/) covers reading and consuming them, and [How Chainguard issues Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/) walks through an advisory's life from disclosure to remediation, including the feed URLs. An advisory is not a mechanism for dismissing a finding. It records a determination about a specific package version, and scanners remain free to disagree. [False positives and false negatives with container image scanners](/chainguard/containers/security-and-compliance/working-with-scanners/false-results/) explains how both arise and how to tell them apart. ## What this means in practice - Scan the build you are actually running, by digest, rather than a tag that may have moved. - Expect findings to accumulate on any container you pin and stop updating. This reflects the software aging rather than any change in the scan. - Consult the advisory before triaging a finding. It may already record the CVE as fixed or not applicable. The following video demonstrates the same three practices, including a scan of an old container image against a current one: [Watch the video on YouTube](https://www.youtube.com/watch?v=Fuw9lYX6Ne8)

Note: In November 2024, after this article was first written, Chainguard made changes to its free tier of container images. In order to access the non-free container images used in this guide, you will need to be part of an organization that has access to them. For a full list of container images that will remain in Chainguard's free tier, please refer to this support page.

## Related reading - [How Chainguard issues Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/) — the stages of an advisory, and where to get the feeds. - [Getting started with distroless containers](/chainguard/containers/concepts/getting-started-distroless/) — what minimal containers leave out, and what that changes. - [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/) — which versions Chainguard actively patches. - [Keeping containers updated](/chainguard/containers/security-and-compliance/updating-containers/) — tooling that moves your pins as new builds ship. --- ### How to set up pull through from Chainguard's registry to Nexus URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/nexus-pull-through.md Last Modified: October 8, 2026 Tags: Chainguard Containers Organizations can use Chainguard Containers along with third-party software repositories in order to integrate with current workflows as the single source of truth for software artifacts. In this situation, you can set up a proxy repository to function as a mirror of [Chainguard's registry](/chainguard/containers/registry/overview/). This mirror can then serve as a pull through cache for your Chainguard Containers. This tutorial outlines how to set up a repository with [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). Specifically, it will walk you through how to set up one repository you can use as a pull through cache for Chainguard's Free containers or for Production containers originating from a private Chainguard repository. ## Prerequisites In order to complete this tutorial, you will need the following: * Administrative privileges over a Nexus instance. If you're interested in testing out this configuration, you can either download a trial from [Sonatype's website](https://www.sonatype.com/products/sonatype-nexus-oss-download) or run it as a [Docker container](https://github.com/sonatype/docker-nexus3). > Note: If you use the Docker solution, you will need to add an extra port for the repository to the `docker run` command. For example, if you run the repository on port `5051`, you would a command like `docker run -d -p 8081:8081 -p 5051:5051 --name nexus sonatype/nexus3` instead of the example given in the linked GitHub overview. * Privileges to create a pull token on a Chainguard registry. (For private Chainguard repository access) * A spare port on the Nexus server to serve the repository (Nexus limits you to 20 ports). Or an alternative solution — such as a reverse proxy — which is beyond the scope of this guide. ## Setting up Nexus as a pull through for free containers [Free container images](/chainguard/containers/concepts/container-categories/#free-containers) are free to use, publicly available, and always represent versions tagged as `:latest`. To set up a remote repository in Nexus from which you can pull Free containers, log in to Nexus with an **admin** account. Once there, click on the Administration mode cog in the top bar, click **Repository** in the left-hand navigation menu, and then select **Repositories**. On the Repositories page, click the **Create Repository** button and select the **docker (proxy)** Recipe. Following that, you can enter the following details for your new remote repository: * **Name** — This is used to refer to your repository. You can choose whatever name you like here, but this guide's examples will use the name `chainguard`. * **Remote storage** — This must be set to `https://cgr.dev/`. * **HTTP[S] port** — Choose an HTTP or HTTPS port as appropriate for your setup. Following that, click the **Create repository** button at the bottom of the page. If everything worked as expected, you'll be taken back to the repository list and should now see an extra repository with your chosen name, with type "proxy". Your Nexus URL is the hostname of the Nexus server AND the port number you chose; for example, `myrepo.local:5051`. If your Nexus server is running from a Docker container, your Nexus URL would be something like `localhost:5051`. ### Testing pull through of a free container If your setup requires authentication, log in with a valid Nexus username and password: ```sh docker login -u ``` After running this command, you'll be prompted to enter a password. After running the `docker login` command, you will be able to pull a Free container image through Nexus. The following example pulls the `wolfi-base` container image: ```sh docker pull /chainguard/wolfi-base ``` Be sure the `docker pull` command contains the correct Nexus URL for your repository. ## Setting up Nexus as a pull through for production containers Chainguard's Production images are enterprise-ready container images that come with patch SLAs and features such as [Federal Information Processing Standard](/platform/fips/fips-images/) (FIPS) readiness. The process for setting up an Nexus repository that you can use as a pull through cache for Production images is similar to the one outlined previously for Free containers, but with a few extra steps. To get started, you will need to create [a pull token](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) for your organization's registry. Pull tokens are longer-lived tokens that can be used to pull containers from other environments that don't support OIDC, such as some CI environments, Kubernetes clusters, or with registry mirroring tools like Nexus. Follow the instructions in the link above to create a pull token and take note of the values for `username` and `password` as you'll need this to configure a repository for pulling through Production container images. You can edit the existing repository and all your users will have access to the private images. Alternatively, you could create a new `chainguard-private` repository exactly as before but with restricted access, though restricting access to repositories in Nexus is beyond the scope of this guide. At the bottom of the configuration screen there will be an HTTP section. Check the **Authentication** box and use the "Username" Authentication type. Enter the `username` and `password` from the pull token in the respective fields. Click the **Save** button to apply the changes. ### Testing pull through of a production container image: If your setup requires authentication, log in with a valid Nexus username and password: ```sh docker login -u ``` After running this command, you'll be prompted to enter a password. After running the `docker login` command, you will be able to pull a Production containers through Nexus. If your organization has access to it, the following example will pull the `chainguard-base` container image: ```sh docker pull //chainguard-base ``` Be sure the `docker pull` command you run includes the name of your organization's registry. ## Debugging pull through from Chainguard’s registry to Nexus If you run into issues when trying to pull Containers from Chainguard's Registry to Nexus, please ensure the following requirements are met: * Ensure that all Containers [network requirements](/chainguard/containers/registry/network-requirements/) are met. * When configuring a remote Nexus repository, ensure that the **URL** field is set to `https://cgr.dev/`. This field **must not** contain additional components. * You can troubleshoot by running `docker login` from another node (using the Nexus pull token credentials) and try pulling a container image from `cgr.dev/chainguard/` or `cgr.dev//`, using your own organization's registry name in place of ``. * It could be that your Nexus repository was misconfigured. In this case, create and configure a new Nexus repository to test with. ## Learn more If you haven't already done so, you may find it useful to review our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by checking out our [Containers documentation](/chainguard/containers/overview/). If you'd like to learn more about Sonatype Nexus, we encourage you to refer to the [official Nexus documentation](https://help.sonatype.com/en/sonatype-nexus-repository.html). --- ### How to use incert to create container images with built-in custom certificates URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/incert-custom-certs.md Last Modified: September 28, 2026 Tags: Chainguard Containers > NOTE: If you are looking for a way to add certificates to existing Chainguard images, check out our doc on [adding custom certificates with Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/). In many enterprise settings, an organization will have its own certificate authority which it uses to issue certificates for its internal services. This is often for security or control reasons but could also be related to regulatory requirements. If you're using a container that needs to communicate with your organization's services and your organization has its own certificate authority, you'll need to add a valid certificate into your container. One way to do this is to mount the certificate as a [volume](https://docs.docker.com/storage/volumes/) at runtime. This works, but it means that everyone who uses the container has to go through the process of mounting the certificate. Another solution is to build the certificate directly into the container. This tutorial outlines how to use [`incert`](https://github.com/chainguard-dev/incert) — a Go tool from Chainguard that builds container images with certificates inserted into them. ## Prerequisites To follow along with this tutorial, you will need to have the following tools installed. * `incert`, a Go program that appends CA certificates to Docker images and pushes the modified image to a specified registry. You can install this by following [the instructions](https://github.com/chainguard-dev/incert#installation) listed in the project's GitHub repository. * Docker, the open-source containerization platform. Set this up by following [the platform-specific instructions](https://docs.docker.com/get-docker/) on the project's website. * A tool for creating a self-signed certificate. This guide highlights using `cfssl`, a public key infrastructure toolkit from CloudFlare, but alternatives like `openssl` could also be used for this purpose. Follow [the `cfssl` installation instructions](https://github.com/cloudflare/cfssl#installation) to set this up. * Note that if you use `cfssl`, you will also need the `cfssljson` utility installed as well. ## Creating a self-signed certificate First, let's create a directory to hold your certificate infrastructure. ```sh mkdir ~/incert-example/ && cd $_ ``` In the new directory, create a certificate signing request (CSR) by running the following command. ```sh cat > csr.json < nginx.default.conf < **Note**: You may encounter permissions errors relating to the `selfsigned.pem` and `selfsigned-key.pem` files after running this command. In these cases, you can update their permissions by running `sudo chmod 644 *.pem`. ## Test connections to the nginx service with `curl` At this point, if you tried to use `curl` to access the running nginx container, the command will fail because `curl` disallows insecure connections by default. ```sh curl https://localhost:8443 ``` ``` curl: (60) SSL certificate problem: self-signed certificate More details here: https://curl.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. ``` You can force `curl` to ignore the self-signed certificate by passing it the `-k` argument, as in `curl -k https://localhost:8443`. However, our goal is to connect to the service securely using the certificate infrastructure created previously. In the next section we will use `incert` to create a new container image (using Chainguard's `curl` image as the foundation) with your `selfsigned.pem` certificate built into it. Before doing this, though, let's attempt to reach the nginx service with a `curl` container that does not have the certificate included. To do this you'll need to find the nginx container's IP address. First, find the name of the container with `docker ps`. ```sh docker ps ``` ``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9e211033635b cgr.dev/chainguard/nginx "/usr/sbin/nginx -c …" 2 minutes ago Up 2 minutes 0.0.0.0:8443->8443/tcp, :::8443->8443/tcp agitated_jones ``` As this output shows, the name of the nginx container in this example is `agitated_jones`. Replace this with the name of your own container in the following command: ```sh docker inspect --format '{{ .NetworkSettings.IPAddress }}' agitated_jones ``` This will return the container's IP address: ``` 172.17.0.2 ``` Next, use Chainguard's `curl` image to attempt to reach the container. Be sure to replace `172.17.0.2` with your nginx container's actual IP address, if different. ```sh docker run -it --add-host example.com:172.17.0.2 cgr.dev/chainguard/curl:latest-dev https://example.com:8443 ``` > **Note**: You might have noticed that [example.com](https://example.com) is a real website. Instead of using the `curl` container to reach the actual `example.com`, this command includes the `--add-host` option to map the hostname `example.com` to the local IP address currently being used by the nginx container. However, the public Chainguard `curl` image doesn't have the certificate inside it, so this command will fail. ``` curl: (60) rustls_connection_process_new_packets: invalid peer certificate: UnknownIssuer More details here: https://curl.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. ``` The next step is to create an image that has our self-signed certificate built into it. For that, we'll use `incert`. ## Using `incert` to insert a custom certificate into an image `incert` is a Go program from Chainguard that appends CA certificates to Docker images and pushes the modified image to a specified registry. This tool is still in active development, so feedback is welcome. Run the following command to build a new image using Chainguard's `curl` image as its base and insert the `selfsigned.pem` certificate into it. ```sh incert -ca-certs-file selfsigned.pem -platform linux/arm64 -image-url cgr.dev/chainguard/curl:latest -dest-image-url ttl.sh/curl-with-cert:1h ``` This command uses the `-ca-certs-file` option to specify that `incert` should use the `selfisgned.pem` certificate file and the `-platform` option to specify that it wants to build an image for `linux/arm64`. Be aware that you should change the value passed to the `-platform` argument to reflect that of the host platform. It also includes the `-image-url` option to specify the image we want to build on as our base image (here we specify Chainguard's `curl` image) and the `-dest-image-url` to pass the registry where we want the resulting image to be uploaded to. For this final option, this example specifies [`ttl.sh`](http://ttl.sh/), an ephemeral Docker image registry. `ttl.sh` is free to use and does not require a login, making it useful for testing. However, it's also public, so be sure that you **do not** upload any important private certificates there. This command will take a few moments to complete, but once it finishes you will receive output showing the image that was created and uploaded to the destination repo. ``` ttl.sh/curl-with-cert:1h@sha256:877762fdd511a3df8aa24faf6a6209036370b7cfc1638e16b81098143c2a0215 ``` Following that, you can re-execute the `docker run` command from the previous section, but replace the standard Chainguard `curl` image with the image you just built. ```sh docker run -it --add-host example.com:[ipaddress] ttl.sh/curl-with-cert:1h https://example.com:8443 ``` This time, the `curl` container is able to reach the running nginx container. ``` . . . Welcome to nginx!

Welcome to nginx!

If you see this page, the nginx web server is successfully installed and working. Further configuration is required.

For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.

Thank you for using nginx.

``` This shows that `incert` built the certificate into the `curl` container as expected and it was able to reach the nginx container. ## Learn more If you'd like to learn more about how you can use Chainguard Containers effectively, we encourage you to check out all of our resources on [Working with Chainguard Containers](/chainguard/containers/using-and-deploying/). Additionally, our [Recommended practices](/chainguard/containers/security-and-compliance/) resources can be useful for ensuring the security of your container images. --- ### Getting started with the Ruby Chainguard Container URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/ruby.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [Ruby container images](https://images.chainguard.dev/directory/image/ruby/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-getting-started-ruby) provide secure foundations for Ruby applications with minimal vulnerabilities through both development and production-ready distroless variants. These images significantly reduce attack surface compared to traditional Ruby base images while maintaining full compatibility with Ruby applications and the Rubygems ecosystem. Because Ruby applications typically require the installation of third-party dependencies via [Rubygems](https://rubygems.org/), using a pure distroless image for building your application would not work. In cases like this, you'll need to implement a [multi-stage Docker build](https://docs.docker.com/build/building/multi-stage/) that uses one of the `-dev` images to set up the application. In this guide, we'll build two example applications that demonstrate how to use Chainguard's Ruby container images. In the first, we’ll use a minimal image containing just Ruby to execute a demo that doesn't have any external dependencies. In the second example, we'll set up a multi-stage Docker build to run a demo that requires the installation of Rubygems via `bundler`.
What is distroless? Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.
What is Wolfi? Wolfi is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.
What are multi-stage builds?

Multi-stage builds are a Docker feature that allow you to use multiple FROM statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.

This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.

Chainguard's container images are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.

Chainguard Containers Chainguard Containers is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.
## Example 1: Minimal Ruby container in single stage build We'll start by creating a small command-line Ruby application to serve as a demo. This application has no external dependencies; it will read from a text file containing facts about octopuses, and output a random line from that file. This demo is also available in our [demos repository](https://github.com/chainguard-dev/edu-images-demos/tree/main/ruby), if you want to review the source files before building it. ### Step 1: Setting up the application First, create a directory and then move into it for your app. Here we'll use `octo-facts`: ```shell mkdir ~/octo-facts && cd ~/octo-facts ``` Next, create a new file to serve as the application entry point. Here, we’ll use `octo.rb`. You can edit this file in whatever code editor you would like. We’ll use `nano` as an example. ```shell nano octo.rb ``` The following Ruby code will read a random line from the `facts.txt` file and print it out to the terminal: ```ruby #!/usr/bin/env ruby class OctoFact attr_accessor :source def initialize(source = "facts.txt") @source = source end def random_line puts File.readlines(@source).sample end end if __FILE__ == $0 fact = OctoFact.new fact.random_line end ``` Copy this code to your `octo.rb` script, then save and close the file. Next, pull down the `facts.txt` file with `curl`. You can [inspect the file's contents](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/ruby/octo-facts/facts.txt) before downloading it to ensure it is safe to do so. Make sure you are still in the same directory where your `octo.rb` script is. ```shell curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/ruby/octo-facts/facts.txt ``` With all files in place, you can now run the demo using Docker. The following command will execute the demo code using the same base image we'll use to build our Dockerfile in the next step. It will set up a volume sharing the files in the current directory with the location `/work` inside the container, then execute the `octo.rb` script: ```shell docker run --rm -v ${PWD}:/work cgr.dev/chainguard/ruby octo.rb ``` And you should get output like this, with a random fact about octopuses: ```shell Octopuses have decentralized brains. ``` In the next step, we'll create a Dockerfile to build and run the demo. ### Step 2: Setting up the Dockerfile With the demo ready, you can now set up a Dockerfile to build a custom image for your Ruby application. Make sure you're still on the same directory as the application files, then create a new Dockerfile using your text editor of choice: ```shell nano Dockerfile ``` The following Dockerfile will: 1. Start a new image based on the `cgr.dev/chainguard/ruby:latest` container image; 2. Set up a workdir at `/app`; 3. Copy the application files to the workdir; 4. Set up the entry point for the container as `ruby octo.rb`. Copy the following content to your `Dockerfile`: ```Dockerfile FROM cgr.dev/chainguard/ruby:latest WORKDIR /app COPY octo.rb facts.txt ./ ENTRYPOINT [ "ruby", "octo.rb" ] ``` Save and close the file when you're done. Next, build the container image with: ```shell docker build . --pull -t octo-ruby-demo ``` Once the build is finished, you can execute the container with: ```shell docker run --rm octo-ruby-demo ``` And you should get output similar to what you got before, with a random octopus fact. ## Example 2: Multi-stage build for Ruby application runtime To demonstrate how to containerize a more complex application that requires the installation of third party dependencies, we'll create a second demo that uses a [Docker multi-stage build](https://docs.docker.com/build/building/multi-stage/), which will combine the `cgr.dev/chainguard/ruby:latest-dev` development image to build the application and the `cgr.dev/chainguard/ruby:latest` distroless image to run it. This demo will use the [rainbow](https://rubygems.org/gems/rainbow) Ruby gem to output to the command line interface a colorful quote, inspired by _cowsay_. ### Step 1: Setting up the application First, create a directory for your app. Here we'll use `linky-says`: ```shell mkdir ~/linky-says && cd ~/linky-says ``` Then, set up your Gemfile: ```shell nano Gemfile ``` Copy the following content into your Gemfile to require the Rainbow Gem: ```ruby source 'https://rubygems.org' gem 'rainbow' ``` Save and close the file. Next, create a new Ruby script file called `linky.rb`: ```shell nano linky.rb ``` The following code outputs a colorful quote provided at runtime, incorporating an ASCII representation of Linky that is pulled from an `linky.txt` file located at the same directory as the ruby script. The printed quote colors alternate randomly between purple and magenta. ```ruby #!/usr/bin/env ruby require 'rainbow' Rainbow.enabled = true class Linky def says(message = "Hello World") colors = [:purple, :magenta] words = message.split(" ") print "\n ".ljust(40, " ") words.each do |n| print Rainbow(n).color(colors.sample) + " " end print "\n" puts File.readlines('linky.txt') end end if __FILE__ == $0 linky = Linky.new inputArray = ARGV message = inputArray.length > 0 ? inputArray.join(' ') : "Hello Wolfi" linky.says(message) end ``` Copy this code to your `linky.rb` script, then save and close the file. Next, pull down the ASCII `linky.txt` file with `curl`. You can [inspect the file contents](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/ruby/linky-says/linky.txt) before downloading it to ensure it is safe to do so. Make sure you are still in the same directory where your `linky.rb` script is. ```shell curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/ruby/linky-says/linky.txt ``` With everything in place, you can now work on the Dockerfile that will install the application dependencies and execute your Ruby script. ### Step 2: Setting up the Dockerfile To make sure our final container is _distroless_ while still being able to install Rubygems, our build will consist of **two** stages: first, we'll build the application using the `dev` image variant, a Wolfi-based image that includes the Gem executable, Bundler, and other useful tools for development. Then, we'll create a separate stage for the final container. The resulting container will be based on the distroless Ruby Wolfi image, which means it doesn't come with the Gem executable or even a shell. Create a new Dockerfile using your code editor of choice, for example `nano`: ```shell nano Dockerfile ``` The following Dockerfile will: 1. Start a new build stage based on the `cgr.dev/chainguard/ruby:latest-dev`container image and call it `builder`; 2. Set up environment variables that define the default location of installed Gems; 3. Copy the Gemfile from the current directory to the `/work` location in the container; 4. Install Bundler and run `bundle install`; 5. Start a new build stage based on the `cgr.dev/chainguard/ruby:latest` image; 6. Set up environment variables that define the default location of installed Gems; 7. Copy build artifacts from `builder` and into the final container 8. Copy the `linky.rb` and `linky.txt` files into the final container 9. Set up the application entry point as `ruby linky.rb`. Copy this content to your own `Dockerfile`: ```Dockerfile FROM cgr.dev/chainguard/ruby:latest-dev AS builder ENV GEM_HOME=/work/vendor ENV GEM_PATH=${GEM_PATH}:/work/vendor COPY Gemfile /work/ RUN gem install bundler && bundle install FROM cgr.dev/chainguard/ruby:latest ENV GEM_HOME=/work/vendor ENV GEM_PATH=${GEM_PATH}:/work/vendor COPY --from=builder /work/ /work/ COPY linky.rb linky.txt /work/ ENTRYPOINT [ "ruby", "linky.rb" ] ``` Save the file when you're finished. You can now build the image with: ```shell docker build . --pull -t linky-says ``` Once the build is finished, run the container with: ```shell docker run --rm linky-says Wolfi says hi ``` And you should get output like this: ``` Wolfi says hi @@@ @@@*******@@@ / @@%**@@@@*******@@@ / @@*@@***********///@@ / @*************///////&@ / @@*****%@@@**////@@@&//@@ / @*****@**##@////@//##@//@ / @@*****@@@@//////@@@@//@@ / ,@@@@***////////////////@@@@, @@@*******/////////////////////(((((@@@ @*******//////////////////////((((((((@ @@@/////////@///////@///(((((%@@@ #@///////@@@///////@@@((((((#@ @@@ /@@///@@, @@@ ``` If you inspect the image with a `docker image inspect linky-says`, you'll notice that it has only **three** layers, thanks to the use of a multi-stage Docker build. ```shell docker image inspect linky-says ``` ```shell ... "RootFS": { "Type": "layers", "Layers": [ "sha256:ec653fe8da922557dd1d78b47b2c0074a6e9257e5a15e596bc4e1fb1e325c3d8", "sha256:d9541a2e82274d8140ed7f4cc79ce92f295d13951585c4a433f1baa35015e1eb", "sha256:a44e5fee28e11702a63418bf76c009b1b4948f1b0426e0f199b83702ae187796" ] }, "Metadata": { "LastTagTime": "2023-05-09T19:53:16.060125796+02:00" } } ] ``` In such cases, the last `FROM` section from the Dockerfile is the one that composes the final image. That's why in our case it only adds two layers on top of the base `cgr.dev/chainguard/ruby:latest` image, containing the two `COPY` commands we use to copy the application files and its dependencies to the final image. It's worth highlighting that no code or data is carried from one stage to the other unless you use a `COPY` command to explicitly copy it. This approach facilitates creating a slim final image with only what's absolutely necessary to execute the application. Using a multi-stage build like this, without shell tools and interactive language interpreters built in also makes your final container image more secure. ## Advanced usage

If your project requires a more specific set of packages that aren't included within the general-purpose Ruby Chainguard Container, you'll first need to check if the package you want is already available on the wolfi-os repository.

Note: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with apk add you need to use the USER root directive.

If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers.

If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

--- ### How to pull packages from Chainguard package repositories through Nexus URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/registry/pull-through-guides/nexus-packages-pull-through.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural

Note: As of Nexus 3.96, the native Alpine repository type doesn’t support Chainguard’s package repositories. This guide describes a setup that uses raw proxy repositories instead.

This tutorial demonstrates how to set up Alpine package (apk) pull-through caches with [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) that front a [Chainguard private APK repository](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) or the public Chainguard repositories. It also covers how to build a container image that installs packages through the resulting proxy. ## Prerequisites To complete this tutorial, you need the following: * Administrative privileges over a Sonatype Nexus Repository instance running **3.94 or later**. If you'd like to test this configuration, you can either download a trial from [Sonatype's website](https://www.sonatype.com/products/sonatype-nexus-oss-download) or run it as a [Docker container](https://github.com/sonatype/docker-nexus3). * `chainctl` — Chainguard's command-line interface — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * Administrative privileges within your Chainguard organization to create role-bindings (`role_bindings.create`); this capability is available to users with [the `owner` role](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/#chainguard-role-capabilities). ## Private repository [Chainguard private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/) hold packages that are exclusive to a specific Chainguard organization. They're served from `apk.cgr.dev/` and require a pull token for access. Specific package versions should be immutable and can be cached indefinitely, whereas the upstream updates `APKINDEX.tar.gz` whenever it adds new packages. To give each artifact type its own caching properties, you'll create two raw proxy repositories (one for the packages, one for the `APKINDEX.tar.gz`) and a pair of routing rules that direct traffic to the correct proxy. Both proxies sit behind a raw group repository. Clients point `apk` at the group's URL. You can name the repositories and rules whatever suits your organization; the examples in this guide use `chainguard-apk-*` throughout. ### Creating a pull token The Nexus proxies need to authenticate to Chainguard to fetch packages from a private APK repository. Generate a pull token with `chainctl`: ```shell chainctl auth pull-token --repository=apk ``` The `--repository=apk` flag binds the pull token identity to the `apk.pull` role, so it can download packages from your organization's private APK repository. This command returns output like the following: ```output Creating new APK registry pull-token in example.org To use this pull token in another environment, supply the following for Basic authorization: Username: Password: ``` Take note of the `Username` and `Password`. You'll need them when you set up the proxies. ### Creating the routing rules The routing rules split incoming requests between the two proxies. They need to exist first, since each proxy references one by name. Log in to Nexus as an administrator. Open **Settings** and select **Repository** ⇒ **Routing Rules**. Click **Create Routing Rule** and enter the following details: * **Name** — `chainguard-apk-block-index` * **Description** — `Block APKINDEX.tar.gz paths from the packages proxy.` * **Mode** — `Block` * **Matchers** — `.*APKINDEX\.tar\.gz` Click **Create Routing Rule** to save. Then repeat the process for the second rule: * **Name** — `chainguard-apk-only-index` * **Description** — `Restrict the index proxy to APKINDEX.tar.gz paths only.` * **Mode** — `Allow` * **Matchers** — `.*APKINDEX\.tar\.gz` ### Creating the packages proxy This proxy handles `.apk` requests. Because packages don't change, its cache never needs to expire. From **Settings**, select **Repository** ⇒ **Repositories**. Click **Create repository** and select the **raw (proxy)** recipe. Enter the following details: * **Name** — `chainguard-apk-packages` * **Online** — Enabled. * **Remote storage** — `https://apk.cgr.dev/`, replacing `` with your organization's name as it appears in the Chainguard Console. * **Preserve encoded characters in URLs** — Enabled. `apk.cgr.dev` redirects `.apk` requests to a Cloudflare R2 presigned URL whose signature covers percent-encoded characters (`%2B`, `%2F`, `%3D`). Nexus's default URL normalization would decode them and break the signature. * **Strict Content Type Validation** — Disabled. Nexus enables this setting on every new proxy, so you have to clear the checkbox. While it's on, Nexus compares each file it fetches against the content type implied by the file's extension and rejects anything that doesn't match. An Alpine package is a gzip archive, but Nexus maps the `.apk` extension to `application/vnd.android.package-archive`, so every package fails the check and clients receive a 404. * **Maximum component age** — `-1` (never expire). * **Routing Rule** — `chainguard-apk-block-index`. In the **HTTP** section, select **Authentication**, choose the `Username` type, and enter the `Username` and `Password` from the pull token you generated earlier. Click **Create repository**. ### Creating the index proxy This proxy handles `APKINDEX.tar.gz` requests. The upstream regenerates the index whenever it adds a package, so the cache TTL is short. Repeat the process to create a second raw (proxy) repository with the following details: * **Name** — `chainguard-apk-index` * **Remote storage** — `https://apk.cgr.dev/`, replacing `` with your organization's name as it appears in the Chainguard Console. * **Preserve encoded characters in URLs** — Enabled. * **Strict Content Type Validation** — Leave this enabled. This proxy only ever serves `APKINDEX.tar.gz`, whose extension correctly implies gzip, so it passes the check that `.apk` files fail. * **Maximum component age** — `15` (minutes). Tune higher to reduce origin fetches, or lower for faster visibility of new packages. * **Routing Rule** — `chainguard-apk-only-index`. Populate the **HTTP** authentication fields with the same pull token credentials, then click **Create repository**. ### Creating the group The group combines both proxies behind a single URL. This is the URL that clients point `apk` at. Return to **Repositories**, click **Create repository**, and select the **raw (group)** recipe. Enter the following details: * **Name** — `chainguard-apk`. * **Online** — Enabled. * **Member repositories** — Add both `chainguard-apk-packages` and `chainguard-apk-index`. Order doesn't matter here. Nexus queries group members in sequence, but the routing rules make these two proxies mutually exclusive, so only one of them can ever answer a given request. Click **Create repository**. ### Testing Your group URL is `/repository/chainguard-apk`, replacing `` with the base URL of your Nexus instance, including the scheme (for example, `http://localhost:8081`). Open a terminal and create a Dockerfile. The single quotes around `'EOF'` stop the shell from expanding `$` variables, so Docker interprets them as build arguments and secret references at build time: ```shell cat > Dockerfile <<'EOF' FROM cgr.dev/chainguard/python:latest-dev USER root ARG NEXUS_URL RUN --mount=type=secret,id=http_auth,env=HTTP_AUTH,required=true \ cp /etc/apk/repositories /etc/apk/repositories.disabled && \ echo "${NEXUS_URL}/repository/chainguard-apk" > /etc/apk/repositories && \ apk update && \ apk add sed USER nonroot EOF ``` This Dockerfile uses the `python:latest-dev` image. You don't have to use this particular one, but because you're using `apk` to install a package from Nexus, pick a Chainguard container image that includes this package manager. Not every package is available in every private APK repository. For example, your organization may not have access to the `sed` package. Refer to our [private APK repository documentation](/chainguard/containers/building-and-modifying/packages/private-apk-repos/#about-private-apk-repositories) for more details. Before building, export your Nexus credentials and base URL. Include the scheme in `NEXUS_URL`, since the Dockerfile writes this value into `/etc/apk/repositories` as-is. For a Nexus instance running locally over plain HTTP, that's something like `http://localhost:8081`: ```shell export NEXUS_USER=my-nexus-user export NEXUS_PASSWORD=my-nexus-password export NEXUS_URL=https://my-nexus-hostname:8081 export HTTP_AUTH="basic:*:${NEXUS_USER}:${NEXUS_PASSWORD}" ```

Note: If your Nexus username is an email address, you must percent-encode the @ sign, as in export NEXUS_USER=linky%40example.com.

Then build the image, passing the credentials as Docker build secrets: ```shell docker build \ --secret id=http_auth,env=HTTP_AUTH \ --build-arg NEXUS_URL=$NEXUS_URL \ -t nexus-apk-build . ``` The build output confirms the `sed` installation: ```output . . . => [4/4] RUN apk update && apk add sed 3.1s . . . ``` To confirm Nexus cached the package, open **Browse** in the left-hand navigation menu, select `chainguard-apk-packages`, and expand the architecture directory (for example, `x86_64`). The cached package appears there. ## Public repositories You also have access to the public `chainguard` and `extra-packages` repositories. The same setup works for these, with two differences: there's no authentication, and the remote URL uses `virtualapk.cgr.dev` instead of `apk.cgr.dev`. For each public repository you want to cache, follow the [private repository procedure](#private-repository) with the following adjustments: * Reuse the `chainguard-apk-block-index` and `chainguard-apk-only-index` routing rules from earlier. They match on request paths and aren't tied to a specific upstream. * Name the repositories after the upstream. For example, use `chainguard-apk-public-packages`, `chainguard-apk-public-index`, and `chainguard-apk-public` for the `chainguard` repository, and the matching `chainguard-apk-extras-*` names for `extra-packages`. * Set **Remote storage** on each proxy to `https://virtualapk.cgr.dev//chainguard` or `https://virtualapk.cgr.dev//extra-packages`, replacing `` with your Chainguard organization's UID. Run `chainctl iam organizations list -o table` to find it, or check the **Settings** ⇒ **General** page in the [Chainguard Console](https://console.chainguard.dev). * Disable **Strict Content Type Validation** on each packages proxy, as in the private setup. The public repositories serve the same gzip `.apk` files, so they fail the same content type check. * Leave the **HTTP** authentication section unchecked. The public repositories don't require authentication. To pull from the public repositories in a container build, add their group URLs to `/etc/apk/repositories` alongside (or in place of) the private one. Since there's no authentication, you don't need to embed credentials in the URLs: ```shell echo "${NEXUS_URL}/repository/chainguard-apk-public" >> /etc/apk/repositories echo "${NEXUS_URL}/repository/chainguard-apk-extras" >> /etc/apk/repositories ``` ## Debugging If you run into issues pulling from Chainguard's package repositories through Nexus, check for these common pitfalls: * If `apk add` returns `package mentioned in index not found` or a 404 right after the index fetches successfully, the packages proxy is fetching the index but failing to serve the package itself. Two settings can cause this. * Check **Strict Content Type Validation** on the packages proxy first, since Nexus enables it by default: while it's on, Nexus rejects every Alpine package as a content type mismatch and records an `InvalidContentException` in its log, naming the detected type (`application/gzip`) and the expected one (`application/vnd.android.package-archive`). * If **Strict Content Type Validation** is already off and the same error persists, Nexus may be normalizing the R2 presigned URL. Check that **Preserve encoded characters in URLs** is enabled on both proxies. When this setting is off, Nexus decodes `%2B`/`%2F`/`%3D` in the redirect target's query string, which invalidates R2's SigV4 signature and returns a 404. * If both proxies return `403` for every request, you've probably swapped the routing rules attached to each proxy. The packages proxy should have the `BLOCK` rule attached; the index proxy should have the `ALLOW` rule. * If `apk update` returns `401` from Nexus itself (not the upstream), `apk` isn't sending credentials that Nexus accepts. Set the `HTTP_AUTH` environment variable as the testing section does, embed the credentials in the repository URL instead, or configure the `nx-anonymous` role to grant read on the group repository. Nexus requires its own credentials unless your instance has anonymous access enabled, so this applies to the public repositories too, even though their upstreams need no authentication. * If `apk update` returns `401` from the upstream via Nexus, the pull token attached to the proxies has expired. Regenerate one with `chainctl auth pull-token --repository=apk` and update the HTTP authentication settings on both proxies. * Check that your environment meets all [network requirements](/chainguard/containers/registry/network-requirements/), and that Nexus can reach both `apk.cgr.dev` and `*.r2.cloudflarestorage.com`. * You may have misconfigured a repository. Delete and recreate the affected proxy or group to test with a clean setup. ## Terraform If you would prefer to deploy this setup with Terraform, the [`nexus-apk-proxy`](https://github.com/chainguard-demo/cookbook/tree/main/terraform-modules/nexus-apk-proxy) module in the Chainguard [cookbook](https://github.com/chainguard-demo/cookbook) repository provides an example of creating a proxy group in the same configuration described by this guide. ## Learn more If you haven't already done so, you may find it useful to read through our [Registry overview](/chainguard/containers/registry/overview/) to learn more about Chainguard's registry. You can also learn more about Chainguard Containers by referring to our [documentation](/chainguard/containers/overview/), and learn more about working with the Chainguard platform by reviewing our [Administration documentation](/platform/administration/). For a walkthrough of how to set up Nexus as a pull-through cache for Chainguard Containers, refer to [our Nexus containers guide](/chainguard/containers/registry/pull-through-guides/nexus-pull-through/). If you'd like to learn more about Sonatype Nexus, we encourage you to refer to the [official Nexus documentation](https://help.sonatype.com/en/sonatype-nexus-repository.html). --- ### Chainguard products changelog URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/changelog.md Last Modified: October 13, 2026 Tags: Chainguard Containers, Changelog This page logs Chainguard product updates week by week, newest first: product announcements, breaking changes, container images that reached end-of-life or are no longer available, and images newly added to the catalog. Each event is listed once, in the week it first appeared. Breaking changes and product announcements cover the entire Chainguard portfolio, while end-of-life, availability, and new-image entries relate specifically to Chainguard Containers. This page summarizes the changes most likely to affect your work rather than every change Chainguard ships. Routine updates, such as new tags for existing images, are not listed individually. For the current tags and versions of any container image, refer to its entry in the [Chainguard Directory](https://images.chainguard.dev/directory). ## Week of 2026-10-12 Product Announcements ### Chainguard Actions is now generally available _Launched October 6, 2026._ Chainguard Actions provides more than 1,000 continuously hardened, drop-in replacements for GitHub Actions. This release adds: - Hardening and pinning of transitive action dependencies. - A hardening report (`Hardening.md`) for every action that lists the vulnerabilities removed. - Self-serve action requests, with a one-business-day SLA so coverage stays gapless. - Automated migration through the Chainguard App or the migration skill. ### The Chainguard Compliance Dashboard is now generally available _Launched October 7, 2026._ The Chainguard Compliance Dashboard gives security and compliance teams a centralized view of container compliance, vulnerability posture, and audit-ready reporting in the Chainguard Console. It continuously logs image attestations and real-time CVE status, so you don't have to scrape SBOMs or stitch together periodic scanner reports between audit cycles. In the Chainguard Console, every FIPS image now shows: - OpenSCAP scan results against the DISA General Purpose Operating System (GPOS) SRG. - FIPS 140-3 validation details. Each architecture has a downloadable XCCDF report you can include as supporting evidence in FedRAMP and other audit packages. ### Guardener is available in Early Access _Launched October 8, 2026._ Guardener, Chainguard's AI-native adoption agent, automates container discovery and migration across your organization. In Early Access, Guardener provides: - Continuous repository scanning across Dockerfiles, Helm charts, and Kubernetes manifests. - Automated pull requests, configured in YAML, that run image swaps. - Non-blocking pull request guardrails that guide engineers toward hardened images in daily workflows. EOL Chainguard offers [a grace period](/chainguard/containers/features/eol-gp-overview/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that are no longer available The following container images reached the end of their grace period and are no longer available: | Image | End-of-life | Grace period ended | | --- | --- | --- | | `spark:3.5` | 2026-04-12 | 2026-10-12 | New Images Chainguard built 17 new container images this week, including both standard and FIPS variants.
ImageTierAdded
aws-sqs-controllerapplication +fips2026-10-05
zeekapplication +fips2026-10-05
istioctlapplication +fips2026-10-06
llm-d-router-coordinatorapplication +fips2026-10-06
llm-d-router-disagg-sidecarapplication +fips2026-10-06
llm-d-router-endpoint-pickerapplication +fips2026-10-06
openstack-keystone-oshelm-fipsfips2026-10-07
verdaccioapplication2026-10-07
whereabouts-fipsfips2026-10-08
openstack-barbican-oshelmapplication2026-10-09
openstack-manila-oshelmapplication2026-10-09
## Week of 2026-10-05 Product Announcements ### First Athena vulnerability disclosures _Launched September 28, 2026._ [Athena](https://www.chainguard.dev/athena), the industry coalition for coordinated defense of open source software, published its first 14 vulnerability disclosures. All 14 are silent vulnerabilities in Java projects: bugs that a later upstream release fixed but that never received a CVE, so vulnerability scanners couldn't flag the affected versions. The batch includes one critical, one high, eight medium, and four low-severity vulnerabilities. For each disclosure, Chainguard publishes: - Patch files for every affected version, in the public [Athena repository](https://github.com/chainguard-dev/athena) under each project's original license. - An advisory in the [Chainguard Libraries VEX feed](/chainguard/libraries/policies-and-security/cve-remediation/#vex), so [scanners that integrate with Chainguard Libraries](/chainguard/libraries/policies-and-security/scanners/) now report these vulnerabilities. - A remediated version of each affected artifact in Chainguard Repository, for Chainguard Libraries customers. To adopt a fix, replace the affected version in your lockfile with the remediated version and rebuild. The remediated version keeps the same package coordinates and adds a `-0.cgr.N` suffix, as described in [CVE remediation for Chainguard Libraries](/chainguard/libraries/policies-and-security/cve-remediation/). Ten new mitigation and detection partners also joined the coalition. For the full partner list and a walkthrough of four of the disclosed vulnerabilities, read the [announcement](https://www.chainguard.dev/unchained/athenas-disclosures-begin). EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `nextcloud-server:32` | 2026-09-30 | 2027-03-30 | | `zabbix:7.4` | 2026-09-30 | 2027-03-30 | | `zabbix-agent2:7.4` | 2026-09-30 | 2027-03-30 | | `tekton-pipelines:1.4` | 2026-10-04 | 2027-04-04 | | `tekton-pipelines:1.5` | 2026-10-04 | 2027-04-04 | New Images Chainguard built 37 new container images this week, including both standard and FIPS variants.
ImageTierAdded
crossplane-* — 8 images (with FIPS variants)
ImageTierAdded
crossplane-azure-computeapplication2026-09-28
crossplane-azure-containerserviceapplication2026-09-28
crossplane-azure-eventhubapplication2026-09-28
crossplane-azure-networkapplication2026-09-28
crossplane-azure-operationalinsightsapplication2026-09-28
crossplane-aws-s3vectorsapplication +fips2026-09-29
crossplane-azure-cognitiveservicesapplication2026-09-29
crossplane-function-extra-resourcesapplication +fips2026-09-30
openstack-* — 10 images
ImageTierAdded
openstack-ceph-config-helperapplication2026-10-01
openstack-cinder-oshelmapplication2026-10-01
openstack-client-oshelmapplication2026-10-01
openstack-glance-oshelmapplication2026-10-01
openstack-keystone-oshelmapplication2026-10-01
openstack-neutron-oshelmapplication2026-10-01
openstack-nova-oshelmapplication2026-10-01
openstack-octavia-oshelmapplication2026-10-01
openstack-placement-oshelmapplication2026-10-01
openstack-libvirt-oshelmapplication2026-10-02
longhorn-engine-fipsfips2026-09-28
nemo-rlai2026-09-28
pmm-serverapplication2026-09-28
ray-fipsfips2026-09-28
datadog-serverless-initapplication +fips2026-09-29
elasticsearch-iamguarded-fipsfips2026-09-29
commercial-kyverno-clicommercial2026-09-30
commercial-kyverno-readiness-checkercommercial2026-09-30
kserve-llmisvc-controllerai +fips2026-10-01
playwright-chromiumapplication2026-10-01
aws-lambda-providedapplication +fips2026-10-02
flociapplication2026-10-02
livekit-sipapplication +fips2026-10-02
## Week of 2026-09-28 Product Announcements ### FIPS 140-3 validation with post-quantum key exchange _Launched September 24, 2026._ NIST has validated the Chainguard FIPS Provider for OpenSSL v3.6 under FIPS 140-3 ([Certificate #5523](https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/5523)). The module includes: - Hybrid post-quantum key exchange for TLS, which pairs a classical key exchange with ML-KEM, as an approved FIPS service. - The standardized post-quantum algorithms ML-KEM, ML-DSA, and SLH-DSA, alongside classical FIPS algorithms such as AES, SHA-2, SHA-3, ECDSA, and RSA. - Fixes for known CVEs inside the validated module boundary. Chainguard FIPS container images can [opt in to the v3.6 module](/platform/fips/verify-fips/#opt-in-to-different-fips-provider-versions) starting October 1, 2026. SHA-1 is not an approved algorithm in v3.6, so check [how your workloads use non-approved algorithms](/platform/fips/non-approved-algorithms/) before you switch. For more on the validation and CNSA 2.0, read the [announcement](https://www.chainguard.dev/unchained/announcing-chainguards-industry-first-validated-fips-140-3-module-delivering-post-quantum-readiness). ### Chainguard Libraries for Go and .NET (invite-only beta) _Launched September 28, 2026._ Chainguard Libraries now covers the Go and .NET ecosystems through an invite-only beta. Organizations accepted into the beta can pull Go and .NET packages from Chainguard Libraries and evaluate them in their existing development workflows. EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `longhorn-ui:1.10` | 2026-09-25 | 2027-03-25 | New Images Chainguard built 19 new container images this week, including both standard and FIPS variants.
ImageTierAdded
chainguard-server-hypervisor-gcpbase2026-09-23
kuredapplication +fips2026-09-23
conftestapplication +fips2026-09-24
foundryapplication2026-09-24
keepalivedapplication +fips2026-09-24
mcp-grafana-fipsfips2026-09-24
nixapplication2026-09-24
rayapplication2026-09-24
vault-secrets-webhookapplication +fips2026-09-24
argocd-agentapplication2026-09-25
katesapplication +fips2026-09-25
kubernetes-pause-fipsfips2026-09-25
victoriatracesapplication +fips2026-09-25
## Week of 2026-09-21 Product Announcements ### Sovereign Artifacts beta for EU data residency _Launched September 15, 2026._ [Sovereign Artifacts](https://www.chainguard.dev/unchained/announcing-the-sovereign-artifacts-beta) is a data-sovereignty capability for Chainguard Containers and Chainguard Libraries. For enrolled customers, it stores artifact bytes in an EU region, serves them from there, and backfills existing artifacts automatically. Pulls from Europe run about 30 percent faster. During the beta, authentication and build pipelines continue to run on US infrastructure. EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `openjdk:26` | 2026-09-17 | 2027-03-17 | | `ceph:19` | 2026-09-19 | 2027-03-19 | New Images Chainguard built 18 new container images this week, including both standard and FIPS variants.
ImageTierAdded
cursor-agentapplication2026-09-14
chainguard-desktop-workstation-pcbase2026-09-15
commercial-avocadocommercial2026-09-15
rustfsapplication2026-09-15
cloudzero-agentapplication +fips2026-09-16
trivy-k8s-wrapperapplication +fips2026-09-16
access-log-exporterapplication +fips2026-09-18
datahub-gmsapplication +fips2026-09-18
kates-connectapplication +fips2026-09-18
kubeflow-trainer-controller-managerapplication +fips2026-09-18
traceeapplication +fips2026-09-18
## Week of 2026-09-14 Breaking Changes ### sonar-scanner-cli default user change _Effective October 12, 2026._ Chainguard's `sonar-scanner-cli` image changes its default user from `root` (UID 0) to `nonroot` (UID 65532), aligning it with the upstream image and the principle of least privilege. The default working directory `/usr/src` is owned by `65532:0` with mode `0775`, and the scanner's scratch directory moves from `/.scannerwork` to `/tmp/.scannerwork`. - **Affected:** organizations running `sonar-scanner-cli` as root by default — including derived images that install packages or write to system paths as root, pipelines that read `report-task.txt` from the old `.scannerwork` location, and scans that write output files into the mounted project directory. Deployments that already override the container user, or that mount a project and run the image as-is, are unaffected. - **Action:** derived images that need root for build steps should add `USER root` before those steps and switch back to `USER 65532` afterward. Pipelines reading `report-task.txt` from the workspace should read it from `/tmp/.scannerwork`, or set `-Dsonar.working.directory=/.scannerwork` and ensure the workspace is writable by the container user. To restore the previous behavior temporarily, run with `--user 0` (Docker), `USER root` (Dockerfile), or `runAsUser: 0` (Kubernetes), or pin to a digest built before the effective date. EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `kuma:2.12` | 2026-09-09 | 2027-03-09 | New Images Chainguard built 26 new container images this week, including both standard and FIPS variants.
ImageTierAdded
clamav-prometheus-exporterapplication +fips2026-09-07
crossplane-azure-marketplaceorderingapplication2026-09-07
kserve-sklearnserverai2026-09-07
commercial-spilo-fipsfips2026-09-08
graphql-hive-gatewayapplication2026-09-08
jupyterhub-k8s-singleuser-sampleapplication +fips2026-09-08
nuxeo-2025application2026-09-08
chainguard-server-hypervisor-awsbase2026-09-09
mongodb-kubernetesapplication +fips2026-09-09
opentelemetry-python-instrumentation-fipsfips2026-09-09
strimzi-kafka-fipsfips2026-09-09
crossplane-aws-emrcontainersapplication +fips2026-09-11
kroapplication +fips2026-09-11
pyrraapplication +fips2026-09-11
starboard-exporterapplication +fips2026-09-11
krakendapplication +fips2026-09-14
mongodb-searchapplication +fips2026-09-14
## Week of 2026-09-07 Product Announcements ### Chainguard Containers for Go 1.27 _Launched August 31, 2026._ Chainguard now ships Go 1.27 in three images: `go`, `go-fips`, and `go-openssl-fips`. The new `go-openssl-fips` image selects its FIPS-validated cryptographic module at deployment rather than at build time. Choosing a FIPS module for Go previously meant choosing a toolchain at compile time. Teams compiled against either the host's OpenSSL FIPS provider (`go-msft-fips`) or the Go Cryptographic Module (`go-geomys-fips`), so supporting both required two pipelines producing two artifacts. The new Go FIPS toolchain ships one artifact that picks the validated module after the build, at the point of deployment. A single build cross-compiles from one command, runs wherever you deploy, keeps your validated module and FedRAMP authorization intact, and is ready for CNSA 2.0 — all without recompilation. Go 1.26 and earlier behave as before and remain supported until end-of-life, so you can migrate on your own schedule. For more information, refer to [Getting started with the Go Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/go/). ### Chainguard Libraries in JFrog _Launched September 2, 2026._ JFrog Catalog now recognizes Chainguard-built packages, so joint Chainguard and JFrog customers can pull Chainguard Libraries through JFrog without build errors. This is what underpins JFrog's Zero-Touch Remediation: when a scan finds a vulnerable artifact, teams can take the patched Chainguard package in place, without changing developer workflows or forcing a major version upgrade. - Java support shipped with this announcement; JavaScript and Python are scheduled to follow by the end of September 2026. - JFrog Xray scanning and Curation policies work as before. - Nothing changes for Chainguard Libraries customers who don't use JFrog. For more information, refer to [Chainguard Libraries overview](/chainguard/libraries/introduction/overview/). New Images Chainguard built 39 new container images this week, including both standard and FIPS variants.
ImageTierAdded
kubeflow-pipelines-* — 10 images
ImageTierAdded
kubeflow-pipelines-api-server-fipsfips2026-08-31
kubeflow-pipelines-cache-deployer-fipsfips2026-08-31
kubeflow-pipelines-cache-server-fipsfips2026-08-31
kubeflow-pipelines-driver-fipsfips2026-08-31
kubeflow-pipelines-frontend-fipsfips2026-08-31
kubeflow-pipelines-metadata-envoy-fipsfips2026-08-31
kubeflow-pipelines-metadata-writer-fipsfips2026-08-31
kubeflow-pipelines-persistenceagent-fipsfips2026-08-31
kubeflow-pipelines-scheduledworkflow-fipsfips2026-08-31
kubeflow-pipelines-viewer-crd-controller-fipsfips2026-08-31
dbmateapplication +fips2026-08-31
go-openssl-fipsfips2026-09-01
hadolintapplication +fips2026-09-01
jrubybase2026-09-01
kserve-huggingfaceserverai +fips2026-09-01
gubernatorapplication +fips2026-09-02
kates-testerapplication +fips2026-09-02
fortiotelapplication +fips2026-09-03
packerapplication +fips2026-09-03
security-profiles-operatorapplication2026-09-03
aws-lambda-dotnetapplication +fips2026-09-04
mongodb-kubernetes-readinessprobeapplication +fips2026-09-04
opentelemetry-go-instrumentationapplication +fips2026-09-04
prom-label-proxyapplication +fips2026-09-04
kserve-sklearnserver-fipsfips2026-09-07
presidio-anonymizerapplication +fips2026-09-07
redpanda-console-fipsfips2026-09-07
## Week of 2026-08-31 Product Announcements ### chainctl fixes for standard Windows accounts _Launched August 26, 2026._ Several `chainctl` workflows now work for standard, non-administrator Windows users: - Configuring Docker credentials no longer requires symlinks or Developer Mode. - Self-update replaces the running executable correctly and checks release attestations before installing. - Library verification checks signatures in-process and cleans up its temporary directories reliably. - Authentication failures report an error instead of a misleading 0.00% coverage result. These are targeted fixes. Chainguard's official Windows support tier is unchanged. ### Custom container policies (open beta) _Launched August 25, 2026._ Organizations enabled for the beta can now write their own container pull policies instead of relying only on Chainguard's system policies. Custom policies are [Rego](https://www.openpolicyagent.org/docs/policy-language) expressions submitted as YAML manifests and managed with `chainctl`, so you can keep them in git and validate them in CI. - Policies can declare parameters, with values set per binding. - `chainctl policies custom validate` reports the exact line and column of an error before the policy is stored. - Evaluation runs in a sandbox with no network or filesystem access. - A custom policy in `ENFORCE` mode blocks pulls, but cannot loosen a system policy. For more information, refer to [Writing custom policies](/chainguard/chainguard-repository/container-policies/#writing-custom-policies). ### Build pinning for Chainguard Libraries _Launched August 24, 2026._ Build pinning is now enabled for all organizations using Chainguard Libraries with upstream fallback, across JavaScript, Python, and Java. The first time your organization pulls a package version, Chainguard records which copy you received — the upstream mirror or the Chainguard rebuild — and keeps resolving that version to the same copy until you choose to move forward. This keeps your lockfiles and checksums valid as Chainguard's library coverage grows. Organizations that do not use upstream fallback see no change. To review what your organization holds, or to opt out, refer to [`chainctl libraries cache`](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/). EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that are no longer available The following container images reached the end of their grace period and are no longer available: | Image | End-of-life | Grace period ended | | --- | --- | --- | | `kube-conformance:1.32` | 2026-02-28 | 2026-08-28 | | `kubernetes:1.32` | 2026-02-28 | 2026-08-28 | | `nextcloud-server:31` | 2026-02-28 | 2026-08-28 | | `rke2-runtime:1.32` | 2026-02-28 | 2026-08-28 | | `teleport:17` | 2026-02-28 | 2026-08-28 | ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `istio:1.29` | 2026-08-31 | 2027-02-28 | | `istio-cni:1.29` | 2026-08-31 | 2027-02-28 | | `istio-envoy:1.29` | 2026-08-31 | 2027-02-28 | | `istio-operator:1.29` | 2026-08-31 | 2027-02-28 | | `istio-pilot-agent:1.29` | 2026-08-31 | 2027-02-28 | | `istio-pilot-discovery:1.29` | 2026-08-31 | 2027-02-28 | | `teleport:18` | 2026-08-31 | 2027-02-28 | | `ztunnel:1.29` | 2026-08-31 | 2027-02-28 | New Images Chainguard built 21 new container images this week, including both standard and FIPS variants.
ImageTierAdded
dynatrace-operatorapplication +fips2026-08-24
netshootapplication +fips2026-08-24
strimzi-drain-cleanerapplication +fips2026-08-24
kube-logging-operator-fluentd-drain-watchapplication +fips2026-08-25
mlrun-api-fipsfips2026-08-25
mlrun-fipsfips2026-08-25
redpanda-consoleapplication2026-08-26
apache-polarisapplication +fips2026-08-28
kserve-pmmlserverai +fips2026-08-28
kserve-xgbserverai +fips2026-08-28
nomadapplication +fips2026-08-28
victorialogs-vlagentapplication +fips2026-08-28
## Week of 2026-08-24 Product Announcements ### SCIM user provisioning (Early Access) _Launched August 20, 2026._ Chainguard now supports SCIM user provisioning for Okta and Microsoft Entra ID in Early Access. Assigning a user to the provisioning application creates and updates their Chainguard provisioning record through the SCIM `/Users` resource; deactivating or unassigning them blocks the next login or token refresh once the current token expires, within about an hour. Roles stay under Chainguard's control through default roles, manual bindings, and group-based role mapping. SCIM group provisioning is not available, so leave your identity provider's SCIM group push turned off. For more information, refer to [SCIM user provisioning](/platform/administration/custom-idps/scim-provisioning/). ### Chainguard API v2 is generally available _Launched August 19, 2026._ Chainguard API v2 is now generally available at `/v2/`, replacing the `v2beta1` designation with a stable contract covering organization and access management, registry metadata, vulnerability and security advisories, event subscriptions, and library artifacts. It standardizes cursor-based pagination, server-side ordering, resource naming, dedicated resource lookups, partial updates, and typed error details, and ships an OpenAPI specification and v2 Go SDK clients. Existing `v2beta1` integrations keep their request and response shapes and need only update the path prefix. API v1 remains supported during the transition. For more information, refer to [Migrating from API v1 to API v2](/platform/api/api-v2-migration/). ### Self-serve Helm chart provisioning _Launched August 18, 2026._ Catalog customers can now provision Chainguard Helm charts from the Chainguard Console instead of filing a request ticket. When you select a chart, the Console identifies the required images your organization is missing, adds them in a single request, and starts provisioning the chart asynchronously; the chart appears in your registry once synchronization completes, without EOL tags. Dependency resolution does not detect renamed images. For more information, refer to [Self-serve Helm charts](/get-started/self-serve/helm-charts/). EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that are no longer available The following container images reached the end of their grace period and are no longer available: | Image | End-of-life | Grace period ended | | --- | --- | --- | | `flux:2.5` | 2026-02-24 | 2026-08-24 | ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `grafana:12.3` | 2026-08-19 | 2027-02-19 | | `py3-numpy:2.1` | 2026-08-19 | 2027-02-19 | New Images Chainguard built 27 new container images this week, including both standard and FIPS variants.
ImageTierAdded
airbyte-* — 5 images (with FIPS variants)
ImageTierAdded
airbyte-bootloaderapplication +fips2026-08-21
airbyte-cronapplication +fips2026-08-21
airbyte-workerapplication +fips2026-08-21
airbyte-workload-api-serverapplication +fips2026-08-21
airbyte-workload-launcherapplication +fips2026-08-21
percona-server-mongodb-fipsfips2026-08-17
application-gateway-kubernetes-ingressapplication +fips2026-08-18
falcosidekick-uiapplication +fips2026-08-18
kubernetes-csi-external-health-monitor-fipsfips2026-08-18
mlrunapplication2026-08-18
mlrun-apiapplication2026-08-18
spiffe-csi-driverapplication +fips2026-08-18
apache-activemq-artemis-fipsfips2026-08-19
aws-iam-controllerapplication +fips2026-08-21
commercial-nginx-plus-fipscommercial2026-08-21
kserve-lgbserverai +fips2026-08-21
rangerapplication2026-08-21
## Week of 2026-08-17 Product Announcements ### Clearer npm errors for blocked packages _Launched August 13, 2026._ When Chainguard Libraries withholds an npm package or version — because of detected malware or greyware, a pending malware scan, or a policy block such as a cooldown — npm now returns a `403` naming the specific reason, for example `MALWARE_DETECTED`, instead of the unexplained `404` it returned before. The other supported package managers (pnpm, yarn, uv, poetry, Maven, and Gradle) still report a blocked version as a generic not-found error, and a `409` when an entire package is blocked for malware. For more information, refer to [Error messages](/chainguard/libraries/troubleshooting/errors/#package-manager-behavior). ### Guardener GitHub App (beta) _Launched August 12, 2026._ Guardener, the automated migration tool, now covers GitHub Actions as well as container images. The GitHub App inventories the Actions in use across your organization's repositories, maps them to hardened Chainguard equivalents, and opens pull requests to swap them in, pinned to a specific SHA rather than a mutable tag. It runs in two modes: an upfront pass that surfaces existing Actions usage and opens migration pull requests, and ongoing standardization that watches workflow files and suggests Chainguard equivalents as new upstream Actions appear. For more information, refer to [Getting started with Guardener](/chainguard/guardener/github/getting-started/). Breaking Changes ### Chainguard Libraries build pinning _Effective August 24, 2026._ Chainguard Repository can serve two copies of the same package version: the upstream copy mirrored from the public registry, and Chainguard's copy rebuilt from source. The two have different checksums, and each request resolves independently, so when Chainguard publishes a rebuild of a version your organization already pulled, the next resolution moves you to the rebuild and its checksum no longer matches your lockfile — surfacing client-side as errors such as `EINTEGRITY` in npm. As of August 24, 2026, build pinning records which copy your organization received the first time it downloads a package version and keeps resolving that version to the same copy until you choose to move forward. Malware and policy blocks apply independently: a version that is later blocked stops being served rather than being replaced with a different copy. - **Affected:** organizations using Chainguard Libraries with upstream fallback, across JavaScript, Python, and Java. Organizations without upstream fallback see no change. - **Action:** none required. To keep the current behavior, run `chainctl libraries cache opt-out`. Both `opt-out` and `chainctl libraries cache opt-in` accept `--ecosystem`, and pinning state is tracked per organization and ecosystem. To see which copy each held version came from, run `chainctl libraries cache list`. For more information, refer to [`chainctl libraries cache`](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/). EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that are no longer available The following container images reached the end of their grace period and are no longer available: | Image | End-of-life | Grace period ended | | --- | --- | --- | | `prometheus:3.9` | 2026-02-17 | 2026-08-17 | ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `mattermost:10.11` | 2026-08-15 | 2027-02-15 | New Images Chainguard built 17 new container images this week, including both standard and FIPS variants.
ImageTierAdded
azuriteapplication2026-08-10
k0s-cni-nodeapplication +fips2026-08-10
peerdb-uiapplication +fips2026-08-10
sccapplication +fips2026-08-10
zitadel-loginapplication2026-08-11
aws-lambda-nodejsapplication +fips2026-08-12
localstackapplication2026-08-12
kserve-router-fipsfips2026-08-13
jdk-openssl-fipsfips2026-08-14
jre-openssl-fipsfips2026-08-14
kube-routerapplication +fips2026-08-14
commercial-nginx-ingress-pluscommercial2026-08-17
## Week of 2026-08-10 Product Announcements ### Chainguard Libraries available through AWS Security Hub Extended _Launched August 4, 2026._ AWS added a Supply Chain category to Security Hub Extended and named Chainguard one of its inaugural partners. You can now subscribe to Chainguard Libraries from the Security Hub console and pay through your existing AWS account. Libraries findings arrive normalized to the Open Cybersecurity Schema Framework (OCSF), so they appear alongside your AWS and other partner findings, and AWS Enterprise Support customers receive Level 1 support from AWS. For more information about the catalog, refer to the [Chainguard Libraries overview](/chainguard/libraries/introduction/overview/). ### Malware avoidance in the Console (beta) _Launched August 4, 2026._ The Console now shows which malicious and suspicious packages Chainguard Libraries blocked before they reached your environment. A weekly chart tracks the malware and greyware stopped across the Python and JavaScript ecosystems, and a search tool reports why any given package was flagged unsafe. Find it under the **Malware** tab in the Libraries section of the Console sidebar; it is enabled by default for every organization entitled to Libraries. For more information, refer to [View malware information](/chainguard/libraries/introduction/browse/#view-malware-information). ### PKCE support for custom identity providers _Launched August 3, 2026._ Chainguard now supports Proof Key for Code Exchange (PKCE) on the OAuth token exchange with custom OIDC identity providers, in line with OAuth 2.1. Administrators can enable it with `chainctl` or the Chainguard API, either alongside an existing client secret or as a secret-free public client. For more information, including setup steps, refer to [Enable PKCE for OAuth Token Exchange](/platform/administration/custom-idps/enabling-pkce/). EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `net-kourier:1.21` | 2026-08-04 | 2027-02-04 | | `tekton-pipelines:1.3` | 2026-08-04 | 2027-02-04 | | `ruby3.2-rails:7.2` | 2026-08-09 | 2027-02-09 | | `ruby3.3-rails:7.2` | 2026-08-09 | 2027-02-09 | | `ruby3.4-rails:7.2` | 2026-08-09 | 2027-02-09 | | `ruby4.0-rails:7.2` | 2026-08-09 | 2027-02-09 | New Images Chainguard built 23 new container images this week, including both standard and FIPS variants.
ImageTierAdded
prometheus-stackdriver-exporterapplication +fips2026-08-03
crossplane-azure-keyvaultapplication2026-08-04
crossplane-azure-kustoapplication2026-08-04
influxdb-fipsfips2026-08-04
moodle-iamguardedapplication2026-08-04
pypiserver-fipsfips2026-08-04
kubevirt-cdi-clonerapplication +fips2026-08-05
yopassapplication2026-08-05
acm-controllerapplication +fips2026-08-06
gremlin-serverapplication2026-08-06
rollouts-plugin-trafficrouter-gatewayapiapplication +fips2026-08-06
claudeapplication2026-08-07
codexapplication2026-08-07
cruise-controlapplication +fips2026-08-07
dotstatsuite-supercoreapplication2026-08-07
opencodeapplication2026-08-08
zalando-pgbouncerapplication +fips2026-08-10
## Week of 2026-08-03 Breaking Changes ### ingress-nginx-controller entrypoint, command, and default user changes _Effective July 15, 2026._ Chainguard aligned the entrypoint and command behavior of all supported `ingress-nginx-controller` images with the upstream image, correcting a startup configuration defect present since the image was first published in 2024. Non-`iamguarded` variants also changed their default runtime user from `root` (UID 0) to `www-data` (UID 101). - **Affected:** all supported `ingress-nginx-controller` images, including FIPS and `-iamguarded` variants. - **Action:** review any configuration that overrides `command`, `args`, `entrypoint`, or `runAsUser`, or that depends on admission policies or file ownership assumptions. Deployments using the upstream Helm chart defaults need no changes. EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that are no longer available The following container images reached the end of their grace period and are no longer available: | Image | End-of-life | Grace period ended | | --- | --- | --- | | `cilium:1.16` | 2026-02-03 | 2026-08-03 | | `neo4j:2025.12` | 2026-02-03 | 2026-08-03 | ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `eks-distro:1.33` | 2026-07-29 | 2027-07-29 | | `mongo:8.2` | 2026-07-31 | 2027-01-31 | | `prometheus:3.5` | 2026-07-31 | 2027-01-31 | | `cockroach:26.1` | 2026-08-02 | 2027-02-02 | | `cockroach-openssl:26.1` | 2026-08-02 | 2027-02-02 | New Images Chainguard built 18 new container images this week, including both standard and FIPS variants.
ImageTierAdded
atlasapplication +fips2026-07-29
azure-metrics-exporterapplication +fips2026-07-29
crossplane-azure-solutionsapplication2026-07-29
crossplane-azure-streamanalyticsapplication2026-07-29
crossplane-azure-webapplication2026-07-29
dns-controller-managerapplication +fips2026-07-29
instrumentisto-harakaapplication +fips2026-07-29
openstack-kolla-toolboxapplication +fips2026-07-29
sftpgoapplication2026-07-29
flink-kubernetes-operator-fipsfips2026-07-30
traccarapplication +fips2026-07-30
metacontroller-fipsfips2026-07-31
## Week of 2026-07-28 Product Announcements ### SUSE NeuVector now natively supports Chainguard Containers _Launched July 24, 2026._ SUSE's NeuVector vulnerability scanner now natively supports Chainguard Containers and ingests Chainguard's OSV advisory feed, which includes recent data-quality improvements. Scans of Chainguard images suppress false positives and report more accurate results. ### Group-based role mapping is now generally available _Launched July 22, 2026._ Administrators can map identity provider groups — Okta or Microsoft Entra ID, for example — directly to Chainguard roles, instead of assigning roles one user at a time. Anyone who authenticates with a mapped group in their token receives that role for the session. For more information, including setup steps, refer to [Grant Chainguard roles from identity provider groups](/platform/administration/custom-idps/grant-roles-from-groups/). EOL Chainguard offers [a grace period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) for eligible end-of-life images: up to six months of continued rebuilds and security updates while you complete your upgrade. ### Images that are no longer available The following container images reached the end of their grace period and are no longer available: | Image | End-of-life | Grace period ended | | --- | --- | --- | | `knative-eventing:1.19` | 2026-01-28 | 2026-07-28 | | `knative-serving:1.19` | 2026-01-28 | 2026-07-28 | | `net-kourier:1.19` | 2026-01-28 | 2026-07-28 | ### Images that have reached end-of-life The following container images reached end-of-life and entered their grace period: | Image | End-of-life | Grace period ends | | --- | --- | --- | | `dnsdist:1.9` | 2026-07-21 | 2027-01-21 | | `longhorn-backing-image-manager:1.8` | 2026-07-22 | 2027-01-22 | | `longhorn-instance-manager:1.8` | 2026-07-22 | 2027-01-22 | | `envoy:1.35` | 2026-07-23 | 2027-01-23 | New Images Chainguard built 15 new container images this week, including both standard and FIPS variants.
ImageTierAdded
coderapplication +fips2026-07-22
flink-kubernetes-operatorapplication2026-07-22
pgheroapplication +fips2026-07-22
kargoapplication2026-07-23
nuclio-controllerapplication +fips2026-07-23
phpmyadminapplication +fips2026-07-23
apache-exporter-iamguardedapplication2026-07-24
glabapplication +fips2026-07-27
metabaseapplication2026-07-27
crossplane-azure-relayapplication2026-07-28
--- ### Custom Assembly FAQs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/custom-assembly/faq.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Custom Assembly ## What is Chainguard’s Custom Assembly? Custom Assembly is a tool from Chainguard that allows users to build customized container images by assembling packages from a curated, secure set of base images provided by Chainguard. ## How does Custom Assembly help with container image security? While no system is perfectly secure, Custom Assembly is designed to help you build container images with a strong security posture. By using only guarded packages from Chainguard’s curated and actively maintained images, you benefit from daily CVE scanning and Chainguard’s CVE remediation SLA. This helps minimize risk and provides a clear path for responding to vulnerabilities. ## Can I remove base packages from an image? No. Base packages such as glibc, busybox, and others included in your selected starting image cannot be removed. You can think of them as the "crust"—you can add toppings, but not change the crust. ## Can I include any package I want in a custom image? Not exactly. You can only include packages from images you’re entitled to through your Chainguard subscription to stay covered by the SLA. If you add packages from the general Wolfi OSS repository (not part of a paid image), those packages are not covered by Chainguard’s SLA and may contain unpatched CVEs. ## Is runtime support included for my application? No. Chainguard supports the Custom Assembly tooling itself but does not debug runtime behavior. For example, if your app crashes due to misconfiguration or conflicting dependencies, you’re responsible for resolving it. ## Can I build FIPS-compliant custom images? FIPS packages can be used with Custom Assembly, but there is no guarantee that your resulting image will operate in FIPS mode. The FIPS Commitment does not apply to custom images. ## Will I know what’s in my custom image? Yes. Each image comes with: * Full build logs * Software Bill of Materials (SBOMs) * Provenance metadata These tools help you understand exactly what's inside your container. ## Is Custom Assembly covered by an SLA? Today, Chainguard delivers an [SLA for CVE remediation](https://www.chainguard.dev/legal/cve-sla?utm_source=docs) for all our container images. However, when you re-configure Chainguard’s standard, off-the-shelf images, we cannot extend our SLA to packages you add on because we do not have control over the build. You’re thus responsible for maintaining these added packages or run the risk of accruing CVEs. Customized images built using Custom Assembly will maintain the same engineering and security best practices as Chainguard’s standard containers, with all packages guarded under our CVE remediation SLA. One bit of nuance in all this is that the SLA doesn’t directly apply to the images customized with Custom Assembly; it only directly applies to the source image. BUT — **as long as you include only _guarded package-versions_ from images that your organization is entitled to**, which are scanned daily for CVEs, you will benefit from the SLA. When you use Custom Assembly, you’re not just randomly sticking together packages from anywhere. You’re assembling from the curated, protected library of packages that your organization has been granted access to by Chainguard. And if a vulnerability crops up in an entitled package? We’re already on it. ## What isn’t covered by an SLA? So customized images benefit from Chainguard’s CVE SLA — that’s great! But what isn’t supported? The following is a list of things that our SLA **won’t** cover: ### You can’t remove base packages At the moment, base packages included in the starting image — like `glibc`, `busybox`, or core utilities — are **locked in**. So if you select mysql as the base image, any customized images built from it will include the mysql container image's base packages. Let’s put it like this: you can add toppings, but you can’t change the crust. ### You can’t pull in just any package With Custom Assembly, you can **only use packages** from the images you’re entitled to through your Chainguard subscription and still be covered by our SLA. For example, if you add packages from sources like Wolfi (OSS packages), those additions are not covered by the SLA. The main reason is that a Wolfi package that’s in a paid image is supported and actively maintained. There isn’t a guarantee that free Wolfi packages will be supported and may include CVEs, so we can’t guarantee that the CVE SLA will hold. It’s important to understand what is included in your Custom Assembly, as unsupported packages added from external sources may leave you exposed to untracked vulnerabilities. Like we said before, you can pick your own toppings, but you can’t bring your own pepperoni from home. ### We don’t debug your app You’ll get full **build logs, SBOMs, and provenance** for each image. But if your app throws a `Segmentation fault (core dumped)` after adding 6 versions of `openssl`… yeah, that one’s on you. Along these same lines, Chainguard cannot guarantee FIPS compliance for container images built with Custom Assembly. You’re of course allowed to use Custom Assembly with FIPS container images, but this use may result in the custom image not being compliant with FIPS requirements. Specifically, the SLA states that “in no event shall the FIPS Commitment apply to any Images used with Custom Assembly.” This indicates that while FIPS packages can be used in a custom image, there is no guarantee that the final custom image will operate in FIPS mode. To sum this point up: Chainguard supports the Custom Assembly tooling, not the runtime behavior of what you build. It’s your party — we're just delivering the pizza! ## What can I do to maintain a strong security posture with Custom Assembly? While you can't eliminate risk entirely, there are several best practices that help you maintain a secure custom image: Use only packages from Chainguard images you’re entitled to, which are covered by the CVE remediation SLA. * Avoid adding external or unsupported packages unless you’re prepared to monitor and patch them independently. * Rebuild regularly to incorporate upstream fixes and updates. * Review SBOMs and provenance metadata to verify what's in your image. Custom Assembly provides the tools and defaults for secure image creation — but ongoing vigilance is key. ## Why not use `apk add` to add packages?

Chainguard's Custom Assembly tool is the officially supported approach for extending Chainguard Containers with additional packages. Custom Assembly automatically ensures all packages remain on compatible versions and handles rebuilds when packages are updated, eliminating version conflicts and reducing maintenance overhead.

While using apk add to add packages in a Dockerfile or similar is effective, issues can arise when packages in a base image conflict with new packages that are added. This can be particularly pronounced when core packages are updated — there can be brief windows when the latest version of a base image has a conflict with the latest version of a package.

For example, when the openssl package is updated, there will be a brief window where base images with an old and potentially incompatible version of libcrypto, and attempting to apk add openssl will fail. This conflict will be resolved in a few hours when a new version of the base image is released. The only way around this is to pin to specific versions of packages and images, which adds its own significant maintenance burden.

Refer to our Custom Assembly documentation for more information.

## How do tags work for a Custom Assembly image (base image vs. added packages)? For a Custom Assembly image, the semantic tag (for example, `vX.Y.Z`) always reflects the version of the Chainguard container image on which it is based. Any added packages do not get their own version tags. When an added package is updated and available, your Custom Assembly image is rebuilt automatically and the new build that includes the updated package becomes the `latest` image. The semantic tag remains tied to the same base image digest until the base image itself is updated and a new semantic tag is created. ## Can I customize only some of a repository's tags? Yes. With [Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/overview/), you create an overlay that holds your customizations and bind it to specific tags, to every `-dev` tag, or to every tag. You can also bind an overlay to every repository in your organization or a folder. This is useful when a package only works with some of an image's versions, such as a Python package built for one Python version. ## Can I use standard Custom Assembly and overlays on the same repository? No. A repository uses one or the other. If a repository has overlay bindings, adding a standard customization fails, including from the Chainguard Console. To change that repository's customizations, update its overlays and bindings with `chainctl` or Terraform. To move a repository from standard Custom Assembly to overlays, contact your Chainguard account team. ## Custom Assembly troubleshooting Build failures can occur for a number of reasons, including the following: * It’s possible for you to select packages that conflict with each other. For example, if two packages install the same files, Custom Assembly may not be able to resolve the conflict and result in a failed build. One example is trying to add newer packages to older bases causing a mismatch because the newer package turned out to be built using a newer version of `glibc`. * Large images taking longer than 1 hour to build will fail with a timeout error. * When using Custom Assembly through `chainctl`, you choose to add a package which you don't have access to. In any case, you won’t know whether a container image build fails until after it’s complete. If you need assistance troubleshooting, please [reach out](https://www.chainguard.dev/contact?utm=docs). --- ### Chainguard Containers FAQs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/faq.md Last Modified: October 8, 2026 Tags: Chainguard Containers, FAQ Learn answers to your questions about [Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). Chainguard provides container images designed with security as the primary focus, featuring zero known CVEs, minimal attack surface, and built-in SBOMs for every image. ## Which Linux distribution is used as base for Chainguard Containers? Chainguard's container images are based on [Wolfi](/open-source/wolfi/), a Linux _undistro_ we built specifically to address software supply chain security issues. We call it an undistro because it doesn't contain certain software you'd normally find in a traditional Linux distribution such as Debian or Alpine. Wolfi is a minimal Linux distribution designed specifically to be used as a base for stripped-down container images. ## How do Chainguard's container images relate to the Google distroless container images? {#how-do-chainguard-containers-relate-to-the-google-distroless-container-images} The [Google distroless](https://github.com/GoogleContainerTools/distroless) images follow a similar philosophy to many of our images: they are minimal images that don't include package managers or shells. The main difference is in the implementation. The Google distroless images are built with [Bazel](https://bazel.build) and based on the Debian distribution, whereas our images are built with [apko](/open-source/build-tools/apko/) and based on [Wolfi](/open-source/wolfi/). We believe our approach is more maintainable and extensible. ## Which images are available? There are currently over a thousand Chainguard Containers available, which are segmented as **Free** or **Production**. You can read more about this in the [next question](#what-options-do-i-have-to-use-chainguard-containers). Chainguard's container images are primarily available from [the Chainguard registry](/chainguard/containers/registry/overview/), but a selection of Free images is also available on [Docker Hub](https://hub.docker.com/u/chainguard). You can find the complete list of available Chainguard Containers in our public [Containers Directory](https://images.chainguard.dev/?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-faq) or within the [Chainguard Console](https://console.chainguard.dev/). ## What options do I have to use Chainguard Containers? You can get free Chainguard Containers for your organization. You can also upgrade for more versions, SLAs, and dedicated support. Free | Production -------|----------------------- Free for everyone, anywhere | [Contact us](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) for pricing Latest versions | Major and minor versions Community support | Enterprise SLAs [Developer docs](/chainguard/containers/) | Customer support You can read more about the differences between Free and Production Containers in our [Containers overview](/chainguard/containers/overview/#production-and-free-containers). ## Are Chainguard's container images available on Docker Hub? {#are-chainguard-containers-available-on-docker-hub} Yes, Chainguard Free Container images are available on [Docker Hub](https://hub.docker.com/u/chainguard?utm_source=academy&utm_medium=referral&utm_campaign=FY25-DockerHub-Orgprofile). As a Docker Verified Publisher, Chainguard has met Docker's stringent standards for security, quality, and transparency. This status signifies that our container images are trusted, reliable, and have undergone rigorous verification processes. To use Production Containers, pull them from [Chainguard's registry](/chainguard/containers/registry/overview/). ## What is an SBOM and why is it important? An SBOM is a Software Bill of Materials, which is a list containing detailed information about all software that is included within a software artifact, whether it's an application, a container image, or a physical appliance. SBOMs provide visibility into the software you depend on. They can allow automated systems to quickly identify issues such as unpatched vulnerabilities, since SBOMs typically include the version of each dependency listed. ## Who maintains Chainguard Containers? [Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) is officially maintained by [Chainguard](https://chainguard.dev) engineers. ## Can anybody build Chainguard Containers themselves? Yes. Chainguard builds from open source, and the tools it uses to build packages and containers are freely available in [Wolfi](https://github.com/wolfi-dev). What's hard to reproduce is the scale. Before Chainguard adds a container, it reviews the upstream source: whether the project is actively maintained, whether it builds from source, and whether its license permits Chainguard to use and distribute it. Getting a complicated project to build the first time can take days or weeks. Once it builds, the [Chainguard Factory](/platform/factory/what-is-factory/) rebuilds it automatically every time that project tags a release. In a do-it-yourself pipeline, the rebuilding falls to a person, usually a project maintainer or a security engineer. Doing it for every image on every upstream release is where most teams run out of hours. ## How often are Chainguard's container images updated? {#how-often-are-chainguard-containers-updated} Our container images are rebuilt every night to ensure that new package versions and security updates in upstream Wolfi are quickly applied. ## Can I replace my current base image with a Chainguard Container and have it work out of the box? Chainguard's container images are designed to be minimal, and many of them don't come with a package manager. Depending on your stack and specific dependencies, you may need to include additional software by combining `-dev` container images and our [distroless](/chainguard/containers/concepts/getting-started-distroless/) images in a multi-stage Docker build. ## What packages are available in Chainguard Containers? Chainguard's container images only contain packages that come from the [Wolfi Project](https://github.com/wolfi-dev) or those that are built and maintained internally by Chainguard. Since March 2024, Chainguard has maintained one version of each Wolfi package at a time. These track the latest version of the upstream software in the package. Chainguard has ended patch support for previous versions of packages in Wolfi. Existing packages remain in Wolfi and you may continue to use them, but be aware that older packages no longer receive updates and accrue vulnerabilities over time. The tools we use to build packages and images remain freely available and open source in [Wolfi](https://github.com/wolfi-dev). This change ensures that Chainguard can provide the most up-to-date patches to all packages for our customers. Note that specific package versions can be made available in Production containers. If you have a request for a specific package version, [contact us](https://www.chainguard.dev/contact?utm=docs). ## How do I add packages to a Chainguard Container?

Chainguard's Custom Assembly tool is the officially supported approach for extending Chainguard Containers with additional packages. Custom Assembly automatically ensures all packages remain on compatible versions and handles rebuilds when packages are updated, eliminating version conflicts and reducing maintenance overhead.

While using apk add to add packages in a Dockerfile or similar is effective, issues can arise when packages in a base image conflict with new packages that are added. This can be particularly pronounced when core packages are updated — there can be brief windows when the latest version of a base image has a conflict with the latest version of a package.

For example, when the openssl package is updated, there will be a brief window where base images with an old and potentially incompatible version of libcrypto, and attempting to apk add openssl will fail. This conflict will be resolved in a few hours when a new version of the base image is released. The only way around this is to pin to specific versions of packages and images, which adds its own significant maintenance burden.

Refer to our Custom Assembly documentation for more information.

## What does Chainguard do when a CVE is published, but a patch is not available from the owner of the OSS code? Chainguard investigates the CVE and marks relevant images as affected or not. If Chainguard can identify a patch that's unreleased, Chainguard may apply a patch before it lands upstream. In either case, when the patch lands upstream, Chainguard picks it up and rolls it out. ## Why are some CVEs persistent in select Chainguard Containers? There are several Chainguard Containers container images--such as Druid and Spark--with a notable number of CVEs that are marked `pending-upstream-fix.` The reasons that these CVEs can't be remediated by standard engineering procedures include: some vulnerabilities can only be patched through major version upgrades, which often break compatibility through broken builds or tests; many of these CVEs (over fifty percent by one internal Chainguard analysis) stem from "shaded" JARs, JAR files that bundle their dependencies internally; and a small portion of these CVEs have no fix available. The Chainguard engineering team continually investigates new approaches to fixing these persistent CVEs. ## I added software on top of one of Chainguard's base container images, why are there CVEs? Chainguard is not responsible for CVEs in software you add on top of base images. ## Do I need to authenticate into Chainguard to use Chainguard Containers? Logging in is optional if you are only using Free containers. That being said, there are benefits for all users who authenticate to Chainguard's registry, as Chainguard provides notifications of version updates, breaking changes, or critical security updates. To learn how to authenticate into Chainguard's registry, you can review our [authentication documentation](/chainguard/containers/registry/authenticating/) . You can read more about the thought process behind authentication in our blog post, [Scaling Chainguard Containers with a growing catalog and proactive security updates](https://www.chainguard.dev/unchained/scaling-chainguard-images-with-a-growing-catalog-and-proactive-security-updates). ## Is Chainguard FedRAMP certified? You need to ingest Chainguard Containers into an image repository within your FedRAMP boundary. Your repository requires FedRAMP but Chainguard does not since we're outside the boundary. [Reach out](https://www.chainguard.dev/contact?utm=docs) if you need more details. ## Will Chainguard Containers run alongside eBPF agents or sensors? Will there be any issues? Chainguard containers are [OCI](https://opencontainers.org/) containers. eBPF operates at the kernel-level at runtime. This means an eBPF agent or sensor doesn't affect Chainguard Containers, and vice versa. --- ### Chainguard Libraries policies URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/chainguard-repository/library-policies.md Last Modified: August 28, 2026 Tags: Chainguard Libraries Chainguard Libraries policies enable you to filter and restrict package versions pulled through Chainguard Repository. Policies can delay newly published upstream packages, block specific packages or versions, and allow deliberate exceptions to an otherwise denied package. Policies apply to an ecosystem and are evaluated when packages are pulled. Users with the Owner role can create, enable, disable, and list library policies. These policies apply to all packages pulled through Chainguard Repository. The [upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) must be enabled for an ecosystem in order to use policies. One custom policy per ecosystem can be enabled at a time. This policy should include all the rules you need, which may include a cooldown period, package block rules, and any overrides. > **Note**: [Upstream fallback](/chainguard/libraries/introduction/overview/#upstream-fallback-and-controls) must be enabled for an ecosystem before you can use library policies. Library policies require `chainctl` v0.2.313 or newer; refer to the [`chainctl` documentation](/get-started/getting-started-with-chainctl/#update-chainctl-to-the-latest-release) for instructions on updating. ## Policy types and definitions This page uses the following terms: * **Policy**: A reusable set of rules that determines whether a package or version is allowed. * **Ecosystem**: The package ecosystem to which a policy applies: Java, JavaScript, or Python. * **Preview mode**: Records what a policy would block without preventing package installs. * **Enforce mode**: Blocks packages that violate the policy. Chainguard Libraries supports the following types of policies: * **Cooldown**: Delays upstream newly published package versions for a set number of days after the upstream registry publish date. * **Block**: Deny a package or version outright. Use a block rule when your organization never wants to allow a package or version. * **Override**: Permit a package or version that would otherwise be denied by a cooldown policy or malware and greyware blocking. Use an override when you need a specific, deliberate exception. * An override policy takes precedence over a block policy. ## Identify packages with a purl Library policies use [package URLs](https://spdx.github.io/spdx-spec/v3.0.1/annexes/pkg-url-specification/), or purls, to identify packages across ecosystems: * Python: `pkg:pypi/` * Example: `pkg:pypi/requests` * JavaScript: `pkg:npm/` or `pkg:npm/%40/` for scoped packages * Example: `pkg:npm/lodash` or `pkg:npm/%40angular/core` * Java: `pkg:maven//` * Example: `pkg:maven/com.fasterxml.jackson.core/jackson-databind` Omit the version to match all versions of the package. Append `@` to target one version. For example: * `pkg:pypi/requests@2.31.0` * `pkg:npm/lodash@4.17.20`, `pkg:npm/%40angular/core@17.0.0` * `pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.0` ## Manage policies ### Preview a policy To understand the impact before enforcing a policy, use `--mode=PREVIEW` with the `chainctl libraries policy enable` command. In preview mode, installs continue to succeed, but Chainguard records *what would have been blocked* over the last 30 days if the policy had been enforced. In the following example, a policy called `3day-cooldown` has already been created. To preview what would have been blocked if that policy had been enforced, run this command: ```bash chainctl libraries policy enable 3day-cooldown --ecosystem=JAVASCRIPT --mode=PREVIEW ``` This returns a list of successful installs that would have been blocked by this policy over the last 30 days. ### Enable a policy After creating a policy, use `--mode=ENFORCE` to enable it for the ecosystem where you want to apply it. For example: ```bash chainctl libraries policy create --name=disable-cooldown --cooldown-days=0 chainctl libraries policy enable disable-cooldown --ecosystem=JAVASCRIPT --mode=ENFORCE ``` ### Describe a policy To see a policy in detail, use `describe`. For example, to see the details of a policy named `disable-cooldown`: ```bash chainctl libraries policy describe disable-cooldown ``` This command returns a list of what is included in the policy, such as [blocked packages](#block-a-package-or-version), [packages on your allowlist](#override-a-blocked-package), and the [cooldown policy](#create-and-enable-a-cooldown-policy). ### List policies and verify bindings To verify which policy is active and its cooldown settings, run the following: ```bash chainctl libraries policy binding list ``` To list all available policies, including policies that are not active, run the following: ```bash chainctl libraries policy list ``` Prior to `chainctl` version 0.2.291, cooldown policies were enabled via the `chainctl entitlements command`. Cooldown policies configured prior to this version of `chainctl` are migrated under the new `chainctl libraries policies` system. ## Cooldown policies The default cooldown period is 7 days. You cannot change the default policy, but you can create and enable a new policy to replace the default. ### Create and enable a cooldown policy When upstream fallback is enabled, users with the Owner role can create and enable a cooldown policy with `chainctl`. The cooldown period provides an additional layer of defense on top of malware and greyware scanning, giving the broader security community time to surface threats that may not be immediately detectable. In the following example, a 10-day cooldown policy is created, then it is enforced on the JavaScript ecosystem: ```bash chainctl libraries policy create --name=js-cooldown --cooldown-days=10 chainctl libraries policy enable js-cooldown --ecosystem=JAVASCRIPT --mode=ENFORCE ``` #### Preview a cooldown policy To understand the impact before enforcing a policy, use `--mode=PREVIEW`. In preview mode, installs continue to succeed, but Chainguard records what *would have been blocked* if the policy were enforced. ### Disable cooldown To disable the cooldown, set it to 0. In the example below, the policy is created, then it is enforced on the Java ecosystem: ```bash chainctl libraries policy create --name=no-cooldown --cooldown-days=0 chainctl libraries policy enable no-cooldown --ecosystem=JAVA --mode=ENFORCE ``` ## Block policies ### Block a package or version Create a custom policy with one or more `--block` entries to deny packages explicitly. Block rules are helpful if your organization has deliberately decided not to allow certain packages. To understand the impact before enforcing a block, [use `--mode=PREVIEW`](#preview-a-policy). For example, if your organization standardizes on React and wants to prevent teams from pulling in Angular, you can add a `--block` entry for each Angular package you want to deny. In the following example, a policy called `team-policy` has previously been created, and this command is run to update the policy to add blocks of specific Angular packages: ```bash chainctl libraries policy update team-policy \ --block=purl=pkg:npm/%40angular/core \ --block=purl=pkg:npm/%40angular/common \ --block=purl=pkg:npm/%40angular/router chainctl libraries policy enable team-policy --ecosystem=JAVASCRIPT --mode=ENFORCE ``` To block one specific version, include the version in the purl. For example, if a particular release is flagged with a known vulnerability that you want to block within your organization, you can block just the affected version while allowing the other versions. Use a command like the following: ```bash chainctl libraries policy update team-policy \ --block=purl=pkg:npm/ua-parser-js@0.7.29 chainctl libraries policy enable team-policy --ecosystem=JAVASCRIPT --mode=ENFORCE ``` To block all versions of a package, omit the version. For example, the following command updates the existing `team-policy` to add a block of all versions of a package: ```bash chainctl libraries policy update team-policy \ --block=purl=pkg:pypi/ chainctl libraries policy enable team-policy --ecosystem=PYTHON --mode=ENFORCE ``` You can also combine `block` rules with a custom cooldown in the same policy. The following example creates a policy that blocks `colourama`, a typosquat of the `colorama` package. In addition, a cooldown is included: ```bash chainctl libraries policy create --name=team-policy \ --cooldown-days=2 \ --block=purl=pkg:pypi/colourama chainctl libraries policy enable team-policy --ecosystem=PYTHON --mode=ENFORCE ``` ### Check blocked packages Use `chainctl libraries packages blocked` to review what has been blocked by your organization's policy. By default, this shows pull events that were blocked under any enforced policies. For example: ```bash chainctl libraries packages blocked --ecosystem=JAVASCRIPT ``` If you have policies enabled in Preview mode, you can check successful pulls that *would have been blocked* in the last 30 days if the policy were to be enforced. For example: ```bash chainctl libraries policy enable example-policy --ecosystem=JAVASCRIPT --mode=PREVIEW chainctl libraries packages blocked --mode=PREVIEW --ecosystem=JAVASCRIPT ``` Replace `example-policy` with the name of the policy you want to preview. To check packages that have been blocked by [Chainguard's global malware/greyware blocklist](/chainguard/libraries/introduction/overview/#malware-and-greyware-detection), use [`chainctl libraries packages malware list`](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware_list/). ## Override policies ### Override a blocked package Use `--allow` to permit a package that has been blocked by a policy or by malware and greyware scanning. Override policies takes precedence over block policies. ### Override cooldown for a package version A cooldown override is useful when a newly published version includes an urgent fix and you need it before the cooldown window expires. For example: ```bash chainctl libraries policy update team-policy \ --allow='purl=pkg:npm/undici@8.4.1,override-cooldown=true,justification="urgent patch"' ``` If the new version pulls in transitive dependencies that are also blocked under a cooldown policy, those transitive packages must be overridden too. ### Override malware or greyware blocking A malware override should be used sparingly and only after your security team has explicitly reviewed the package. A justification is required when you set `override-malware=true`. ```bash chainctl libraries policy update team-policy \ --allow='purl=pkg:npm/node-ipc@10.1.3,override-malware=true,justification="approved in SEC-1234"' ``` ## Troubleshooting and FAQ ### Why does `chainctl` return "Invalid argument: enable takes no arguments"? Prior to `chainctl` v0.2.313, when enabling a policy, you needed to include the `--policy` flag to pass in the policy name. In v.0.2.313 and later, you do not need to include a flag. For example, to enable a policy called `3day-cooldown`, run the following command: `chainctl libraries policy enable 3day-cooldown`. [Update `chainctl` to the latest version](/get-started/getting-started-with-chainctl/#update-chainctl-to-the-latest-release) to use this functionality. --- ## Compliance ### SLSA compliance at Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/compliance/slsa/slsa-chainguard.md Last Modified: October 2, 2026 Tags: Compliance, SLSA, Standards SLSA (pronounced "salsa"), or Supply chain Levels for Software Artifacts, is a security framework consisting of standards and controls that prevent tampering, improve integrity, and secure packages and infrastructure. It is described in depth in [What is SLSA?](https://www.chainguard.dev/supply-chain-security-101/what-is-slsa). All Chainguard products — including Chainguard Containers, Guarded VMs, and Chainguard Libraries — are SLSA Level 3 compliant to provide confidence in the security of these products. This page describes what we have done to bring Chainguard products into full SLSA Level 3 compliance. ## Isolated and tamper-resistant builds Every Chainguard build now runs in a dedicated, ephemeral environment that starts from a clean slate. This build isolation ensures that no build can influence another, eliminating insider meddling or cross-contamination between builds. By provisioning short-lived build environments on demand, we greatly reduce the risk of any malware persistence or [cache poisoning](https://en.wikipedia.org/wiki/Cache_poisoning) across builds. ## Unforgeable provenance and chain of trust SLSA 3 requires that each artifact’s provenance (the record of how, when, and by whom it was built) be generated by a trusted service and cryptographically signed. In our process, the build system’s control plane creates this tamper-proof provenance data — build runners cannot falsify it. This means every container image or VM has an auditable "paper trail" back to its source, establishing a robust chain-of-trust for our software. ## Secured signing keys and secrets We’ve further hardened our build platform to protect sensitive credentials. Signing keys and other secrets never reside on the build workers; instead, signing happens in a separate, secure service after the build completes. This separation ensures that even if a build job were compromised, it cannot access the keys needed to sign artifacts or attestations. By keeping signing operations isolated and secured, we prevent attackers or rogue insiders from forging trusted artifacts. ## Full traceability and transparency With SLSA 3, every Chainguard artifact comes with detailed metadata and documentation about how it was built. We generate build provenance and a Software Bill of Materials (SBOM) for each release, with complete visibility into the software supply chain behind our Container and VM images. ## Looking to the future Reaching SLSA Level 3 compliance with any product is a major achievement, but even more is planned. Refer to [the blog post announcement](http://chainguard.dev/unchained/this-shit-is-hard-slsa-l3-and-beyond) for more details. ## Sample provenance code To pull an attestation, you must first [authenticate to Chainguard's registry](/chainguard/containers/registry/authenticating/). Then you can download an attestation of provenance. If you are a customer, replace $CUSTOMERNAME in this example with your Chainguard registry customer name. ``` cosign download attestation --predicate-type=https://slsa.dev/provenance/v1 cgr.dev/$CUSTOMERNAME/node-fips:latest | jq -r .payload | base64 -d | jq .predicate ``` This example returns the following output: ``` { "buildDefinition": { "buildType": "https://chainguard.dev/buildtypes/apkoaas/v1", "externalParameters": { "image-configuration": { "accounts": { "groups": [ { "gid": 65532, "groupname": "nonroot" } ], "run-as": "0", "users": [ { "gid": 65532, "homedir": "/home/nonroot", "uid": 65532, "username": "nonroot" } ] }, "annotations": { "dev.chainguard.package.main": "bash", "org.opencontainers.image.authors": "Chainguard Team https://www.chainguard.dev/", "org.opencontainers.image.base.digest": "sha256:fbc73f29218d5f88f09a0b5593a6f8ad7ea521e6406a3d30fad211935fb6a6a7", "org.opencontainers.image.source": "https://github.com/chainguard-images/images-private/tree/main/images/bash", "org.opencontainers.image.url": "https://images.chainguard.dev/directory/image/bash/overview", "org.opencontainers.image.vendor": "Chainguard" }, "archs": [ "amd64" ], "contents": { "packages": [ "bash=5.2.37-r33", "busybox=1.37.0-r46", "ca-certificates-bundle=20241121-r42", "chainguard-baselayout=20230214-r13", "curl=8.14.1-r2", "cyrus-sasl=2.1.28-r42", "gdbm=1.25-r2", "glibc-locale-posix=2.41-r50", "glibc=2.41-r50", "heimdal-libs=7.8.0-r41", "keyutils-libs=1.6.3-r35", "krb5-conf=1.0-r6", "krb5-libs=1.21.3-r42", "ld-linux=2.41-r50", "libbrotlicommon1=1.1.0-r5", "libbrotlidec1=1.1.0-r5", "libcom_err=1.47.2-r22", "libcrypt1=2.41-r50", "libcrypto3=3.5.0-r3", "libcurl-openssl4=8.14.1-r2", "libgcc=15.1.0-r1", "libidn2=2.3.8-r2", "libldap=2.6.10-r2", "libnghttp2-14=1.66.0-r0", "libpsl=0.21.5-r5", "libssl3=3.5.0-r3", "libunistring=1.3-r3", "libverto=0.3.2-r5", "libxcrypt=4.4.38-r2", "ncurses-terminfo-base=6.5_p20241228-r3", "ncurses=6.5_p20241228-r3", "readline=8.2.13-r5", "sqlite-libs=3.50.1-r0", "wolfi-baselayout=20230201-r21", "zlib=1.3.1-r50" ], "repositories": [ "https://apk.cgr.dev/f69ce42a381de40f0f386e5633b145e40ef84de3" ] }, "entrypoint": { "command": "/bin/bash -c" }, "layering": { "budget": 10, "strategy": "origin" } } }, "internalParameters": { "cloud": { "instanceId": "0069c7a98814814175f936ea53c1d4106d910363a23ae2c7e1a8caae21b5c3a0e95ff40d47fcba2fa188ee3fef00c860323d445a8e89c2f864fc5bf5525d6c87f9fb6ea7caf33008b353bb7f0f74b095dc1d769c5dcf15", "region": "projects/554579655540/regions/us-central1", "type": "gcp" } } }, "runDetails": { "builder": { "id": "https://chainguard.dev/prod/builders/apkoaas/v1", "version": { "apko": "v0.27.9", "apkoaas": "16913c6fa81a3c5448ae25068359c040dcb50a73" } }, "metadata": { "finishedOn": "2025-06-24T08:00:45.200379604Z", "startedOn": "2025-06-24T08:00:42.738341658Z" } } } ``` --- ### PCI DSS at Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/compliance/pci-dss-4/pci-dss-chainguard.md Last Modified: October 8, 2026 Tags: Compliance, PCI DSS 4.0, Standards Compliance with PCI DSS 4.0, or Payment Card Industry Data Security Standard, requires adherence to strong security standards. Rigorous requirements must be met in order to secure your networks, systems, storage, and access according to the guidelines. Chainguard doesn't build images specifically for PCI DSS, but our images can help you meet the requirements in many ways, easing your burden in the process of achieving compliance. Securing your software supply chain provides a solid foundation for minimizing vulnerabilities. All of Chainguard's container images save time and costs required to triage, patch, and remediate CVEs. They are created by and officially maintained by Chainguard engineers. Our images are designed to be minimal, removing unnecessary software that is not specifically used. This eliminates a number of potential attack vectors. On top of this, you must authenticate into Chainguard to use Chainguard Containers, giving you reassurance of the provenance of your images. They include digitally signed [build-time SBOMs](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) (software bill of materials) documenting and attesting to the full provenance. Our FIPS-compliant [Federal Information Processing Standard](/platform/fips/fips-images/) images, combined with STIG-hardened (Security Technical Implementation Guide) configurations, provide an even stronger foundation for meeting the requirements of PCI DSS because they are hardened further to meet the more stringent FedRAMP requirements. ## What are STIG-hardened FIPS images? STIG-hardened FIPS images are pre-configured container images that have been secured according to the Security Technical Implementation Guide (STIG) standards set by the Defense Information Systems Agency (DISA). These images meet stringent federal security requirements, combining FIPS-compliant encryption with robust security configurations that protect against vulnerabilities and threats. By using STIG-hardened FIPS images, organizations ensure that their systems adhere to federal encryption standards and best practices for cybersecurity, making them particularly valuable in environments that require high levels of security. ## How does Chainguard Containers help? {#how-do-chainguard-containers-help} One of the main requirements of PCI DSS is to maintain a vulnerability management program. PCI DSS requires you to scan for vulnerabilities once every three months (Requirement 11.3.1) and triage and address all vulnerabilities (Requirement 11.3.11). Chainguard protects you from malicious attacks by supplying you with images where CVEs have already been dealt with, removing vulnerabilities for you. PCI DSS requires that you catalog and classify vulnerabilities in bespoke and third-party software (Requirements 6.3.1 and 6.3.2). You must fix all critical and high vulnerabilities and have a plan of action for the rest (Requirement 11.4.4). Chainguard does that for you. Vulnerability scanners can be noisy and sifting through false positives while cataloging true vulnerabilities can be tedious work. Providing justifications for vulnerabilities that aren't applicable takes time, and that is after investigating them thoroughly. Chainguard's container images are carefully engineered to contain low-to-no CVEs. Organizations can use them as their source to build their applications on. The benefits of our solution are: - **You’re secured by default** : Our images contain low-to-no CVEs. Check out our [Images Directory](https://images.chainguard.dev) yourself. ‍ - **Extensive scanner partnerships**: We partner with industry-leading scanners such as Snyk, Crowdstrike, Wiz, [and many others](https://www.chainguard.dev/scanners). - **SBOM for all Chainguard Containers**: Get full transparency into the packages actually used in our images and ultimately run in your environment. ‍ - **Less ongoing human overhead**: Every new Chainguard Container version is carefully scanned and any addressable CVEs are fixed. ‍ - **Trust in our industry-leading CVE SLA**: We are committed to supplying secure software and commit to fixing CVEs [quickly](https://www.chainguard.dev/cve-sla) so you don’t have to. ## Learn more about PCI DSS 4.0 - [What is PCI DSS 4.0? Requirements and who must comply](https://www.chainguard.dev/supply-chain-security-101/what-is-pci-dss-4-0) **[Get started with FIPS Chainguard Containers today!](https://images.chainguard.dev/?category=fips?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement)** --- ### CMMC at Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/compliance/cmmc-2/cmmc-chainguard.md Last Modified: October 8, 2026 Tags: Compliance, CMMC 2.0, Standards Achieving Cybersecurity Maturity Model Certification (CMMC) 2.0 Level 2 or Level 3 certification can be a complex and resource-intensive process, particularly for organizations managing containerized environments and addressing vulnerabilities. Chainguard simplifies this journey by offering specialized solutions that drastically reduce the time and effort needed to meet compliance requirements. Our FIPS-compliant [Federal Information Processing Standard](/platform/fips/fips-images/) images, combined with detailed SBOM (Software Bill of Materials) and STIG-hardened (Security Technical Implementation Guide) configurations, provide a strong foundation for meeting the requirements of CMMC 2.0. ## What are STIG-hardened FIPS images? STIG-hardened FIPS images are pre-configured container images that have been secured according to the Security Technical Implementation Guide (STIG) standards set by the Defense Information Systems Agency (DISA). These images meet stringent federal security requirements, combining FIPS-compliant encryption with robust security configurations that protect against vulnerabilities and threats. By using STIG-hardened FIPS images, organizations ensure that their systems adhere to federal encryption standards and best practices for cybersecurity, making them particularly valuable in environments that require high levels of security, such as those governed by CMMC 2.0. ## Why STIG-hardened FIPS images for CMMC 2.0? STIG-hardened FIPS images are highly beneficial for achieving CMMC 2.0 compliance due to their enhanced security features and adherence to strict guidelines. Here’s how they can support your CMMC 2.0 efforts: 1. **Enhanced Security Posture** STIG hardening applies a set of security configurations and practices designed to protect systems from vulnerabilities and threats. By utilizing STIG-hardened FIPS images, organizations ensure that their systems meet rigorous security standards, reducing the risk of exploitation. This is particularly important for CMMC 2.0 Level 2 and Level 3 requirements, which emphasize advanced security practices and robust protection of Controlled Unclassified Information (CUI) and Federal Contract Information (FCI). 2. **Streamlined Compliance** FIPS-compliant images meet federal encryption standards, which are a key component of CMMC 2.0 requirements. STIG hardening adds an additional layer of security by ensuring that the system configurations are in line with best practices for securing systems. These hardened images come with pre-configured settings that address many of the CMMC controls, such as those related to access control, vulnerability management, and incident response, thereby simplifying the compliance process and reducing the time and effort needed to achieve certification. 3. **Simplified Reporting and Documentation** STIG-hardened FIPS images typically include detailed scan reports and documentation that can be used to demonstrate compliance with CMMC 2.0 controls. These reports help organizations quickly identify and address security gaps, and the detailed documentation supports the creation of necessary reports for auditors and assessors. This streamlining of the reporting process aids in maintaining and proving compliance with CMMC requirements, such as those related to vulnerability management (e.g., CM.2.062 and CM.3.068) and continuous monitoring (e.g., SC.3.177). By leveraging Chainguard’s resources, organizations can accelerate their path to CMMC 2.0 certification while effectively managing and reporting on critical security controls. Our integrated approach not only ensures that compliance requirements are met but also enhances overall security posture, allowing organizations to focus on their core operations with confidence. ## Learn more about CMMC 2.0 - [What is CMMC 2.0? Maturity levels and who must comply](https://www.chainguard.dev/supply-chain-security-101/what-is-cmmc-2-0) - [CMMC 2.0 practices: All 14 domains and naming conventions](https://www.chainguard.dev/supply-chain-security-101/cmmc-2-0-practices) **[Get started with FIPS Chainguard Containers today!](https://images.chainguard.dev/?category=fips?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement)** --- ## Get Started ### Set up Chainguard Checks and Chainguard Workspaces URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/checks-and-workspaces/set-up.md Last Modified: October 13, 2026 Tags: Chainguard Checks, Chainguard Workspaces, Getting Started, Procedural [Chainguard Checks](/chainguard/checks/overview/) runs your repository's CI on every GitHub pull request, with each check in its own microVM. [Chainguard Workspaces](/chainguard/workspaces/overview/) runs your development work, and the coding agents that do it, in a remote microVM session that holds none of your long-lived credentials. You can use either product on its own, but they're built to work together, and you set them up together: one command, `chainctl develop init`, turns on both. This page explains what the products share, then walks you through signing up and giving teammates access. For how the two products isolate your code, refer to [How Chainguard Checks and Chainguard Workspaces isolate your code](/get-started/checks-and-workspaces/how-it-works/).

Note: Each product is in beta.

## What the products share {#what-the-products-share} | Shared piece | What it means | | ------------ | ------------- | | One sign-up | `chainctl develop init` turns on both products, each with its own free monthly allowance. Refer to [Sign up with chainctl develop init](#sign-up-with-chainctl-develop-init). | | One GitHub App | The Chainguard App delivers pull request events to Chainguard Checks and, by default, fetches and pushes your repository for Workspaces sessions. A GitHub account can be linked to only one Chainguard organization. | | One configuration file | Chainguard Checks runs the checks in your repository's `.chainguard/ci.yaml` file on every pull request. In a Workspaces session, `run-ci` runs the same checks against your working tree, and a session can use the file's environment. | | One role | The `sandbox.user` role lets a member use both products. Refer to [Give teammates access](#give-teammates-access). | ## How they work together {#how-they-work-together} A typical loop uses both products: 1. Open a Workspaces session on your repository, and let Claude Code make a change. 2. Run `run-ci` in the session to run your repository's checks against the change. Refer to [Run checks before you push](/chainguard/checks/run-checks/). 3. Push the change and open a pull request. Chainguard Checks runs the same checks and reports the result on the pull request. 4. If a check fails, read why from the session with `chainctl checks failures`, fix the change, and push again. Refer to [Inspect pull request runs](/chainguard/checks/inspect-and-run-checks/#inspect-pull-request-runs). ## Prerequisites {#prerequisites} Before you begin, make sure you have the following: - A GitHub account that can install GitHub Apps on the personal account or organization that owns your repository. To install the app on an organization and link it, you need the owner role on that organization. - A local clone of a repository on github.com that you can push to. - A work email address. A personal email address, such as a Gmail address, can't create a Chainguard organization. ## Install or update chainctl {#install-or-update-chainctl} You sign up with `chainctl`, and you use it with both products: `chainctl checks` for Chainguard Checks and `chainctl develop` for Chainguard Workspaces. If you don't have it yet, follow the [chainctl installation guide](/platform/chainctl-usage/how-to-install-chainctl/). Chainguard Checks and Chainguard Workspaces need `chainctl` 0.2.382 or later. Earlier releases can't sign you up as this page describes or run Claude Code in a session. Both products change often during the beta, so if you already have `chainctl`, update it to the latest release: ```shell sudo chainctl update ``` If you installed `chainctl` with Homebrew, run `brew upgrade chainctl` instead. Refer to [Updating chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl) for details. ## Install the Chainguard App {#install-the-chainguard-app} The Chainguard App is a GitHub App that connects your repositories to Chainguard. Chainguard Checks receives pull request events and reports results through it, and by default, Workspaces sessions fetch and push your repository through it. [Guardener](/chainguard/guardener/) uses the same app. 1. Go to the [Chainguard App installation page](https://github.com/apps/chainguard/installations/new). 2. Choose the personal account or organization that owns your repository. 3. Choose **All repositories**, or select the repositories you want to use with Chainguard Checks or Chainguard Workspaces. 4. Review the requested permissions and select **Install**. Installing the app doesn't change your repositories. Chainguard Checks runs only on repositories that have a `.chainguard/ci.yaml` file, after you link the account in the next step. ## Sign up with chainctl develop init {#sign-up-with-chainctl-develop-init} The `chainctl develop init` command signs you in, creates a free [Chainguard Starter](/chainguard/containers/reference/catalog-starter/) organization if you don't have one, and turns on Chainguard Checks and Chainguard Workspaces. Each product gets its own free monthly allowance, which resets on the first day of each month (UTC). For what each allowance includes, refer to Availability and limits for [Chainguard Checks](/chainguard/checks/overview/#availability-and-limits) and for [Chainguard Workspaces](/chainguard/workspaces/overview/#availability-and-limits). Run the command in an interactive terminal, because it opens your browser and asks questions as it goes: ```shell chainctl develop init ``` To create an organization, sign in with a work email address, using a password or a Google account. A personal email address, such as a Gmail address, can't create one, and neither can other sign-in methods, such as GitHub or GitLab. The command does the following: 1. Signs you in through your browser, and registers a new Chainguard identity if you don't have one. 2. Picks the Chainguard organization to use: - If you don't belong to one, it asks your name, company, job title, and whether you want updates from Chainguard, then creates one, named after your email domain. - If you belong to exactly one organization on the Starter or Team plan, it uses that one. - Otherwise, it stops and asks you to choose an organization with `--parent`. 3. Asks you to accept the terms that Chainguard Checks and Chainguard Workspaces are offered under, if your organization hasn't accepted them yet: the [Code Analysis Terms](https://www.chainguard.dev/legal/code-analysis-terms), the [Secured Platform Terms](https://www.chainguard.dev/legal/secure-platform-terms), the [Master Service and License Agreement](https://www.chainguard.dev/legal/master-service-and-license-agreement), and, for the GitHub link, the Guardener [Terms of Service](https://www.chainguard.dev/legal/guardener) and [Data Privacy Agreement](https://www.chainguard.dev/legal/supplemental-dpa). 4. If your organization isn't linked to a GitHub account yet, asks which one to link. Link the account that owns your repository: press `Enter` to link your own GitHub user, or enter the name of a GitHub organization where you have the owner role, such as `acme`, not a URL. The command opens your browser so you can authorize the link. If the Chainguard App isn't installed on the account, the command offers to open the installation page, then authorizes and links the account again. 5. Turns on Chainguard Checks and Chainguard Workspaces with their monthly allowances, and prints each product's limits. If Chainguard already granted your organization an allowance, the command keeps it. 6. Offers to connect your GitHub account, if you haven't yet and your organization allows it, so that the Workspaces sessions you create act on GitHub as you. The connection uses Chainguard Develop, a GitHub App separate from the Chainguard App. If you run the command in your repository's directory, it also offers to install Chainguard Develop on the GitHub accounts your repository needs. Chainguard Checks doesn't need this connection, so you can decline and connect later with `chainctl develop github login`, as described in [Let sessions act on GitHub as you](/chainguard/workspaces/github-as-you/). When you authorize the link, GitHub might ask you to authorize single sign-on for your GitHub organizations. If you're linking your own GitHub user, select **Continue**. To link an organization that uses SAML single sign-on, authorize single sign-on for that organization. A GitHub account can be linked to only one Chainguard organization at a time. If the account you choose is already linked to another Chainguard organization, `chainctl develop init` names the account and, in an interactive terminal, asks for a different one to link. To move the account to your organization instead, press `Ctrl+C` to stop the command, and then refer to [Moving a GitHub organization to a different Chainguard organization](/chainguard/guardener/github/app-connections/#moving-a-github-organization-to-a-different-chainguard-organization). If `chainctl develop init` stops with an error, refer to [`chainctl develop init` fails](/chainguard/workspaces/troubleshooting/#chainctl-develop-init-fails). ### Answer questions ahead of time {#answer-questions-ahead-of-time} You can answer some of the command's questions with flags: | Flag | What it does | | ---- | ------------ | | `--github-org` | Links the named GitHub organization instead of your own GitHub user. | | `--parent` | Turns on Chainguard Checks and Chainguard Workspaces in an organization you already belong to, including an Enterprise organization, instead of creating one. You must be an owner of the organization. | For example, to link the `acme` GitHub organization: ```shell chainctl develop init --github-org acme ``` For every flag, refer to the [`chainctl develop init` reference](/chainguard/workspaces/chainctl-develop-reference/#chainctl-develop-init). ### Use an organization your company already has {#use-an-organization-your-company-already-has} If your company already has a Chainguard organization, `chainctl develop init` can't create a second one for your email domain. Ask one of its owners to add you, as described in [Give teammates access](#give-teammates-access). An owner can then turn on both products in it, replacing `$ORGANIZATION` with the organization's name: ```shell chainctl develop init --parent $ORGANIZATION ``` If the organization already has Chainguard Checks or Chainguard Workspaces through your Chainguard account team, the command keeps that allowance. An organization that has a product through the account team must also accept that product's terms, which takes the `owner` role. Until an owner accepts them, Chainguard Checks runs no checks for the organization, so the summary check on each pull request fails with **Run creation failed**, and no one can open a Workspaces session in it. To accept the terms for one product only, use its own command: ```shell chainctl checks accept-terms --parent $ORGANIZATION chainctl develop accept-terms --parent $ORGANIZATION ``` If your GitHub account isn't linked to the organization yet, link it as described in [Getting started with Guardener](/chainguard/guardener/github/getting-started/#step-2-link-your-chainguard-organization-to-github), which uses the same Chainguard App. ## Give teammates access {#give-teammates-access} Your teammates don't need a Chainguard account to get checks on their pull requests or to read the results on GitHub. A teammate needs a role in your Chainguard organization to open the Chainguard Checks run page, to use `chainctl checks` commands such as `status` and `rerun`, or to open Workspaces sessions. Each teammate opens and manages their own sessions, which count toward your organization's [limits](/chainguard/workspaces/overview/#availability-and-limits). | Role | In Chainguard Checks | In Chainguard Workspaces | | ---- | -------------------- | ------------------------ | | `viewer` | Lets `chainctl` find your organization by name, which `--parent $ORGANIZATION` needs. | Lets `chainctl develop` find your organization by name. | | `sandbox.user` | Opens the run page, and reads, reruns, and cancels runs with `chainctl checks`. | Opens and manages the teammate's own sessions, and runs checks in them. | | `limited_owner` | Starts runs with `chainctl checks run`, but can't open the run page or read runs. | Opens sessions. Also manages your organization's billing and invites or removes members. | A teammate who uses `sandbox.user` also needs `viewer`. For more about roles, refer to [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). ### Add a teammate {#add-a-teammate} In a Starter organization, the steps differ, as described in [Add a teammate to a Starter organization](#add-a-teammate-to-a-starter-organization). If you have the `owner` role in your organization, invite your teammate and grant them the `sandbox.user` role as follows. Replace `$ORGANIZATION` with the name of your Chainguard organization: 1. Create an invite for your teammate with the `viewer` role. Your teammate needs it as well as `sandbox.user`, because `sandbox.user` alone doesn't let `chainctl` find your organization. Replace `$EMAIL` with your teammate's email address: ```shell chainctl iam invite create $ORGANIZATION --role viewer --email $EMAIL ``` 2. Send your teammate the invite code that the command prints. Your teammate accepts the invite by logging in with it, replacing `$INVITE_CODE` with the code: ```shell chainctl auth login --invite-code $INVITE_CODE ``` 3. After your teammate accepts, list your organization's role-bindings. The output shows each member's identity ID next to their email address: ```shell chainctl iam role-bindings list --parent $ORGANIZATION ``` 4. Grant your teammate the `sandbox.user` role, replacing `$IDENTITY_ID` with their identity ID: ```shell chainctl iam role-bindings create --identity $IDENTITY_ID --role sandbox.user --parent $ORGANIZATION ``` Your teammate can then install `chainctl`, as described in [Install or update chainctl](#install-or-update-chainctl), and open a session, as described in [Get started with Chainguard Workspaces](/chainguard/workspaces/getting-started/#step-2-open-a-session). ### Add a teammate to a Starter organization {#add-a-teammate-to-a-starter-organization} If you created your organization with `chainctl develop init`, it's a Starter organization, and you have the `limited_owner` role rather than `owner`. You can't create role-bindings for other users, so you can't grant the `sandbox.user` role, and members read Chainguard Checks results on GitHub instead of on the run page. An invite can give only a role whose permissions you also have, and of those roles, only `limited_owner` can open sessions. To invite a teammate, run steps 1 and 2 of [Add a teammate](#add-a-teammate). To let your teammate open sessions, use `--role limited_owner` instead of `--role viewer` in step 1: ```shell chainctl iam invite create $ORGANIZATION --role limited_owner --email $EMAIL ``` The `limited_owner` role also lets its holder manage your organization's billing and invite or remove members. Each invite must name your teammate's email address with `--email`, and the address must be in your organization's domain or one of its subdomains. A coworker whose email address is in your organization's domain can also ask to join by running `chainctl starter request-access`, which emails you the request. For details, refer to [Request access with chainctl](/chainguard/containers/reference/catalog-starter/#request-access-with-chainctl). ## Next steps {#next-steps} - [Get started with Chainguard Checks](/chainguard/checks/getting-started/#step-2-add-a-configuration-file-to-your-repository): add a `.chainguard/ci.yaml` file and see your first checks run. - [Get started with Chainguard Workspaces](/chainguard/workspaces/getting-started/#step-2-open-a-session): open your first session. - [Use Claude Code in a session](/chainguard/workspaces/coding-agents/): work with a coding agent in a session. --- ### What is Chainguard? URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/what-is-chainguard.md Last Modified: October 13, 2026 Tags: Getting Started Nearly every modern application is built on open source software. That software is powerful, but it arrives with problems: vulnerabilities (including publicly disclosed CVEs), unpatched dependencies, unclear provenance, and the constant work of keeping it all up to date. Tracking and fixing these issues across a large codebase consumes engineering time that could go toward building products. Chainguard's mission is to be the secure source for open source. Rather than leaving you to patch and harden open source software yourself, Chainguard rebuilds it from source in a hardened build environment, keeps it continuously updated, and distributes it with the metadata you need to verify what you're running. The result is software that carries low-to-no known CVEs and requires far less remediation work from your team. ## What Chainguard offers Chainguard rebuilds open source software into products you can adopt directly, depending on how you consume dependencies: - **[Chainguard Containers](/chainguard/containers/overview/)** is a collection of minimal, hardened container images. Following a distroless philosophy, each image includes only your application and its essential runtime dependencies, minimizing the overall attack surface. This minimalism is a large part of why they carry [low-to-no CVEs](/chainguard/containers/concepts/zerocve/). - **[Chainguard Libraries](/chainguard/libraries/introduction/overview/)** bring the same approach to language dependencies. They're drop-in replacements for open source packages in the Java, Python, and JavaScript ecosystems, rebuilt from verified sources and continuously monitored. You pull Chainguard Containers and Libraries from a single, policy-aware endpoint, the [Chainguard Repository](/chainguard/chainguard-repository/overview/). Containers and Libraries are where most teams start, but Chainguard secures more than these. Its other products include: - **[Chainguard OS](/chainguard/chainguard-os/overview/)**, the hardened Linux foundation the other products build on. - **[Chainguard VMs](/chainguard/vms/overview/)**, minimal virtual machine images for cloud and hypervisor workloads. - **[Chainguard Actions](/chainguard/actions/overview/)**, hardened replacements for popular GitHub Actions. - **[Guardener](/chainguard/guardener/)**, tooling to harden your own source code. - **[Chainguard Agent Skills](/chainguard/agent-skills/overview/)**, security-reviewed skills for AI agents. - **[Chainguard Checks](/chainguard/checks/overview/)**, CI for GitHub pull requests that runs each check in its own isolated microVM. - **[Chainguard Workspaces](/chainguard/workspaces/overview/)**, remote microVM sessions for development and coding agents that keep your credentials out of the session. ## The Chainguard Factory Behind these products is the [Chainguard Factory](/platform/factory/overview/), the automated build system at the heart of what Chainguard does. The Factory continuously monitors thousands of open source projects. When a new upstream release appears, it fetches the source, verifies it, rebuilds it, retests it, and publishes signed packages built from source along with SBOMs and provenance metadata. Because the Factory does this work for you, adopting a Chainguard artifact immediately reduces your software supply chain risk. Because Chainguard maintains the artifact continuously, those security improvements continue over time with minimal changes to your existing workflows. ## Why Chainguard Compared with using artifacts from public repositories, Chainguard gives you: - **Low-to-no CVEs**, so your team spends less time triaging and patching vulnerabilities. - **A minimal attack surface**, because artifacts ship with only what they need to run. - **Verifiable provenance** through signatures, SBOMs, and builds from source, so you can prove what's in your software supply chain. - **Continuous updates**, so patches land automatically instead of waiting for manual upgrades. ## Where to go next - Ready to try an image? Work through a [container example](/get-started/containers-examples/) for your language or service. - Replacing dependencies instead? Start with the [libraries on-ramp](/get-started/libraries-examples/). - Moving existing workloads over? Step through the [migration guides](/get-started/migration/). - Rolling Chainguard out to your teams? See how to [onboard your teams](/get-started/onboard-your-teams/). --- ### Self-serve overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/self-serve/overview.md Last Modified: September 28, 2026 **Self-serve** refers to the ways you can provision Chainguard products, such as container images and Helm charts, on your own, without direct involvement from Chainguard employees. This section covers the self-serve options available today: - **[Catalog Starter](/chainguard/containers/reference/catalog-starter/)** is a free plan that lets you try up to five Chainguard container images. Any Helm charts that depend on those images are included. - **[Self-serve Helm charts](/get-started/self-serve/helm-charts/)** lets Catalog customers provision Chainguard Helm charts, along with the container images they depend on, directly from the Chainguard Console. --- ### How Chainguard Checks and Chainguard Workspaces isolate your code URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/checks-and-workspaces/how-it-works.md Last Modified: October 16, 2026 Tags: Chainguard Checks, Chainguard Workspaces, Security, Overview Chainguard Checks runs your pull request checks, and Chainguard Workspaces runs your development sessions and coding agents, in microVMs that hold none of your long-lived credentials. This page collects the security model of both products in one place, for security reviewers who need to decide whether to allow them and for coding agents that need the rules. It describes how each product isolates code, where credentials come from, what code can reach on the network, who decides what a pull request gets, and how runs and sessions move through their lifecycles. A [glossary](#glossary) at the end defines the terms both products use. For sign-up and roles, refer to [Set up Chainguard Checks and Chainguard Workspaces](/get-started/checks-and-workspaces/set-up/).

Note: Each product is in beta.

## Isolation {#isolation} Each check, and each session, runs in its own microVM built on Chainguard OS: | | Chainguard Checks | Chainguard Workspaces | | -- | -- | -- | | What gets a VM | Each check on each run. A check that runs once for each of several [variations](#glossary) gets one VM for each variation. | Each session. You can open several sessions on one repository, and each is its own VM. | | How long the VM lasts | One check. The next run, and every retry, starts a fresh VM. | The life of the session. The VM stops when the session parks, and resumes from a snapshot. | | What starts in the VM | BusyBox, GNU tar, and the packages the configuration declares. Your repository's code is already in the VM. | Your synced directory at `/work`, the packages the configuration declares, and by default `git`, `gh`, Claude Code, and `chainctl`. By default, you work as `root`. | Chainguard treats the code in every VM as hostile, so the VM is the boundary that holds it: - Chainguard puts no credential in the VM in a form its code could spend elsewhere. Credentials are added outside the VM, as described in [Credentials](#credentials). - No host directory is mounted in the VM. Chainguard streams your repository, or the directory you sync to a session, into it. - A session's snapshot is sealed and bound to its owner, so a copy of it can't restore anyone else's session. A check starts with nothing from earlier runs unless it declares a cache. Even then, a run gets a cache only from a cache pool that your default branch defines for the run's [ring](#rings). In a session, `run-ci` runs your checks inside the session's own VM, not in fresh VMs, so its environment can differ from a pull request run. Only Chainguard Checks, which runs each check in a fresh VM, decides what GitHub shows. Refer to [How run-ci differs from a pull request run](/chainguard/checks/run-checks/#how-run-ci-differs-from-a-pull-request-run). ## Credentials {#credentials} Neither product puts a long-lived credential in the VM. Where a check or session needs access, the credential is added to its requests outside the VM. | | Chainguard Checks | Chainguard Workspaces | | -- | -- | -- | | How code authenticates | A check can request a [workload identity](/chainguard/checks/identity-and-trust/). Chainguard Checks issues the check an OpenID Connect (OIDC) token that lasts five minutes, and renews it while the check runs. | The session reaches each service through a local hostname, such as `http://github/` or `http://claude/`. `chainctl` on your laptop, or Chainguard, adds the credential to each request. | | What the VM holds | For Google Cloud, a credential configuration and sealed stand-in tokens. Chainguard Checks swaps in the real tokens only on requests to `sts.googleapis.com` and other `googleapis.com` subdomains. For Chainguard, nothing. | A placeholder at most. For `github` and `cgr.dev` from your laptop, the credential never leaves your machine. For `claude` and `gcp`, `chainctl` sends it to the session's host, outside the VM, and the host discards it when the session parks. | | Who decides | `identities.yaml` on the pull request's base branch, and the `trust` section on your default branch. | Your organization's policy and the session's credential settings. A request beyond the defaults needs your approval on your terminal. | Code in the VM can't read the real credentials, but it can use them. Each request it sends carries the credential's access, and anything it creates with that access, such as a Google Cloud service account key, outlives the check or session. Grant each workload identity only the access its checks need, and review a session's credentials before you let an agent work in it unattended. Refer to [Manage session credentials](/chainguard/workspaces/credentials/). For a check's workload identity, and for your Google Cloud credential in a session, a proxy outside the VM swaps in the real credential only on requests that it has verified over TLS as going to an allowed host. For a check's Chainguard identity, the proxy also refuses opaque TLS tunnels to Chainguard hosts, and sends each request only to the host whose TLS name it verified. Sessions can't use workload identities, so `run-ci` skips checks that use one. Two credentials sit outside these controls: - A credential that you paste into a session, or that a tool there stores, such as with `aws configure` or `npm login`, stays on the session's disk or in its memory, and in its snapshot when it parks. Any program in the session can read it. Do that work on your laptop instead. - If you connected your GitHub account, sessions you create afterward act on GitHub as you, through the Chainguard Develop GitHub App. They can push, approve pull requests, and merge as you, and neither an `egress` list nor `chainctl develop creds` limits them. Refer to [Protect your branches](/chainguard/workspaces/github-as-you/#protect-your-branches). ## Network {#network} Both products use the same `network.egress` allowlist, but they read a missing `network` key in opposite ways: | The check's configuration | In a check | In a session | | -- | -- | -- | | No `network` key | No network access | The open internet | | `network.egress` lists hosts | Only those hosts | Only those hosts, when you open the session on that check | | `network.egress: ["*"]` | Any public host | Any public host | | `policy: destroy` | A connection to an unlisted host stops the VM, and the check fails | Treated as `deny`: the connection is refused, and the session keeps running | A session that uses the default environment, a `.chainguard/develop.yaml` file, or a file's top-level `environment` reaches the open internet, because none of these can set `network`. To restrict a session, open it on a check that has an `egress` list. Refer to [Network access](/chainguard/workspaces/session-environment/#network-access). Whatever a check or session allows, it never reaches cloud metadata endpoints, loopback addresses, or private IP ranges. The `egress` list doesn't apply to a session's credential hostnames, such as `http://github/` and `http://claude/`, so a session with a narrow list can still push to GitHub and use Claude Code. Google Cloud is different, because its requests go to Google's own hosts: a check that uses a Google Cloud workload identity, and a session that uses your Google Cloud credentials with an `egress` list, must list `sts.googleapis.com` and the Google APIs they call. To see which hosts a check tried to reach, and whether Chainguard Checks allowed each one, open the check's run page from its **Details** link on GitHub. Refer to [A check can't reach a host](/chainguard/checks/troubleshooting/#a-check-cant-reach-a-host). ## Trust {#trust} Chainguard Checks reads the rules that limit a pull request, such as which checks gate it, which identities it can get, and how far it's trusted, from branches the pull request can't change. The pull request controls only what its checks run: | What a run gets | Where Chainguard Checks reads it | | -- | -- | | Which checks must run, and which of them block | The pull request's base branch | | An identity's definition: which checks, branches, and audiences get it | `identities.yaml` on the pull request's base branch | | The `trust` section: groups, subrings, rings, and cache pools | Your default branch's latest commit | | Each check's commands, its `identity` key, and its `network.egress` | The commit being tested, which includes the pull request's changes | A pull request can add checks and change what they run, but it can't weaken its own gate: - A change that makes a check advisory, or makes it run only on request, takes effect after the pull request merges. Until then, the check keeps blocking. - A change that removes a check, or stops it from running for this pull request, fails the summary check with **Run creation failed**. - A change that leaves no presubmit checks fails the same way when the base branch has any. Refer to [Change checks in a pull request](/chainguard/checks/github/#change-checks-in-a-pull-request). ### Rings {#rings} Chainguard Checks places each run in a *ring*, based on the `trust` section on your default branch: | Ring | Runs in the ring | | -- | -- | | `2` | Runs after merging, on your default branch | | `1.` | Pull requests opened by an account in one of the subring's `authors` groups | | `0` | Every other pull request, including pull requests from forks by anyone outside your subrings | An identity's `ring` is the lowest ring that gets the identity. Only the account that opened the pull request counts: who pushed its commits, who reviewed it, and whether it comes from a fork don't change the ring. Without a `trust` section, every open pull request whose base branch an identity allows gets that identity, including pull requests from forks, which run without approval. Refer to [Limit which pull requests get an identity](/chainguard/checks/identity-and-trust/#limit-which-pull-requests-get-an-identity). ## Run lifecycle {#run-lifecycle} A *run* is one pass of a repository's checks for one commit. The following table follows a pull request run from start to finish: | Stage | What happens | What can end the run here | | -- | -- | -- | | 1. Event | GitHub tells Chainguard Checks, through the Chainguard App, that a pull request was opened, reopened, or updated. | Pushes to other branches, tags, and closed pull requests start no run. | | 2. Test merge | Chainguard Checks uses the commit GitHub would create by merging the pull request into its base branch. | Merge conflicts: the summary check fails with **Merge conflict with the base branch**. | | 3. Plan | Chainguard Checks reads the configuration from the merge commit, applies the base branch's rules, and selects the checks that this change runs. | An invalid configuration fails with **Invalid config**. A change that weakens its own gate, or that changes 3,000 or more files, fails with **Run creation failed**. | | 4. Admission | Chainguard Checks admits the run only if Chainguard Checks is turned on for the organization and the organization is within its limits. | Chainguard Checks isn't turned on for the organization, or the organization is at its run limit, such as 10 runs queued or running with the Starter allowance. Either way, no summary check appears. The summary check fails with **Run creation failed** when the organization has used up its monthly compute allowance or hasn't accepted the terms. | | 5. Checks | Each selected check runs in its own fresh VM, after the checks it `needs`. Before a check gets a workload identity, Chainguard Checks verifies again, against GitHub, that the pull request is still open at the head and base this run tests, and that the base branch allows the identity for this check. A check that can't finish for a reason outside the check, such as a VM that doesn't start, runs again in a new VM, up to three attempts in all. | A check fails, times out, is stopped by `policy: destroy`, or doesn't get the workload identity it requests. | | 6. Summary | Each check reports a GitHub check named `presubmit / / ` as it finishes. The summary check, **Chainguard Checks (presubmit)**, passes only when every blocking check passes. | | A new push to the pull request cancels the current run and starts a new one at stage 1. After a change merges, each new commit on your default branch gets its own run in ring `2`, which reports **Chainguard Checks (postsubmit)**. Refer to [Use Chainguard Checks on GitHub](/chainguard/checks/github/). ## Session lifecycle {#session-lifecycle} A session moves through the following states: | State | What it means | What moves it on | | -- | -- | -- | | Creating | `chainctl develop` creates the VM, syncs your directory into `/work`, and installs the session's packages. | When it's ready, your terminal attaches. | | Attached | Your terminal is connected to the session's shell. `chainctl` answers the requests that use your laptop's credentials, and sends your Claude and Google Cloud credentials to the session's host. | When you detach, the session keeps running. **Park now** in the session menu parks it right away. | | Running, detached | The session keeps running, and an agent in it can keep working. It still uses compute. | After about 10 minutes, or after the time you chose with **Detach — keep running** or `--keep`, up to 24 hours, the session parks. A `--keep always` session parks 7 days after you last detach. Attaching again returns it to attached. | | Parked | Chainguard saves the session's memory and disk to a snapshot and stops the VM, so it uses no compute. The host discards the credentials `chainctl` sent it. | Attaching resumes it from the snapshot in seconds, with processes, files, and shell as you left them. Five days after parking, it expires. | | Expired | Chainguard discards the VM and the snapshot. The session can't be resumed, but it stays in `chainctl develop list --expired`, and its name stays in use. | `chainctl develop delete` removes it. | However a session is used, its VM runs for at most 6 days at a stretch. Then Chainguard parks the session and resumes it right away, the same way the session menu's **Restart the host** option does, so a session in long use isn't cut off. You can delete a session in any state. Anything you didn't push is lost. A parked session counts toward your session limit until it expires or you delete it. Refer to [Manage Chainguard Workspaces sessions](/chainguard/workspaces/manage-sessions/). ## Glossary {#glossary} **Chainguard App** : The GitHub App that you install on your GitHub organization or personal account. It delivers pull request events to Chainguard Checks, and by default it fetches and pushes a session's repository. **Chainguard Develop** : A separate GitHub App, `chainguard-develop`. After you connect your GitHub account with `chainctl develop github login`, the sessions you create act on GitHub as you through this app instead of through the Chainguard App. **Check** : One named entry under `checks` in your `.chainguard/ci.yaml` file or a file in `.chainguard/ci/`. On GitHub, each check reports as `presubmit / / `. **Park** : To save a session's memory and disk to a snapshot and stop its VM. A parked session resumes where it left off. **Ring** : The trust level of a run: `2` for runs after merging, `1.` for pull requests from accounts you trust, and `0` for every other pull request. Rings decide which runs get workload identities and caches. **Run** : One pass of a repository's checks for one commit: a pull request's test merge commit, or a new commit on your default branch. Each run reports one summary check. The word also appears in messages such as **Run creation failed** and in capability names such as `sandbox.run.list`. **Session** : A Linux microVM in Chainguard's cloud that `chainctl develop` opens, with your repository at `/work`. **Variation** : One entry in a check's `foreach` list. Each variation runs in its own VM, and all of a check's variations report as one GitHub check. **Workload identity** : Short-lived credentials that Chainguard Checks gives a check while it runs, for Google Cloud or Chainguard. Sessions don't get them. Several names in `chainctl` and in the configuration don't match the product names: | Name | What it is | | -- | -- | | `sandbox.user` | The IAM role that lets a member use both Chainguard Checks and Chainguard Workspaces. | | `CHAINCTL_SANDBOX_GROUP` | An environment variable that sets the default organization or folder for `chainctl develop --parent`. | | `::sandbox-summary::` | A line that a check prints to start a Markdown summary on its GitHub check. `::end-sandbox-summary::` ends it. | --- ### Self-serve Helm charts URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/self-serve/helm-charts.md Last Modified: October 13, 2026 Catalog customers can provision Chainguard Helm charts, along with the container images each chart depends on, directly from the Chainguard Console. This replaces the previous manual request process, which required coordination with Chainguard sales, customer success, and support and could take 1 to 4 days.

Note: This capability is available to Catalog customers whose plan includes the APPLICATION tier. It does not apply to the free Catalog Starter plan. Customers on per-image pricing can request charts through a support workflow, described in Per-image pricing customers.

## Add a chart in the Chainguard Console To add Helm charts to your organization from the Console, complete the following steps: 1. In the Chainguard Console, go to the **Helm charts** section in the sidebar. 1. Review the available charts. Charts already enabled for your organization are marked. 1. Select one or more charts to add. The Console checks whether your organization already has the container images each chart requires, then lists any missing images by name. 1. Optional: If your organization is entitled to FIPS images, select the FIPS option to add FIPS variants where they are available. 1. Confirm your selection to add the charts and their required images to your organization. Provisioning runs in the background. The Console shows in-progress and completion states while it adds the charts and images, which can take some time to finish. ## Add a chart with `chainctl` You can perform the same action from the command line with `chainctl`. For a Catalog customer, the following command resolves the chart's image dependencies and creates any missing chart and image repositories in your organization, matching the Console flow: ```shell chainctl images helm add-chart $CHART ``` Replace `$CHART` with the chart you want to add. The command will output a small chart showing the name of every image in the Helm chart and the image status, such as whether it already exists in your org or is being added.

Note: This command is available starting in a specific chainctl release. If it isn’t available in your installation, update chainctl to the latest version.

You can use the command to add `iamguarded` charts, too: ```shell chainctl images helm add-chart iamguarded-charts/$CHART ``` If you want to test the process before actually adding the chart, use: ```shell chainctl images helm add-chart $CHART --dry-run ``` To learn more, use `chainctl images helm add-chart --help` or refer to the [`chainctl` Reference documentation](/platform/chainctl/). ## Per-image pricing customers If your organization uses per-image pricing, or a chart requires images outside your active tiers, the Console doesn't provision the chart instantly. Instead, it generates a single support request that lists the chart and any missing images, prefilled with your organization's details. Chainguard support then provisions the chart and images, typically within 1 to 2 days. ## Use your charts After provisioning completes, authenticate and deploy your charts by following [How to Use Chainguard Helm Charts](/chainguard/containers/using-and-deploying/helm-charts/use-chainguard-helm-charts/). --- ### Onboard your teams URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/onboard-your-teams.md Last Modified: October 13, 2026 Tags: Getting Started Your organization has adopted Chainguard, and now you need to bring your teams on board. This guide explains what your teams can pull from Chainguard Containers and Chainguard Libraries, why that depends on your subscription, and how to retrieve the SBOMs and provenance that ship with every image and package. It's written for two readers: - **Platform and security administrators** who manage Chainguard for the organization and need to explain it to the teams they support. Early on, you may be the only people with [Chainguard Console](/platform/console/) access, so your teams rely on you to communicate how this works. - **Software engineers** who were pointed here and want to know how to find and pull the resources they're cleared to use. To decide where to start, identify which Chainguard products your organization uses; many use both. Read [Chainguard Containers](#chainguard-containers) if your teams pull container images, or [Chainguard Libraries](#chainguard-libraries) if they pull language dependencies for Java, Python, or JavaScript. Your administrators know which subscriptions apply. ## Chainguard Containers ### What you can browse compared to what you can pull There are two different surfaces, and it helps to keep them separate. The **Chainguard Containers Directory** at [images.chainguard.dev](https://images.chainguard.dev) is public. Anyone can browse the entire catalog there, inspect tags and metadata, and view the SBOM and provenance for each image. Browsing the Directory does not mean your organization can pull every image it lists. Your **organization's registry** is what your teams pull from, and it holds only the images your organization has access to. Authenticated production images live under your organization's namespace: ```shell cgr.dev/$ORGANIZATION/$IMAGE:$TAG ``` For example, an organization registered as `example.com` pulls its Python image from `cgr.dev/example.com/python`. Public Starter images remain available to everyone under `cgr.dev/chainguard/`. See the [registry overview](/chainguard/containers/registry/overview/) for the access tiers and [authentication](/chainguard/containers/registry/authenticating/) to set up your credentials. Your organization may also front the registry with a pull-through cache, such as Artifactory. In that case, the concepts here still hold, but you pull from your cache's address instead of `cgr.dev` directly. Ask your administrators for the path. ### What your organization can pull Whether an image is available to pull depends on your subscription. If a pull fails for an image you can see in the Directory, it most likely hasn't been added to your organization yet. To work through the possible causes, including a missing version rather than a missing image, see [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/). **Catalog customers.** A Catalog subscription covers the full Chainguard catalog. Even so, only a subset of images is loaded into your organization's registry at any given time, and administrators add more as teams need them. To use an image that isn't there yet, find it in the [Chainguard Containers Directory](https://images.chainguard.dev) and ask an administrator to add it. If you have the `owner` role, you can add it yourself from the Console; refer to [Catalog pricing](/chainguard/containers/reference/pricing/) for the steps and the roles required. **Per-image customers.** A per-image subscription covers a specific, licensed set of images rather than the whole catalog. You can browse everything in the Directory, but only your licensed images are permitted for builds, deployments, and production workloads. To add an image that isn't in your set, ask your administrators to start a request; once they approve it, they add the image to your organization's registry. ### Access SBOMs and provenance Every Chainguard container image ships with a signed SBOM and provenance attestations. You can retrieve them three ways; the [full guide](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) covers each in detail. - **From the Chainguard Containers Directory.** Open an image at [images.chainguard.dev](https://images.chainguard.dev), then download the SBOM from the **SBOM** tab in SPDX or CycloneDX format. This needs no tooling and works for any image in the catalog. - **With `cosign`.** Download the SBOM attestation for an image directly from the registry: ```shell cosign download attestation \ --platform linux/amd64 \ --predicate-type https://spdx.dev/Document \ cgr.dev/$ORGANIZATION/$IMAGE | jq -r '.payload' | base64 -d | jq -r '.predicate' ``` - **With `syft`.** Generate an SBOM locally from an image you've pulled. Use this for images you've customized, where you want an SBOM of the final artifact. To pin what you pull so builds stay reproducible, reference images by digest. Refer to [Inspecting Chainguard Containers](/chainguard/containers/troubleshooting/inspecting-containers/). ## Chainguard Libraries Chainguard Libraries is a secure catalog of language dependencies for Java, Python, and JavaScript. Each package goes through multiple layers of defense, including malicious behavior scanning, building from source, and configurable policies. A package pulled from Chainguard is a drop-in replacement for the one you'd normally pull from Maven Central, PyPI, or npm. See the [overview](/chainguard/libraries/introduction/overview/) for how they're built and what guarantees they carry. ### What your organization can access Libraries don't have a public browse site like the Chainguard Containers Directory. Instead, you browse the packages your organization is entitled to by signing in to the [Chainguard Console](/chainguard/libraries/introduction/browse/). Access is granted per language: your organization is entitled to Java, Python, or JavaScript individually, so which ecosystems you can pull depends on your subscription. There's no catalog-versus-per-image distinction as there is for containers. ### Pull libraries into your project You consume Chainguard Libraries by pointing your package manager at your organization's Chainguard repository and authenticating with a pull token. Each ecosystem has its own endpoint under `libraries.cgr.dev`: ```shell https://libraries.cgr.dev/java/ https://libraries.cgr.dev/python/ https://libraries.cgr.dev/javascript/ ``` The [quickstart](/chainguard/libraries/introduction/quickstart/) walks through creating a pull token and configuring your build tools, and [access and authentication](/chainguard/libraries/introduction/access/) covers the details. ### Verify provenance and SBOMs Every package that Chainguard builds from source ships with a signed SBOM and SLSA provenance, attached alongside each package. Use a command similar to the following to verify which of your dependencies were built by Chainguard: ```shell chainctl libraries verify /path/to/artifact ``` See [verifying Chainguard Libraries](/chainguard/libraries/policies-and-security/verification/) for what the command checks and for retrieving the SBOM and attestation files directly. ## Other products your company may have adopted Chainguard's platform reaches beyond containers and libraries. Your organization may also use one or more of these: - **[Chainguard Agent Skills](/chainguard/agent-skills/overview/)** — hardened AI agent skills that Chainguard reviews, scopes, and publishes with a full audit trail, so your teams can install them without inheriting unknown risk. - **[Chainguard Actions](/chainguard/actions/overview/)** — hardened, drop-in replacements for popular GitHub Actions that protect your CI/CD pipelines from supply chain attacks. - **[Chainguard Checks](/chainguard/checks/overview/)** — CI for GitHub pull requests that runs each check in its own isolated microVM, with no network access unless a check asks for it. - **[Chainguard Workspaces](/chainguard/workspaces/overview/)** — remote microVM sessions for development and coding agents that keep your credentials out of the session. - **[Guardener](/chainguard/guardener/)** — a tool for managing and hardening your source code through a suite of capabilities you opt into independently. ## Next steps - New to the platform? Start with [What is Chainguard?](/get-started/what-is-chainguard/). - Ready to pull your first container image? Work through a [language- or service-specific example](/get-started/containers-examples/). - Adopting Chainguard Libraries? Follow the [libraries on-ramp](/get-started/libraries-examples/). - Managing resources from the command line? See [Get started with chainctl](/get-started/getting-started-with-chainctl/). - Wiring Chainguard into a build pipeline? See [Chainguard in your CI/CD pipeline](/get-started/cicd-pipeline/). --- ### Set up Chainguard in your CI/CD pipeline URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/cicd-pipeline.md Last Modified: October 14, 2026 Tags: Getting Started A CI/CD pipeline touches most of your software supply chain: the code you commit, the credentials your runners hold, the dependencies you pull, the image you build, and the artifact you ship. Chainguard has something to offer at each of those points. This page walks the whole path in order so you can see how the pieces relate before you start wiring any of them up. It's a map rather than a tutorial. Each stage gives you the one command that anchors it, then links to the guide that covers the details. You don't have to adopt every stage, and you don't have to adopt them in order. Most organizations start at [Pull trusted inputs](#4-pull-trusted-inputs), because swapping a base image is the smallest change with the largest effect, then work outward from there. ![Vertical flow diagram of a CI/CD pipeline in nine stages, each labeled with the Chainguard products and practices that apply. Stage 0, set up access: Chainguard Console, chainctl, IAM roles. Stage 1, harden the repository: Guardener, Hardened Actions, Commit Verification. Stage 2, authenticate the pipeline: assumable identities, OIDC tokens, setup-chainctl. Stage 3, replace workflow steps: Chainguard Actions, the cg-actions skill, Chainguard Checks, Chainguard Agent Skills. Stage 4, pull trusted inputs: Chainguard Containers, Chainguard Libraries, Chainguard OS Packages. Stage 5, build the image: Custom Assembly, Chainguard VMs, Dockerfile migration. Stage 6, verify before shipping: signature verification, SBOMs, SLSA provenance. Stage 7, gate the deploy: admission policies, repository policies. Stage 8, stay current: Digestabot, CloudEvents, security advisories, EOL Grace Period.](cicd-lifecycle.svg) ## 0. Set up access Before anything else, you need a Chainguard organization, `chainctl` installed and authenticated, and enough permission to create identities and entitlements. Several stages that follow need the `owner` role. Work through [Get started with chainctl](/get-started/getting-started-with-chainctl/) to install the tool, then log in: ```shell chainctl auth login ``` The [Chainguard Console](/platform/console/) covers the same ground in a browser, and [comparing chainctl to the Console](/platform/chainctl-usage/comparing-chainctl-to-console/) shows which tasks belong to which. To plan who can do what, see [roles and role bindings](/platform/administration/iam-organizations/roles-role-bindings/). **Why it matters.** Access lives in one place instead of scattered across registry credentials on individual machines. When someone changes teams, you revoke a role binding rather than hunting for keys. ## 1. Harden the repository Recent supply chain attacks have targeted the workflow file, not the artifact it produces. [Guardener](/chainguard/guardener/) hardens the repository itself through a suite of capabilities you turn on one at a time. Two of them run through the [Chainguard App](/chainguard/guardener/github/), enabled per repository by a file you commit to `.chainguard/`: - [Hardened Actions](/chainguard/guardener/github/actions-security/) recommends and migrates your GitHub Actions to Chainguard's hardened, SHA-pinned equivalents, either as non-blocking review comments or as a migration pull request. - [Commit Verification](/chainguard/guardener/github/commit-verification/) enforces cryptographically signed commits against a policy you control, covering both keyless Sigstore signatures and static keys such as GPG. A third runs locally rather than through the app: - [Dockerfile migration](/chainguard/guardener/dockerfile-migration/) converts your Dockerfiles to Chainguard Containers through the `chainctl agent dockerfile` commands. To install the app and link your Chainguard organization to your GitHub organization, see [getting started with Guardener](/chainguard/guardener/github/getting-started/). **Why it matters.** An attacker who can edit a workflow already has your secrets. Hardening the repository closes that door before anything reaches your build. ## 2. Authenticate the pipeline Your pipeline needs credentials to pull from Chainguard, and a long-lived API key stored in repository secrets is the weakest way to supply them. Chainguard uses [assumable identities](/platform/administration/assumable-ids/) instead: your CI job presents the OIDC token its platform already issues, and exchanges it for a short-lived Chainguard token. On GitHub Actions, the `setup-chainctl` action handles the exchange: ```yaml permissions: id-token: write contents: read steps: - uses: chainguard-dev/setup-chainctl@2cddd35a2f120d9973e58094dc6878c93cf58c28 # v0.5.1 with: identity: "" ``` On GitLab, Jenkins, or a shell script, pass the platform's token to `chainctl` directly: ```shell chainctl auth login \ --identity="$IDENTITY_ID" \ --identity-token="$OIDC_TOKEN" ``` [Automating with chainctl](/platform/chainctl-usage/automating-chainctl/) covers non-interactive use, and the [identity examples](/platform/administration/assumable-ids/identity-examples/) include worked setups for GitHub, GitLab, and several cloud providers. **On Catalog Starter.** [Chainguard Catalog Starter](/chainguard/containers/reference/catalog-starter/) organizations can't create assumable identities, so neither approach in this stage works. Authenticate with a pull token instead, as described in [Authenticate CI pipelines with a pull token](/chainguard/containers/reference/catalog-starter/#authenticate-ci-pipelines-with-a-pull-token). **Why it matters.** There's no long-lived credential to leak, rotate, or track. A token that shows up in a build log has already expired by the time anyone reads it. ## 3. Replace workflow steps [Chainguard Actions](/chainguard/actions/overview/) are hardened drop-in replacements for popular GitHub Actions. Each one keeps the same inputs and outputs as the upstream version, so migrating a step means changing the `uses:` line and nothing else. Enable the entitlement for your organization: ```shell chainctl actions entitlements create ``` Then point each step at its hardened equivalent, pinned to a commit digest: ```yaml - uses: chainguard-actions/tj-actions-changed-files@ # v47 ``` Repository names in `chainguard-actions` carry the upstream organization as a prefix, so `tj-actions/changed-files` becomes `tj-actions-changed-files`. Run `chainctl actions discover` to list every action and container image your workflows reference, which tells you what there is to migrate. If your GitHub organization restricts which actions can run, add `chainguard-actions/*` to the allowed patterns first. You don't have to make these edits by hand. Two tools do the migration for you, and which one fits depends on how much you're moving at once: - [Hardened Actions](/chainguard/guardener/github/actions-security/), through the Chainguard App described in stage 1, inventories the actions in use across your organization and opens migration pull requests on a schedule. You can also trigger a run [on demand](/chainguard/guardener/github/actions-security/#run-an-on-demand-migration) with `chainctl guardener github migrate create`. Use this for a centralized, organization-wide rollout. - [cg-actions](https://github.com/chainguard-dev/cg-skills/tree/main/skills/cg-actions), a Claude Code skill, audits one repository's Actions usage and opens a pull request swapping in the hardened equivalents. Use this for a pilot, or where installing an app across the organization isn't an option. To move pull request checks off GitHub Actions runners instead, [Chainguard Checks](/chainguard/checks/overview/) runs each check in its own microVM. Refer to [Migrate from GitHub Actions to Chainguard Checks](/chainguard/checks/migrate-from-github-actions/). Separately, if agents run anywhere in your pipeline, [Chainguard Agent Skills](/chainguard/agent-skills/overview/) applies the same hardening idea to the skills those agents load. **Why it matters.** Migration is a one-line change per step, and it removes whole classes of attack — tag hijacking, `pull_request_target` abuse, and secret exfiltration — without changing what your workflow does. ## 4. Pull trusted inputs This is where most teams start. Point your builds at Chainguard for the three kinds of input a pipeline pulls: - **Container images.** Authenticate to `cgr.dev` and pull from your organization's namespace. See [authenticating to the registry](/chainguard/containers/registry/authenticating/). ```shell chainctl auth configure-docker ``` - **Language dependencies.** [Chainguard Libraries](/chainguard/libraries/introduction/overview/) rebuilds Java, Python, and JavaScript packages from source, and they're drop-in replacements for what you'd pull from Maven Central, PyPI, or npm. Configure your package manager with `chainctl auth configure-npm` or the equivalent for your ecosystem, then follow the [quickstart](/chainguard/libraries/introduction/quickstart/). - **System packages.** [Chainguard OS Packages](/chainguard/chainguard-os/chainguard-os-packages/) are the APK packages the container images are assembled from, available when you need to install something at build time. All of these are served through the [Chainguard Repository](/chainguard/chainguard-repository/overview/), which is also where you set the policies that govern what your organization is allowed to pull. **Why it matters.** Remediation happens upstream of your build. When a CVE is fixed, your next build inherits the fix instead of your team opening a ticket to chase it. ## 5. Build the image Build your application on a Chainguard base image, using the `-dev` variant for the build stage and the minimal runtime variant for the final stage. [Migrating to Chainguard Containers](/chainguard/containers/migration/migrations-overview/) covers the patterns, and the [Dockerfile conversion tool](/chainguard/containers/migration/migration-tools/dockerfile-conversion/) does the mechanical part. When your runtime image needs packages the standard image doesn't carry, [Custom Assembly](/chainguard/containers/custom-assembly/overview/) builds a variant to your specification. You can trigger those builds from CI: ```shell chainctl images repos build apply --file custom-jre.yaml \ --parent \ --repo \ --yes ``` [Triggering builds in CI/CD workflows](/chainguard/containers/custom-assembly/custom-assembly-gitops/) shows the full GitOps pattern. If you ship virtual-machine images rather than containers, [Chainguard VMs](/chainguard/vms/overview/) applies the same approach to VM base images. **Why it matters.** You maintain your application layer and Chainguard maintains everything underneath it. That's the difference between patching a distribution's backlog and shipping your own code. ## 6. Verify before you ship Every Chainguard container image and library ships with a signed SBOM and provenance attestation. Verifying them in CI turns those signatures into a gate rather than a document nobody reads. Check an image signature with `cosign`: ```shell cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev//.*" \ cgr.dev//:latest ``` See [verifying signatures with cosign](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/) for what the command proves, and [retrieving image SBOMs](/chainguard/containers/security-and-compliance/retrieve-image-sboms/) for pulling the SBOM itself. For dependencies, `chainctl libraries verify` reports which of your artifacts Chainguard built; [verifying Chainguard Libraries](/chainguard/libraries/policies-and-security/verification/) covers the detail. **Why it matters.** The evidence an auditor asks for is generated by the pipeline that shipped the artifact, not reconstructed from memory months later. ## 7. Gate the deploy Verification in CI only covers what went through CI. An admission controller enforces the same rules at the cluster boundary, so an image that skipped your pipeline can't run. Chainguard documents policies for both [Kyverno](/chainguard/containers/security-and-compliance/enforcement/kyverno/) and [OPA Gatekeeper](/chainguard/containers/security-and-compliance/enforcement/opa-gatekeeper/). Further upstream, [container policies](/chainguard/chainguard-repository/container-policies/) and [library policies](/chainguard/chainguard-repository/library-policies/) in the Chainguard Repository govern what your organization can pull in the first place. **Why it matters.** The guarantee holds at runtime, not only at build time. Policy catches the deployment that bypassed the pipeline, which is the one you'd otherwise never hear about. ## 8. Stay current Chainguard rebuilds images continuously, so the value of a low-CVE image depends on how quickly you pick up the new digest. Automate that: - [Digestabot](/chainguard/containers/security-and-compliance/updating-containers/digestabot/) opens pull requests that bump pinned digests to the current build. - [Renovate](/chainguard/containers/security-and-compliance/updating-containers/renovate/) and [Dependabot](/chainguard/containers/security-and-compliance/updating-containers/dependabot/) handle the same job if you already run them. - [CloudEvents](/platform/administration/cloudevents/) notify your systems when a new image is pushed, so you can trigger a rebuild rather than poll for one. Track what's changing with [security advisories](/chainguard/containers/security-and-compliance/security-advisories/) and the [changelog](/chainguard/changelog/). When an image version approaches end of life, the End-of-Life Grace Period gives you a defined window to migrate; see [considerations for image updates](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/). **Why it matters.** Pinning a digest and walking away turns a current image into a stale one. Automating the bump is what keeps "low CVE" true next quarter instead of only on the day you adopted it. ## Next steps - Bringing engineers onto an organization that's already adopted Chainguard? See [Onboard your teams](/get-started/onboard-your-teams/). - Ready to pull your first image? Work through a [language- or service-specific example](/get-started/containers-examples/). - Moving existing workloads over? Step through the [migration guides](/get-started/migration/). - Want the background on CI/CD attacks these stages defend against? Watch [Securing CI/CD with Chainguard](/software-security/learning-labs/ll202604/). --- ### Get support URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/get-support.md Last Modified: September 28, 2026 Tags: Getting Started When the documentation doesn't answer your question, Chainguard offers several ways to get help. This guide covers what to check first, which channel handles your kind of request, and how to open a support ticket when you need one. ## Before you open a ticket Many questions already have a published answer you can find in seconds: - **This documentation site.** Search from the box at the top of any page. - **Ask AI.** Click **Ask AI** in the upper-right corner of any page on this site and ask your question in plain language. The assistant draws on Chainguard's documentation and support content and cites its sources. Review the answer and its sources before you act on it. - **The [Chainguard knowledge base](https://support.chainguard.dev/hc/en-us).** Chainguard support engineers publish articles there for problems that come up repeatedly. ## Where to send your request The support portal handles most requests, but it isn't the only route, and for some situations it isn't the right one. Find your situation in the following list: - **You have a problem with a Chainguard product or with your organization's configuration.** Open a ticket in the [support portal](https://support.chainguard.dev/). - **You need to move your multi-factor authentication to a new device, or you've lost the device running your authenticator app.** Where you make that change depends on how you sign in, and for most sign-in paths it isn't Chainguard that manages your MFA. See [Change or reset your MFA device](/get-started/mfa-devices/). - **You can't sign in at all, so you can't reach the portal.** Email [support@chainguard.dev](mailto:support@chainguard.dev). - **Your organization uses Catalog Starter.** Use the [knowledge base](https://support.chainguard.dev/hc/en-us), the [community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw), and the free [Chainguard courses](https://courses.chainguard.dev/). Catalog Starter doesn't include ticket access. - **Your request concerns your subscription or what your organization is licensed for — for example, whether a particular image is included in your entitlements.** Contact your account team, meaning the customer success manager or solutions architect assigned to your organization. - **You can't pull a container image or a specific version of one.** Work through [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/) first. It tells you which cases you can resolve yourself and which need a ticket. ## Open a ticket ### Confirm that you can reach the portal The support portal has two prerequisites. First, your organization needs a paid plan. Catalog Starter doesn't include support tickets or root cause analysis. For what the free plan does cover, see [Chainguard Catalog Starter](/chainguard/containers/reference/catalog-starter/). Second, your identity has to be linked to an organization. The portal identifies you through your Chainguard Console account, and an unlinked identity produces an error. To check, sign in to the [Chainguard Console](https://console.chainguard.dev) and look for your organization in the left sidebar. If it isn't there, ask an administrator in your organization to invite you, then open the invitation email and complete the setup flow. Signing in without completing that flow leaves your identity unlinked.

Note: Administrators can invite users from the Console under Settings > Users > Invite users, or from the command line. See How to manage IAM organizations in Chainguard.

### Submit the request 1. Sign in to the [Chainguard Console](https://console.chainguard.dev). 1. In the left sidebar, click **Support**. You can also go to [support.chainguard.dev](https://support.chainguard.dev/) directly. 1. Fill in the request form. Before you submit, the portal offers relevant documentation and a generated answer. If that resolves your question, you're finished. Otherwise, submit the request as usual. ### If the portal returns an error Work through the following steps: 1. Open the Console and the support portal in a private browsing window. 1. Clear your browser cache and cookies. 1. Confirm that you're signing in with the same identity you were invited with. If the error persists, email [support@chainguard.dev](mailto:support@chainguard.dev) with a screenshot of the error, the email address of the account you're trying to use, and the invitation link if you still have it. ## What to include in your request A support engineer can start on a problem instead of asking you for more information when your request includes: - Your organization name. - The image or package name and tag, and the digest if you have it. - The command you ran and its complete output, including any error message. - The `chainctl` version, if the problem involves the command-line tool. Run `chainctl version` to get it. - What you expected to happen instead. - Anything else you think might help: logs, screenshots, or a short screen recording. --- ### Change or reset your MFA device URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/mfa-devices.md Last Modified: September 28, 2026 Tags: Getting Started You might need to move your multi-factor authentication (MFA, also called 2FA) to a new device because you replaced your phone, changed authenticator apps, or lost the device that generates your codes. Where you make that change depends on how you sign in to the Chainguard Console, because Chainguard manages MFA for only one of the sign-in paths. ## Find out which sign-in path you use The Console login screen has buttons for Google, GitHub, and GitLab, along with a single field that accepts either your email address or your organization name. What you click or enter there determines your path: - **You click Google, GitHub, or GitLab.** That provider signs you in. - **You enter your organization name.** The Console sends you to the identity provider your organization registered. - **You enter an email address whose domain your organization registered with a custom identity provider.** The Console sends you to that provider. - **You enter any other email address.** The Console sends you to Chainguard's email and password sign-in, which always asks for a one-time code from an authenticator app. Your path determines who holds your MFA enrollment: | How you sign in | Who manages your MFA | Where to change it | | --- | --- | --- | | Google, GitHub, or GitLab | That provider | Your account settings with that provider | | Your organization's identity provider, such as Okta, Microsoft Entra ID, Ping Identity, or Keycloak | Your own organization | Your internal IT or identity provider administrator | | Your email address and a password | Chainguard | A support ticket opened by an Owner in your organization | ## Change MFA managed by an identity provider When you sign in through Google, GitHub, GitLab, or your organization's own identity provider, that provider holds your MFA enrollment. Chainguard can neither see it nor change it, so Chainguard support can't reset it for you. Change it where you manage the rest of that account: - **Google.** See [Turn on 2-Step Verification](https://support.google.com/accounts/answer/185839) in Google Account Help. - **GitHub.** See [Configuring two-factor authentication](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication). - **GitLab.** See [Two-factor authentication](https://docs.gitlab.com/user/profile/account/two_factor_authentication/). - **A corporate identity provider.** Contact your internal IT helpdesk or the administrator who manages the provider. After you change MFA with your provider, sign in to the Console as usual. Nothing needs to change on the Chainguard side. ## Change MFA for email and password sign-in Chainguard manages this path through Auth0, which hosts the sign-in screen at `auth.chainguard.dev` and holds your MFA enrollment. MFA is required here, so this path always asks for a code. You can't change or reset your own device: the Console has no MFA settings page, and Chainguard's sign-in configuration includes no self-service reset. If you still have your old device, check whether your authenticator app can move the entry for you. Some apps can transfer an existing entry to a new device, though whether yours can, and how, depends on the app. If it can't, or if you no longer have the old device, ask Chainguard to reset your enrollment. ### Ask for a reset Chainguard doesn't act on an MFA reset requested from the account's own email address. Anyone who controlled your mailbox could already request a password reset, so treating an emailed request as proof of identity would defeat the purpose of the second factor. Instead, ask an Owner in your Chainguard organization to open a support ticket on your behalf. [Resetting your MFA device for the Chainguard Console](https://support.chainguard.dev/hc/en-us/articles/52523310297115-Resetting-your-MFA-device-for-the-Chainguard-Console) in the Chainguard knowledge base describes the process. Once support clears your enrollment, sign in to the Console again. The sign-in flow prompts you to enroll and displays a new QR code, which you scan with your authenticator app to finish. You won't receive an automatic notification when the reset happens, so watch your support ticket for the update. ## One email address, two identities If your organization registered a custom identity provider after you had already created an email and password account, you might hold two separate Chainguard identities that share one email address. Chainguard identifies you by the provider that issued your login together with your ID at that provider, not by your email address, so the platform treats the two as distinct accounts. Each carries its own MFA, in a different place. When the Console asks for a code from an authenticator app and you expected to sign in through your organization's provider, you're on the email and password path. Enter your organization name in the login field instead of your email address to reach your provider. ## Get more help If you can't sign in at all and therefore can't reach the support portal, see [Get support](/get-started/get-support/) for the right channel. --- ### Get started with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/get-started/getting-started-with-chainctl.md Last Modified: October 8, 2026 Tags: chainctl, Getting Started Chainguard's `chainctl` provides command-line access to manage container images, identity resources, and security configurations across your organization. This guide covers essential commands to begin using `chainctl` effectively in your security and DevOps workflows. For comprehensive command documentation, refer to the [chainctl reference](/platform/chainctl/). ## Authenticate and check auth status To use `chainctl`, the first thing you must do is [authenticate with the Chainguard platform](/chainguard/containers/registry/authenticating/). Do so with: ```shell chainctl auth login ``` This will present a list of identity providers for you to select from. Use the one that is tied to your Chainguard Account. Once you select the one you wish to use, such as Google, `chainctl` will open a browser window for you to log in with your credentials. Upon successful login, you will have the option to save this identity provider as your default for future logins. Then a token will be exchanged and you will be able to use `chainctl`.

Note: chainctl auth login supports several login flows, including device-code login for machines without a browser and assumable identities for CI. If the default browser flow doesn't work in your environment, see Authentication options.

To check your authentication status at any time, enter: ```shell chainctl auth status ``` This will list your identity and other attributes tied to your account, including your account's assigned roles and capabilities. To create a pull token, use: ```shell chainctl auth pull-token ``` One important flag you can pass to this command is the `--repository` flag, which allows you to specify the repository type to create the pull token for. For example, specifying `--repository=apk` binds the pull-token identity to the `apk.pull` role, allowing the identity to download packages from the private APK repository of the parent organization. To configure a Docker credential helper, which will use a token to pull images when using Docker, use: ```shell chainctl auth configure-docker ``` > **Note**:

Zscaler, a popular decrypting proxy, may not support encrypted HTTP/2 traffic by default. If you are having connection issues using chainctl behind Zscaler, follow these steps to enable support for HTTP/2.

  1. Open a Support Ticket with Zscaler

    Before any UI configuration option appears you may need to contact Zscaler Support (TAC) and request that HTTP/2 support be provisioned/activated for your tenant. This is currently a backend setting only Support can toggle.

    You typically need to provide:

    • Your Company ID
    • Tenant details
    • Reason/use case for enabling HTTP/2 inspection

    This will allow HTTP/2 negotiation to be available in policies.

  2. Enable HTTP/2 in SSL Inspection Policies

    Once Support has enabled HTTP/2 on your tenant:

    1. Log in to the Zscaler Admin Portal.
    2. Navigate to Policy → SSL Inspection (or Administration → Advanced Settings depending on UI version).
    3. Edit your relevant SSL Inspection Policy.
    4. Look for the option "Enable HTTP/2" and turn it ON for the required rules/policies.

    This setting ensures Zscaler will maintain HTTP/2 where possible rather than downgrading to HTTP/1.1. You may need to enable it on multiple rules if you use granular SSL policies (for example, by URL category).

  3. Check Advanced settings for API/CLI traffic

    Zscaler has an advanced setting under Admin → Advanced that controls how non-browser (API or CLI) HTTP/2 traffic is handled. This setting is disabled by default, which means Zscaler will downgrade non-browser HTTP/2 traffic to HTTP/1.1. For chainctl usage, this must be set to Enabled.

See this Zscaler blog post for more details.

If the above approach is not available to you, you may need to reconfigure Zscaler to disable its "Block Undecryptable Traffic" setting. Another option is to configure direct (proxyless) access to HTTP/2 endpoints and use the no_proxy environment variable, depending on your IT policies. Please consult your proxy software's documentation for guidance on adjusting these settings in order to authenticate.

## Update chainctl to the latest release To see which `chainctl` version you have installed, use: ```shell chainctl version ``` To update your `chainctl` installation, use: ```shell chainctl update ``` Because this replaces the installed binary, you need write access to the directory where `chainctl` lives. On Linux and macOS, updating a system-wide install such as `/usr/local/bin` requires administrative privileges (for example, with `sudo`). The command also verifies the downloaded binary's signature in-process before installing it; refer to [Updating chainctl](/platform/chainctl-usage/how-to-install-chainctl/#updating-chainctl) for details. ## Configure chainctl `chainctl` comes with a default configuration, but there are aspects of it that can be adjusted. Examples include setting the registry location that will be used when one is not mentioned in an issued command. To edit the current configuration, use: ```shell chainctl config edit ``` If you make a mistake and can't recall the original settings, reset the configuration to default settings with: ```shell chainctl config reset ``` Learn more at [How to manage chainctl configuration](/platform/chainctl-usage/manage-chainctl-config/). ## List available images To see which container images are available to your account, use: ```shell chainctl images list ``` Be warned, that list may take a while to generate and is likely to scroll past quickly in your command line terminal. ## Compare two image versions Let's say you want to compare two versions of an image for the same package. You need to know the URL for your repo, the image name, and the two versions you want to compare. For versions, you can use release numbers like `8.12.0` or you can use `latest` or `latest-dev`. Use this, where we show the repo used by our Chainguard Developer Education team and where both instances of `` are the same: ```shell chainctl images diff cgr.dev/chainguard.edu/$IMAGENAME:latest cgr.dev/chainguard.edu/$IMAGENAME:latest-dev ``` If a requested image or release being requested is not available in the repo you are using, this will return a `Forbidden` error, just like if you tried to pull an image you did not have access to or from a repository your account is not authorized to use. Learn more at [How to compare Chainguard Containers with chainctl](/platform/chainctl-usage/comparing-images/). ## List available package versions If you want to get details about the various package versions available that can be used in images, use: ```shell chainctl packages versions list $PACKAGENAME ``` This will list all the versions that Chainguard has built and the end-of-life date for each version that has one assigned. It will also list older package versions that are no longer available. ## Output formats Commands may have a default format for output, but that doesn't mean you have to stick with it. There is an option available to tell `chainctl` the output format to use, like this: ```shell chainctl $COMMAND -o $FORMAT ``` The `-o` is followed by one of the following strings: `csv`, `id`, `json`, `none`, `table`, `terse`, `tree`, or `wide`. Not all output formats make sense for all commands, so test thoroughly before you use any specific format in automation. --- ## Open Source ### Enforce SBOM attestation with Policy Controller URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/enforce-sbom-attestation-with-policy-controller.md Last Modified: October 8, 2026 This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to verify image attestations before admitting an image into a Kubernetes cluster. In this guide, you will create a `ClusterImagePolicy` that checks the existence of a SBOM attestation attached to a container image, and then test the admission controller by running a `registry.enforce.dev/chainguard/node` image with SBOM attestations. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ``` kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ``` NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image k8s.gcr.io/pause:3.9 test ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will deny the admission request with a message like the following: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image k8s.gcr.io/pause@sha256:7031c1b283388d2c2e09b57badb803c05ebed362dc88d84b480cc47f72a21097 ``` In the next step, you will define a policy that verifies Chainguard's container images have a SBOM attestation and apply it to your cluster. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ```yaml # Copyright 2022 Chainguard, Inc. # SPDX-License-Identifier: Apache-2.0 apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: must-have-spdx-cue annotations: catalog.chainguard.dev/title: Enforce SBOM attestation catalog.chainguard.dev/description: Enforce a signed SPDX SBOM attestation from a custom key catalog.chainguard.dev/labels: attestation,cue spec: images: - glob: "**" authorities: - name: my-authority keyless: identities: - issuer: "https://token.actions.githubusercontent.com" subject: "https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main" attestations: - name: must-have-spdx-attestation predicateType: https://spdx.dev/Document policy: type: cue data: | predicateType: "https://spdx.dev/Document" ``` The `glob: **` line, working in combination with the `authorities` and `policy` sections, will allow any image that has at least a SBOM attestation with predicate type `https://spdx.dev/Document` to be admitted into your cluster. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/must-have-spdx-cue created ``` Now run the `k8s.gcr.io/pause:3.9` image which does not have a SBOM attestation: ```bash kubectl run --image k8s.gcr.io/pause:3.9 noattestedimage ``` Since the image does not contain any attached SBOM, you will receive a message that the pod was rejected: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: demo: spec.containers[0].image k8s.gcr.io/pause:3.9 no matching attestations with type https://spdx.dev/Document ``` Finally, we run `registry.enforce.dev/chainguard/node` image which contains a SBOM attestation of type `https://spdx.dev/Document`: ```bash kubectl run --image registry.enforce.dev/chainguard/node mysbomattestedimage ``` Since the image has now a SBOM attestation, you will receive a message that the pod was created successfully: ``` pod/mysbomattestedimage created ``` Delete the pod once you're done experimenting with it: ``` kubectl delete pod mysbomattestedimage ``` To learn more about how the Policy Controller uses Cosign to verify and admit images, review the [Cosign](https://docs.sigstore.dev/cosign/signing/overview/) Sigstore documentation. --- ### Disallowing non-default capabilities URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/disallowing-non-default-capabilities-with-policy-controller.md Last Modified: September 28, 2026 Tags: policy-controller, Procedural, Policy This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to prevent running containers with extra capabilities. You will create a `ClusterImagePolicy` that uses the [CUE](https://cuelang.org/) language to examine a pod spec, and only allow admission into a cluster if the pod is running with one or many [Linux capabilities](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container) from defined set of safe capabilities flags. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ```shell kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ```output NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will deny the admission request with a message like the following: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image cgr.dev/chainguard/nginx@sha256:628a01724b84d7db2dc3866f645708c25fab8cce30b98d3e5b76696291d65c4a ``` In the next step, you will define a policy that ensures pods only run with safe capabilities and apply it to your cluster. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: non-default-capabilities-cue spec: match: - version: "v1" resource: "pods" images: [glob: '**'] authorities: [static: {action: pass}] mode: enforce policy: includeSpec: true type: "cue" data: | #Allowed: "AUDIT_WRITE" | "CHOWN" | "DAC_OVERRIDE" | "FOWNER" | "FSETID" | "KILL" | "MKNOD" | "NET_BIND_SERVICE" | "SETFCAP" | "SETGID" | "SETPCAP" | "SETUID" | "SYS_CHROOT" spec: { initContainers: [...{ securityContext: { capabilities: { add: [...#Allowed] } } }] containers: [...{ securityContext: { capabilities: { add: [...#Allowed] } } }] ephemeralContainers: [...{ securityContext: { capabilities: { add: [...#Allowed] } } }] } ``` The Policy Controller will check each type of container's definition (`initContainers`, `containers`, and `ephemeralContainers`) in a pod spec for any added capabilities. The controller will only admit a pod if the added capabilities are in the `#Allowed` set. The set of allowed capabilities is defined in this portion of the CUE policy and can be added to or changed to match your specific workload's needs: ``` #Allowed: "AUDIT_WRITE" | "CHOWN" | . . . ``` Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/non-default-capabilities-cue ``` Next you will test the policy with a failing pod spec. Once you have confirmed that the admission controller is rejecting pods running with privileges, you'll create a pod that runs without unnecessary capabilities and admit it into your cluster. ## Step 3 — Testing a `ClusterImagePolicy` Now that you have a policy defined, you can test that it successfully rejects or accepts admission requests. Use `nano` or your preferred editor to create a new file `/tmp/pod.yaml` and copy in the following pod spec that runs with elevated privileges: ``` apiVersion: v1 kind: Pod metadata: name: yolo spec: containers: - name: "app" image: docker.io/ubuntu securityContext: capabilities: add: # Violates restricted-capabilities - NET_ADMIN drop: - ALL ``` Apply the pod spec and check for the Policy Controller admission denied message: ``` kubectl apply -f /tmp/pod.yaml ``` ``` Error from server (BadRequest): error when creating "pod.yaml": admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: non-default-capabilities-cue: spec.containers[0].image index.docker.io/library/ubuntu@sha256:2adf22367284330af9f832ffefb717c78239f6251d9d0f58de50b86229ed1427 failed evaluating cue policy for ClusterImagePolicy: failed to evaluate the policy with error: spec.containers.0.securityContext.capabilities.add.0: 12 errors in empty disjunction: (and 12 more errors) ``` The first line shows the error message and the failing `ClusterImagePolicy` name. The second line contains the image ID, along with the specific CUE error message showing the policy violation. Edit the `/tmp/pod.yaml` file and remove or edit the `add` portion of the `capabilities` section. If there are no extra capabilities then the section should look like the following: ``` capabilities: drop: - ALL ``` Save and apply the spec: ``` kubectl apply -f /tmp/pod.yaml ``` The pod will be admitted into the cluster with the following message: ``` pod/yolo created ``` Since the pod spec now ensures the container does not have and extra capabilities, or only those from the `#Allowed` set, the Policy Controller evaluates the pod spec against the CUE policy and admits the pod into the cluster. Delete the pod once you're done experimenting with it: ``` kubectl delete pod yolo ``` --- ### Disallowing privileged pods URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/disallowing-privileged-containers-with-policy-controller.md Last Modified: September 28, 2026 Tags: policy-controller, Procedural, Policy This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to prevent running containers with elevated privileges. You will create a `ClusterImagePolicy` that uses the [CUE](https://cuelang.org/) language to examine a pod spec, and only allow admission into a cluster if the pod is running without the `privileged: true` setting. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ```shell kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ```output NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will deny the admission request with a message like the following: ```output Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image cgr.dev/chainguard/nginx@sha256:628a01724b84d7db2dc3866f645708c25fab8cce30b98d3e5b76696291d65c4a ``` In the next step, you will define a policy that only admits unprivileged pods and apply it to your cluster. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: privileged-containers-cue spec: match: - version: "v1" resource: "pods" images: [glob: '**'] authorities: [static: {action: pass}] mode: enforce policy: includeSpec: true type: "cue" data: | spec: { initContainers: [...{ securityContext: { privileged: false } }] containers: [...{ securityContext: { privileged: false } }] ephemeralContainers: [...{ securityContext: { privileged: false } }] } ``` This policy will ensure that any kind of container in a pod spec will only be admitted if the `privileged` setting is not set, or is set to `false`. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/privileged-containers-cue ``` Next you will test the policy with a failing pod spec. Once you have confirmed that the admission controller is rejecting pods running with privileges, you'll create a pod that runs without elevated privileges and admit it into your cluster. ## Step 3 — Testing a `ClusterImagePolicy` Now that you have a policy defined, you can test that it successfully rejects or accepts admission requests. Use `nano` or your preferred editor to create a new file `/tmp/pod.yaml` and copy in the following pod spec that runs with elevated privileges: ``` apiVersion: v1 kind: Pod metadata: name: yolo spec: containers: - name: "app" image: docker.io/ubuntu securityContext: privileged: true ``` Apply the pod spec and check for the Policy Controller admission denied message: ``` kubectl apply -f /tmp/pod.yaml ``` ``` Error from server (BadRequest): error when creating "pod.yaml": admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: privileged-containers-cue: spec.containers[0].image index.docker.io/library/ubuntu@sha256:2adf22367284330af9f832ffefb717c78239f6251d9d0f58de50b86229ed1427 failed evaluating cue policy for ClusterImagePolicy: failed to evaluate the policy with error: spec.containers.0.securityContext.privileged: conflicting values false and true ``` The first line shows the error message and the failing `ClusterImagePolicy` name. The second line contains the image ID, along with the specific CUE error message showing the policy violation. Edit the `/tmp/pod.yaml` file and change the `privileged` setting to `false`: ``` privileged: false ``` Save and apply the spec: ``` kubectl apply -f /tmp/pod.yaml ``` The pod will be admitted into the cluster with the following message: ``` pod/yolo created ``` Since the pod spec now ensures the container does not have elevated privileges, the Policy Controller evaluates the pod spec against the CUE policy and admits the pod into the cluster. Delete the pod once you're done experimenting with it: ``` kubectl delete pod yolo ``` --- ### Disallowing run as root user URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/disallowing-run-as-root-user-with-policy-controller.md Last Modified: September 28, 2026 Tags: policy-controller, Procedural, Policy This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to prevent running containers as the `root` user in a Kubernetes cluster. You will create a `ClusterImagePolicy` that uses the [CUE](https://cuelang.org/) language to examine a pod spec, and only allow admission into a cluster if the pod is running as a non-root user. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ``` kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ``` NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will deny the admission request with a message like the following: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image cgr.dev/chainguard/nginx@sha256:628a01724b84d7db2dc3866f645708c25fab8cce30b98d3e5b76696291d65c4a ``` In the next step, you will define a policy that ensures pods do not run as the root user and apply it to your cluster. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: disallow-runasuser-root-cue spec: match: - version: "v1" resource: "pods" images: [glob: '**'] authorities: [static: {action: pass}] mode: enforce policy: includeSpec: true type: "cue" data: | spec: { initContainers: [...{ securityContext: { runAsUser: != 0 } }] containers: [...{ securityContext: { runAsUser: != 0 } }] ephemeralContainers: [...{ securityContext: { runAsUser: != 0 } }] } ``` This policy will ensure that any kind of container in a pod spec will only be admitted if the user is not root. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/disallow-runasuser-root-cue ``` Next you will test the policy with a failing pod spec. Once you have confirmed that the admission controller is rejecting pods running as root, you'll create a pod that runs as a non-root user and admit it into your cluster. ## Step 3 — Testing a `ClusterImagePolicy` Now that you have a policy defined, you can test that it successfully rejects or accepts admission requests. Use `nano` or your preferred editor to create a new file `/tmp/pod.yaml` and copy in the following pod spec that runs as root: ``` apiVersion: v1 kind: Pod metadata: name: yolo spec: containers: - name: "app" image: docker.io/ubuntu securityContext: # Violates restricted-capabilities runAsUser: 0 ``` Apply the pod spec and check for the Policy Controller admission denied message: ``` kubectl apply -f /tmp/pod.yaml ``` ``` Error from server (BadRequest): error when creating "/tmp/pod.yaml": admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: disallow-runasuser-root-cue: spec.containers[0].image index.docker.io/library/ubuntu@sha256:2adf22367284330af9f832ffefb717c78239f6251d9d0f58de50b86229ed1427 failed evaluating cue policy for ClusterImagePolicy: failed to evaluate the policy with error: spec.containers.0.securityContext.runAsUser: invalid value 0 (out of bound !=0) ``` The first line shows the error message and the failing `ClusterImagePolicy` name. The second line contains the image ID, along with the specific CUE error message showing the policy violation. Edit the `/tmp/pod.yaml` file and change the `runAsUser` setting to use a non-root user: ``` - runAsUser: 65532 ``` Save and apply the spec: ``` kubectl apply -f /tmp/pod.yaml ``` The pod will be admitted into the cluster with the following message: ``` pod/yolo created ``` Since the pod spec now uses a non-root user to run its processes, the Policy Controller evaluates the pod spec against the CUE policy and admits the pod into the cluster. Delete the pod once you're done experimenting with it: ``` kubectl delete pod yolo ``` --- ### Maximum container image age URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/maximum-image-age-policy-controller.md Last Modified: September 28, 2026 Tags: policy-controller, Procedural, Policy This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to verify image signatures before admitting an image into a Kubernetes cluster. In this guide, you will create a `ClusterImagePolicy` that checks the maximum age of a container image verifying that isn’t older than 30 days. For that, we’ll attempt to create two distroless images one older than 30 days and a fresh one. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ``` kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ``` NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image ghcr.io/distroless/static myoldimage ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will allow the admission request. In the next step, you will define a policy that verifies Chainguard Containers has an age below 30days and apply it to your cluster. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ```yaml # Copyright 2022 Chainguard, Inc. # SPDX-License-Identifier: Apache-2.0 apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: maximum-image-age-rego annotations: catalog.chainguard.dev/title: Maximum image age catalog.chainguard.dev/description: | This checks that the maximum age an image is allowed to have is 30 days old. This is measured using the container image's configuration, which has a "created" field. Some build tools may fail this check because they build reproducibly, and use a fixed date (e.g. the Unix epoch) as their creation time, but many of these tools support specifying SOURCE_DATE_EPOCH, which aligns the creation time with the date of the source commit. catalog.chainguard.dev/labels: rego spec: images: [{ glob: "**" }] authorities: [{ static: { action: pass } }] mode: enforce policy: fetchConfigFile: true type: "rego" data: | package sigstore nanosecs_per_second = 1000 * 1000 * 1000 nanosecs_per_day = 24 * 60 * 60 * nanosecs_per_second # Change this to the maximum number of days to allow. maximum_age = 30 * nanosecs_per_day isCompliant[response] { created := time.parse_rfc3339_ns(input.config[_].created) response := { "result" : time.now_ns() < created + maximum_age, "error" : "Image exceeds maximum allowed age." } } ``` The `glob: **` line, working in combination with the `authorities` and `policy` sections, will allow any image that has been built in the last 30 days to be admitted into your cluster. The `fetchConfigFile` options instruct the Policy Controller to check the image configuration looking for the age of the image. The rest of fields are: * `authorities`: this setting tells the Policy Controller to skip any verification looking for the presence of an image signature. * `mode`: this blocks the creation of any image older than 30days. * `policy.data`: contains the rego policy itself that verifies when the image has been created. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/maximum-image-age-rego created ``` Now run the `cgr.dev/chainguard/static` image again: ```bash kubectl run --image cgr.dev/chainguard/static mydailyfreshimage ``` Since the image is built on daily basis, you will receive a message that the pod was created successfully: ``` pod/mydailyfreshimage created ``` However, if we now create a pod using our old image `myoldimage`, PolicyController rejects the admission request with a message like the following: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: ghcr.io/distroless/static@sha256:a9650a15060275287ebf4530b34020b8d998bd2de9aea00d113c332d8c41eb0b failed evaluating rego policy for type ClusterImagePolicy: policy is not compliant for query 'isCompliant = data.sigstore.isCompliant' with errors: Image exceeds maximum allowed age. ``` Delete the pod once you're done experimenting with it: ``` kubectl delete pod mydailyfreshimage ``` To learn more about how the Policy Controller uses Cosign to verify and admit images, review the [Cosign](https://docs.sigstore.dev/cosign/signing/overview/) Sigstore documentation. --- ### Disallowing unsafe sysctls URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/disallowing-unsafe-sysctls-with-policy-controller.md Last Modified: September 28, 2026 Tags: policy-controller, Procedural, Policy This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to only allow pods that use `sysctls` to modify kernel behaviour to run with the [safe set](https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/#safe-and-unsafe-sysctls) of parameters. You will create a `ClusterImagePolicy` that uses the [CUE](https://cuelang.org/) language to examine a pod spec that uses sysctls, and only allow admission into a cluster if the pod is running a safe set parameters. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ``` kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ``` NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image docker.io/ubuntu ubuntu ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will deny the admission request with a message like the following: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image index.docker.io/library/ubuntu@sha256:854037bf6521e9c321c101c269272f756e481fb5f167ae032cb53da08aebcd5a ``` In the next step, you will define a `ClusterImagePolicy` that verifies a pod spec is using safe sysctl parameters. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: unsafe-sysctls-mask-cue spec: match: - version: "v1" resource: "pods" images: [glob: '**'] authorities: [static: {action: pass}] mode: enforce policy: includeSpec: true type: "cue" data: | spec: { securityContext: sysctls: [...{ name: "kernel.shm_rmid_forced" | "net.ipv4.ip_local_port_range" | "net.ipv4.ip_unprivileged_port_start" | "net.ipv4.tcp_syncookies" | "net.ipv4.ping_group_range" }] } ``` This policy will ensure that any pod that has a sysctl defined in its spec will only be admitted if it matches a parameter from the list. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/unsafe-sysctls-mask-cue ``` Next, you will test the policy with a failing pod spec. Once you have confirmed that the admission controller is rejecting pods using unsafe sysctls, you'll create a pod with a safe parameter and admit it into your cluster. ## Step 3 — Testing the `ClusterImagePolicy` Now that you have a policy defined, you can test that it successfully rejects or accepts admission requests. Use `nano` or your preferred editor to create a new file `/tmp/pod.yaml` and copy in the following pod spec that uses an unsafe sysctl: ``` apiVersion: v1 kind: Pod metadata: name: yolo spec: securityContext: sysctls: - name: kernel.msgmax value: "65536" containers: - name: "app" image: docker.io/ubuntu ``` Apply the pod spec and check for the Policy Controller admission denied message: ``` kubectl apply -f /tmp/pod.yaml ``` ``` Error from server (BadRequest): error when creating "/tmp/pod.yaml": admission webhook "policy.sigstore.dev" denied the request: validation failed: failed policy: unsafe-sysctls-mask-cue: spec.containers[0].image index.docker.io/library/ubuntu@sha256:854037bf6521e9c321c101c269272f756e481fb5f167ae032cb53da08aebcd5a failed evaluating cue policy for ClusterImagePolicy: failed to evaluate the policy with error: spec.securityContext.sysctls.0.name: 5 errors in empty disjunction: (and 5 more errors) ``` The first line shows the error message and the failing `ClusterImagePolicy` name. The second line contains the image ID, along with the specific CUE error message showing the policy violation. Edit the `/tmp/pod.yaml` file and change the `sysctls` section to use the following safe parameter: ```yaml sysctls: - name: net.ipv4.tcp_syncookies value: "1" - name: net.ipv4.tcp_syncookies value: "1" ``` ``` Save and apply the spec: ``` kubectl apply -f /tmp/pod.yaml ``` The pod will be admitted into the cluster with the following message: ``` pod/yolo created ``` Since the `net.ipv4.tcp_syncookies` sysctl is considered safe and only runs in specific Kubernetes namespaces, the Policy Controller evaluates the pod spec against the CUE policy and admits the pod into the cluster. Delete the pod once you're done experimenting with it: ``` kubectl delete pod yolo ``` --- ### Verify signed Chainguard Containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/using-policy-controller-to-verify-signed-chainguard-images.md Last Modified: October 8, 2026 Tags: policy-controller, Procedural, Policy, Chainguard Containers This guide demonstrates how to use the [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) to verify image signatures before admitting an image into a Kubernetes cluster. In this guide, you will create a `ClusterImagePolicy` that checks for a keyless Cosign image signature, and then test the admission controller by running a signed `nginx` image. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) installed in your cluster. Follow our [How to install Sigstore Policy Controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) guide if you do not have it installed, and be sure to label any namespace that you intend to use with the `policy.sigstore.dev/include=true` label. Once you have everything in place you can continue to the first step and confirm that the Policy Controller is working as expected. ## Step 1 - Checking the Policy Controller is denying admission Before creating a `ClusterImagePolicy`, check that the Policy Controller is deployed and that your `default` namespace is labeled correctly. Run the following to check that the deployment is complete: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` When both deployments are finished, verify the `default` namespace is using the Policy Controller: ``` kubectl get ns -l policy.sigstore.dev/include=true ``` You should receive output like the following: ``` NAME STATUS AGE default Active 24s ``` Once you are sure that the Policy Controller is deployed and your `default` namespace is configured to use it, run a pod to make sure admission requests are handled and denied by default: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` Since there is no `ClusterImagePolicy` defined yet, the Policy Controller will deny the admission request with a message like the following: ``` Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image cgr.dev/chainguard/nginx@sha256:628a01724b84d7db2dc3866f645708c25fab8cce30b98d3e5b76696291d65c4a ``` In the next step, you will define a policy that verifies Chainguard's container images are signed and apply it to your cluster. ## Step 2 — Creating a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ```yaml apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: chainguard-images-are-signed annotations: catalog.chainguard.dev/title: Chainguard Containers catalog.chainguard.dev/description: Enforce Chainguard Containers are signed catalog.chainguard.dev/labels: chainguard spec: images: - glob: cgr.dev/chainguard/** authorities: - keyless: url: https://fulcio.sigstore.dev identities: - issuer: https://token.actions.githubusercontent.com subject: https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main ctlog: url: https://rekor.sigstore.dev name: authority-0 ``` The `glob: cgr.dev/chainguard/**` line, working in combination with the `authorities` section, will allow any image in the `cgr.dev/chainguard` image registry that has a [keyless signature](https://docs.sigstore.dev/cosign/signing/overview/) to be admitted into your cluster. The `- keyless` options instruct the Policy Controller what to check for when it examines the signature on any image from the `cgr.dev/chainguard` registry. The specific fields are: * `url`: this setting tells the Policy Controller where to find the Certificate Authority (CA) that issued an image signature. * `issuer`: the [issuer field](https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726411--issuer) contains the URI of the OpenID Connect (OIDC) Identity Provider that digitally signed the identity token. * `subject`: the [subject field](https://github.com/sigstore/fulcio/blob/main/docs/certificate-specification.md#issued-certificate) must contain a URI or an email address that identifies where the signed image originated. * `ctlog`: this setting tells the Policy Controller which [Certificate Transparency log](https://www.chainguard.dev/supply-chain-security-101/what-is-sigstore-rekor) to query when it is validating a signature. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ``` clusterimagepolicy.policy.sigstore.dev/chainguard-images-are-signed created ``` Now run the `cgr.dev/chainguard/nginx:latest` image again: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` Since the image matches the policy, you will receive a message that the pod was created successfully: ``` pod/nginx created ``` In the background, the Policy Controller queries the specified `ctlog` from the policy that you created to find a record in the log that matches the image being requested (`cgr.dev/chainguard/nginx:latest`). The Policy Controller ensures that the SHA256 hash of the image matches the hash that is recorded in the certificate issued by the OIDC `issuer` when the image was first signed. Finally, the Policy Controller verifies the issued certificate was signed by the specified Certiciate Authority's (`https://fulcio.sigstore.dev`) root signing certificate. Once the Policy Controller verifies the signature of the image's hash in the transparency log matches the computed hash of the image, and the certificate's validity based on the CA chain of trust, it will admit the pod into the cluster. Delete the pod once you're done experimenting with it: ``` kubectl delete pod nginx ``` To learn more about how the Policy Controller uses Cosign to verify and admit images, review the [Cosign](https://docs.sigstore.dev/cosign/signing/overview/) Sigstore documentation. --- ### How to install Sigstore Policy Controller URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/how-to-install-policy-controller.md Last Modified: October 16, 2026 Tags: policy-controller, Procedural The [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) is a Kubernetes [admission controller](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/) that can verify image signatures and policies. You can define policies using the [CUE](https://cuelang.org/) or [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/) policy languages. This guide will demonstrate how to install the Policy Controller in your Kubernetes cluster and enable policy enforcement. ## Prerequisites To follow along with this guide, you will need the following: * A Kubernetes cluster with administrative access. You can set up a local cluster using [**kind**](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or use an existing cluster. * **kubectl** — to work with your cluster. Install `kubectl` for your operating system by following the official [Kubernetes kubectl documentation](https://kubernetes.io/docs/tasks/tools/#kubectl). * The [Helm](https://helm.sh) command line tool to install the Policy Controller. Once you have everything in place you can continue to the next step and install the Policy Controller. ## Step 1 — Creating the `cosign-system` Kubernetes namespace The first step that you need to complete is to create a Kubernetes `namespace` for the Policy Controller to run in. Call it `cosign-system` and run the following command to create it: ```bash kubectl create namespace cosign-system ``` Now you can move on to the next step, which is installing the Policy Controller into the namespace you just created. ## Step 2 — Installing the Policy Controller In this step we'll use the `helm` command line tool to install the Policy Controller. First, add the [Sigstore Helm Repository](https://sigstore.github.io/helm-charts) to your system with the following command: ```bash helm repo add sigstore https://sigstore.github.io/helm-charts ``` You should receive output like this: ```output "sigstore" has been added to your repositories ``` Next, update your local Helm repository information using the `helm repo update` command: ```bash helm repo update ``` You'll receive output like the following: ```output Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "sigstore" chart repository Update Complete. ⎈Happy Helming!⎈ ``` Now install the Policy Controller into the `cosign-system` namespace that you created in the first step of this guide: ```bash helm install policy-controller -n cosign-system sigstore/policy-controller --devel ``` The `--devel` flag will include any alpha, beta, or release candidate versions of a chart. You can specify a particular version with the `--version` flag if you prefer. It may take a few minutes for your cluster to deploy all of the manifests needed for the Policy Controller. Check the status of your cluster using the `kubectl wait` command like this: ```bash kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-webhook && \ kubectl -n cosign-system wait --for=condition=Available deployment/policy-controller-policy-webhook ``` Once the Policy Controller deployments are done you will receive output like the following: ```output deployment.apps/policy-controller-webhook condition met deployment.apps/policy-controller-policy-webhook condition met ``` A full list of the resources that the Policy Controller deploys into your cluster is available at the end of this guide in [Appendix — Resource types](#appendix--resource-types). You have now deployed the Policy Controller into your cluster. The next step is to enable it for the namespaces that you want to enforce policies in. ## Step 3 — Enabling the Policy Controller Now that you have the Policy Controller installed into your cluster, the next step is to decide which namespaces should use it. By default, namespaces must enroll into enforcement, so you will need to label any namespace that you will use with the Policy Controller. Run the following command to include the `default` namespace in image validation and policy enforcement: ```bash kubectl label namespace default policy.sigstore.dev/include=true ``` Apply the same label to any other namespace that you want to use with the Policy Controller. Now you can test enforcement by running a sample pod: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` The Policy Controller will deny the admission request with a message like the following: ```output Error from server (BadRequest): admission webhook "policy.sigstore.dev" denied the request: validation failed: no matching policies: spec.containers[0].image cgr.dev/chainguard/nginx@sha256:628a01724b84d7db2dc3866f645708c25fab8cce30b98d3e5b76696291d65c4a ``` The image is not admitted into the cluster because there are no `ClusterImagePolicy` (CIP) definitions that match it. In the next step you will define a policy that allows specific images and apply it to your cluster. ## Step 4 — Defining a `ClusterImagePolicy` Now that you have the Policy Controller running in your cluster, and have the `default` namespace configured to use it, you can now define a `ClusterImagePolicy` to admit images. The following policy will allow any [Chainguard Image](https://www.chainguard.dev/chainguard-images) hosted on the `cgr.dev/chainguard` registry to run on a cluster, while denying any other images. Open a new file with `nano` or your preferred editor: ```shell nano /tmp/cip.yaml ``` Copy the following policy to the `/tmp/cip.yaml` file: ```yaml apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: chainguard-image-policy spec: images: - glob: "cgr.dev/chainguard/**" authorities: - static: action: pass ``` The `glob: "cgr.dev/chainguard/**"` line in combination with the `action: pass` portion of the `authorities` section will allow any image in the `cgr.dev/chainguard` image registry to be admitted into your cluster. Save the file and then apply the policy: ```bash kubectl apply -f /tmp/cip.yaml ``` You will receive output showing the policy is created: ```output clusterimagepolicy.policy.sigstore.dev/chainguard-image-policy created ``` Now run the `cgr.dev/chainguard/nginx:latest` image again: ```bash kubectl run --image cgr.dev/chainguard/nginx:latest nginx ``` Since the image matches the policy, you will receive a message that the pod was created successfully: ```output pod/nginx created ``` Delete the pod once you're done experimenting with it: ```shell kubectl delete pod nginx ``` To learn more about how the Policy Controller admits images, review the [Admission of images page](https://docs.sigstore.dev/policy-controller/overview/#admission-of-images) Sigstore documentation. ## Appendix — Resource types A complete Policy Controller installation consists of the following resources in a cluster: | Type | Name | | ---------- | ----- | | ClusterRole | `policy-controller-policy-webhook` | | | `policy-controller-webhook` | | ClusterRoleBinding | `policy-controller-policy-webhook` | | | `policy-controller-webhook` | | ConfigMap | `config-image-policies` | | | `config-policy-controller` | | | `policy-controller-policy-webhook-logging` | | | `policy-controller-webhook-logging` | | CustomResourceDefinition | `clusterimagepolicies.policy.sigstore.dev` | | Deployment | `policy-controller-policy-webhook` | | | `policy-controller-webhook` | | MutatingWebhookConfiguration | `defaulting.clusterimagepolicy.sigstore.dev` | | | `policy.sigstore.dev` | | Role | `policy-controller-policy-webhook` | | | `policy-controller-webhook` | | RoleBinding | `policy-controller-policy-webhook` | | | `policy-controller-webhook` | | Secret | `policy-webhook-certs` | | | `webhook-certs` | | Service | `policy-webhook` | | | `policy-controller-policy-webhook-metrics` | | | `policy-controller-webhook-metrics` | | | `webhook` | | ServiceAccount | `policy-controller-policy-webhook` | | | `policy-controller-webhook` | | ValidatingWebhookConfiguration | `validating.clusterimagepolicy.sigstore.dev` | | | `policy.sigstore.dev` | --- ### How to keyless sign a container image with Sigstore URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/how-to-keyless-sign-a-container-with-sigstore.md Last Modified: September 28, 2026 Tags: Cosign, Sigstore, Procedural _An earlier version of this material was published in the [lab in chapter 5](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@5fb8482a3b764ce0903e2c424842d32f/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2e3388391d6040f5beb5bacbf19f96f5) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ This tutorial will bring some of the components of Sigstore together in an example project. In this demonstration, we’ll be using GitHub Actions to perform keyless signing on a sample container. In this example, we’ll use a Django container that displays a generic “Hello, World” style landing page. Django is a Python web framework. ## Prerequisites You should have the following in place before continuing: * The latest version of [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) installed, and an account on Docker Hub. At the time of writing (June 2022), Docker Engine should be version 20.10 and Docker Compose should be version 2.6. If you are on macOS, you will need to use [Docker Desktop; refer to the official documentation](https://docs.docker.com/desktop/) for your operating system to ensure that your system meets the necessary requirements. Docker Desktop should be version 4.8 or higher. * Cosign installed, follow [How to install Cosign](/open-source/sigstore/cosign/how-to-install-cosign/). * The Rekor CLI installed, follow the [installation guide](/open-source/sigstore/rekor/how-to-install-rekor/) * Familiarity with Git, GitHub, and GitHub Actions is helpful, but we'll provide some context and also walk you through setting up a GitHub account. With these prerequisites in place, let's begin. ## Sign up for GitHub To create a GitHub account, navigate to https://github.com/join and fill in a valid username, email address, and password. For a username, you may want to think about whether you want a name that represents your name, or a more anonymous one. You may want to click off the email marketing box. You should also verify your account. GitHub provides additional documentation on [signing up for an account](https://docs.github.com/en/get-started/signing-up-for-github/signing-up-for-a-new-github-account). You’ll be using a [free personal account](https://github.com/pricing) to work with GitHub. If you are not familiar with Git and GitHub, you can review the official GitHub official docs on [About Git](https://docs.github.com/en/get-started/using-git/about-git). We will walk you through the relevant commands in this section. [GitHub Actions](https://docs.github.com/en/actions) can perform CI/CD on your repository. You can learn more about GitHub Actions through the official GitHub docs. We will walk you through the relevant files here. ## Create a GitHub repository When you are logged into GitHub, create a new repository by clicking on the **+** button in the upper right-hand corner of the page (next to your user icon). The menu will drop down and you can select **New repository**. On the Create a new repository page, you can create a repository, you can leave the defaults, but write a meaningful name for the Repository name field, such as django-keyless-signing. Note that you’ll need to keep the repository public so that the signed image you build will be able to be uploaded to Rekor’s public transparency log. ## Create a local directory for the repository Now, you’ll need to create a local directory for this repository. For our example, we’ll want our path to be `~/Documents/GitHub/django-keyless-signing`, but you can choose an alternate path. Create the GitHub directory if necessary, and then navigate into that folder. ```sh cd ~/Documents/GitHub ``` Within the GitHub folder, create the new directory for your repository, and move into it. ```sh mkdir django-keyless-signing && cd $_ ``` You’ll be making a few files in this directory that you’ll then push up to the GitHub repository. ## Create Django container files First, create a `requirements.txt` file for your Django container. This is a common file in Python projects that you can run to get the necessary dependencies at the right versions. The Django Docker container will pull from this file to set up the image. You need the Django package, and Psycopg, which is a PostgreSQL database adapter for Python. Create your file with a text editor like nano. ```sh nano requirements.txt ``` Once the file is open, write the following into it to set and pin your dependencies. ``` Django>=3.0,<4.0 psycopg2>=2.8 ``` Save and close the file. Next, create your `Dockerfile`, again with a text editor like nano. ```sh nano Dockerfile ``` Within this file you will set up the version of Python, the environments, and tell the container to install the dependencies in `requirements.txt`. ```js # syntax=docker/dockerfile:1 FROM python:3 ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 WORKDIR /code COPY requirements.txt /code/ RUN pip install -r requirements.txt COPY . /code/ ``` Once you are satisfied that your `Dockerfile` reflects the content above, you can save and close the file. Finally, you’ll create a `docker-compose.yml` file. This file allows you to document and configure all of your application’s service dependencies. If you would like to read more about [Docker Compose](https://docs.docker.com/compose/), please refer to the official Docker documentation. Again, use nano or similar text editor to create your file. ```sh nano docker-compose.yml ``` You can add the following contents to this file. This sets up the environment and Postgres database, and can build the web server on port 8000 of the present machine. ```js version: "3.9" services: db: image: postgres volumes: - ./data/db:/var/lib/postgresql/data environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres web: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - "8000:8000" environment: - POSTGRES_NAME=postgres - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres depends_on: - db ``` At this point, your Django container is set up. You can run the `tree` command to review the file structure. Note, `tree` may not come automatically installed on your machine; use your package manager to install it if you would like to run this optional command. ```sh tree ``` ``` . ├── Dockerfile ├── docker-compose.yml └── requirements.txt 0 directories, 4 files ``` If your output matches the output above, you are all set to continue. ## Steps to automate keyless signing We next create a GitHub Actions YAML file. There is some boilerplate in this file common to GitHub Actions, but the high-level overview of this is that we need to enable OIDC, install Cosign, build and push the container image, and then sign the container image. We’ll discuss each of these steps here, and then write the entire file in the next section. After a cron job to automate the Actions, your first step will be to enable GitHub Actions OIDC tokens. Fulcio is a free root certificate authority that issues certificates based on an OIDC email address. This is essentially enabling the certificate step of our action. The key piece here is `id-token: write`, which you will have under `build` and under `jobs` in your Actions workflow. ```js jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write ``` The rest of this build is telling us that the container is running on the latest version of Ubuntu, that the contents are to be read, and the packages are to be written. The `id-token: write` line enables our job to create tokens as this workflow. This permission may only be granted to workflows on the main repository, so it cannot be granted during pull request workflows. You can learn more about GitHub Actions’s OIDC support from their document on “[Security hardening your deployments](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#updating-your-actions-for-oidc).” The next major part of this YAML file is installing Cosign. ```js - name: Install cosign uses: sigstore/cosign-installer@main ``` Cosign is available through the [GitHub Action Marketplace](https://github.com/marketplace/actions/cosign-installer), which is why we can add it to our GitHub Action as above. You can pin your workflow to a particular release of Cosign. For example, here you will use version 2.1.1. ```js - name: Install cosign uses: sigstore/cosign-installer@main with: cosign-release: 'v2.1.1' ``` After this step, there will be some actions to setup the Docker build, log into the GitHub Container Registry, and build and push the container image. The next piece that is most relevant to our work with Sigstore is signing the container image. ```js - name: Sign the container image run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.push-step.outputs.digest }} ``` Here you’ll run the `cosign sign` command on the container we are pushing to GitHub Container Registry with the relevant variable calling our repository and digest. Because we are doing a public repository, this will automatically be pushed to the public instance of the Rekor transparency log Now that you understand the main pieces of the YAML file, let’s create it and review the contents of the entire file. ## Create GitHub Actions file You’ll next create a hidden directory called `.github` and a subdirectory called workflows. Ensure that you are in your `django-keyless-signing` and create these two directories. ```sh mkdir .github && cd $_ mkdir workflows && cd $_ ``` Within this directory, you’ll be creating a YAML file to run a GitHub Action Workflow. ```sh nano docker-publish.yml ``` This is how we will be building, publishing, and signing the container. We will start by naming it **Publish and Sign Container Image** and then will set up a scheduled cron job for continuous running, and also when there is a push to the main branch or pull request that is merged into the main branch. The rest of the file will follow what we discussed in the previous section. ```js name: Publish and Sign Container Image on: schedule: - cron: '32 11 * * *' push: branches: [ main ] # Publish semver tags as releases. tags: [ 'v*.*.*' ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write steps: - name: Checkout repository uses: actions/checkout@v2 - name: Install cosign uses: sigstore/cosign-installer@main with: cosign-release: 'v1.4.1' - name: Setup Docker buildx uses: docker/setup-buildx-action@v2 - name: Log into ghcr.io uses: docker/login-action@master with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push container image id: push-step uses: docker/build-push-action@master with: push: true tags: ghcr.io/${{ github.repository }}:latest - name: Sign the container image run: cosign sign ghcr.io/${{ github.repository }}@${{ steps.push-step.outputs.digest }} ``` Now, your demo Django container project is complete and ready for GitHub Actions to run on it. Verify that your project is configured correctly. Run the `tree` command with the `-a` flag from your project’s root directory to view invisible directories. ```sh cd ~/Documents/GitHub/django-keyless-signing tree -a ``` ``` . ├── .github │ └── workflows │ └── docker-publish.yml ├── Dockerfile ├── docker-compose.yml └── requirements.txt 2 directories, 4 files ``` If your setup matches, we can proceed. ## Generate GitHub personal access token In order to use GitHub on the command line and run GitHub Actions, you’ll need a personal access token. In your web browser, navigate to https://github.com/settings/tokens in order to set those up. You’ll click on the **Generate new token** button and fill out the form on the next page. Fill in the Note field about what the token is for, the 30 days expiration is adequate, and you’ll need to select the **repo**, **workflow**, and **write:packages** scopes, as indicated in the screenshot below. ![GitHub Generate New Token Example](github-token.png) With this filled out, you can click on the green **Generate token** button at the bottom of the page and then your token will display on the page. _Be sure to copy this token_; you won’t have access to it again. You’ll be using this token to authenticate on the command line. ## Initialize Git repository and push changes From your local repository of `django-keyless-signing` you will be initializing your repository to use with Git. ```sh git init ``` Next, you will add the files you created to the Git stage. ```sh git add .github Dockerfile docker-compose.yml requirements.txt ``` At this point, you can check that your Git stage is all set for committing and then pushing your changes to the remote GitHub repository. ```sh git status ``` ``` On branch main No commits yet Changes to be committed: (use "git rm --cached ..." to unstage) new file: .github/workflows/docker-publish.yml new file: Dockerfile new file: docker-compose.yml new file: requirements.txt ``` The output indicates that changes are ready to be committed. You will now commit with a message, as in the next command. ```sh git commit -m "first commit" ``` ``` [main (root-commit) 301800b] first commit 4 files changed, 93 insertions(+) create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 requirements.txt ``` Now, set up the main branch as main. ```sh git branch -M main ``` So far we have not connected to the remote repository. You should add that repository now. This will be the URL for your repository plus `.git` at the end. Ensure that you replace `github-username` with your actual username on GitHub. ```sh git remote add origin https://github.com/github-username/django-keyless-signing.git ``` With this set up, you’ll be able to push your changes to the remote repository that’s hosted on GitHub. ```sh git push -u origin main ``` With this command, you will be prompted to enter your GitHub username and the GitHub personal access token. In the first prompt, enter your GitHub username, where it reads `Username`. In the second prompt, where it reads `Password`, enter your personal access token, _not your GitHub password_. Username for 'https://github.com': Password for 'https://github-username@github.com': Once you enter these, you’ll receive output that your changes were committed to the remote repository. ``` Enumerating objects: 8, done. Counting objects: 100% (8/8), done. Delta compression using up to 10 threads Compressing objects: 100% (5/5), done. Writing objects: 100% (8/8), 1.62 KiB | 1.62 MiB/s, done. Total 8 (delta 0), reused 0 (delta 0), pack-reused 0 To https://github.com/github-username/django-keyless-signing.git * [new branch] main -> main branch 'main' set up to track 'origin/main'. ``` With this complete, you can navigate to the URL of your GitHub repository. ## Confirm keyless signing via GitHub Actions With your repository set up, you can move to the **Actions** tab of your GitHub repository. Here, you’ll be able to inspect the workflows that have run. Since there is only one workflow in this repo, you can inspect the one for `first commit`. Here, a green checkmark and **build** will be displayed on the page under `docker-publish.yml`. This action ran when you pushed your code into the repository. You can click on **build** and inspect the steps of the action. Your page will appear similar to the following. Ensure that your action ran and that your output is similar. ![First commit example](first-commit-example.png) From here, you can click into each step of the build process and dial in further. Click into **Sign the container image**. This will dropdown and provide you with more information, like so. ``` Run cosign sign ghcr.io/github-username/django-keyless-signing@sha256:a53e24bd4ab87ac4764fb8736dd76f388fd2672c1d372446c9a2863e977f6e54 Generating ephemeral keys... Retrieving signed certificate... client.go:196: root pinning is not supported in Spec 1.0.19 Successfully verified SCT... tlog entry created with index: XXXXXXX Pushing signature to: ghcr.io/github-username/django-keyless-signing ``` This provides a bit of information, including the SHA, the Rekor log index number (as indicated by `tlog entry created with index`), and the URL of the container that the signature was pushed to. You can also inspect the image itself under **Packages** on the main page of your repository. If you would like, you can pull down the Docker image. This is not necessary for our next step, where we will check that the signature was signed and that the signature is in the Rekor transparency log. ## Verify signatures With your container signed by Cosign keyless signing in GitHub Actions, you next need to verify that everything worked as expected and that the container is indeed signed, and that an entry for that was generated in Rekor. You can do that by using the `cosign verify` command against the published container image. ```sh cosign verify ghcr.io/github-username/django-keyless-signing \ --certificate-identity https://github.com/github-username/django-keyless-signing/.github/workflows/docker-publish.yml@refs/heads/main \ --certificate-oidc-issuer https://token.actions.githubusercontent.com | jq ``` Your output should be similar to the following, though note that the strings are abbreviated. ``` Verification for ghcr.io/github-username/django-keyless-signing:latest -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - Any certificates were verified against the Fulcio roots. [ { "critical": { "identity": { "docker-reference": "ghcr.io/github-username/django-keyless-signing" }, "image": { "docker-manifest-digest": "sha256:a4aa08ce4593" }, "type": "cosign container image signature" }, "optional": { "Bundle": { "SignedEntryTimestamp": "8XFlAArYeA", "Payload": { "body": "E5ha0V5VFVSTmVFNXFSWGROUkZwaFJuY3dlVTFxUVRKTlJFMTRUbXBKZDAxRVZtRk5", "integratedTime": 1654272608, "logIndex": XXXXXXX, "logID": "a4aa08ce4593" } }, "Issuer": "https://token.actions.githubusercontent.com", "Subject": "https://github.com/github-username/django-keyless-signing/.github/workflows/docker-publish.yml@refs/heads/main" } } ] ``` You can also review the log on Rekor by using the logIndex as above, which matches the `tlog entry created with index` you found in the output from the GitHub Actions. You can use either `verify` or `get` with the Rekor CLI. In the first case, your command will be formatted like so and provide a lot of output with a full inclusion proof. Note that this output is abbreviated. Substitute the Xs in the command for your log index number. ```sh rekor-cli verify --rekor_server https://rekor.sigstore.dev --log-index XXXXXX ``` ``` Current Root Hash: 1ce1a05f2ec146e503d78649c093 Entry Hash: e739fb04525a9e8a0d590b9f944714ce469c Entry Index: XXXXXX Current Tree Size: 2251200 Inclusion Proof: SHA256(0x01 | 3742364ed095572728c5c4c6abcc55cda3111833bb01260b6dfd50ce0214bbfe | b0f3127874d6ce2ca520797f4ab9e739fb04525a9e8a0d590b9f944714ce469c) = b94433c839343e37b42cdf2281731571971202c77defdae51a6c386a4d1bfb7b … SHA256(0x01 | efb36cfc54705d8cd921a621a9389ffa03956b15d68bfabadac2b4853852079b | 5a35a58d7624edfb9adf6ea9f0cbed558f5e5d45ca91acb5243757d72f1b2454) = 2c0c0e511e071ab024da0ebd89f67b39ae7a1ce1a05f2ec146e503d78649c093 ``` In the second instance, you’ll receive JSON formatted output. Note the output here is abbreviated. Substitute the Xs in the command for your log index number. ```sh rekor-cli get --rekor_server https://rekor.sigstore.dev --log-index XXXXXX ``` ``` LogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d Index: XXXXXXX IntegratedTime: 2022-06-03T17:12:38Z UUID: 0d590bf944714ce469c Body: { "HashedRekordObj": { "data": { "hash": { "algorithm": "sha256", "value": "abb1bef9a31c634cfc" } }, "signature": { "content": "RxAva1EnlCS5AIhAN", "publicKey": { "content": "jeTlvWldGa2N5OXRZV2x1TUM0fNpc0dBUVFCZzc4d0FRUUVJRkIxWW14cGMyZ2cKWVc1" } } } } ``` Congratulations! You have signed a container with Cosign through GitHub Actions by using OIDC through Fulcio, and can verify this on the Rekor log. --- ### An introduction to Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/an-introduction-to-cosign.md Last Modified: September 28, 2026 Tags: Cosign, Overview _An earlier version of this material was published in the [Cosign chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@204b98f35bca48c194d1868e0356bef1/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2f0ad9cb8f124a39ab555ac8bf1a114c) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ Cosign supports software artifact signing, verification, and storage in an OCI (Open Container Initiative) registry. While Cosign was developed with containers and container-related artifacts in mind, it can also be used for open source software packages and other file types. Cosign can therefore be used to sign blobs (binary large objects), files like READMEs, SBOMs (software bills of materials), Kubernetes Helm Charts, Tekton bundles (an OCI artifact containing Tekton CI/CD resources like tasks), and more. By signing software, you can authenticate that you are who you say you are, which can in turn enable a trust root so that developers and consumers who leverage your software can verify that you created the software artifact that you have said you’ve created. They can also ensure that the artifact was not tampered with by a third party. As someone who may use software libraries, containers, or other artifacts as part of your development lifecycle, a signed artifact can give you greater assurance that the code or container you are incorporating is from a trusted source. ## Code signing with Cosign Software artifacts are distributed widely, can be incorporated into the software of other individuals and organizations, and are often updated throughout their life spans. End users and developers who build upon existing software are increasingly aware of the possibility of threats and vulnerabilities in packages, containers, and other artifacts. How can users and developers decide whether to use software created by others? One answer that has been increasingly gaining traction is _code signing_. While code signing is not new technology, the growing prevalence of software in our everyday lives coupled with a rising number of attacks like [SolarWinds](https://www.businessinsider.com/solarwinds-hack-explained-government-agencies-cyber-security-2020-12) and [Codecov](https://www.reuters.com/technology/codecov-hackers-breached-hundreds-restricted-customer-sites-sources-2021-04-19/) has created a more pressing need for solutions that build trust, prevent forgery and tampering, and ultimately lead to a more secure software supply chain. Similar in concept to a signature on a document that was signed in the presence of a notary or other professional who can certify your identity, a signature on a software artifact attests that you are who you say you are and that the code was not altered after signing. Instead of a recognized notary when you sign software, it is a recognized **certificate authority** (CA) that validates your identity. These checks that go through recognized bodies able to establish a developer’s identity support the root of trust that security relies on so that bad actors cannot compromise software. Code signing involves a developer, software publisher, or entity (like an automated workload) digitally signing a software artifact to confirm their identity and ensure that the artifact was not tampered with since having been signed. Code signing has several implementations, and Cosign is one such implementation, but all code signing technology follows a similar process as Cosign. The recommended practice for a developer (or organization) looking to sign their code with Cosign is to use _keyless signing_. This process will first generate an ephemeral key pair which will then be used to create a digital signature for a given software artifact. A _key pair_ is a combination of a signing key to sign data, and a verification key that is used to verify data signed with the corresponding signing key. With the `cosign sign` command, the developer will sign their software artifact, and that signature will be stored in the registry (if applicable). This signature can later be verified by others through searching for an artifact, finding its signature, and then verifying it. ## Keyless signing Code signing is a solution for many use cases related to attestation and verification with the goal of a more secure software supply chain. While key pairs are a technology standard that have a long history in technology (SSH keys, for instance), they create their own challenges for developers and engineering teams. The contents of a public key are very opaque; humans cannot readily discern who the owner of a given key is. Traditional public key infrastructure, or PKI, has done the work to create, manage, and distribute public-key encryption and digital certificates. A new form of PKI is keyless signing, which prevents the challenges of long-lived and opaque signing keys. In keyless signing, short-lived certificates are generated and linked into the chain of trust through completing an identity challenge that confirms the identity of the signer. Because these keys persist only long enough for signing to take place, signature verification ensures that the certificate was valid at the time of signing. Policy enforcement is supported through an encoding of the identity information onto the certificate, allowing others to verify the identity of the developer who signed. Through offering short-lived credentials, keyless signing can support the recommended practice of operating your build environment like a production environment. This prevents the opportunity for long-lived keys to be stolen and used to sign malicious artifacts. Even if these short-lived keys used in keyless signing were stolen, they’d be useless! While keyless signing can be used by individuals in the same manner as long-lived key pairs, it is also well suited for continuous integration and continuous deployment workloads. Keyless signing works by sending an OpenID Connect (OIDC) token to a certificate authority like [Fulcio](/open-source/sigstore/fulcio/) to be signed by a workload’s authenticated OIDC identity. This allows the developer to cryptographically demonstrate that the software artifact was built by the continuous integration pipeline of a given repository, for example. Cosign uses ephemeral keys and certificates, gets them signed automatically by the Fulcio root certificate authority, and stores these signatures in the [Rekor](/open-source/sigstore/rekor/) transparency log, which automatically provides an attestation at the time of creation. You can manually create a keyless signature with the following `cosign` command. In our example, we’ll use [Docker Hub](https://hub.docker.com/) to store the signature. If you would like to follow along, ensure you are logged into Docker Hub on your local machine and that you have a Docker repository with an image available. The following example assumes a username of `docker-username` and a repository name of `demo-container`: ```sh cosign sign docker-username/demo-container ``` You'll be taken through a workflow that requests you to grant permission to have your information stored permanently in transparency logs, and moves to a workflow with an OIDC provider: ```output The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/. Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record. This may include the email address associated with the account with which you authenticate your contractual Agreement. This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/. By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above. Are you sure you would like to continue? [y/N] y Your browser will now be opened to: ... ``` At this point, a browser window will open and you will be directed to a page that asks you to log in to Sigstore. You can authenticate with GitHub, Google, or Microsoft. Note that the email address that is tied to these credentials will be permanently visible in the Rekor transparency log. This makes it publicly visible that you are the one who signed the given artifact, and helps others trust the given artifact. That said, it is worth keeping this in mind when choosing your authentication method. Once you log in and are authenticated, you’ll receive feedback of “`Sigstore Authentication Successful!`”, and you may now safely close the window. If you followed along with Docker Hub, you can check the user interface of your repository and verify that you pushed a signature. You can then further verify that the keyless signature was successful by using `cosign verify` to check. You will need to know some information in order to verify the entry. You'll need to use the identity flags `--certificate-identity` which corresponds to the email address of the signer, and `--certificate-oidc-issuer` which corresponds to the OIDC provider that the signer used. For example, a Gmail account using Google as the OIDC issuer, will be able to be verified with the following command: ```sh cosign verify \ --certificate-identity username@gmail.com \ --certificate-oidc-issuer https://accounts.google.com \ docker-username/demo-container ``` ```output Verification for index.docker.io/docker-username/demo-container:latest -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates [{"critical":{"identity":{"docker-reference":"index.docker.io/docker-username/demo-container"},"image":{"docker-manifest-digest":"sha256:e..."},"type":"cosign container image signature"},"optional":{"1.3.6.1.4.1.57264.1.1":"https://accounts.google.com","Bundle":{"SignedEntryTimestamp":"...","Payload":{"body":"eyJhcGlWZX...X19","integratedTime":...,"logIndex":...,"logID":"..."}},"Issuer":"https://accounts.google.com","Subject":"username@gmail.com"}}] ``` As part of the JSON output, you should get feedback on the issuer that you used and the email address associated with it. For example, if you used Google as your OIDC provider, you will have `"Issuer":"https://accounts.google.com","Subject":"username@gmail.com"}}]` as the last part of your output. ## Cosign with keys You can also use Cosign with long-lived key pairs. If you would like to follow along, please first [install Cosign](/open-source/sigstore/cosign/how-to-install-cosign/): ```sh cosign generate-key-pair ``` ```output Enter password for private key: Enter again: Private key written to cosign.key Public key written to cosign.pub ``` You can sign a container and store the signature in the registry with the cosign sign command: ```sh cosign sign --key cosign.key docker-username/demo-container ``` ```output Enter password for private key: Pushing signature to: index.docker.io/sigstore-course/demo:sha256-87ef60f558bad79beea6425a3b28989f01dd417164150ab3baab98dcbf04def8.sig ``` Finally, you can verify a software artifact against a public key with the cosign verify command. This command will return 0 if at least one Cosign formatted signature for the given artifact is found that matches the public key. Any valid formats are printed to standard output in a JSON format: ```sh cosign verify --key cosign.pub docker-username/demo-container ``` ```output The following checks were performed on these signatures: - The cosign claims were validated - The signatures were verified against the specified public key {"Critical":{"Identity":{"docker-reference":""},"Image":{"Docker-manifest-digest":"sha256:87ef60f558bad79beea6425a3b28989f01dd417164150ab3baab98dcbf04def8"},"Type":"cosign container image signature"},"Optional":null} ``` You should now have some familiarity with the process of signing and verifying code in Cosign. For a more thorough tutorial, please review [How to sign a container with Cosign](/open-source/sigstore/cosign/how-to-sign-a-container-with-cosign/). Code signing provides developers and others who release code a way to attest to their identity, and in turn, those who are consumers (whether end users or developers who incorporate existing code) can verify those signatures to ensure that the code is originating from where it is said to have originated, and check that that particular developer (or vendor) is trusted. --- ### Set up and use Octo STS URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/octo-sts/set-up-octo-sts.md Last Modified: October 2, 2026 Tags: octo-sts, Procedural, OIDC, Security Octo STS is a GitHub App that exchanges OIDC tokens from your workloads for short-lived GitHub tokens, so your automation doesn't need personal access tokens (PATs). This page shows you how to install Octo STS, write trust policies, exchange tokens, and fix failed exchanges. For background on why Octo STS exists, read [Octo STS: Short-lived GitHub tokens without PATs](https://www.chainguard.dev/supply-chain-security-101/octo-sts-overview) in Supply Chain Security 101. ## How the token exchange works Octo STS issues tokens according to trust policies that you keep in your repositories: 1. You install the Octo STS GitHub App on your organization or repositories. 1. You write trust policies that say which identities can get a token and what permissions the token carries. 1. Your workload presents an OIDC token to Octo STS. 1. If the OIDC token matches a trust policy, Octo STS returns a short-lived GitHub token with the permissions that policy grants. ![Sequence diagram of the requests a workload, Octo STS, and GitHub exchange during a token exchange.](octo-arch.webp) Tokens expire after one hour and can't be refreshed. When a token expires, exchange a new OIDC token for a new GitHub token. ## Install Octo STS 1. Go to the [Octo STS app page](https://github.com/apps/octo-sts) and click **Install**. 1. Select the organization or user account. 1. Choose the repositories that Octo STS can access. Octo STS works with both public and private repositories. 1. Approve the permissions. Octo STS requests a broad set of permissions so that it can support many use cases, but the tokens it issues carry only the permissions that your trust policies grant. The app itself needs `contents: read` so that it can read your trust policies. Until you create a trust policy, Octo STS can't issue any tokens. The hosted service at `octo-sts.dev` is free to use. Octo STS is open source, so you can also host it yourself; the [Octo STS repository](https://github.com/octo-sts/app) has deployment instructions. When Octo STS needs to add or remove GitHub permissions, the maintainers open an issue in the Octo STS repository that explains the change, and GitHub asks you to approve the updated permissions for your installation. These updates happen quarterly, except for critical changes. ## Write a trust policy Trust policies are YAML files at `.github/chainguard/{name}.sts.yaml` in your repository. Octo STS typically reads them from the default branch. A repository can have several policies, each with its own identity requirements and permissions: - `.github/chainguard/renovate.sts.yaml` - `.github/chainguard/deploy.sts.yaml` - `.github/chainguard/ci.sts.yaml` When your workload exchanges a token, it names the policy to use with the `identity` parameter. To change a policy's permissions, edit the file, then commit and push the change. New permissions apply to every exchange after that. Tokens that Octo STS already issued keep their original permissions until they expire. ### Match subjects exactly Prefer exact subject matching: ```yaml subject: repo:org@/repo@:ref:refs/heads/main ``` Use a pattern only when you need the flexibility: ```yaml subject_pattern: "repo:org@/repo@:ref:refs/heads/.*" ``` An exact match makes it harder to grant broader access than you intended.

Note: These subjects use GitHub’s immutable format, which embeds the numeric owner ID and repository ID in the sub claim (for example, repo:org@123456/repo@654321:ref:refs/heads/main). This format is the default for repositories created after July 15, 2026, and an opt-in for older repositories. Match the exact subject your repository’s token carries. For how to find the IDs, refer to Finding your repository’s numeric identifiers.

### Grant access to several repositories To issue one token that works across several repositories, use an organization trust policy with a `repositories` field: ```yaml issuer: https://token.actions.githubusercontent.com subject: repo:org@/automation-repo@:ref:refs/heads/main permissions: contents: read repositories: - org/repo-one - org/repo-two - org/repo-three ``` The resulting token can access every listed repository. Whatever a policy grants, GitHub still enforces branch protection. A token with `contents: write` must still meet requirements such as pull request reviews and status checks. ## Exchange tokens outside GitHub Actions Octo STS works with any system that can obtain an OIDC token, such as Jenkins, GitLab CI, or CircleCI, and make an HTTP request to exchange it. The system needs an OIDC identity provider that Octo STS can validate. For example, Terraform can exchange a token through its `external` data source: ```hcl data "external" "github_token" { program = ["bash", "-c", <<-EOT OIDC_TOKEN=$(get_oidc_token) RESPONSE=$(curl -s -H "Authorization: Bearer $OIDC_TOKEN" \ "https://octo-sts.dev/sts/exchange?scope=org/repo&identity=terraform") echo $RESPONSE | jq '{token: .access_token}' EOT ] } provider "github" { token = data.external.github_token.result.token } ``` ## Migrate from personal access tokens You can use PATs and Octo STS side by side during a migration, which lets you move one workload at a time and roll back if you need to. 1. Find where your automation uses PATs. 1. Check whether each of those systems can provide OIDC tokens. 1. Write a trust policy for each use case. 1. Update the automation to exchange OIDC tokens instead of using PATs. 1. Test the changes in a non-production environment. 1. Revoke the PATs once Octo STS is working. ## Troubleshoot a failed token exchange If a token exchange fails, check these common causes: - **The trust policy doesn't exist.** Verify that the file exists at `.github/chainguard/{identity}.sts.yaml`. - **The OIDC token doesn't match the policy.** Check that the token's issuer and subject match the policy. - **The app isn't installed.** Make sure Octo STS is installed and has access to the repository. - **The policy is on the wrong branch.** Octo STS typically reads trust policies from the default branch. - **The permissions are invalid.** The policy requests permissions that don't exist or that the app can't grant. ## Get help - To report a bug, open an issue in the [Octo STS repository](https://github.com/octo-sts/app/issues). - To ask a question, use GitHub Discussions in the [Octo STS repository](https://github.com/octo-sts/app/). - To contribute, follow the contribution guidelines in the [repository](https://github.com/octo-sts/app). For a worked example, watch [Updating container images with Renovate (and no PATs!)](/open-source/octo-sts/updating-container-images-with-renovate/). --- ### Building a Wolfi package URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/wolfi/building-a-wolfi-package.md Last Modified: October 2, 2026 Tags: Wolfi, Procedural Wolfi is a Linux distro created specifically for building stripped-down container images that only include the essential packages needed to run applications in containers. This makes it more secure, as there are fewer potential attack vectors due to the reduced surface area. Thanks to a fine-tuned maintenance process combining top-notch automation and established best practices from maintainers, Wolfi packages are updated quickly. This ensures that Wolfi users get patches and latest versions of packages at a much faster pace than other distributions. Additionally, Wolfi includes a number of features that help to ensure the provenance and authenticity of packages. For example, all packages are built directly from source and signed with cryptographic signatures. This helps to prevent malicious code from being introduced into the system. Wolfi also provides a high-quality build-time [SBOM](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom) as standard for all packages. That being said, it's important to note that Wolfi is rather new; it just recently crossed the mark of 1,000 packages in the Wolfi OS repository. That means some packages that you would find in a more established distro won't be available yet in Wolfi. In this article, we'll cover the whole process involved in building a new Wolfi package, or how a Wolfi package comes to be. > Note: Many of the examples shown in this article are based on the [Wolfi PHP package](https://github.com/wolfi-dev/os/blob/main/php-8.2.yaml), which is a slightly complex build that generates several subpackages from a single melange YAML file. You can keep that link open in a separate tab to use as reference as you go through this guide. ## How does it compile? The first step in building a new Wolfi package is finding official documentation with guidance on how to build the package from source. All Wolfi packages need to be built from source in order to assure provenance and authenticity of package contents. Because Wolfi uses [apk](https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper) and thus has some similar design principles to Alpine, it is a good idea to review the [Alpine package index](https://pkgs.alpinelinux.org/packages) to find out how the package is built there. This can give you insights about configuration options, dependencies, and eventual subpackages that can be stripped from the main package. For example, when compiling PHP from source, you have the choice of compiling several extensions either as built-in or as shared libraries. Although compiling said extensions as built-in packages makes for a simpler build, it also increases the size of the original package and creates a wider surface for possible vulnerabilities. If you aren't very familiar with building packages from source using tools such as `cmake` and `autoconf`, it's a good idea to compile the package locally first - you don't need to run `make install` at the end to get the package installed on your own system, but running the `configure` and `make` processes will give you a better understanding of the build requirements and configure options. ## The melange YAML file The melange YAML file is where you'll define the details about the package and its build pipeline. If you are familiar with GitHub Actions, you'll find out that melange definitions are very similar to GitHub Actions workflows. ### The `package` section The melange YAML file starts with a `package` section, used to define metadata information and runtime dependencies. The following excerpt demonstrates how this section is declared in the Wolfi [PHP package](https://github.com/wolfi-dev/os/blob/34aa71ac4898c2b7c529548eafa51b0ea7a4dbd3/php.yaml#L1C3-L1C3) YAML: ```yaml package: name: php version: 8.2.8 epoch: 0 description: the PHP programming language copyright: - license: PHP-3.01 dependencies: runtime: - libxml2 ``` - **Package name**: the current convention is to use the same name as the YAML file without extension. This is what people will search for, so it's a good idea to keep it consistent with how the package is named in other distributions. - **Description**: this information shows up when searching for the package with apk. - **Version**: the version of the package. - **Epoch**: a numeric field set to zero by default; this only needs to be incremented when there is a non-version change in the package. For instance, when build options such as compiler flags have changed or new subpackages have been added but the upstream package version hasn't changed — in such cases, you'd need to "bump the epoch" in order to trigger the build. - **License**: the package license. It is important to note that only packages with OSI-approved licenses can be included in Wolfi. You can check the relevant package info in the [licenses page at opensource.org](https://opensource.org/licenses/). - **Runtime dependencies**: any dependencies needed by your package at runtime. Not to be confused with build dependencies, these will come up in the environment section of the file. ### The `environment` section The next section is the `environment` section. It defines how the build environment should look in order to build your package. Packages listed in this section won't be included in the final package, because they are only needed at build time. When building locally, you'll also need to include information about where to find Wolfi packages. This is not needed when submitting the package to the Wolfi OS repository. The `contents` node is used for that: ```yaml environment: contents: repositories: - https://packages.wolfi.dev/os keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub ``` The `packages` section is where you can define dependencies. The following example is an excerpt from the Wolfi PHP package, which is a fairly complex build with many dependencies: ```yaml environment: contents: packages: - build-base - busybox - file - bison - libtool - ca-certificates-bundle - bzip2-dev - libxml2-dev - curl-dev - openssl-dev - readline-dev - sqlite-dev - libsodium-dev - libpng-dev - libjpeg-turbo-dev - libavif-dev - libwebp-dev - libxpm-dev - libx11-dev - freetype-dev - gmp-dev - icu-dev - openldap-dev - oniguruma-dev - libxslt-dev - postgresql-15-dev - libzip ``` Don't worry if you don't know everything you'll need upfront at build time. Even if you build the package locally first, your system most likely has many dependencies already installed; by paying attention to the output provided by melange, you will be able to figure out what is missing, and iterate until your build environment looks right. One thing that may happen during this process is finding out that one or more dependencies needed by your package are not yet available in Wolfi, so they need to be built first. It is a normal part of the process, so don't worry — you will be able to build incrementally and test everything locally. ### The `pipeline` section With package metadata and build environment defined, it's time to create the pipeline that will build your package. The `pipeline` section has a structure similar to a GitHub Actions workflow, defining a series of steps that must be executed in the same order they are defined, creating output that will be packaged into one or more apk packages. A package build pipeline typically starts with fetching the package (as a tarball or directly from a Git branch) and matching the downloaded artifact against an expected sha hash. Some of the actions executed in build pipelines are very similar across packages: downloading a package, running configure and make, fetching a package from git, etc. Luckily for us, melange bakes a lot of repetitive tasks into reusable [pipelines](https://github.com/chainguard-dev/melange/tree/main/pkg/build/pipelines): - Downloading Packages - `fetch` - `git-checkout` - Autoconf - `autoconf/configure` - `autoconf/make` - `autoconf/make-install` - Cmake - `cmake/build` - `cmake/configure` - `cmake/install` - Go - `go/build` - `go/install` - Meson - `meson/compile` - `meson/configure` - `meson/install` - Ruby - `ruby/build` - `ruby/clean` - `ruby/install` - Split - `split/debug` - `split/dev` - `split/infodir` - `split/locales` - `split/manpages` - `split/static` - Other - `strip` - `patch` Each pipeline can have one or more parameters that should be provided as keypairs in a `with` entry. For example, a download-and-check has the following structure in the melange YAML, using the built-in pipeline `fetch`: ```yaml - uses: fetch with: uri: https://libzip.org/download/libzip-${{package.version}}.tar.gz expected-sha256: 52a60b46182587e083b71e2b82fcaaba64dd5eb01c5b1f1bc71069a3858e40fe ``` Naturally, you can also run raw bash commands in your pipeline. The following example shows the build of [Composer](https://getcomposer.org/), the PHP package manager, which is not compiled as a typical package: ```yaml - name: Install Composer runs: | EXEC_DIR="${{targets.destdir}}/usr/bin" mkdir -p "${EXEC_DIR}" mv composer.phar "${EXEC_DIR}/composer" chmod +x "${EXEC_DIR}/composer" ``` As indicated, a pipeline step will have either a `uses` or a `run` directive. You can have as many steps as you need, and you can use special variable substitutions inside steps: - `${{package.name}}`: Package name. - `${{package.version}}`: Package version. - `${{package.epoch}}`: Package epoch. - `${{targets.destdir}}`: Directory where final package files will be stored. Everything that lives here will be packed into your final apk. - `${{targets.subpkgdir}}`: Directory where final subpackage files will be stored. Works the same way as `targets.destdir`, but for subpackages. You can find more details about available pipelines in the [melange pipelines documentation](https://github.com/chainguard-dev/melange/blob/main/docs/PIPELINES.md). ### The `subpackages` section As mentioned previously, a package may extract parts of its contents into subpackages in order to make for a slimmer final apk. Many packages have resources that are not required at execution time, including development headers, man pages, shared libraries that are optional. This part is really important in Wolfi, because we want packages to be minimal. The `subpackages` section of the melange YAML file looks a lot like the pipeline section, and it essentially works the same way. You'll just have to make sure you place any subpackage files in the `targets.subpkgdir` location. The `split` built-in pipelines were created to facilitate the creation of subpackages. They implement code to remove development headers (`split/dev`), man pages (`split/manpages`), among other resources that aren't typically required at runtime. You can experiment with those, just be aware that they use standard path locations and some compiled packages may use different paths for certain resources. For example, this is how a step in the subpackages section would be written, using the `split/dev` built-in pipeline to generate the `php-dev` subpackage: ```yaml - name: php-dev description: PHP 8.2 development headers pipeline: - uses: split/dev ``` ### Looping with ranges In some cases, you may find yourself repeating the same task over and over with just a couple different values (such as package names). In such scenarios, you can define a range of data that you can "loop" through in a step. For example, let's have a look at how the PHP package uses this feature to create its subpackages. First, we define an `extensions` range. This should go on a `data` node at the same level as the `pipelines` section of your YAML: ```yaml data: - name: extensions items: bz2: Bzip2 curl: cURL gd: GD imaging gmp: GNU GMP support ldap: LDAP mysqlnd: MySQLnd openssl: OpenSSL pdo_mysql: MySQL driver for PDO pdo_sqlite: SQLite 3.x driver for PDO soap: SOAP sodium: Sodium calendar: Calendar ``` In the subpackages section, we define a pipeline for that range: ```yaml - range: extensions name: "php-${{range.key}}" description: "The ${{range.value}} extension" pipeline: - runs: | export EXTENSIONS_DIR=usr/lib/php/modules export CONF_DIR="${{targets.subpkgdir}}/etc/php/conf.d" mkdir -p "${{targets.subpkgdir}}"/$EXTENSIONS_DIR $CONF_DIR mv "${{targets.destdir}}/$EXTENSIONS_DIR/${{range.key}}.so" \ "${{targets.subpkgdir}}/$EXTENSIONS_DIR/${{range.key}}.so" prefix= [ "${{range.key}}" != "opcache" ] || prefix="zend_" echo "${prefix}extension=${{range.key}}.so" > $CONF_DIR/"${{range.key}}.ini" ``` And this will loop through all values of the `extensions` range and execute the described pipeline. ### The `update` section This final section of the YAML file is only required when submitting the package to the Wolfi OS repository. The `update` section is used by Wolfi CI/CD systems to detect new package releases. Wolfi uses multiple tools and services to keep track of upstream releases, including the [Release Monitoring](https://release-monitoring.org/) service. For packages that are released via GitHub, tracking occurs using the project org/name and a monitored tag. Here's an example of the update section of the PHP package, which uses the Release Monitoring service: ```yaml update: enabled: true release-monitor: identifier: 3627 ``` You can obtain the identifier from the [release monitoring page](https://release-monitoring.org/) - search for the package and grab the ID that shows up at the URL. Here is another example, this time from a package that is released via GitHub: ```yaml update: enabled: true github: identifier: php-amqp/php-amqp strip-prefix: v tag-filter: v ``` Again, this section is only required when submitting the package to Wolfi. For more details about Wolfi's automated package updates, check [the official docs](https://github.com/wolfi-dev/os/blob/main/docs/UPDATES.md) on the subject. ## Building packages When you feel your YAML is good for a first run, it's time to build the package with melange. In this guide we'll use Docker to execute melange in a local environment, using [Wolfi's SDK](https://github.com/wolfi-dev/tools/pkgs/container/sdk) image. This image contains everything you need to build Wolfi packages with melange and Wolfi-based images with apko. The procedure to build apk packages with melange is explained in more detail in our [Getting started with melange](/open-source/build-tools/melange/getting-started-with-melange/) tutorial. ### Setting up a local development environment Start by cloning the [Wolfi-os](https://github.com/wolfi-dev/os) repository to your local machine. If you plan on sending a pull request to Wolfi later, you may want to create a fork now and clone your fork instead. ```shell git clone https://github.com/wolfi-dev/os.git ``` From the root of the project, run the following command to build your Docker-based development environment: ```shell make dev-container ``` This will create an ephemeral container based on the Wolfi SDK image, with a few predefined settings. We'll call this your **Wolfi development environment**. ``` ❯ make dev-container docker run --privileged --rm -it \ -v "/home/erika/Projects/os:/home/erika/Projects/os" \ -w "/home/erika/Projects/os" \ -e SOURCE_DATE_EPOCH=0 \ ghcr.io/wolfi-dev/sdk:latest@sha256:99babbe4897d68ec1a342bd958fda7274a072bf112670fa691f64753b04774a9 Welcome to the development environment! [sdk] ❯ ``` You are now ready to build your Wolfi package. ### Building a package To build a package, run the following command from your Wolfi SDK environment: ```shell make package/ ``` For instance, to build PHP 8.3, which is defined in a file named `php-8.3.yaml`, you would run `make package/php-8.3`: ``` [sdk] ❯ make package/php-8.3 make yamlfile=php-8.3.yaml pkgname=php-8.3 packages/x86_64/php-8.3-8.3_rc3-r1.apk make[1]: Entering directory '/home/erika/Projects/os' ############################################################## # build output - removed for readability # ############################################################## ℹ️ | signing apk index at packages/x86_64/APKINDEX.tar.gz ℹ️ | signing index packages/x86_64/APKINDEX.tar.gz with key local-melange.rsa ℹ️ | appending signature to index packages/x86_64/APKINDEX.tar.gz ℹ️ | writing signed index to packages/x86_64/APKINDEX.tar.gz ℹ️ | signed index packages/x86_64/APKINDEX.tar.gz with key local-melange.rsa make[1]: Leaving directory '/home/erika/Projects/os' [sdk] ❯ ``` When the build is finished, you can find the newly built apk(s) in a `./packages` directory, in the root of your cloned Wolfi repository. ### When the build fails It is likely that your build won't work on the first run, and that is completely normal because there are many moving parts and hidden dependencies when building packages from source. In this scenario, it is often useful to check the build environment, which is preserved for debugging. The build output will inform you where to find these files in your development environment. ```output 2023/10/25 15:37:27 ERROR: failed to build package. the build environment has been preserved: ℹ️ x86_64 | workspace dir: /tmp/melange-workspace-4269468499 ℹ️ x86_64 | guest dir: /tmp/melange-guest-3734950176 ``` The **workspace dir** is where you will find the `melange_out` directory, which contains the output of your package. The **guest dir** directory contains the filesystem of your build environment. Another useful strategy is to include `set -x` before commands in your pipeline, in order to get extended debug information. ```yaml - name: Install Composer runs: | set -x EXEC_DIR="${{targets.destdir}}/usr/bin" mkdir -p "${EXEC_DIR}" mv composer.phar "${EXEC_DIR}/composer" chmod +x "${EXEC_DIR}/composer" ``` Most build issues are caused by missed dependencies, even when the error message might be misleading. Another common reason for build errors are wrong file or directory paths. The [melange documentation](/open-source/build-tools/melange/troubleshooting/) has more pointers to help with debugging, in case you need it. As mentioned before, there might be cases where you'll need to first build a dependency, and then use this dependency to build the package you need. When working with local dependencies, use the following notation in your `packages` list, inside the `environment` section: ```yaml environment: contents: repositories: - https://packages.wolfi.dev/os - '@local /work/packages' keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub packages: - busybox - mypackage@local ``` This will look for a package named "mypackage" in your local packages/ folder. ### When the build is successful First of all, celebrate! 🎉 Check the packages folder, you should find a directory for each built architecture (in my case I get x86_64) with your built apks (package + subpackages) along with an `APKINDEX.tar.gz` file: ```shell ./php-full-wolfi-demo/packages/x86_64 ├── APKINDEX.tar.gz ├── php-8.2.7-r1.apk ├── php-amqp-1.11.0-r0.apk ├── php-bcmath-8.2.7-r1.apk ├── php-bz2-8.2.7-r1.apk ├── php-calendar-8.2.7-r1.apk ├── php-cgi-8.2.7-r1.apk ├── php-ctype-8.2.7-r1.apk ├── php-curl-8.2.7-r1.apk ├── php-dbg-8.2.7-r1.apk ├── php-dev-8.2.7-r1.apk ├── php-dom-8.2.7-r1.apk ├── php-exif-8.2.7-r1.apk … ``` In the next section, we'll demonstrate how you can use the Wolfi SDK for running these checks. ## Testing your packages With a successful build, it's time to test the packages to make sure they are installable and functional, and also to verify they are free of CVEs. ### Local installation The first test you'll want to run with your package is to check if you can use `apk` to install it without errors. For that, we'll use the `local-wolfi` environment, which brings up a new container environment using the Wolfi-base image, with additional settings to make your new package available in the test environment alongside the melange keys that were created to sign your package at build time. We'll call this your **Wolfi Test Environment**. ``` ❯ make local-wolfi docker run --rm -it \ --mount type=bind,source="/home/erika/Projects/os/packages",destination="/work/packages",readonly \ --mount type=bind,source="/home/erika/Projects/os/local-melange.rsa.pub",destination="/etc/apk/keys/local-melange.rsa.pub",readonly \ --mount type=bind,source="/tmp/tmp.LXnQu0hkFn/repositories",destination="/etc/apk/repositories",readonly \ -w "/work/packages" \ cgr.dev/chainguard/wolfi-base:latest d2df519c59df:/work/packages# ``` Your newly built packages should now be available for installation from this environment via `apk add`. You should use the full path to the `.apk` file, for instance: ```shell apk add ./x86_64/composer-2.6.5-r0.apk ``` Make sure the package can be installed without errors, including dependencies. ### Checking for CVEs Checking the package for CVEs is a good practice to avoid submitting unpatched packages into Wolfi. If CVEs are found, you may want to apply a security patch before submitting your package, if that is available. From your **Wolfi development environment**, run the following command, providing the full path to your `.apk` file: ```shell [sdk] ❯ wolfictl scan ./packages/x86_64/composer-2.6.5-r0.apk 🔎 Scanning "./packages/x86_64/composer-2.6.5-r0.apk" ✅ No vulnerabilities found [sdk] ❯ ``` For more information about patching CVEs in Wolfi, check [the official docs on this subject](https://github.com/wolfi-dev/os/blob/main/HOW_TO_PATCH_CVES.md). ## Submitting the package to Wolfi OS Once you are satisfied with your set of packages and subpackages, you may consider submitting your package to [Wolfi OS](https://github.com/wolfi-dev/os). From this point, the process is essentially the following: - Create a fork of the [wolfi-dev/os](https://github.com/wolfi-dev/os) repository - Create a branch with the name of your package, for instance: add-php-package - Remove the `repositories` and `keyring` sections of the YAML file - Add the `release-monitor` info to the YAML file - Run [`yam`](https://github.com/wolfi-dev/os/blob/main/docs/UPDATES.md) to fix any YAML formatting issues - Create a signed commit - Open a pull request following the instructions from the PR template. The [Wolfi Contributing Guide](https://github.com/wolfi-dev/os/blob/main/CONTRIBUTING.md) on GitHub has more details about this process. ## Resources to learn more If you haven't yet, check the [Wolfi PHP package source file](https://github.com/wolfi-dev/os/blob/main/php-8.2.yaml) for a more comprehensive view of the melange YAML structure and how that looks in a more complex build. To learn more about Wolfi and the ecosystem around it, read [What is Wolfi?](https://www.chainguard.dev/supply-chain-security-101/wolfi-overview) and the rest of the [Wolfi documentation](/open-source/wolfi/). --- ### Limit high or critical CVEs in your images workloads URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/critical-cve-policy.md Last Modified: September 28, 2026 Tags: policy-controller, Policy, Policy Controller While Common Vulnerabilities and Exposures (CVEs) are undesirable at any time, the software security standards of certain industries strictly regulate the allowance of _high_ or _critical_ CVEs. For example, in the payment industry, the [PCI Security Standards Council](https://www.pcisecuritystandards.org/) requires that all vulnerabilities with a Common Vulnerability Scoring System (CVSS) score higher than 4 are addressed. For engineers and security professionals working in these contexts, it’s essential to know if container images have high or critical CVEs before deploying them. But tracking these CVEs manually can be difficult, especially when regularly pulling or updating large numbers of images for your workloads. ## Policy solution: vulnerability attestation with no high or critical CVEs One way of addressing this concern is to use Chainguard’s policy that checks an image’s attestation to determine whether the image has any high or critical CVEs. Used with an admissions controller or the open source [Sigstore policy-controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/)), this policy enables you to restrict images or receive a warning whenever an image fails to meet the policy’s requirements. For this policy to work, the image under inspection must have an attached attestation containing output from its vulnerability scan. These vulnerability attestations are typically generated by the upstream maintainer who inserts the output of an image’s vulnerability scan into a vulnerability attestation and then signs the attestation to assure downstream users of its integrity. If the image doesn’t have a vulnerability attestation or you want to double check the attestation, you can also create a vulnerability attestation yourself using a scanner like [Trivy](https://trivy.dev/). In either case, the vulnerability attestation is checked by this policy to determine whether the image contains any high or critical vulnerabilities. Here is the policy in full: ``` ############################################################################################# # To generate an attestation with a scan report and attest it to an image follow these steps: # $ trivy image --format cosign-vuln --output vuln.json # $ cosign attest --key /path/to/cosign.key --type https://cosign.sigstore.dev/attestation/vuln/v1 --predicate vuln.json # # $ cosign verify-attestation --key /path/to/cosign.pub --type --type https://cosign.sigstore.dev/attestation/vuln/v1 ############################################################################################# apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: vuln-no-high-or-critical-rego annotations: catalog.chainguard.dev/title: Fail on high or critical CVEs catalog.chainguard.dev/description: Vulnerability attestation with no High or Critical CVEs catalog.chainguard.dev/labels: attestation,rego spec: images: - glob: "**" authorities: - name: my-authority key: # REPLACE WITH YOUR PUBLIC KEY! data: | -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESWmPfv6b083TNcwY4SlYcZULn7jX /vfUyU7CPr2zssLc3+8SWAv2ZY59pofKnvYBp9dNiNwVTkrxab1bcpocVg== -----END PUBLIC KEY----- attestations: - name: must-not-have-high-critical-cves predicateType: https://cosign.sigstore.dev/attestation/vuln/v1 policy: type: rego data: | package sigstore isCompliant[response] { input.predicateType = "https://cosign.sigstore.dev/attestation/vuln/v1" filteredHighSeverity = [c | c := input.predicate.scanner.result.Results[_].Vulnerabilities[_]; c.Severity == "HIGH"] filteredCriticalSeverity = [c | c := input.predicate.scanner.result.Results[_].Vulnerabilities[_]; c.Severity == "CRITICAL"] result = ((count(filteredHighSeverity) + count(filteredCriticalSeverity)) == 0) errorMsg = sprintf("Found HIGH '%d' and CRITICAL '%d' vulnerabilities", [count(filteredHighSeverity) ,count(filteredCriticalSeverity)]) warnMsg = "" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } } ``` ## Implementing this policy You can use this policy freely with the open source [Sigstore policy-controller](/open-source/sigstore/policy-controller/how-to-install-policy-controller/) to block new deployments of images that don’t meet the policy’s requirements. --- ### Getting started with OpenVEX and vexctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sbom/getting-started-openvex-vexctl.md Last Modified: October 2, 2026 Tags: SBOM, VEX, Procedural The `vexctl` CLI is a tool to make VEX work. As part of the open source [OpenVex](https://www.chainguard.dev/supply-chain-security-101/what-is-openvex) project, `vexctl` enables you to create, apply, and attest VEX (Vulnerability Exploitability eXchange) data in order to filter out false positive security alerts. The `vexctl` tool was built to help with the creation and management of VEX documents, communicate transparently to users as time progresses, and enable the "turning off" of security scanner alerts of vulnerabilities known not to affect a given product. Using VEX, software authors can communicate to their users that an otherwise vulnerable component has no security implications for their product. This tutorial will walk you through some common commands in `vexctl`. ## Installing vexctl If you would like to install `vexctl` on your local or virtual machine, you will need a current, supported release of Go. You can install it by following the official [Go documentation](https://go.dev/doc/install). Using Go, run the following to install `vexctl`: ```sh go install github.com/openvex/vexctl@latest ``` This command will install the latest version of `vexctl` on your machine. ## Confirming installation You can confirm that `vexctl` was installed and is ready to use by running the following command: ```sh vexctl version ``` You should receive output similar to the following. ```output _ _ _____ __ __ _____ _____ _ | | | || ___|\ \ / // __ \|_ _|| | | | | || |__ \ V / | / \/ | | | | | | | || __| / \ | | | | | | \ \_/ /| |___ / /^\ \| \__/\ | | | |____ \___/ \____/ \/ \/ \____/ \_/ \_____/ vexctl: A tool for working with VEX data GitVersion: ... ... Platform: ... ``` This indicates the current version of `vexctl` on your working machine. You are ready to proceed with working with `vexctl`. ## Creating VEX documents With `vexctl`, VEX data can be created to a file on disk, or it can be captured in a signed attestation that can be attached to a container image. You can create a VEX document by using the `vexctl create` command. For example, to create a VEX document with a single statement asserting that the [WolfiOS](https://github.com/wolfi-dev/) package `git-2.38.1-r0` is not affected by a given common vulnerability and exposure (CVE) — let's say, `CVE-2014-123456` — because it has already been mitigated in the distribution, you can run the following. ```sh vexctl create --product="pkg:apk/wolfi/git@2.38.1-r0?arch=x86_64" \ --vuln="CVE-2014-123456" \ --status="not_affected" \ --justification="inline_mitigations_already_exist" ``` This command notes the following: * The software product — `product` — in this case a Wolfi package * The vulnerability — `vuln` — in this case a specific CVE * The current status — `status` — which can be `not_affected`, `affected`, `fixed`, or `under_investigation` * When the `status` is noted as `not_affected`, the reason for the status — `justification` — must be included, and can read `inline_mitigations_already_exist` or `component_not_present` The `vexctl create` command renders a document similar to the following. The `@id` and timestamps will differ on each run. ```json { "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://openvex.dev/docs/public/vex-b6081638d51cd5cdf2e810d120353dd48d3c829c169123df3037979ec824df94", "author": "Unknown Author", "version": 1, "statements": [ { "vulnerability": { "name": "CVE-2014-123456" }, "products": [ { "@id": "pkg:apk/wolfi/git@2.38.1-r0?arch=x86_64" } ], "status": "not_affected", "justification": "inline_mitigations_already_exist", "timestamp": "2026-07-27T15:46:08.152188473Z" } ], "timestamp": "2026-07-27T15:46:08Z" } ``` You can also create a VEX document with abbreviated information. For instance, when a given CVE was addressed in the image and you want to attest that it has been fixed. ```sh vexctl create "pkg:apk/wolfi/git@2.39.0-r1?arch=x86_64" CVE-2023-12345 fixed ``` The above workflow demonstrates how to create a VEX document with `vexctl` on the command line. ## Merging existing VEX documents When more than one stakeholder is issuing VEX metadata about a piece of software, `vexctl` can merge the documents to get the most up-to-date impact assessment of a vulnerability. Let's begin with two test documents. You can create these two test documents with a CLI editor such as nano. The first document is `document1.vex.json`: ```json { "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://openvex.dev/docs/public/vex-0f3be8817faafa24e4bfb3d17eaf619efb1fe54923b9c42c57b156a936b91431", "author": "John Doe", "role": "Senior Trusted VEX Issuer", "version": 1, "statements": [ { "vulnerability": { "name": "CVE-1234-5678" }, "products": [ { "@id": "pkg:apk/wolfi/bash@1.0.0" } ], "status": "under_investigation", "timestamp": "2023-12-05T05:04:34.77929922Z" } ], "timestamp": "2023-12-05T05:04:34.77929844Z" } ``` The second document is `document2.vex.json`: ```json { "@context": "https://openvex.dev/ns/v0.2.0", "@id": "https://openvex.dev/docs/public/vex-3cd938c9a706eba0915883640116cfe813f7d59150cf758b8c869b4926a7cf11", "author": "John Doe", "role": "Senior Trusted VEX Issuer", "version": 1, "statements": [ { "vulnerability": { "name": "CVE-1234-5678" }, "products": [ { "@id": "pkg:apk/wolfi/bash@1.0.0" } ], "status": "fixed", "timestamp": "2023-12-05T05:06:38.099731287Z" } ], "timestamp": "2023-12-05T05:06:38.099730576Z" } ``` The two files are generated from a known rule set, also known as "golden data" or a "golden file," which is reused and reapplied to new releases of the same project. We can merge the two VEX documents with the `vexctl merge` command: ```sh vexctl merge --product=pkg:apk/wolfi/bash@1.0.0 \ document1.vex.json \ document2.vex.json ``` The resulting document combines the VEX statements that express data about `bash@1.0.0` into a single document. ```json { "@context": "https://openvex.dev/ns/v0.2.0", "@id": "merged-vex-6b33f63783819578804350ba1cde277ddd8f90aa2bbcb706d19a61d211a8443f", "author": "Unknown Author", "version": 1, "statements": [ { "vulnerability": { "name": "CVE-1234-5678" }, "products": [ { "@id": "pkg:apk/wolfi/bash@1.0.0" } ], "status": "under_investigation", "timestamp": "2023-12-05T05:04:34.77929922Z" }, { "vulnerability": { "name": "CVE-1234-5678" }, "products": [ { "@id": "pkg:apk/wolfi/bash@1.0.0" } ], "status": "fixed", "timestamp": "2023-12-05T05:06:38.099731287Z" } ], "timestamp": "2026-07-27T15:46:08Z" } ``` This final document tells the whole story of how `CVE-1234-5678` was `under_investigation` and then `fixed` about two minutes later, all documented in a single VEX file that was merged with `vexctl`. The merged document preserves each statement's original timestamp, so the order of events is retained. ## Attesting and attaching VEX documents To attest to and attach VEX statements within a given document to a container image, you can use the `vexctl attest` command with the `--attach` and `--sign` flags. For example, if you have a container image `your-username/your-container-image:latest` in a container registry, and a related VEX document `hello.vex.json`, you can run the following command to attest to that document, attach the document and sign that attestation. If you want to try this example, make sure to replace `your-username/your-container-image:latest` with the path to your container. ```sh vexctl attest --attach --sign hello.vex.json your-username/your-container-image:latest ``` Upon running this command, you'll be taken through a signing workflow with [Sigstore](https://www.sigstore.dev/). Your terminal output will indicate your progress. ```output Generating ephemeral keys... Retrieving signed certificate... ``` A browser window will open for you to select an OIDC provider. When the attestation is complete, you'll receive feedback that it was successful. ```output Successfully verified SCT... {"payloadType":"application/vnd.in-toto+json","payload":"e...o=","signatures":[{"keyid":"","sig":"MEY...z"}]} ``` This attestation with `.att` extension will now live in the container registry as an attachment to your container. ## Chronology and VEX documents Assessing the impact of CVEs on a software product is a process that takes time and the status will change over time. VEX is designed to communicate with users as the status changes, and there may therefore be multiple VEX documents associated with a product. To understand how this may work in practice, below is an example timeline for the VEX documents associated with a given product and CVE. 1. The software product _Linky App_ becomes aware of `CVE-2014-123456`, associated with one of its components. 2. _Linky App_ developers issue a VEX data file with a status of `under_investigation` to inform their users that they are aware of the CVE, but are reviewing whether it has an impact on _Linky App_. 3. After investigation, the developers determine the CVE has no impact on _Linky App_ because the vulnerable function in the component is never executed. 4. The developers issue a second VEX document with a status of `not_affected` using the `vulnerable_code_not_in_execute_path` justification. When analyzing the VEX documents associated with _Linky App_, `vexctl` will review them chronologically and "replay" the known impact statuses in the order they were found, effectively computing the `not_affected` status. If a SARIF report is formatted as a VEX document with `vexctl`, any entries alerting of `CVE-2014-123456` will be filtered out. ## Learn more The `vexctl` tool is open source, you can review the [`vexctl` repository on GitHub](https://github.com/openvex/vexctl), as well as the [`go-vex` Go library](https://github.com/openvex/go-vex) for generating, consuming, and operating on VEX documents. The following blog posts have some background about VEX and OpenVEX: * [What is OpenVex](https://www.chainguard.dev/supply-chain-security-101/what-is-openvex) * [Putting VEX To Work](https://www.chainguard.dev/unchained/putting-vex-to-work) * [Reflections on Trusting VEX (or when humans can improve SBOMs)](https://www.chainguard.dev/unchained/reflections-on-trusting-vex-or-when-humans-can-improve-sboms) * [Understanding The Promise of VEX](https://www.chainguard.dev/unchained/understanding-the-promise-of-vex) The [OpenVEX Specification](https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md) is owned and steered by the community. You can find the organization page with additional repositories at [openvex.dev](https://openvex.dev). --- ### Rego policies URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/chainguard-enforce-rego-policies.md Last Modified: September 28, 2026 Tags: Policy Controller, Procedural, Policy, Reference, SBOM The [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) supports the [Rego Policy Language](https://www.openpolicyagent.org/docs/latest/policy-language/), which is a declarative policy language that is used to evaluate structured input data such as Kubernetes manifests and JSON documents. This feature enables users to apply policies that can evaluate Kubernetes admission requests and object metadata to make comprehensive decisions about the workloads that are admitted to their clusters. Rego support also enables users to enhance existing cloud-native policies by adding additional software supply chain security checks. If you would like to write a Rego policy from scratch, or learn more about how to use this format, you can follow this guide. ## Rego policy template ```bash # Copyright 2022 Chainguard, Inc. # SPDX-License-Identifier: Apache-2.0 apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: my-rego-policy spec: images: [glob: '**'] authorities: [static: {action: pass}] mode: warn policy: includeSpec: true type: rego data: | package sigstore default isCompliant = false isCompliant { # Rego logic goes here; must evaluate to true for policy to pass } ``` In this policy, you should change the `name` to be meaningful to you. The `spec` fields are defined at [ClusterImagePolicySpec](https://github.com/sigstore/policy-controller/blob/main/docs/api-types/index.md#clusterimagepolicyspec). By default, this policy will apply to all images, as noted with the `glob: '**'` parameter. If we keep this as is, this means that we are evaluating everything running in our cluster. The `authorities` field is used in evaluating image signatures. Since we aren’t using signatures in this policy, we will set it to pass. This will be a common setting in Rego-based policies unless you are also evaluating signatures simultaneously. The policy is being implemented in `warn` mode, which can generate an alert through `CloudEvents` to notify administrators of violations without blocking deployments. You can alternatively use `mode: enforce` to block deployments that violate the policy. The Rego policy itself is defined within the `policy` section. The first requirement is to include the input data that is to be evaluated. By default, the image in the registry is available. To include additional metadata, one of more of the following should be set: * `includeSpec:` allows you to access the fields in the `spec` portion of the Kubernetes manifest, including the container configuration, image names, replicas, resources, and more. * `includeObjectMeta:` allows you to access the fields in the `metadata:` portion of the manifest, including the object’s name and labels. * `includeTypeMeta:` allows access to the top level fields in the manifest, such as the `kind` and `apiVersion`. * `fetchConfigFile:` fetches the OCI config file from the registry, which contains metadata about the image in the registry. Rego policies must specify `type: rego` and the `data` field must contain `package sigstore`. For the policy to pass, the `isCompliant` field must evaluate to `true` within the curly braces. The `isCompliant` Boolean is set to `false` by default, and the logic in the braces must flip the boolean to `true` for the policy to pass. If you define multiple conditions within the `isCompliant` braces, these can be combined using the `AND` keyword to the Boolean logic, meaning that each condition must pass for `isCompliant` to resolve to `true`. You can also define multiple evaluations (meaning, multiple sets of `isCompliant` braces) in the same policy. You would combine these in your policy with the `OR` keyword, meaning that if _any_ of the stated conditions evaluate to `true`, then the `isCompliant` Boolean will _also_ be `true`. This same structure must be present in all Rego-based policies. ## Rego policy to check metadata labels You can set a Rego policy to ensure that it is compliant with certain labels within your metadata. For example, within the production environment (with the "production" label) you can ensure that the compliance team is the approver. ```sh isCompliant { input.metadata.labels.env == "production" input.metadata.labels.approved-by == "compliance-team" } ``` Here, the policy is requiring and checking that the labels exist in the `ObjectMeta` data. This policy will evaluate to true only if both labels exist in the metadata portion of the manifest. ## Rego policy to check Kubernetes pod security As a cluster-level resource, a Kubernetes Pod Security Policy allows a cluster administrator to control security-sensitive aspects of a Pod's specification. This defines a set of conditions that a Pod must meet so that it can be allowed into the cluster. You can think of it as a built-in admission controller which enforces security policies on Pods across a cluster. This policy checks to make sure our Pod security specifications are properly set. ```sh isCompliant { input.spec.hostNetwork == "false" input.spec.hostPID == "false" input.spec.hostIPC == "false" } ``` Here, `hostNetwork` refers to the host's networking namespace, `hostPID` refers to the host process ID namespace, and `hostPIC` refers to the IPC (interprocess communication) namespace. This policy will pass if all the restricted values are set to `false`. ## Rego Policy that Disallows Specified Images In some cases, you may want to evaluate an item elsewhere in the manifest, such as an image source that is included within the container specs of the same manifest. For example, a manifest may have a snippet with a disallowed NGINX image from Docker Hub: ```sh spec: containers: - name: "your-container-name" image: nginx:latest - name: "another-container-name" image: nginx ``` In this case, within the `policy` section of your Rego policy, you’ll need to iterate over the image array and check all the relevant fields for the restricted value. You can use the `[_]` syntax to iterate through the array. You can use the `not` keyword in conjunction with the `contains()` built-in function to evaluate all the items within the array. ```sh isCompliant { result:= input.spec.containers.image[_] not contains(result,"docker.io") } ``` This policy will not admit Pods that come from docker.io. ## Rego Policy that Disallows Privilege Escalation in Pods This example Rego policy will disallow privilege escalation in Pods following the [Kubernetes Pod Security Baseline](https://kubernetes.io/docs/concepts/security/pod-security-standards/) Standard. The Baseline Standard is a minimally restrictive policy which prevents known privilege escalations and allows the default and minimally specified Pod configuration. ```sh isCompliant { filteredContainers = [c | c := input.spec.containers[_]; c.securityContext.allowPrivilegeEscalation == true ] filteredInitContainers = [c | c := input.spec.initContainers[_]; c.securityContext.allowPrivilegeEscalation == true ] filteredEphemeralContainers = [c | c := input.spec.ephemeralContainers[_]; c.securityContext.allowPrivilegeEscalation == true ] (count(filteredContainers) + count(filteredInitContainers) + count(filteredEphemeralContainers)) == 0 } ``` Setting the `allowPrivilegeEscalation` Boolean controls whether a process can gain more privileges than its parent process. This value will evaluate to `true` when the container is run as privileged. You can review more information about [how to configure a security context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) on the Kubernetes docs. This Rego policy shows a method of declaring a variable and using it to count up all the instances of privilege escalation across Pod types, and evaluate that the final count is `0` in order for the policy to pass. ## Rego Policy that Checks Maximum Age of Images This example Rego policy checks the maximum age (in days) allowed for an image running in your cluster. Policy Controller measures this through the `created` field of a container image's configuration. This ensures that your images are regularly updated and maintained. Note that some build tools may fail this check due to using a fixed time (like the Unix epoch) for creation in their reproducible builds. However, many of these tools support specifying `SOURCE_DATE_EPOCH`, which aligns creation time with the date of the source commit. ```sh policy: fetchConfigFile: true type: "rego" data: | package sigstore nanosecs_per_second = 1000 * 1000 * 1000 nanosecs_per_day = 24 * 60 * 60 * nanosecs_per_second # Change this to the maximum number of days you would like to allow maximum_age = 30 * nanosecs_per_day default isCompliant = false isCompliant { created := time.parse_rfc3339_ns(input.config[_].created) time.now_ns() < created + maximum_age } ``` Here, the policy defines a variable for the `maximum_age`, in this case set to `30`, which you can change to the number of days old you would permit an image to be. Within the `isCompliant` braces, the Rego policy leverages `time` to evaluate whether the current time is less than the maximum allowed age. To review the different methods of implementing `time` within Rego, review the [Time reference documentation](https://www.openpolicyagent.org/docs/latest/policy-reference/#time). ## Rego Policies that Define Custom Error and Warning Messages Rego policies have the added benefit of allowing you to define custom error and warning messages. This example `attestations` block requires clusters to have a vulnerability report in order to be deemed compliant. Notice, though, that it also defines an `errorMsg` string. ``` attestations: - name: must-have-vuln-report predicateType: vuln policy: type: rego data: | package sigstore isCompliant[response] { result = (input.predicateType == "chainguard.dev/attestation/vuln/v1") errorMsg = "Not found expected predicate type 'chainguard.dev/attestation/vuln/v1'" warnMsg = "" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } } ``` Here, the custom error message reads `Not found expected predicate type 'chainguard.dev/attestation/vuln/v1'`. Rather than returning the default error, Policy Controller will return this string as a custom error message. Notice, too, that the previous example defines a `warnMsg` variable. Policy Controller will only return a warning message to the caller if the policy in question is in `warn` mode, so in that case it was left as an empty string. The following `attestations` block is similar to the previous one, but this time it defines the `warnMsg` variable to be used as a custom warning message. ``` attestations: - name: must-have-vuln-report predicateType: vuln policy: type: rego data: | package sigstore isCompliant[response] { result = (input.predicateType == "cosign.sigstore.dev/attestation/vuln/v1") errorMsg = "" warnMsg = "WARNING: Found an attestation with predicate type 'cosign.sigstore.dev/attestation/vuln/v1'" response := { "result" : result, "error" : errorMsg, "warning" : warnMsg } } ``` Defining custom error and warning messages with Rego can help with troubleshooting, as they can explain specific policy issues that otherwise may not be clearly understandable. ## Learn More To understand more about the Rego policy format, you can review the [Rego Policy Reference](https://www.openpolicyagent.org/docs/latest/policy-reference/) which includes details on assignment and equality, arrays, objects, sets, and rules. --- ### How to install the Rekor CLI URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/rekor/how-to-install-rekor.md Last Modified: September 28, 2026 Tags: Rekor, Procedural _An earlier version of this material was published in the [Rekor chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@e785fae1be184e2c929db62dbe7444fa/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@a48c33126e2c4ee6ad3bfa6b7bc9c957) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ Follow this tutorial for an overview of how to install `rekor-cli`. To install the Rekor command line interface (rekor-cli) with Go, you will need Go version 1.16 or greater. For Go installation instructions, refer to the [official Go documentation](https://go.dev/doc/install). If you have Go installed already, you can check your Go version via this command. ```sh go version ``` If Go is installed, you'll receive output similar to the following. ``` go version go1.13.8 linux/amd64 ``` You will also need to set your `$GOPATH`, the location of your Go workspace. ```sh export GOPATH=$(go env GOPATH) ``` You can then install `rekor-cli`: ```sh go install -v github.com/sigstore/rekor/cmd/rekor-cli@latest ``` Check that the installation of `rekor-cli` was successful using the following command: ```sh rekor-cli version ``` You should receive output similar to that of below: ``` GitVersion: v0.4.0-59-g2025bf8 GitCommit: 2025bf8aa50b368fc3972bb276dfeae8b604d435 GitTreeState: clean BuildDate: '2022-01-26T00:20:33Z' GoVersion: go1.17.6 Compiler: gc Platform: darwin/arm64 ``` Now that you have the Rekor CLI tool successfully installed, you can start working with it. --- ### How to generate a Fulcio certificate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/fulcio/how-to-generate-a-fulcio-certificate.md Last Modified: September 28, 2026 Tags: Fulcio, Procedural _An earlier version of this material was published in the [Fulcio chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@2fbe6328019c4b1fbf934bd3bfb7e308/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@1f71fcbe8219471fb82e25731b18be11) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ In this tutorial, we are going to create and examine a Fulcio certificate to demonstrate how Fulcio can work in practice. To follow along, you will need Cosign installed on your local system. If you haven't installed Cosign yet, you can follow the instructions described in [How to install Cosign](/open-source/sigstore/cosign/how-to-install-cosign/), or you can follow one of the installation methods described in the [official documentation](https://docs.sigstore.dev/cosign/system_config/installation/). Please note that using Cosign requires Go v1.16 or higher. The Go Project provides [official download instructions](https://go.dev/doc/install). To get started, place some text in a text file. For instance: ```sh echo "test file contents" > test-file.txt ``` Next, let’s generate a key pair with Cosign: ```sh cosign generate-key-pair ``` Enter and confirm a password after running this command. Then, use Cosign to sign this test-file.txt, outputting a Fulcio certificate named “fulcio.crt.base64”. The sign-blob subcommand allows Cosign to sign a blob. This command will open a browser tab and will require you to sign in through one of the OIDC providers: GitHub, Google, or Microsoft. This step represents the user proving their identity. ```sh cosign sign-blob test-file.txt --output-certificate fulcio.crt.base64 --output-signature fulcio.sig ``` After authentication, you can close the browser tab. In your terminal, you will receive output similar to this: ``` Using payload from: test-file.txt Generating ephemeral keys... Retrieving signed certificate... Note that there may be personally identifiable information associated with this signed artifact. This may include the email address associated with the account with which you authenticate. This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later. By typing 'y', you attest that you grant (or have permission to grant) and agree to have this information stored permanently in transparency logs. Are you sure you would like to continue? [y/N] y ``` If you agree, enter `y` and continue. You will receive output like this: Your browser will now be opened to: https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=... Successfully verified SCT... using ephemeral certificate: -----BEGIN CERTIFICATE----- (...) -----END CERTIFICATE----- tlog entry created with index: 2494952 Signature wrote in the file fulcio.sig using ephemeral certificate: -----BEGIN CERTIFICATE----- (...) -----END CERTIFICATE----- Certificate wrote in the file fulcio.crt.base64 ``` The output indicates that Sigstore is using ephemeral keys to generate a certificate for `test-file.txt`. The certificate, which we'll verify in the next section, is saved to a file named `fulcio.crt.base64`. --- ### How to inspect and verify Fulcio certificates URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/fulcio/how-to-inspect-and-verify-fulcio-certificates.md Last Modified: September 28, 2026 Tags: Fulcio, Procedural _An earlier version of this material was published in the [Fulcio chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@2fbe6328019c4b1fbf934bd3bfb7e308/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@1f71fcbe8219471fb82e25731b18be11) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ To inspect a certificate generated by Fulcio, we will first decode it with the `base64` command line tool, which is used for encoding and decoding binary to text. Base64 is widely used on the world wide web for binary-to-text encoding. You can check whether the tool is installed by checking whether `base64 --help` will run. If not, install Base64 with the package manager of your choice, such as apt or [Homebrew for macOS](https://formulae.brew.sh/formula/base64). We will also use a third-party tool called `step` to inspect the decoded certificate. To install `step`, which is a tool related to public key infrastructure workflows, follow the instructions from their [official documentation](https://smallstep.com/docs/step-cli/installation). In addition to having `base64` and `step` installed on your machine, you should also have Cosign installed, which you can achieve by following the instructions described in [How to install Cosign](/open-source/sigstore/cosign/how-to-install-cosign/). With these prerequisites in place, you are ready to begin. First, we'll decode the certificate with Base64. If you don't have a certificate ready to inspect, you can generate one by following [How to generate a Fulcio certificate](/open-source/sigstore/fulcio/how-to-generate-a-fulcio-certificate/). ```sh base64 -d < fulcio.crt.base64 > fulcio.crt ``` Then, inspect the certificate using `step`’s `inspect` command. ```sh step certificate inspect fulcio.crt ``` A sample output is below. Pay attention especially to the `x509v3 Subject Alternative Name` field, which is the e-mail associated with the party that created the signature and the issuer, which is Sigstore. The ten minute time window of validity also details the period of time for which the signature is valid. ``` Certificate: Data: Version: 3 (0x2) Serial Number: 445971695346061852979091305347141417164194935 (0x13ff8105719cba6ad0caa5ce9f34603ce9c477) Signature Algorithm: ECDSA-SHA384 Issuer: O=Sigstore.dev,CN=sigstore Validity Not Before: Mar 24 20:14:37 2022 UTC Not After : Mar 24 20:24:36 2022 UTC Subject: Subject Public Key Info: Public Key Algorithm: ECDSA Public-Key: (256 bit) X: 4b:fc:7d:9c:4a:56:30:75:67:fd:d6:1f:a6:f3:05: 04:ff:c8:ad:c6:2c:5f:ea:59:f9:ed:07:fa:c2:ae: 04:19 Y: 15:44:38:f3:77:87:63:91:0c:08:b6:4f:ca:67:36: 3f:38:dc:fc:bc:07:5c:8f:ec:d3:b2:31:66:a8:3d: fa:98 Curve: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: Code Signing X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: 5A:F0:DE:DA:CF:D0:73:F1:5A:88:B2:9F:8E:03:5F:51:6E:8C:57:19 X509v3 Authority Key Identifier: keyid:58:C0:1E:5F:91:45:A5:66:A9:7A:CC:90:A1:93:22:D0:2A:C5:C5:FA X509v3 Subject Alternative Name: critical email:email@example.com 1.3.6.1.4.1.57264.1.1: https://github.com/login/oauth Signature Algorithm: ECDSA-SHA384 30:65:02:31:00:98:00:17:7a:98:f2:d4:89:05:d2:7a:91:93: 73:92:e6:3f:9d:69:a5:7c:28:9f:60:72:29:e3:b7:d3:5e:2f: 1a:00:35:99:4f:92:da:02:cd:ec:83:49:f3:27:3a:39:21:02: 30:04:a6:0c:42:a4:38:d9:ac:da:8f:b5:2f:4c:f5:ad:4b:d4: c6:7d:8b:43:46:91:c1:9d:80:43:44:a9:26:26:26:0f:cf:e2: ab:aa:ef:6d:ec:1c:28:df:d3:ac:aa:fd:1b ``` We will then verify the certificate against the Fulcio certificate authority root, by using `step certificate verify` to execute the certificate path validation algorithm for x.509 certificates. ```sh step certificate verify fulcio.crt --roots ~/.sigstore/root/targets/fulcio_intermediate_v1.crt.pem ``` The final command checks the signature in the `fulcio.sig` file, tracing the certificate up to the Fulcio root certificate. You'll need to use the identity flags `--certificate-identity` which corresponds to the email address of the signer, and `--certificate-oidc-issuer` which corresponds to the OIDC provider that the signer used. For example, a Gmail account using Google as the OIDC issuer, will be able to be verified with the following command: ```sh cosign verify-blob test-file.txt \ --signature fulcio.sig \ --cert fulcio.crt.base64 \ --certificate-identity username@gmail.com \ --certificate-oidc-issuer https://accounts.google.com ``` You will receive output following this command. You should receive a `Verified OK` message if the signature, certificate, and identities match. --- ### Getting started with melange URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/build-tools/melange/getting-started-with-melange.md Last Modified: October 8, 2026 Tags: melange, Procedural [melange](https://github.com/chainguard-dev/melange) is an [apk](https://wiki.alpinelinux.org/wiki/Package_management) builder tool that uses declarative pipelines to create apk packages. From a single YAML file, users are able to generate multi-architecture apks that can be injected directly into [apko](https://github.com/chainguard-dev/apko) builds. Understanding melange can help you better understand the [Wolfi](/open-source/wolfi/) operating system and how [Chainguard's container images](/chainguard/containers/) are made to be minimal and secure, but it is not necessary to have a background in melange in order to use Chainguard Containers. In this guide, you'll learn how to build a software package with melange. To demonstrate the versatile combination of melange and apko builds, we'll package a small command-line PHP script and build a minimalist container image based on Wolfi with the generated apk. All files used in this demo are open source and available at the [melange-php-demos](https://github.com/chainguard-dev/melange-php-demos/tree/main/hello-minicli) repository. ## Requirements Our guide is compatible with operating systems that support Docker and shared volumes. Please follow the [appropriate Docker installation instructions](https://docs.docker.com/get-docker/) for your operating system. You won't need PHP or Composer installed on your system, since we'll be using Docker to build the demo app. ### Note for Linux users In order to be able to build apks for multiple architectures using Docker, you may need to register additional QEMU headers within your kernel. This is done automatically for Docker Desktop users, so if you are on macOS you don't need to run this additional step. Run the following command to register the necessary handlers within your kernel, using the [multiarch/qemu-user-static](https://github.com/multiarch/qemu-user-static) image. ```shell docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ``` You should now be able to build apks for all architectures supported by melange. ## 1 — Downloading the melange image The fastest way to get melange up and running on your system is by using the [official melange image](https://images.chainguard.dev/directory/image/melange/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-open-source-build-tools-melange-getting-started-with-melange) with Docker. Start by pulling the melange image into your local system: ```shell docker pull cgr.dev/chainguard/melange:latest ``` This will download the latest version of the melange image, which is rebuilt every night for extra freshness. Check that you're able to run melange with `docker run`. ```shell docker run --rm cgr.dev/chainguard/melange version ``` You should get output similar to the following: ```output __ __ _____ _ _ _ _ ____ _____ | \/ | | ____| | | / \ | \ | | / ___| | ____| | |\/| | | _| | | / _ \ | \| | | | _ | _| | | | | | |___ | |___ / ___ \ | |\ | | |_| | | |___ |_| |_| |_____| |_____| /_/ \_\ |_| \_| \____| |_____| melange GitVersion: v0.11.1 GitCommit: a52edcc075ebf1dc89aea87893e3821944171ee3 GitTreeState: clean BuildDate: '2024-07-19T16:04:17Z' GoVersion: go1.22.5 Compiler: gc Platform: linux/amd64 ``` With melange installed, you’re ready to proceed. ## 2 — Cloning the demo repository To demonstrate melange's features with a minimalist application that has real-world functionality, our demo consists of a PHP command line app that queries the [Slip advice](https://api.adviceslip.com/) API and outputs a random piece of advice. The app is a single-file script built with [Minicli](https://github.com/minicli). Start by cloning the demo repository to your local machine and navigating to the `melange-php-demos/hello-minicli` directory: ```shell git clone git@github.com:chainguard-dev/melange-php-demos.git cd melange-php-demos/hello-minicli ``` Run the following command, which will use the official Composer image to generate a composer.json file and download minicli/minicli: ```shell docker run --rm -it -v "${PWD}":/app composer require minicli/minicli ``` Once you receive confirmation that the download was completed, you'll need a second dependency to query the Advice Slip API. Run the following command to include `minicli/curly`, a `curl` wrapper for Minicli: ```shell docker run --rm -it -v "${PWD}":/app composer require minicli/curly ``` Now you can run the application to make sure it's functional. You can do that using Docker and Chainguard's PHP image: ```shell docker run --rm -it -v "${PWD}":/app cgr.dev/chainguard/php /app/minicli advice ``` You should get a random piece of advice such as: ``` Gratitude is said to be the secret to happiness. ``` With the application ready, you can start building your package. ## 3 — The melange YAML file The `melange.yaml` file is where you declare the details and specifications of your apk package. For code that generates self-contained binaries, this is typically where you'll build your application artifacts with compiler tools. In the case of interpreted languages, you'll likely build your application by downloading vendor dependencies, setting up relevant paths, and setting the environment up for production. The melange specification file contains three main sections: - **package**: defines package specs, such as name, license, and runtime dependencies. Runtime dependencies will be brought into the system automatically as dependencies when the apk is installed. - **environment**: defines how the environment should be prepared for the build, including required packages and their source repositories. Anything that is only required at build time goes here, and shouldn't be part of the runtime dependencies. - **pipeline**: defines the build pipeline for this package. One of the best advantages of using melange is to be able to control all steps of your build pipeline, and include only what's necessary. This way, you'll be able to build smaller and more secure container images by removing unnecessary dependencies. This is what the `melange.yaml` included in our demo looks like, for your reference: ```yaml package: name: hello-minicli version: 0.1.0 description: Minicli melange demo target-architecture: - all copyright: - license: MIT dependencies: runtime: - php - php-curl environment: contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub - ./melange.rsa.pub repositories: - https://packages.wolfi.dev/os packages: - ca-certificates-bundle - busybox - curl - git - php - php-phar - php-iconv - php-openssl - php-curl - composer pipeline: - name: Build Minicli application runs: | MINICLI_HOME="${{targets.destdir}}/usr/share/minicli" EXEC_DIR="${{targets.destdir}}/usr/bin" mkdir -p "${MINICLI_HOME}" "${EXEC_DIR}" cp ./composer.json "${MINICLI_HOME}" /usr/bin/composer install -d "${MINICLI_HOME}" --no-dev cp ./minicli "${EXEC_DIR}" chmod +x "${EXEC_DIR}/minicli" ``` Our build pipeline will set up two distinct directories, separating the application dependencies from its executable entry point. The executable `minicli` script will be copied into `/usr/bin`, while the vendor files will be located at `/usr/share/minicli`. ## 4 — Building the minicli apk with melange Before building the package, you'll need to create a temporary keypair to sign it. You can use the following command for that: ```shell docker run --rm -v "${PWD}":/work cgr.dev/chainguard/melange keygen ``` This will generate a `melange.rsa` and `melange.rsa.pub` files in the current directory.

Note: melange doesn’t require you to sign packages, but signing is recommended: it lets people and automated systems verify who built a package and that it hasn’t been tampered with. Some tools refuse unsigned packages. By default, apko fails any build that references an unsigned package.

``` 2024/08/01 16:55:31 INFO generating keypair with a 4096 bit prime, please wait... 2024/08/01 16:55:33 INFO wrote private key to melange.rsa 2024/08/01 16:55:33 INFO wrote public key to melange.rsa.pub ``` Next, build the apk defined in the `melange.yaml` file with the following command: ```shell docker run --privileged --rm -v "${PWD}":/work \ cgr.dev/chainguard/melange build melange.yaml \ --arch amd64,aarch64 \ --signing-key melange.rsa ``` This will set up a volume sharing your current folder with the location `/work` inside the container. We'll build packages for `amd64` and `aarch64` platforms and sign them using the `melange.rsa` key created in the previous command. When the build is finished, you should be able to find a `packages` folder containing the generated apks (and associated apk index files): ``` packages ├── aarch64 │   ├── APKINDEX.json │   ├── APKINDEX.tar.gz │   └── hello-minicli-0.1.0-r0.apk └── x86_64 ├── APKINDEX.json ├── APKINDEX.tar.gz └── hello-minicli-0.1.0-r0.apk 3 directories, 6 files ``` You have successfully built a multi-architecture software package with melange! ## 5 — Building a container image with apko With the apk packages and apk index in place, you can now build a container image and have your apk(s) installed within it. The following `apko.yaml` file will create a container image tailored to the application we built in the previous steps. Because we defined the PHP dependencies as runtime dependencies within the apk, you don't need to require these packages again here. The container entrypoint command will be set to `/usr/bin/minicli`, where the application executable is located. One important thing to note is how we reference the `hello-minicli` apk as a local package within the `repositories` section of the YAML file. The `@local` notation tells apko to search for apks in the specified directory, in this case `/work/packages`. This is what the `apko.yaml` file included in our demo looks like, for your reference: ```yaml contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub - ./melange.rsa.pub repositories: - https://packages.wolfi.dev/os - '@local /work/packages' packages: - wolfi-base - ca-certificates-bundle - hello-minicli@local accounts: groups: - groupname: nonroot gid: 65532 users: - username: nonroot uid: 65532 run-as: 65532 entrypoint: command: /usr/bin/minicli advice ``` The following command will set up a volume sharing your current folder with the location `/work` in the apko container, running the `apko build` command to generate an image based on your `apko.yaml` definition file. ```shell docker run --rm --workdir /work -v ${PWD}:/work cgr.dev/chainguard/apko \ build apko.yaml hello-minicli:test hello-minicli.tar --arch host ``` This will build an OCI image based on your host system's architecture (specified by the `--arch host` flag). If you receive warnings at this point, those are likely related to the types of SBOMs being uploaded and can be safely ignored. The command will generate a few new files in the app's directory: - `hello-minicli.tar` — the packaged OCI image that can be imported with a `docker load` command - `sbom-%host-architecture%.spdx.json` — an SBOM file for your host architecture in `spdx-json` format Next, load your image within Docker: ```shell docker load < hello-minicli.tar ``` ``` 7cbaefdf1c30: Loading layer 13.7MB/13.7MB Loaded image: hello-minicli:test-%host-architecture% ``` Note that the `%host-architecture%` will vary, and there may be multiple images loaded into your Docker daemon. Be sure to edit the variable in the following `docker run` command to match your target architecture. Now you can run your Minicli program with: ```shell docker run --rm hello-minicli:test-%host-architecture% ``` The demo should output an advice slip such as: ``` Only those who attempt the impossible can achieve the absurd. ``` You have successfully built a minimalist container image with your apk package installed on it. This image is fully [OCI](https://opencontainers.org/) compatible and can be signed with [Cosign](/open-source/sigstore/cosign/how-to-sign-a-container-with-cosign/) for provenance attestation. ## Package sources and pipelines melange works with apk-based systems, including Alpine, but you can't mix Alpine and Wolfi packages in the same build environment. A pipeline step can reuse another pipeline with `uses:`. melange ships built-in pipelines, such as `fetch` and `autoconf/make`, in the [`pkg/build/pipelines`](https://github.com/chainguard-dev/melange/tree/main/pkg/build/pipelines) directory of its repository. To use pipelines of your own, put them in a directory and pass it to `melange build` with the `--pipeline-dir` flag. ## Conclusion In this guide, we packaged a PHP command-line app with melange. We also built a container image to install and run our custom apk, using the apko tool. For more information about apko, check our [Getting started with apko](/open-source/build-tools/apko/getting-started-with-apko/) guide. The demo files are available at the [melange-php-demos](https://github.com/chainguard-dev/melange-php-demos) repository, in the `hello-minicli` subfolder. For additional information on how to debug your builds and other features, check the [melange](https://github.com/chainguard-dev/melange) and [apko](https://github.com/chainguard-dev/apko) repositories on GitHub. --- ### How to install Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/how-to-install-cosign.md Last Modified: September 28, 2026 Tags: Cosign, Procedural _An earlier version of this material was published in the [Cosign chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@204b98f35bca48c194d1868e0356bef1/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2f0ad9cb8f124a39ab555ac8bf1a114c) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ Cosign supports software artifact signing, verification, and storage in an OCI (Open Container Initiative) registry. By signing software, you can authenticate that you are who you say you are, which can in turn enable a trust root so that developers and consumers who leverage your software can verify that you created the software artifact that you have said you’ve created. They can also ensure that that artifact was not tampered with by a third party. As someone who may use software libraries, containers, or other artifacts as part of your development lifecycle, a signed artifact can give you greater assurance that the code or container you are incorporating is from a trusted source. There are a few different ways to install Cosign to your local machine or remote server. The approach you choose should be based on the way you set up packages, the tooling that you use, or the way that your organization recommends. We will go through several options. Please refer to the [official Cosign installation documentation](https://docs.sigstore.dev/cosign/system_config/installation/) for additional context and updates. ## Installing Cosign with Homebrew or Linuxbrew Those who are running macOS locally may be familiar with Homebrew as a package manager. There is also a [Linuxbrew](https://docs.brew.sh/Homebrew-on-Linux) version for those running a Linux distribution. If you are using macOS and would like to leverage a package manager, you can review the [official documentation to install Homebrew](https://brew.sh/) to your machine. To install Cosign with Homebrew, run the following command: ```sh brew install cosign ``` To update Cosign in the future, you can run `brew upgrade cosign` to get the newest version. ## Installing Cosign with Linux package managers Cosign is supported by the Arch Linux, Alpine Linux, and Nix package managers. You can check to ensure that Cosign is successfully installed using the `cosign version` command following installation. When you run the command, you should receive output that indicates the version you have installed. ### Arch Linux To install Cosign on Arch Linux, use the `pacman` package manager: ```sh pacman -S cosign ``` ### Alpine Linux If you are using Alpine Linux or an Alpine Linux image, you can add Cosign with `apk`: ```sh apk add cosign ``` ### NixOS For NixOS, you can install Cosign with the following command: ```sh nix-env -iA nixpkgs.cosign ``` And for NixOS Linux, you can install Cosign using `nixos.cosign` with the `nix-env` package manager: ```sh nix-env -iA nixos.cosign ``` ### `dpkg` and `rpm` On the [releases page](https://github.com/sigstore/cosign/releases), you’ll also find `.deb` and `.rpm` packages for manual download and installation. Both examples in this section assume you've found the latest version of Cosign and set it to an environment variable named `LATEST_VERSION`: ```sh LATEST_VERSION=$(curl https://api.github.com/repos/sigstore/cosign/releases/latest | grep tag_name | cut -d : -f2 | tr -d "v\", ") ``` For Ubuntu and Debian distributions, download and install the latest `.deb` package: ```sh curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign_${LATEST_VERSION}_amd64.deb" sudo dpkg -i cosign_${LATEST_VERSION}_amd64.deb ``` For CentOS and Fedora, download the latest `.rpm` package from the releases page and install Cosign with: ```sh curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-${LATEST_VERSION}-1.x86_64.rpm" sudo rpm -ivh cosign-${LATEST_VERSION}-1.x86_64.rpm ``` ## Installing Cosign with Go You can install Cosign using the Go package manager. Installing with Go will work across different operating systems and distributions. First, check that you have Go installed on your machine, and ensure that it is Go version `1.22.7` or later: ```sh go version ``` ```output go version go1.25.4 linux/amd64 ``` If you run into an error or don’t receive output like the above, you’ll need to install Go in order to install Cosign with Go. Navigate to the official Go website in order to download the appropriate version of Go for your machine. With Go installed, you are ready to install Cosign using the following command: ```sh go install github.com/sigstore/cosign/v3/cmd/cosign@latest ``` The resulting binary from this installation will be placed at `$GOPATH/bin/cosign`. ### Installing a Cosign release with Go You can install Cosign with Go directly from the [Cosign GitHub releases page](https://github.com/sigstore/cosign/releases). At the time of writing, the newest release is [v3.1.3](https://github.com/sigstore/cosign/releases/tag/v3.1.3). You can download this version with the following command: ```sh go install github.com/sigstore/cosign/v3/cmd/cosign@v3.1.3 ``` The resulting binary from this installation will be placed at `$GOPATH/bin/cosign`. Check the [Cosign GitHub releases page](https://github.com/sigstore/cosign/releases) for additional releases. ## Installing Cosign with the Cosign binary Installing Cosign via its binary offers you greater control over your installation, but this method also requires you to manage your installation yourself. In order to install via binary, check for the most updated version in the open source GitHub repository for Cosign under the [releases page](https://github.com/sigstore/cosign/releases). You can use the `curl` command to install the most recent binary: ```sh curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" ``` Next, move the Cosign binary to your bin folder: ```sh sudo mv cosign-linux-amd64 /usr/local/bin/cosign ``` Finally, update permissions so that Cosign can execute within your filesystem: ```sh sudo chmod +x /usr/local/bin/cosign ``` You’ll need to ensure that you keep Cosign up to date if you install via binary. You can always later opt to use a package manager to update Cosign in the future. --- ### Updating container images with Renovate (and no PATs!) URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/octo-sts/updating-container-images-with-renovate.md Last Modified: October 2, 2026 Tags: octo-sts, Renovate, Video, Tutorial, GitHub Actions In this video, Developer Relations Engineer Adrian Mouat shows you how you can update container images using Renovate with Octo STS, eliminating the need for GitHub Personal Access Tokens. ## Video [Watch the video on YouTube](https://www.youtube.com/watch?v=I0hWRMtdUyI) ## What you'll learn - How to set up Renovate as a GitHub Action - Using Octo STS to eliminate Personal Access Tokens - Configuring trust policies for automated workflows - Setting up assumable identities for private registries - Automating container image and GitHub Actions updates ## Transcript In this video, I'm going to show you how you can use Renovate to update container images and GitHub actions. At Chainguard, we do talk a lot about the need to keep software up to date. And in my opinion, it's essential you do that for both security and maintainability. So, if you stay on an old version of a package for too long, there's a strong chance that you'll be bitten by an unpatched vulnerability. But also, the longer you stay on an old version, the harder updating becomes. So, it's much better to do frequent small updates than it is to occasionally be forced into having to do major breaking updates. Another thing we talk a lot about is getting rid of long-lived tokens. So, long-lived tokens always come up when you look at reports on how cyber attacks occurred. And if you're a current Renovate user, I'm willing to bet that you probably gave it a PAT or personal access token for GitHub. And I'm going to show you how you can get rid of that PAT by using an open-source tool from Chainguard called Octo STS. Do know in this video I'll be focusing on container images and updating container images, not packages. So the FROM lines in your Dockerfiles, not the APK add lines. ### Setting up the demo Okay, let's jump to the demo. What I'm going to do is fork this demo application and set up Renovate to run as a GitHub action to update everything. And this demo application's a little bit old, so I know it needs updates to both the Dockerfiles and the GitHub action workflows. There is this version Dockerfile, and this one's interesting as it uses the cgr.dev organization. So that's going to require OAuth. So we'll see how to set up OAuth so Renovate can check out if there's new images available in this repo. Yeah, let's just go ahead and try forking this. So click this big fork button. Could do it at the command line, but I'm here. We're going to call it "production builds renovate" and okay. And then we're going to clone this and use GitHub CLI and we'll jump to the terminal. First thing: `git brief clone`. I also want to set the default. I don't want to push up to the original project right now. So, we want this to be our mode production build with renovate. Good. And now I do have some files we're going to copy over to set up Renovate. ### Configuring Renovate Okay, so here's the `renovate.json` file. This is the configuration file for Renovate. We're basically going to use the default configuration. I have added a few things here. This one I'd really recommend trying: `pinDigests` equals true. That will add digests for any images or actions that don't have digests. And you'll see what that means later. I've added these—this is just so that by default, Renovate will try not to overwhelm you and will only give you a couple of PRs at a time to update things. But we're saying here, hey, give us everything at once. And that's just sort of the purposes of this demo. So, I don't recommend you do that in your own projects. The other thing I should say at this point is we're going to be running Renovate from a GitHub action. There are multiple ways to run Renovate. This is GitHub action on a schedule, which is great for small projects like this. For larger projects, you might want to look at running Renovate self-hosted, so it runs continuously or using a hosted service from the creators at Mend, which is a really great option as well. We have some workflows for building images. There's also an old one for the Jester bot that we're not going to worry about in this talk. Dependabot does do something similar to Renovate and it helps you update image digests. So, do take a look at it if you're interested, but this video is going to focus on Renovate. ### The Renovate workflow And we're going to copy our Renovate workflow over here. So let's take a look at that. Yeah. So, we schedule it to run at 3:00 a.m. every morning. It runs on Ubuntu latest. Because we're doing stuff with the GitHub token, we need to ask for write permissions to ID token. I know "write" sounds a bit weird, but that's the way GitHub does it and write permissions to the repo. So, first thing we do is set up chainguard. This is to auth to the cgr.dev registry so that we can check for new versions of images in the cgr.dev organization. This identity is wrong. It's from a previous workflow. So, I'll show you how to update that in a second. And then we've got Octo STS, which is the magic that allows us to get rid of the personal access token for Renovate. Finally, yeah, down here is basically where we run Renovate. A bit of magic to set up the password for cgr.dev. The Renovate token—that's actually a GitHub token. So maybe not the best named variable ever, but that's what they call it. I quite often have to set log level to debug to figure out what's going on. For this demo, hopefully we'll be able to leave it here. Oh yeah. So that's about the Renovate JSON. Let's jump back. ### Setting up Chainguard identity And what we're going to do is start by setting up chainctl. This is only for people—for Chainguard customers—that need to access a private registry. If you're just using a free tier of Chainguard images, you don't need this because you don't need to auth to the registry. So, what we're going to do now is create something called an assumable identity that the GitHub action can use to access the cgr.dev repo. So what we're doing here is `chainctl create` this identity called `pb-renovate` and it's going to look at the GitHub token and it's going to say, "Is the GitHub repo that's asking for this identity amouat/production-builds-with-renovate?" If so, it's okay. And is the ref the main branch? So only the main branch on this repo can assume this identity. And yeah, here's the project we're using cgr.dev view. Yes. Okay. So, that's created our identity and here is the ID. So, we can now put that into our workflow. So, it's going to replace this one. So, that should be all we need to set up the assumed identity. ### Setting up Octo STS Next thing we're going to look at is setting up Octo STS. So let's jump back to the web browser so I can show you that. So this is the GitHub app page for Octo STS and it describes a bit about what it is, what it does, you know getting rid of personal access tokens, which is exactly what we're doing in this use case. Certain workload trust, trust policies, etc., and federating a token, which is exactly what we're going to be doing here. And some more low-level stuff. Also a link to the Octo STS page where you can go and look at all the source code. It's a relatively small project so you can actually look at the source code and understand what's going on and what's happening. Now up here you'll see "install" in your case, probably—in my case it's "configure" so I can select which repositories can be used with Octo STS and things like that. In this case I've already set it up. So the next thing we do is set up the trust policy for Octo STS. So we'll see here we've got given it this identity "renovate." So what that's going to mean is it's going to look in this directory `.github/chainguard` and it's going to look for a `renovate.sts.yaml` file. And it's going to use the values in that and compare it to the token it's given. So we need to create this value. I'm going to copy an existing file from earlier. And this is what it looks roughly like. Now this is changed. Our repo name's different. So, `production-builds-with-renovate`. Got to make sure you get this right or it will break. Right. So, issuer is the GitHub token issuer and the repo is `amouat/production-builds-renovate` and the refs is the main branch. So what we're saying here is issuer token has to be from GitHub and it has to be for this repo and it has to be for the main branch. You can use regex here. So you can be a bit more flexible in what the matches are. And then we're going to say the permissions we are asking for. So in this case we're asking for quite a lot of permissions, which is all the stuff that Renovate needs for creating pull requests and checking projects etc. And that's about it. ### Testing the setup So, I think the next step is just to check in this code and push it back up to the repo. So, we'll add all these files. If something looked a bit funny there, that was just me using git sign to sign this commit. Okay, there we go. So, I'm going to jump back to the GitHub repo now and see what that looks like. So, here's the repo. I did just push to main, which I know is dangerous, but it's just a demo. Yeah, and you can see it's got this "configure renovate" PR. So, let's take a look in actions. We see a bunch of stuff for building images that was already there, but you've also got this Renovate one there. And I'm very hopeful if we click "run workflow," something will happen. So if you go into it, you can see a bit more of what's happening. We can see Octo STS works. So it got the token. That's really good. It looks like it also got access to cgr.dev. And Renovate is running. It's downloaded the Renovate image. It's looking at Dockerfile, GitHub actions, and Go dependencies. And there we go. So, that's run successfully. Let's go and take a look at what PRs it's created. ### Results Yeah, a whole bunch here. So, I'm extremely happy this has worked. So, we have ones for our actions, for example. So, it's saying we're using an old version of the Cosign installer. We should update that. But it also has things like here we have—you know this version of the static image—we're using an old version. So it's updated that by looking at what is the latest digest for or what's the newest digest for the latest tag. And what else do we have? Where's cgr.dev? Here. So we can see it's updated cgr.dev from 1.25.3 to 1.25.5 and it's pinned it to a digest. So that's both good for security and reproducibility and keeping everything up to date. So I'm really happy what's going on here. One thing to note is I do have 15 PRs here which is a little bit hard to deal with because one problem is if I start merging some of these you'll probably get conflicts and because of that there is this really useful—if you want to rebase, retry this PR, click this checkbox which should kick off another build. Okay, so let's try merging this one. Yeah, it looked like that works. It successfully updated our version of Go here. And we really did it with just a few clicks. So you can see it's really powerful, relatively easy to use once it's set up. We're using it here without the need for a PAT via Octo STS, which makes me very happy indeed. So please try it out. Let me know how you get on. ## Key takeaways - Renovate can automatically update container images and GitHub Actions - Octo STS eliminates the need for Personal Access Tokens by using OIDC federation - Trust policies provide fine-grained control over what workloads can access - Regular small updates are better than occasional large breaking updates - Pinning to digests improves both security and reproducibility ## Related resources - [Set up and use Octo STS](/open-source/octo-sts/set-up-octo-sts/) - [Octo STS GitHub Repository](https://github.com/octo-sts/app) - [Renovate Documentation](https://docs.renovatebot.com/) --- ### How to query Rekor URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/rekor/how-to-query-rekor.md Last Modified: September 28, 2026 Tags: Rekor, Procedural _An earlier version of this material was published in the [Rekor chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@e785fae1be184e2c929db62dbe7444fa/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@a48c33126e2c4ee6ad3bfa6b7bc9c957) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ Rekor is the transparency log of Sigstore, which stores records of artifact metadata. Before querying Rekor, you should have the `rekor-cli` installed, which you can achieve by following the "[How to install the Rekor CLI](/open-source/sigstore/rekor/how-to-install-rekor/)" tutorial. In order to access the data stored in Rekor, the `rekor-cli` requires either the log index of an entry or the UUID of a software artifact. For instance, to retrieve entry number 100 from the public log, use this command: ```sh rekor-cli get --rekor_server https://rekor.sigstore.dev --log-index 100 ``` An abridged version of the output is below: ``` LogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d Index: 100 IntegratedTime: 2021-01-19T19:38:52Z UUID: 2343d145e62b1051b6a2a54582b69a821b13f31054539660a020963bac0b33dc Body: { "RekordObj": { "data": { "hash": { "algorithm": "sha256", "value": "bf9f7899c65cc4decf96658762c84015878e5e2e41171bdb39e6ac39b4d6b797" } }, "signature": { "content": "LS0tL…S0=", "format": "pgp", "publicKey": { "content": "LS…0tLS0=" } } } } ``` The next command will produce the same output but uses the UUID to retrieve the artifact: ```sh rekor-cli get --uuid 2343d145e62b1051b6a2a54582b69a821b13f31054539660a020963bac0b33dc ``` It is also possible to use a web API to return results that are similar to those above. For instance, we can use curl to fetch the same artifact by its UUID with the following query: ```sh curl -X GET "https://rekor.sigstore.dev/api/v1/log/entries/2343d145e62b1051b6a2a54582b69a821b13f31054539660a020963bac0b33dc" ``` By appending the UUID value returned by the `rekor-cli` get command that we ran before, we can obtain detailed information about a specific artifact that has been previously registered within the Rekor public instance. --- ### Troubleshooting melange builds URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/build-tools/melange/troubleshooting.md Last Modified: September 28, 2026 Tags: melange, Troubleshooting ## Debug options To include debug-level information on melange builds, edit your `melange.yaml` file and include `set -x` in your pipeline. You can add this flag at any point of your pipeline commands to further debug a specific section of your build. ```yaml ... pipeline: - name: Build Minicli application runs: | set -x APP_HOME="${{targets.destdir}}/usr/share/hello-minicli" ... ``` ## Common errors When melange is unable to finish a build successfully, you will get an error similar to this: The build could not be completed due to an error at some point of your pipeline. Enable debug by including `set -x` at the beginning of your build pipeline so that you can nail down where the issue occurs. ### Missing QEMU user-space emulation packages Linux users using the Docker melange image may get errors when building packages for other architectures than `x86` and `x86_64`. This won't happen for Docker Desktop users, since the additional architectures are automatically enabled upon installation. To enable additional architectures, you'll need to enable them within your kernel with the following command: ```shell docker run --rm --privileged multiarch/qemu-user-static --reset -p yes ``` An alternate approach to achieve the same result is to run the following command: ```shell docker run --privileged --rm tonistiigi/binfmt --install all ``` ### Missing build-time dependencies You may get errors from missing build-time dependences such as `busybox`. In this case you may get "No such file or directory" errors when enabling debug with `set -x`. To fix this, you'll need to locate which package has the commands that your build needs, and add it to the list of your build-time dependencies. ## Further resources For additional guidance, please refer to the [melange repository](https://github.com/chainguard-dev/melange) on GitHub, where you can find [more examples](https://github.com/chainguard-dev/melange/tree/main/examples) or [open an issue](https://github.com/chainguard-dev/melange/issues/new/choose) in case of problems. --- ### Example policies URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/policy-controller/policies/chainguard-enforce-policy-examples.md Last Modified: September 28, 2026 Tags: Policy Controller, Procedural, Policy, policy-controller, Reference, SBOM The [Sigstore Policy Controller](https://docs.sigstore.dev/policy-controller/overview/) allows users to create their own security policies that they can be enforced on Kubernetes clusters. Here are a few example policies to help you get started. You may also review the [Sigstore Policy Controller documentation](https://docs.sigstore.dev/policy-controller/overview). In particular, we encourage you to review the Policy Controller documentation relating to the [Admission of images](https://docs.sigstore.dev/policy-controller/overview/#admission-of-images) to learn how to admit images through the cluster image policy. ## Policy enforcing signed containers ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: signed-keyless spec: images: # All images - glob: "**" authorities: - keyless: url: https://fulcio.sigstore.dev ctlog: url: https://rekor.sigstore.dev ``` Example using Chainguard Containers from Chainguard's registry: ``` ... images: - glob: cgr.dev/chainguard/** ... ``` An example using Docker Hub images: ``` ... images: - glob: "index.docker.io/*" - glob: "index.docker.io/*/*" ... ``` An example using Google Cloud Registry: ``` ... images: - glob: gcr.io/your-image-here/* ... ``` ## Policy enforcing signer identity through an OIDC provider and subject ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: enforce-signer-oidc spec: images: - glob: "**" authorities: - keyless: identities: # <<<-- REPLACE the following with your OIDC provider & subject --> # - issuer: https://token.actions.githubusercontent.com subject: https://github.com/chainguard-dev/gke-demo/.github/workflows/release.yaml@refs/heads/main ``` An alternate issuer and subject: ``` ... - issuer: https://accounts.google.com subject: your-gmail@gmail.com ``` ## Policy enforcing that images have a signed SPDX SBOM attestation from a custom key This policy asserts that all images must have a signed SPDX SBOM attestation from a custom key. ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: custom-key-attestation-sbom-spdxjson spec: images: - glob: gcr.io/your-image-here/* authorities: - name: custom-key key: data: | -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- attestations: - name: must-have-spdxjson predicateType: spdxjson policy: type: cue data: | predicateType: "https://spdx.dev/Document" ``` Set the `POLICY` and `IMAGES` environment variables appropriately, pointing to the sample policy and the image you would like to test. ```sh POLICY="policies/custom-key-attestation-sbom-spdxjson.yaml" ``` Generate an SPDX SBOM, then attach the SBOM to your image: ```sh cosign attest --type spdxjson ``` Next, sign it with a private key (for example, one located in a keys directory as in `keys/cosign.key`). ```sh export COSIGN_PASSWORD="" cosign attest --yes --type spdxjson \ --predicate sboms/example.spdx.json \ --key keys/cosign.key \ "${IMAGE}" ``` ## Policy enforcing that releases are signed by GitHub Actions ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: image-is-signed-by-github-actions spec: images: # This is the release v0.3.0 - glob: "gcr.io/projectsigstore/policy-webhook@sha256:d1e7af59381793687db4673277005276eb73a06cf555503138dd18eaa1ca47d6" authorities: - keyless: # Signed by Fulcio url: https://fulcio.sigstore.dev identities: # Matches the Github Actions OIDC issuer - issuer: https://token.actions.githubusercontent.com # Matches a specific GitHub workflow on main branch. Here we use the # Sigstore policy controller example testing workflow as an example. subject: "https://github.com/sigstore/policy-controller/.github/workflows/release.yaml@refs/tags/v0.3.0" ``` ## Policy allowing trusted GKE images ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: gke-trusted spec: images: - glob: gke.gcr.io/** - glob: gcr.io/gke-release/* authorities: - static: action: pass ``` ## Enforce that cert-manager is signed ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: certmanager-signed spec: images: - glob: quay.io/jetstack/cert-manager-* authorities: - key: data: | -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsZZKaaIRjOpzbiWYIDKO yry9XGBqAfve1iOGmt5VO1jpjNoEseT6zewozHfWTM7osxayy2WjN8G+QV39MlT3 Vxo91/31g+Zcq8KcvxG+iB8GRaD9pNgLmghorv+eYDiPYMO/+fhsLImyG5WEoPct MeCBD7umZ/A2t96U9DQxVDqQbTHlsNludno1p1wsgRnfUM3QHexNljDvJg5FcDMo dCpVLpRNvbw0lbJVfybJ4siJ5o/MmXzy0QCJpw+yMIqvqMc8qgKJ1yooJtuTVF4t 4/luP+EG/oVIiSWCFeRMqYdbJ3R+CJi+4LN7vFNYQM1Q/NwOB52RteaR7wnqmcBz qSYK32MM8xdPCQ5tioWwnPTRbPZuzsZsRmJsKBO9JUrBYdDntZX1xY5g4QNSufxi QgJgJSU7E4VGMvagEzB1JzvOr6A/qNFCO1Z6JsA3jw3cJLV1rSHfxqfSXBACTLDf 6bOPWRILRKydTJA6uLKNKmo1/nFm3jvd5tHKOjy4VAQLJ/Vx9wBsAAiLa+06veun Oz3AJ9sNh3wLp21RL11u9TuOKRBipE/TYsBYp8jpIyWPXDSV+JcD/TZqoT8y0Z6S 0damfUmspuK9DTQFL2crpeaqJSG9RA+OuPZLxGD1IMURTsPJB7kXhPtmceeirBnw sVcRHHDitVt8oO/x4Wus1c0CAwEAAQ== -----END PUBLIC KEY----- hashAlgorithm: sha512 ``` ## Enforce that Chainguard agent is signed ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: chainguard-agent-is-signed spec: images: - glob: us.gcr.io/prod-enforce-fabc/** authorities: - ctlog: url: https://rekor.sigstore.dev keyless: identities: - issuer: https://token.actions.githubusercontent.com subject: https://github.com/chainguard-dev/mono/.github/workflows/.release-drop.yaml@refs/heads/main - issuer: https://token.actions.githubusercontent.com subject: https://github.com/chainguard-dev/mono/.github/workflows/.build-drop.yaml@refs/heads/main url: https://fulcio.sigstore.dev ``` ## Enforce that Google's distroless images are signed ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: google-distroless-signed spec: images: - glob: gcr.io/distroless/static* authorities: - ctlog: url: https://rekor.sigstore.dev keyless: identities: - issuer: https://accounts.google.com subject: keyless@distroless.iam.gserviceaccount.com url: https://fulcio.sigstore.dev ``` ## Enforce that Istio images are signed ``` apiVersion: policy.sigstore.dev/v1beta1 kind: ClusterImagePolicy metadata: name: istio-signed spec: images: - glob: index.docker.io/istio/* authorities: - key: data: | -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEej5bv2n2vOecKineYGWwq1WaQa7C 7HTEVN+BkNI4D1+66ufzn1eGTrbaC9dceJqCAkhp37vMxhWOrGufpBUokg== -----END PUBLIC KEY----- ``` --- ### How to sign a container with Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/how-to-sign-a-container-with-cosign.md Last Modified: September 28, 2026 Tags: Cosign, Procedural _An earlier version of this material was published in the [Cosign chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@204b98f35bca48c194d1868e0356bef1/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2f0ad9cb8f124a39ab555ac8bf1a114c) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ Cosign is a tool you can use to sign software artifacts, which in turn allows you to verify that you are who you say you are, instilling trust across the software ecosystem. Signing software also allows people to understand the provenance of the software, and prevents tampering. Let’s step through signing a container with Cosign. We are using a container to provide a sense of how you may use Sigstore with containerized workloads, but the steps we are taking to sign a container are very similar to the steps that we would take to sign any other software artifact that can be published in a container registry, and we will discuss signing blobs a little later. ## Prerequisites Before beginning this section, ensure that you have Docker installed and that you are running Docker Desktop if that is relevant for your operating system. For guidance on installing and using Docker, refer to the [official Docker documentation](https://docs.docker.com/get-docker/). In order to push to the Docker container registry, you will need a [Docker Hub account](https://hub.docker.com/signup). If you are familiar with using a different container registry, feel free to use that. Additionally, you will need Cosign installed, which you can achieve by following our [How to install Cosign guide](/open-source/sigstore/cosign/how-to-install-cosign/). ## Creating a container You’ll now be creating a new container. Create a new directory within your user directory that is the same as your Docker username and, within that, a directory called `hello-container`. If you will be opting to use a registry other than Docker, feel free to use the relevant username for that registry: ```sh mkdir -p ~/docker-username/hello-container ``` Move into the directory: ```sh cd ~/docker-username/hello-container ``` Let’s create the Dockerfile that describes the container. This will be essentially a “Hello, World” container for demonstration purposes. Use the text editor of your choice to create the Dockerfile. You can use [Visual Studio Code](https://code.visualstudio.com/) or a command line text editor like nano. Just ensure that the file is called exactly `Dockerfile` with a titlecase and no extension: ```sh nano Dockerfile ``` Type the following into your editor: ```Dockerfile FROM alpine CMD ["echo", "Hello, Cosign!"] ``` This file is instructing the container to use the Alpine Linux distribution, which is lightweight and secure. Then, it prints a “Hello, Cosign!” message onto the command-line interface. Once you are satisfied that your Dockerfile is the same as the text above, you can save and close the file. Now you are ready to build the container. ## Building and running a container Within the same `hello-container` directory, you can build the container. You should use the format `docker-username/image-name` to tag your image, since you'll be publishing it to a registry: ```sh docker build -t docker-username/hello-container . ``` If you receive an error message or a “failed” message, check that your user is part of the `docker` group and that you have the right permissions to run Docker. For testing, you may also try to run the above command with `sudo`. You should get guidance in the output that your build was successful when you receive no errors: ```output => => naming to docker.io/docker-username/hello-container ``` At this point your container is built and you can verify that the container is working as expected by running the container: ```sh docker run docker-username/hello-container ``` You should receive the expected output of the echo message you added to the Dockerfile: ```output Hello, Cosign! ``` You can further confirm that the Docker container is among your listed containers by listing all of your active containers: ```sh docker ps -a ``` ```output CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c828db494203 hello-container "echo 'Hello, Cosign…" 13 seconds ago Exited (0) 9 seconds ago confident_lamarr ``` Your output will be similar to the above, but the timestamps and name will be different. Now that you have built your container and are satisfied that it is working as expected, you can publish and sign your container. ## Publishing a container to a registry We will be publishing our container to the Docker registry. If you are opting to use a different registry, your steps will be similar. At this point, you can access the Docker container registry at [hub.docker.com](https://hub.docker.com/) and create a new repository under your username called `hello-container`. We will be making this public, but you can make it private if you prefer. If you are happy for it to be public, you can skip this step as the repository will be created when pushing the container. In any case, you can delete this once you are satisfied that you have signed the container. Once this is set up, you can push the container you created to the Docker Hub repository: ```sh docker push docker-username/hello-container ``` You should be able to now access your published container via your Docker Hub account. Once you ensure that this is there, you are ready to push a signature to the container. ## Signing a container and pushing the signature to a registry Now that the container is in a registry (in our example, it is in Docker Hub), you are ready to sign the container and push that signature to the registry. You will call your registry user name and container name with the following `cosign` command. Note that we are signing the image in Docker Hub keylessly with Cosign: ```sh cosign sign docker-username/hello-container ``` You will be asked to verify that you agree with having your information in the transparency log and will be taken through an OIDC workflow: ```output The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/. Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record. This may include the email address associated with the account with which you authenticate your contractual Agreement. This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/. By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above. Are you sure you would like to continue? [y/N] y Your browser will now be opened to: ... ``` In the case of Docker Hub, on the web interface there should be a SHA (secure hash algorithm) added to the tag, enabling you to confirm that your pushed signature was registered. We’ll now manually verify the signature with Cosign. ## Verify a container’s signature We’ll be demonstrating this on the container we just pushed to a registry, but you can also verify a signature on any other signed container using the same steps. While you will more likely be verifying signatures in workloads versus manually, it is still helpful to understand how everything works and is formatted. Let’s use Cosign to verify that the signature exists on the transparency log and matches our expected information. You will need to know some information in order to verify the entry. You'll need to use the identity flags `--certificate-identity` which corresponds to the email address of the signer, and `--certificate-oidc-issuer` which corresponds to the OIDC provider that the signer used. For example, a Gmail account using Google as the OIDC issuer, will be able to be verified with the following command: ```sh cosign verify \ --certificate-identity username@gmail.com \ --certificate-oidc-issuer https://accounts.google.com \ docker-username/hello-container ``` Here, we are passing the public key contained in the cosign.pub file to the `cosign verify` command. You will receive output indicating that the Cosign claims were validated: ```output Verification for index.docker.io/docker-username/hello-container:latest -- The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key [{"critical":{"identity":{"docker-reference":"index.docker.io/docker-username/hello-container"},"image":{"docker-manifest-digest":"sha256:690ecfd885f008330a66d08be13dc6c115a439e1cc935c04d181d7116e198f9c"},"type":"cosign container image signature"},"optional":null}] ``` The whole output will include JSON format which includes the digest of the container image, which is how we can be sure these detached signatures cover the correct image. --- ### Getting started with apko URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/build-tools/apko/getting-started-with-apko.md Last Modified: October 2, 2026 Tags: apko, Procedural [apko](http://github.com/chainguard-dev/apko) is a command-line tool to build container images using a declarative language based on YAML. apko is so named as it uses the [apk](https://wiki.alpinelinux.org/wiki/Package_management) package format and is inspired by the [ko](https://github.com/google/ko) build tool. It is part of the open source tooling Chainguard developed to create the [Wolfi](/open-source/wolfi/) operating system which is used in [Chainguard Containers](/chainguard/containers/). ## Why apko Container images are typically assembled in multiple steps. A tool like Docker, for example, combines building steps (as in, running commands to copy files, build and deploy applications) and composition (as in, composing a base image with pre-built packages) in a single piece of software. apko, on the other hand, is solely a **composition** tool that focuses on producing lightweight, "flat" base images that are fully reproducible and contain auto generated [SBOM](https://www.cisa.gov/sbom) files for every successful build. Instead of building your application together with your components and system dependencies, you can build your application once and compose it into different architectures and distributions, using a tool such as [melange](https://github.com/chainguard-dev/melange) in combination with apko. For more information on how melange and apko work together, you can check this blog post: [Secure your Software Factory with melange and apko](https://www.chainguard.dev/unchained/secure-your-software-factory-with-melange-and-apko). In this guide, we'll learn how to use apko to build a base [Wolfi](/open-source/wolfi/) image. ## Requirements The fastest way to get apko up and running on your system is by using the [official apko image](https://images.chainguard.dev/directory/image/apko/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-open-source-build-tools-apko-getting-started-with-apko) with Docker. This method is compatible with all operating systems that support Docker and shared volumes. Please follow the [appropriate Docker installation instructions](https://docs.docker.com/get-docker/) for your operating system. If you want to run apko on CI/CD pipelines built on top of GitHub Actions, check the [apko build action](https://github.com/chainguard-images/actions/tree/main/apko-build) on GitHub. The instructions in this document were validated on an Ubuntu 22.04 workstation running Docker 20.10. ## Step 1 — Download the apko image Start by pulling the official apko image into your local system: ```shell docker pull cgr.dev/chainguard/apko ``` This will download the latest version of the distroless apko image, which is rebuilt every night for extra freshness. Check that you're able to run apko with: ```shell docker run --rm cgr.dev/chainguard/apko version ``` You should get output similar to this: ```output _ ____ _ __ ___ / \ | _ \ | |/ / / _ \ / _ \ | |_) | | ' / | | | | / ___ \ | __/ | . \ | |_| | /_/ \_\ |_| |_|\_\ \___/ apko GitVersion: v0.6.0 ... ``` In the next step, you'll build your first apko image. ## Step 2 — Build a test image To test that you're able to build images, you can use one of the example `yaml` definition files that are included in the [official apko code repository](https://github.com/chainguard-dev/apko/tree/main/examples). Here we'll use the `wolfi-base.yaml` for demonstration. Create a new folder to save your image files, then move to that directory: ```shell mkdir ~/apko cd ~/apko ``` Next, create a file named `wolfi-base.yaml` to save your image definition. You can use `nano` for that: ```shell nano wolfi-base.yaml ``` The `wolfi-base.yaml` example image is defined as follows: ```yaml contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://packages.wolfi.dev/os packages: - ca-certificates-bundle - wolfi-base entrypoint: command: /bin/sh -l archs: - x86_64 ``` The `contents` node is used to define allowed package sources and which packages should be included in the image. Here we'll be using only packages from the main Wolfi APK repository. In the `packages` section, we require the [wolfi-base](https://github.com/wolfi-dev/os/blob/main/wolfi-base.yaml) package, which is a meta-package to set up a bare minimum Wolfi system. The `command` field within the `entrypoint` node defines the image entry point command `/bin/sh -l`, which will land you in a shell prompt whenever the image is executed. Finally, the `archs` node specifies that this image will be built for the `x86-64` architecture. Save and close the file after you're done including these contents. With `nano`, you can do that by pressing `CTRL+X`, then confirming with `Y` and `ENTER`. The only thing left to do now is run apko to build this image. The following build command will: - set up a volume share in the current directory, synchronizing its contents with apko's image workdir; this way, the generated artifacts will be available on your host system. - execute the `cgr.dev/chainguard/apko` image with the `build` command, tagging the image as `wolfi-base:test-amd64` and saving the build as `wolfi-test.tar`. ```shell docker run --rm -v ${PWD}:/work -w /work cgr.dev/chainguard/apko build wolfi-base.yaml wolfi-base:test wolfi-test.tar ``` You should get output similar to this: ```output . . . Mar 15 20:17:02.023 [INFO] [arch:x86_64] Building images for 1 architectures: [amd64] Mar 15 20:17:02.023 [INFO] [arch:x86_64] building tags [wolfi-base:test] . . . Mar 15 20:17:04.261 [INFO] loading config file: wolfi-base.yaml Mar 15 20:17:04.416 [INFO] [arch:x86_64] adding amd64 to index Mar 15 20:17:04.419 [INFO] [arch:x86_64] Generating index SBOM Mar 15 20:17:04.420 [INFO] [arch:x86_64] Final index tgz at: wolfi-test.tar ``` From the output, you can notice that the image was successfully built as `wolfi-test.tar` in the container, which is shared with your local folder on the host thanks to the volume you created when running the `docker run` command. ## Step 3 — Test the image with Docker To test the generated image with Docker, you'll need to use the `docker load` command and import the `.tar` file you created in the previous step: ```shell docker load < wolfi-test.tar ``` You'll get output like this: ``` bf6e72d71c13: Loading layer [==================================================>] 5.491MB/5.491MB Loaded image: wolfi-base:test-amd64 ``` You can check that the image is available at the host system with: ```shell docker image list ``` You should be able to find the `wolfi-base` image with the `test-amd64` tag among the results. Now you can run the image with: ```shell docker run -it wolfi-base:test-amd64 ``` This will get you into a container running the apko-built image `wolfi-base:test-amd64`. It's a regular shell that you can explore to see what's included - just keep in mind that this is a minimalist image with only the base Wolfi system. To include additional software packages, check the [Wolfi repository](https://github.com/wolfi-dev/os) to find the packages you'll need for your specific use case, or check out [melange](/open-source/build-tools/melange/), apko's companion project that allows users to build their own APK packages from source. ## Limits of apko builds Keep these limits in mind as you build your own images: - **No arbitrary commands.** apko can't run commands at build time the way a `RUN` step does in a Dockerfile. It provides directives for creating users and setting up directories and permissions. Any other build step, such as installing software or running a shell command, belongs in an apk package that you list as a dependency. This keeps builds reproducible and their SBOMs accurate. - **One package ecosystem per build.** You can't mix Wolfi and Alpine packages in the same apko build. - **Standard OCI output.** apko images are OCI compliant, so they run with Docker or any other runtime that supports the OCI image format. ## Conclusion In this guide, you learned what apko is and what makes it a powerful resource in your cloud-native tooling. If you need help debugging your build, check our [Troubleshooting apko](/open-source/build-tools/apko/troubleshooting/) page for more information. Check the [official apko repository](https://github.com/chainguard-dev/apko/) if you want to report an issue or suggest new features. --- ### Why apk URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/wolfi/apk-package-manager.md Last Modified: September 28, 2026 Tags: apko, Conceptual [apko](/open-source/build-tools/apko/getting-started-with-apko/) uses the [apk](https://wiki.alpinelinux.org/wiki/Package_management) package manager to compose container images based on declarative pipelines. The apk format was introduced by [Alpine Linux](https://www.alpinelinux.org/) to address specific design requirements that could not be met by existing package managers such as `apt` and `dnf`. But what makes it different, and why does that matter in the context of apko? ## Manipulating the desired state In traditional package managers like `dnf` and `apt`, requesting the installation or removal of packages causes those packages to be directly installed or removed, after a consistency check. In `apk`, when you run `apk add package1` to add a package or `apk del package2` to delete a package, `package1` and `package2` are added (or removed) as a dependency constraint in `/etc/apk/world`, which describes the desired system state. Package installation or removal is done as a side effect of modifying this system state. It is also possible to edit `/etc/apk/world` with the text editor of your choice and then use `apk fix` to synchronize the installed packages with the desired system state. Because of this design, you can also add conflicts to the desired system state to prevent bringing in certain packages. For example, there was [a bug in Alpine in 2021](https://gitlab.alpinelinux.org/alpine/apk-tools/-/issues/10742) where `pipewire-pulse` was preferred over `pulseaudio` because the former had a simpler dependency graph. This did not prove to be a problem though, as users could add a conflict against `pipewire-pulse` by running `apk add !pipewire-pulse`, thus preventing the package from being brought in. Another result of this design is that `apk` will never commit a change to the system that leaves it unbootable. If it cannot verify the correctness of the requested change, it will back out adding the constraint before attempting to change what packages are actually installed on the system. This allows the apk dependency solver to be rigid: there is no way to override or defeat the solver other than providing a scenario that results in a valid solution. ## Verification and unpacking in parallel to package fetching Unlike other package managers, `apk` is completely driven by the package fetching I/O when installing or upgrading packages. When the package data is fetched, it is verified and unpacked on the fly. This allows package installations and upgrades to be extremely fast. To make this safe, package contents are initially unpacked to temporary files and then atomically renamed once the verification steps are complete and the package is ready to be committed to disk. ## Constrained solver Lately, traditional package managers have promoted their advanced [SAT solvers](https://en.wikipedia.org/wiki/SAT_solver) for resolving complicated constraint issues automatically. For example, [aptitude is capable of solving Sudoku puzzles](https://web.archive.org/web/20080823224640/http://algebraicthunk.net/~dburrows/blog/entry/package-management-sudoku/). `apk`'s lack of these solvers can actually be considered a feature. While it is true that `apk` does have a deductive dependency solver, it does not perform backtracking. The solver is also constrained: it is not allowed to make changes to the `/etc/apk/world` file. This ensures that the solver cannot propose a solution that will leave your system in an inconsistent state. Trying to make a smart solver instead of appropriately constraining the problem can indicate a poor design choice. The fact that `apt`, `aptitude`, and `dnf` have all written code to constrain their SAT solvers in various ways proves this point. ## Fast and safe package management Package managers can be made to go fast — and can be safe while doing so — but require a careful design that is well-constrained. `apk` makes its own tradeoffs: a less powerful but easy to audit solver, trickier parallel execution instead of phase-based execution. These were the right decisions for `apk`, but may not be the right decisions for other distributions. ## Final considerations The reproducible nature of apk makes it the ideal solution for declarative pipelines, since it allows you to describe your desired system state without having to implement a series of individual steps that are not guaranteed to reach completion. When the apk dependency solver is unable to reach an installable set of packages, the build fails, without causing incomplete system changes. This is the ideal behavior for automated pipelines since it eliminates the need for rollbacks, in addition to avoiding the risks of inconsistent environments. _An earlier version of this article was published on [Ariadne Conill's Blog](https://ariadne.space/2021/04/24/why-apktools-is-different-than.html)._ --- ### Hello Wolfi workshop URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/wolfi/hello-wolfi.md Last Modified: September 28, 2026 Tags: Wolfi, Workshop ## Introduction Software supply chain threats have been growing exponentially in the last few years, according to [industry leaders and security researchers (PDF)](https://www.usenix.org/system/files/login/articles/login_winter20_17_geer.pdf). With the popularization of automated workflows and cloud native deployments, it is more important than ever to provide users with the ability to attest the provenance of all relevant software artifacts that compose the container images being used as build and production runtimes. In this workshop, you'll learn more about Wolfi, a community Linux _undistro_ designed for the container and cloud-native era. You'll also learn about melange and apko, Chainguard's open source toolkit created to build more secure container images.

Note: This presentation was recorded on November 16, 2022. Although most of the content holds true to date, some commands and configurations have changed, which caused the demo to become obsolete. For a more up-to-date resource on how to build Wolfi packages, check the Building a Wolfi package guide. If you are looking for Wolfi-based images for your containerized workloads, check our Images Directory.

[Watch the video on YouTube](https://www.youtube.com/watch?v=2pqhLXA6NaI) --- ### Creating Wolfi images with Dockerfiles URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/wolfi/wolfi-with-dockerfiles.md Last Modified: October 2, 2026 Tags: Wolfi, Procedural ## Introduction [Wolfi](https://www.chainguard.dev/supply-chain-security-101/wolfi-overview) is a minimal open source Linux distribution created specifically for cloud workloads, with an emphasis on software supply chain security. Using [apk](https://wiki.alpinelinux.org/wiki/Package_management) for package management, Wolfi differs from Alpine in a few important aspects, most notably the use of glibc instead of musl and the fact that Wolfi doesn't have a kernel as it is intended to be used with a container runtime. This minimal footprint makes Wolfi an ideal base for both _distroless_ images and fully-featured builder images. A distroless image is a minimal container image that typically doesn't include a shell or package manager. The extra tightness improves security in several aspects, but it requires a more sophisticated strategy for image composition since you can't install packages so easily. Wolfi-based builder images are still a better and more secure option to use as base images in your Dockerfile than using a full-fledged Linux distribution, as they are smaller and have fewer CVEs. You can learn more about distroless in our [Going distroless](/chainguard/containers/concepts/getting-started-distroless/) guide. The [wolfi-base](https://github.com/chainguard-images/images/tree/main/images/wolfi-base) image, which we'll be using in this tutorial, is not distroless because it includes `apk-tools` and `bash`. In some cases, it can still be used to build a final distroless image, when combined with a distroless runtime in a [Docker multi-stage build](https://docs.docker.com/build/building/multi-stage/). That depends on the complexity of the image, the number of dependencies required, and whether these dependencies are system libraries or language ecosystem packages, for example. In this article, we'll learn how to leverage Wolfi to create safer runtime environments based on containers. To demonstrate Wolfi usage in a Dockerfile workflow (using a Dockerfile to build your image), we'll create an image based on the [wolfi-base](https://github.com/chainguard-images/images/tree/main/images/wolfi-base) image maintained by Chainguard. The goal is to have a final runtime image able to execute a Python application. [Step 4](#step-4-optional-composing-distroless-images-in-a-docker-multi-stage-build) of this guide, which is optional, demonstrates how to turn that into a distroless image by combining it with a Python distroless image, also provided by Chainguard. ## Requirements You'll need Docker to build and run the application. ## Step 1: Obtaining the demo application We'll use the same demo application from the [Getting started with the Python Chainguard Container](/chainguard/containers/getting-started/languages-and-runtimes/python/) tutorial to demonstrate how to build a Wolfi Python image with a Dockerfile. The application files are available in the [edu-images-demos](https://github.com/chainguard-dev/edu-images-demos) repository. We'll start by cloning that repository in a temporary folder so that we can obtain the relevant application files to run the **second** demo from that tutorial. The following command will clone the demos repository in your `/tmp` folder: ```shell mkdir /tmp/images-demos && \ git clone https://github.com/chainguard-dev/edu-images-demos.git \ /tmp/images-demos ``` We'll now copy the demo application to a location inside your home folder. ```shell mkdir ~/linky && cp -R /tmp/images-demos/python/linky/* ~/linky/ ``` You can now enter the newly created directory in your home folder and inspect its contents: ```shell cd ~/linky && ls -la ``` This application will take in an image (`linky.png`) file and convert it to ANSI escape sequences to render it on the CLI. The code is on the `linky.py` file, while the `requirements.txt` file has the dependencies required by the application: [setuptools](https://pypi.org/project/setuptools/) and [climage](https://pypi.org/project/climage/). For your reference, here is the complete `linky.py` script: ```python '''import climage module to display images on terminal''' from climage import convert def main(): '''Take in PNG and output as ANSI to terminal''' output = convert('linky.png', is_unicode=True) print(output) if __name__ == "__main__": main() ``` You'll notice that there's already a Dockerfile in that directory, but it uses the [Python Chainguard image](https://images.chainguard.dev/directory/image/python/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-open-source-wolfi-wolfi-with-dockerfiles) in a multi-stage build. In the next step, we'll replace that with a new Dockerfile that uses the [Wolfi-base](https://images.chainguard.dev/directory/image/wolfi-base/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-open-source-wolfi-wolfi-with-dockerfiles) image to build a Python image from scratch, using Wolfi apks. ## Step 2: Creating the Dockerfile Now we'll create the Dockerfile to run the application. This Dockerfile will set up a new user and WORKDIR, copy relevant files, and install dependencies with Pip. It will also define the entry point that will be executed when we run this image with `docker run`. You can rename the old Dockerfile if you want to keep it for tests later. ```shell mv Dockerfile _DockerfileBkp ``` Then, create a new Dockerfile: ```shell nano Dockerfile ``` Copy the following content to it: ```Dockerfile FROM cgr.dev/chainguard/wolfi-base ARG version=3.12 WORKDIR /app RUN apk add python-${version} py${version}-pip && \ chown -R nonroot:nonroot /app/ USER nonroot COPY requirements.txt linky.png linky.py /app/ RUN pip install -r requirements.txt --user ENTRYPOINT [ "python", "/app/linky.py" ] ``` This Dockerfile uses a variable called `version` to define which Python version is going to be installed in the resulting image. You can change this to one of the Python versions available in Wolfi. To find out which versions are available, please refer to the [Searching for packages](https://edu.chainguard.dev/chainguard/containers/migration/migrating-to-chainguard-images/#searching-for-packages) section of our migration guide. Save the file when you're done. In the next step, we'll build and run the image with `docker`. ## Step 3: Building and running the image With the Dockerfile ready, you can now build your application runtime. If you're on macOS, make sure Docker is running. Build your image with: ```shell docker build . -t linky-demo ``` If you run into issues, try using `sudo`. Finally, run the image with: ```shell docker run --rm linky-demo ``` You’ll receive a representation of the Chainguard Linky logo on the command line. ## Step 4 (optional): Composing distroless images in a Docker multi-stage build As discussed in the introduction, in some cases it is possible to combine your fully-featured image with a distroless runtime in a Docker multistage build, and this will give you a final image that is also distroless. Keep in mind that this technique for building distroless images is only viable when there aren't additional system dependencies that require installation via `apk`. The [Getting started with Python](/chainguard/containers/getting-started/languages-and-runtimes/python/) tutorial shows in detail how to accomplish that using a `-dev` variant as **builder**, and the distroless Chainguard Python image as production image. You can also accomplish the same results by using your newly-built image based on `wolfi-base` in place of the `-dev` variant of the Python image. We'll change the build to use a virtual environment to package the dependencies and add an extra step to create the final image. The following Dockerfile uses a multi-stage build to obtain a final distroless image that contains everything the application needs to run. The build requires additional software that is not carried along to the final image. Open a new file and call it `DockerfileDistroless`: ```shell nano DockerfileDistroless ``` Copy the following code into your new file: ```Dockerfile FROM cgr.dev/chainguard/wolfi-base AS builder ARG version=3.12 ENV LANG=C.UTF-8 ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 ENV PATH="/app/venv/bin:$PATH" WORKDIR /app RUN apk update && apk add python-$version py${version}-pip && \ chown -R nonroot:nonroot /app/ USER nonroot RUN python -m venv /app/venv COPY requirements.txt /app/ RUN pip install --no-cache-dir -r requirements.txt FROM cgr.dev/chainguard/python:latest ENV PYTHONUNBUFFERED=1 ENV PATH="/app/bin:$PATH" WORKDIR /app COPY --from=builder /app/venv /app COPY linky.py linky.png /app/ ENTRYPOINT [ "python", "/app/linky.py" ] ``` Save and close the file when you're finished. Now, build this image using a custom tag so that you can compare the previously built `linky-demo` image with its distroless version: ```shell docker build . -f DockerfileDistroless -t linky-demo:distroless ``` If you run the new image, it should give you the same result as before. ```shell docker run --rm linky-demo:distroless ``` But these images are not the same. The following command will give you a glimpse of their differences: ```shell docker images linky-demo ``` ```output REPOSITORY TAG IMAGE ID CREATED SIZE linky-demo distroless 619ef9b6c52d 6 seconds ago 90.3MB linky-demo latest 4832e9093348 4 minutes ago 110MB ``` You'll notice that the `:distroless` version is significantly smaller, because it doesn't carry along all the software necessary to build the application. More important than size, however, is the smaller attack surface that results in fewer CVEs. ## Final considerations In this tutorial, we've demonstrated how to build a Python image from scratch using the `wolfi-base` image. We've also shown how to compose a distroless image using a multi-stage build. This technique is useful when you need to reduce the attack surface of your application runtime, which is especially important in security-sensitive environments. If your application runtime requires system dependencies that are not already included within a distroless variant available in our [images directory](https://images.chainguard.dev), you can still use a builder image (identified by the `-dev` suffix) or the `wolfi-base` image in a standard Dockerfile to build a suitable runtime. These images come with `apk` and a shell, allowing for further customization based on your application's requirements. If you can't find an image that is a good match for your use case, or if your build has dependencies that cannot be met with the regular catalog, [get in touch with us](https://www.chainguard.dev/contact?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) for alternative options. --- ### How to sign and upload metadata to Rekor URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/rekor/how-to-sign-and-upload-metadata-to-rekor.md Last Modified: September 28, 2026 Tags: Rekor, Procedural _An earlier version of this material was published in the [Rekor chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@e785fae1be184e2c929db62dbe7444fa/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@a48c33126e2c4ee6ad3bfa6b7bc9c957) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ This tutorial will walk you through signing and uploading metadata to the Rekor transparency log, which is a project of Sigstore. In order to follow along, you'll need the `rekor-cli` installed, which you can accomplish by following the "[How to install the Rekor CLI](/open-source/sigstore/rekor/how-to-install-rekor/)" tutorial. We will use SSH to sign a text document. SSH is often used to communicate securely over an unsecured network and can also be used to generate public and private keys appropriate for signing an artifact. First, generate a key pair. This command will generate a public key and a private key file. You'll be able to easily identify the public key because it uses the .pub extension. The command below will create a new file in ~/.ssh called id_ed25519 but you may want to call it something else; you can do that by passing a different filename after the -f flag. ```sh ssh-keygen -t ed25519 -f id_ed25519 ``` Then, create a text file called README.txt with your favorite text editor. You can enter as little or as much text in that file as you would like. For example, we can use nano: ```sh nano README.txt ``` Then within the file, we can type some text into it, such as the following. ``` [label README.txt] Hello, Rekor! ``` Save and close the file. Next, sign this file with the following command. This command produces a signature file ending in the .sig extension. ```sh ssh-keygen -Y sign -n file -f id_ed25519 README.txt ``` You should receive the following output. ``` Signing file README.txt Write signature to README.txt.sig ``` Then, upload this artifact to the public instance of the Rekor log. ```sh rekor-cli upload --artifact README.txt --signature README.txt.sig --pki-format=ssh --public-key=id_ed25519.pub ``` The returned value will include a string similar to: https://rekor.sigstore.dev/api/v1/log/entries/83140d699ebc33dc84b702d2f95b209dc71f47a3dce5cce19a197a401852ee97 Save the UUID returned after using this command. In this example, the UUID is `83140d699ebc33dc84b702d2f95b209dc71f47a3dce5cce19a197a401852ee97`. Now you can query Rekor for your recently saved entry. Run the following command, replacing UUID with the UUID number obtained in the previous command. ```sh rekor-cli get --uuid UUID ``` Once you receive output formatted as a JSON with details on the signature, you will know you have successfully stored a signed metadata entry in Rekor. --- ### Troubleshooting apko builds URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/build-tools/apko/troubleshooting.md Last Modified: September 28, 2026 Tags: apko, Troubleshooting ## Debug options To include debug-level information on apko builds, add `--debug` to your build command: ```shell docker run --rm -v ${PWD}:/work cgr.dev/chainguard/apko build --debug \ apko.yaml hello-minicli:test hello-minicli.tar \ -k melange.rsa.pub ``` ## Common errors When the apk package manager is unable to resolve your requirements to a set of installable packages, you will get an error similar to this: There are two main root causes for this error, which we'll explain in more detail in the upcoming section: - apk cannot find the package in the included repositories, or - apk cannot find the apk index for your custom-built packages. ### The requested package is not in the included repositories Make sure you've added the relevant package repositories you need and the package name is correct. Check the [Wolfi repository](https://github.com/wolfi-dev/os) for available packages if you are building Wolfi images, or the [Alpine APK index](https://pkgs.alpinelinux.org/packages) if you are using Alpine as base. If this is your case, you should find error messages similar to this when enabling debug info with the `--debug` flag: ### apko is unable to find the local packages folder With melange-built package(s), make sure you have a volume sharing your apko / melange files with the location `/work` inside the apko container. ### The apk index is missing If you have a functional volume sharing your packages with the apko container and you're still getting this error, make sure you built a valid apk index as described in [step 4 of the Getting started with melange guide](/open-source/build-tools/melange/getting-started-with-melange/#4--building-the-minicli-apk-with-melange). If this is your case, you should find error messages similar to this when enabling debug info with the `--debug` flag: This is how your packages directory tree should be set up, including the `APKINDEX.tgz` file for each architecture: ``` packages ├── aarch64 │   ├── APKINDEX.tar.gz │   └── hello-minicli-0.1.0-r0.apk ├── armv7 │   ├── APKINDEX.tar.gz │   └── hello-minicli-0.1.0-r0.apk ├── x86 │   ├── APKINDEX.tar.gz │   └── hello-minicli-0.1.0-r0.apk └── x86_64 ├── APKINDEX.tar.gz └── hello-minicli-0.1.0-r0.apk 4 directories, 8 files ``` ## Further resources For additional guidance, please refer to the [apko repository](https://github.com/chainguard-dev/apko) on GitHub, where you can find [more examples](https://github.com/chainguard-dev/apko/tree/main/examples) or [open an issue](https://github.com/chainguard-dev/apko/issues/new/choose) in case of problems. --- ### How to sign blobs and standard files with Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/how-to-sign-blobs-with-cosign.md Last Modified: September 28, 2026 Tags: Cosign, Procedural _An earlier version of this material was published in the [Cosign chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@204b98f35bca48c194d1868e0356bef1/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2f0ad9cb8f124a39ab555ac8bf1a114c) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ Cosign can sign more than just containers. Blobs, or binary large objects, and standard files can be signed in a similar way. You can publish a blob or other artifact to an OCI (Open Container Initiative) registry with Cosign. This tutorial assumes you have a Cosign key pair set up, which you can achieve by following our [Introduction to Cosign](/open-source/sigstore/cosign/an-introduction-to-cosign/) guide. Navigate to the directory which contains your `cosign.pub` and `cosign.key` key pair as generated in the [Introduction to Cosign](/open-source/sigstore/cosign/an-introduction-to-cosign/#cosign-with-keys) guide. We’ll create an artifact (in this case, a standard file that contains text). We’ll call the file `artifact` and fill it with the “hello, cosign” text: ```sh echo "hello, cosign" > artifact ``` Cosign offers support for signing blobs with the `cosign sign-blob` and `cosign verify-blob` commands. To sign our file, we’ll pass our signing key and the name of our file to the `cosign sign-blob` command: ```sh cosign sign-blob --key cosign.key --bundle artifact.sigstore.json artifact ``` Note that this command includes the `--bundle` flag, which writes everything required to verify the blob to a file named `artifact.sigstore.json`. You’ll get output similar to the following, including prompts asking you to confirm that you'd like to sign the artifact and to enter your password for your signing key: ```output Are you sure you would like to continue? [y/N] y Enter password for private key: Using payload from: artifact Wrote bundle to file artifact.sigstore.json ``` You'll need your signature, which this command writes to the `artifact.sigstore.json` bundle, to verify the artifact signature. Retrieve it with the following command: ```sh cat artifact.sigstore.json | jq | grep signature ``` ```output "signature": "MEQCI************************************************************************************G0knw==" ``` With this signature in hand, run the `cosign verify-blob` command and pass in the public key, the signature, and the name of your file: ```sh cosign verify-blob --key cosign.pub --signature MEQCI************************************************************************************G0knw== artifact ``` Note that the full signature must be passed to this command. You’ll get feedback that the blob’s signature was verified: ```output Verified OK ``` You can also publish the artifact to a container registry such as Docker Hub and sign the artifact’s generated image with Cosign. Running this command will create a new repository in your Docker Hub account . We will call this `artifact` but you can use any name you choose: ```sh cosign upload blob -f artifact docker-username/artifact ``` You’ll receive feedback that the file was uploaded, and it will already have the SHA signature as part of the artifact: ```output Uploading file from [artifact] to [index.docker.io/docker-username/artifact:latest] with media type [text/plain] File [artifact] is available directly at [index.docker.io/v2/docker-username/artifact/blobs/sha256:dcf8ff… Uploaded image to: index.docker.io/docker-username/artifact@sha256:d10846… ``` Being able to sign blobs provides you with the opportunity to sign README files and scripts rather than just containers. This can ensure that every piece of a software project is accounted for through signatures and provenance. --- ### Package version selection URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/wolfi/apk-version-selection.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Wolfi, apk, melange This document explains how to specify version constraints for packages installed with the apk tool, as well as apko and melange. Understanding version selection will enable you to choose the version you're looking for, determine what updates and vulnerability fixes you receive, and can allow you to reproduce an image's digest through exact version matching. ## Version selection in apko and melange All the examples in this document focus on usage with the `apk` tool, but the same semantics apply to `apk add` as well as references in an apko or melange `packages` field: ```yaml environment: packages: - go>1.21 # install anything newer than 1.21, excluding 1.21 - foo=~4.5.6 # install any version with a name starting with "4.5.6" (e.g., 4.5.6-r7) - python3 # install the latest stable version of python3. ``` ## Basic usage ```sh apk add go ``` This will install the latest stable version of Go. This is nearly always what you want, since it gives you stable software, with as many updates and vulnerability fixes as possible. Refer to the section below for information on the behavior of pre-release versions. ## Fuzzy matching You can also use fuzzy version matching. For example, if you don't care about the epoch, you can request any version of Go with a version string starting in `1.21.1`: ```sh apk add go=~1.21.1 ``` Or you can add any 1.21: ```sh apk add go=~1.21 ``` If multiple versions match that prefix (e.g., `1.21.1-r0`, `1.21.1-r1`, etc.), then the highest numbered next version segment will be chosen. Fuzzy matching means you can request Go 1.21 without getting 1.20 or 1.22, like you would with `<` or `>`. Wolfi provides a separate `go-1.21` package that `provides:go=1.21.x` so in Go's case you can request `go-1.21` and get the same behavior as `go~1.21`. But this might not be the same case for all packages. The operators `~`, `~=` and `=~` are equivalent, and all do the same thing. You can also use fuzzy matching to request a major version prefix: ```sh apk add go=~1 ``` Go will not have a version 2, but other packages might. For example, `erlang=~26` will match any release of Erlang in the 26 major version, and not match Erlang 25 and below or 27 and above. `erlang~2` will _not_ match Erlang 26 or 27. It only fuzzy matches whole segments of a semantic versioning version string. ## Version constraints To request a minimum or maximum acceptable version of a package to install, you can use `>`, `<`, `>=` and `<=`: ```sh apk add go>1.20 # install anything newer than 1.20, excluding 1.20.0, but including 1.20.1 apk add go<1.20 # install anything older than 1.20, excluding 1.20.0, but including 1.19.14 apk add go>=1.20 # install anything newer than 1.20, including 1.20.0 apk add go<=1.20 # install anything older than 1.20, including 1.20.0 ``` This comparison logic is aware of semantic versioning semantics, so `1.9.10` is less than `1.10`, and `1.9.9` is less than `1.9.10`, even though they may be alphabetically sorted later. Version constraints can be useful when you want to ensure a minimum or maximum major or minor version, but still want to receive minor or patch updates. Fuzzy matching can produce the same behavior – `go~1.20` is equivalent to `go>=1.20` for example. ## Installing future versions Using `apk add go` installs the latest _stable_ release of Go. For most packages, there is no distinction between the "latest" and "latest stable" release. Some projects, like Go, Node, Python, etc., produce pre-release versions before, or have more nuanced release maturity and support processes. Please refer to the respective larger project for more information. Go versions often have Release Candidates. For example, when Go 1.21 is the latest release, the Go team may prepare for Go 1.22 by releasing a Go `1.22_rc1` to let folks try it out before it's fully released. To install this version, you can specify the pre-release package name, which for go will be `go-1.22`: ```sh apk add go-1.22 ``` Note that this string uses a dash (`-`), which means it's specifying the full name of the package, which Wolfi's convention is `go-1.22`. Since it doesn't include any other version constraint or fuzzy matching, it's requesting "the latest version of a package named `go-1.22`", which may install `go-1.22` with version `1.22_rc3`. When Go 1.22 is fully released, it will become the latest release of Go and `provides:[go=$version]`, so `apk add go` will install Go 1.22.0. ## Exact version matching You can also specify an exact version and epoch to install: ```sh apk add go=1.21.1-r0 ``` This will install exactly this version and epoch, as long as that is available. Because package updates or vulnerability fixes won't be picked up, this is not generally useful for day-to-day usage. However, this is useful for reproducing an environment exactly, and it's the form we use in the resolved apko configuration attestation we attach to images. This makes it clear exactly what versions of which packages were installed in an image, so you can reproduce it with `apko` to get exactly the same image digest. --- ### Bazel rules for apko URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/build-tools/apko/bazel-rules.md Last Modified: September 28, 2026 Tags: apko, Procedural `rules_apko` is an open source plugin for Bazel that makes it possible to build secure, minimal Wolfi-based container images using the Bazel build system. It wraps the [apko](https://github.com/chainguard-dev/apko) tool for use under Bazel, providing hermetic, reproducible image builds with full Bazel caching support. By the end of this guide you will have a working Bazel project that builds a minimal Wolfi-based container image using `rules_apko`. ## How to build a container with Bazel using `rules_apko` This page covers `rules_apko` version `1.5.37` with Bazel `9.0.1` using **Bzlmod**, which is the only supported dependency management method in Bazel 9. If you are on an earlier version of Bazel, you should upgrade to Bazel 9 before following this guide. > Note: You do not need to install `apko` separately. `rules_apko` manages > its own hermetic `apko` toolchain and automatically downloads `apko v1.1.12` > on first build. ### Prerequisites Before you begin, ensure you have the following: - **Bazel 9.x** installed. This page was written and tested against Bazel `9.0.1`. Follow the [Bazel installation guide](https://bazel.build/install) for details. Note the Bazel version in use by confirming the `dev.chainguard.package.main` label on your image is `bazel-9`. - **`chainctl`** installed and authenticated. `chainctl` is the Chainguard command line tool. See the [chainctl documentation](/platform/chainctl/) for installation instructions. - **`rules_apko` version `1.5.37`** available in the [Bazel Central Registry](https://registry.bazel.build/modules/rules_apko). No separate download is required — Bazel fetches it automatically when you declare it in `MODULE.bazel`. ### Project structure A complete `rules_apko` project requires the following files: ``` my-apko-image/ ├── .bazelrc ← create manually ├── MODULE.bazel ← create manually, updated in two stages ├── BUILD.bazel ← create manually, updated in two stages ├── apko.yaml ← create manually ├── apko.lock.json ← generated by bazel run //:lock ├── MODULE.bazel.lock ← auto-generated by Bazel, do not edit └── .apko/ ├── .bazelrc ← generated by bazel run //:apko_bazelrc └── range.sh ← generated by bazel run //:apko_bazelrc ``` The files marked "generated" should be committed to your repository after generation. The files marked "create manually" are written by you as part of this guide. ## Setup ### Step 1: Create your project directory Create a new directory for your project and change into it: ```shell mkdir my-apko-image && cd my-apko-image ``` ### Step 2: Create the root .bazelrc file Create a root `.bazelrc` file in your project directory. This file does two things: - It activates the apko credential helpers for partial HTTP range requests (once they are generated in a later step) - It keeps the Bazel repository cache outside your project directory, which is required by Bazel 9: ``` # Import apko credential helper configuration for partial package fetches try-import .apko/.bazelrc # Keep Bazel repo cache outside the project directory (required by Bazel 9) build --repo_contents_cache=/tmp/bazel-cache ``` > Note: The try-import directive tells Bazel to load `.apko/.bazelrc` if it exists. This file is generated in a later step by running `bazel run //:apko_bazelrc`. Until that step is complete the directive is safely ignored. ### Step 3: Create the stage 1 MODULE.bazel file Create a `MODULE.bazel` file in your project directory. This file declares your project's external dependencies and sets up the `apko` toolchain. At this stage it does not yet include the lock file translation — that is added after the lock file is generated: ```shell module( name = "my-apko-image", version = "0.0.0", ) bazel_dep(name = "rules_apko", version = "1.5.37") # Set up the apko toolchain. # apko v1.1.12 is downloaded automatically — no separate installation needed. toolchain = use_extension("@rules_apko//apko:extensions.bzl", "apko") toolchain.toolchain(apko_version = "v1.1.12") use_repo(toolchain, "apko_toolchains") register_toolchains("@apko_toolchains//:all") ``` ### Step 4: Create the stage 1 BUILD.bazel file Create a `BUILD.bazel` file in your project directory. At this stage it contains only the `apko_bazelrc` and `apko_lock`rules. The `apko_image` rule is added after the lock file is generated: ```shell load("@rules_apko//apko:defs.bzl", "apko_bazelrc", "apko_lock") # Generates .apko/.bazelrc and .apko/range.sh for partial package fetches. # Run with: bazel run //:apko_bazelrc apko_bazelrc() # Generates the lock file that pins all package versions and checksums. # Run with: bazel run //:lock apko_lock( name = "lock", config = "apko.yaml", lockfile_name = "apko.lock.json", ) ``` > Note: The reason `apko_image` is not included at this stage is that the `apko_image` rule checks for `apko.lock.json` at Bazel load time — before any targets are run. Since the lock file does not exist yet, including a `apko_image` at this stage would cause a load-time error. Once the lock file exists you will add `apko_image` in a later step. ### Step 5: Create the apko.yaml configuration file Create an `apko.yaml` file in your project directory. This file defines the container image — its base packages, repository, signing key, and target architectures. The following example builds a minimal Wolfi-based image: ```yaml contents: keyring: - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub repositories: - https://packages.wolfi.dev/os packages: - wolfi-base entrypoint: command: /bin/sh archs: - aarch64 - x86_64 ``` ## Generating configuration and the lock file With your project files in place, run the following two commands in order. ### Step 6: Generate the credential helper configuration Run the `apko_bazelrc` target to generate `.apko/.bazelrc` and `.apko/range.sh`: ```shell bazel run //:apko_bazelrc ``` You will see output similar to the following: ```shell INFO: Analyzed target //:apko_bazelrc (6 packages loaded, 14 targets configured). INFO: Found 1 target... Target //:apko_bazelrc up-to-date: bazel-bin/apko_bazelrc_update.sh INFO: Build completed successfully, 2 total actions INFO: Running command line: bazel-bin/apko_bazelrc_update.sh Copying file .../range.sh to .apko/range.sh in /home/user/my-apko-image Copying file .../apko_bazelrc_bazelrc to .apko/.bazelrc in /home/user/my-apko-image ``` This generates two files in the `.apko/` subdirectory: - `.apko/.bazelrc` — configures Bazel credential helpers for the Wolfi and Alpine package repositories, enabling partial HTTP range requests so Bazel fetches only the specific byte ranges of APK packages it needs - `.apko/range.sh` — the credential helper script used by Bazel when making range requests Both files should be committed to your repository. They are activated by the try-import `.apko/.bazelrc` directive in your root `.bazelrc` file. > Note: By default, `apko_bazelrc` configures credential helpers for `dl-cdn.alpinelinux.org` and `packages.wolfi.dev`. If you are using additional repositories, pass them to the repositories attribute: `apko_bazelrc(repositories = ["my.repo.example.com"])`. ### Step 7: Generate the lock file Run the lock target to generate apko.lock.json: ```shell bazel run //:lock ``` You will see output similar to the following: ```shell INFO: Analyzed target //:lock (84 packages loaded, 487 targets configured). INFO: Found 1 target... Target //:lock up-to-date: bazel-bin/_lock_run.sh INFO: Build completed successfully, 1 total action INFO: Running command line: bazel-bin/_lock_run.sh 2026/03/12 13:07:06 INFO Determining packages for 2 architectures: [arm64 amd64] 2026/03/12 13:07:06 INFO Discovered 0 auto-discovered keys ``` This generates `apko.lock.json` in your project directory. The lock file pins the exact versions and checksums of all packages required to build your image for each target architecture. Commit this file to your repository to ensure reproducible builds. > Note: The `DEBUG` message `apko toolchain apko has multiple versions ["v1.1.12", "v1.1.12"], selected v1.1.12` may appear in your output. This is normal and expected — it occurs because both the toolchain setup and the lock translation in `MODULE.bazel` reference the same `apko` extension. It does not indicate a problem. ## Building the image Now that the lock file exists, update your project files to add the image build target. ### Step 8: Update MODULE.bazel to add lock file translation Add the translate_lock extension call to the end of your `MODULE.bazel` file: ``` module( name = "my-apko-image", version = "0.0.0", ) bazel_dep(name = "rules_apko", version = "1.5.37") # Set up the apko toolchain. # apko v1.1.12 is downloaded automatically — no separate installation needed. toolchain = use_extension("@rules_apko//apko:extensions.bzl", "apko") toolchain.toolchain(apko_version = "v1.1.12") use_repo(toolchain, "apko_toolchains") register_toolchains("@apko_toolchains//:all") # Translate the lock file into Bazel repository targets. # Add this section after apko.lock.json has been generated. apk = use_extension("@rules_apko//apko:extensions.bzl", "apko") apk.translate_lock( name = "wolfi_base_lock", lock = "//:apko.lock.json", ) use_repo(apk, "wolfi_base_lock") ``` ### Step 9: Update BUILD.bazel to add the image target Add the `apko_image` rule to your `BUILD.bazel` file: ```shell load("@rules_apko//apko:defs.bzl", "apko_bazelrc", "apko_image", "apko_lock") # Generates .apko/.bazelrc and .apko/range.sh for partial package fetches. # Run with: bazel run //:apko_bazelrc apko_bazelrc() # Generates the lock file that pins all package versions and checksums. # Run with: bazel run //:lock apko_lock( name = "lock", config = "apko.yaml", lockfile_name = "apko.lock.json", ) # Builds the container image from the lock file contents. # Run with: bazel build //:wolfi_base apko_image( name = "wolfi_base", config = "apko.yaml", contents = "@wolfi_base_lock//:contents", tag = "wolfi-base:latest", ) ``` The `contents` attribute references `@wolfi_base_lock//:contents` — this is the Bazel repository generated by the `translate_lock` call in `MODULE.bazel`. The name `wolfi_base_lock` in both files must match. ### Step 10: Build the image Run the build: ```shell bazel build //:wolfi_base ``` You will see output similar to the following: ```shell INFO: Analyzed target //:wolfi_base (123 packages loaded, 656 targets configured). INFO: From Action wolfi_base: 2026/03/12 12:46:27 INFO installing wolfi-keys (1-r13) arch=aarch64 2026/03/12 12:46:27 INFO installing wolfi-baselayout (20230201-r28) arch=aarch64 2026/03/12 12:46:27 INFO installing wolfi-keys (1-r13) arch=x86_64 2026/03/12 12:46:27 INFO installing wolfi-baselayout (20230201-r28) arch=x86_64 2026/03/12 12:46:27 INFO installing ca-certificates-bundle (20251003-r4) arch=x86_64 ... 2026/03/12 12:46:27 INFO installing wolfi-base (1-r7) arch=x86_64 2026/03/12 12:46:27 INFO layer digest: sha256:44cc053506b4e236f7e32026147836ce082fa58d0a329ff2aab1bb61d0c6bcfc arch=x86_64 INFO: Found 1 target... Target //:wolfi_base up-to-date: bazel-bin/wolfi_base INFO: Build completed successfully, 128 total actions ``` The built image is available at `bazel-bin/wolfi_base`. > Note: You may see `INFO` messages about duplicate package IDs in the SBOM during the build, for example: `INFO duplicate package ID found in SBOM, deduplicating package...` These are normal and expected — `apko` deduplicates packages that appear multiple times in the dependency graph when generating the Software Bill of Materials (SBOM). They do not indicate a problem with your build. On subsequent builds, Bazel's caching means most actions will be retrieved from cache rather than rebuilt. You will see output like `127 action cache hit, 1 internal` — this is expected and demonstrates one of the key benefits of building images with Bazel. ## Updating the lock file When you update `apko.yaml` to add, remove, or change packages, regenerate the lock file by running: ```shell bazel run //:lock ``` Then rebuild the image: ```shell bazel build //:wolfi_base ``` ## Rules reference ### `apko_image` Builds an OCI container image from APK packages using an apko.yaml configuration file and a pre-generated lock file. ```shell apko_image( name = "my_image", config = "apko.yaml", contents = "@my_image_lock//:contents", tag = "my-image:latest", ) ``` An example demonstrating usage with `rules_oci`: ```shell apko_image( name = "wolfi_base", config = "apko.yaml", contents = "@wolfi_base_lock//:contents", tag = "wolfi-base:latest", ) oci_image( name = "app", base = ":wolfi_base", ) ``` #### Attributes | Name | Description | Type | Mandatory | Default | | :--- | :--- | :--- | :--- | :--- | | `name` | A unique name for this target. | Name | required | | | `architecture` | The CPU architecture this image should be built for. Refer to [apko architecture documentation](https://github.com/chainguard-dev/apko/blob/main/docs/apko_file.md#archs-top-level-element). | String | optional | `""` | | `args` | Additional arguments to pass to the `apko build` command. | List of strings | optional | `[]` | | `config` | Label to the `apko.yaml` configuration file. | Label | required | | | `contents` | Label to the contents repository generated by `translate_lock`. Refer to [Generating Configuration and the Lock File](#generating-configuration-and-the-lock-file). | Label | required | | | `output` | Output format for the image. | String | optional | `"oci"` | | `tag` | Tag to apply to the resulting image. Only applicable when `output` is `docker`. | String | required | | ### `apko_lock` Generates a lock file that pins the exact versions and checksums of all packages required to build your image. The lock file is written directly into your project directory and should be committed to your repository. ```shell apko_lock( name = "lock", config = "apko.yaml", lockfile_name = "apko.lock.json", ) ``` Run with: ```shell bazel run //:lock ``` #### Parameters | Name | Description | Default | | :--- | :--- | :--- | | `name` | Name of the target. | required | | `config` | Label to the `apko.yaml` configuration file. | required | | `lockfile_name` | Name of the generated lock file. | required | ### `apko_bazelrc` Generates `.apko/.bazelrc` and `.apko/range.sh` to enable partial HTTP range requests when fetching APK packages. This significantly reduces download size by fetching only the byte ranges of each package that Bazel needs. ```shell apko_bazelrc() ``` Run with: ```shell bazel run //:apko_bazelrc ``` The generated `.apko/.bazelrc` file configures Bazel credential helpers for the specified repositories. Activate it by adding the following to your root `.bazelrc`: ```shell try-import .apko/.bazelrc ``` #### Parameters | Name | Description | Default | | :--- | :--- | :--- | | `name` | Name of the target. | `"apko_bazelrc"` | | `repositories` | List of package repository hostnames to configure credential helpers for. | `["dl-cdn.alpinelinux.org", "packages.wolfi.dev"]` | | `kwargs` | Standard Bazel attributes such as `tags` and `testonly`. | none | ## Using private APK repositories If you are using a private Chainguard APK repository, you need to provide your Chainguard token to the `apko` runtime via the `HTTP_AUTH` environment variable. Set it before running any `bazel run` or `bazel build` commands: ```shell export HTTP_AUTH="basic:apk.cgr.dev:user:$(chainctl auth token --audience apk.cgr.dev)" ``` In your apko.yaml, reference your private repository using your Chainguard organization name: ```yaml contents: repositories: - https://apk.cgr.dev/$ORGANIZATION packages: - your-package ``` Replace `$ORGANIZATION` with your Chainguard organization name. For full details on setting up and using private APK repositories with Chainguard, including how to configure your organization and authenticate, see [Chainguard's private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/). --- ### How to set up an instance of Rekor locally URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/rekor/install-a-rekor-instance.md Last Modified: September 28, 2026 Tags: Rekor, Procedural _An earlier version of this material was published in the [Rekor chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@e785fae1be184e2c929db62dbe7444fa/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@a48c33126e2c4ee6ad3bfa6b7bc9c957) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ While individual developers may not generally need to set up their own instance of Rekor, it may be worthwhile to set up your own local instance in order to further understand how Rekor works under the hood. We will have multiple terminal sessions running to set up the Rekor server. You may want to use a tool such as [tmux](https://github.com/tmux/tmux/wiki) to keep terminal sessions running in the background within the same window. ## Create and run a database backend To start, we’ll need to create a database backend; while Sigstore accepts several different databases, we’ll work with MariaDB here, so make sure you have it installed. If you are on Debian or Ubuntu, you can install it with the following command. ```sh sudo apt install -y mariadb-server ``` If you are on macOS, you can install it with Homebrew. If you don’t already have Homebrew installed, visit [brew.sh](https://brew.sh) to set it up. ```sh brew install mariadb ``` If you’re using another operating system, review the [official MariaDB installation documentation](https://mariadb.com/docs/server/mariadb-quickstart-guides/installing-mariadb-server-guide). With MariaDB installed, start the database. For Debian or Ubuntu, you can run: ```sh sudo mysql_secure_installation ``` For macOS, you can run: ```sh brew services start mariadb && sudo mysql_secure_installation ``` Once you run the above command, you will be prompted to enter your system password, and then will receive a number of prompts as terminal output. You can answer “no” or `N` to the first question on changing the root password, and “yes” or `Y` to the remaining prompts. ``` Switch to unix_socket authentication [Y/n] n … Change the root password? [Y/n] n … Remove anonymous users? [Y/n] Y … Disallow root login remotely? [Y/n] Y … Remove test database and access to it? [Y/n] Y … Thanks for using MariaDB! ``` Once you receive the `Thanks for using MariaDB!` output, you’re ready to create your database. We’ll create a directory to store our work in this example, feel free to create a directory or move into a directory that is meaningful for you. ```sh mkdir lf-sigstore && cd $_ ``` From this directory, we’ll clone the Rekor GitHub repository. ```sh git clone https://github.com/sigstore/rekor.git ``` Now, move into the directory of Rekor where the database creation script is held. ```sh cd $HOME/lf-sigstore/rekor/scripts ``` From here, you can run the database creation script. ```sh sudo sh -x createdb.sh ``` You should receive output that indicates that the test database and user were created. ``` + DB=test + USER=test + PASS=zaphod + ROOTPASS= + echo -e 'Creating test database and test user account' -e Creating test database and test user account + mysql + echo -e 'Loading table data..' -e Loading table data.. + mysql -u test -pzaphod -D test ``` At this point, we are ready to move onto installing Trillian. ## Install and set up Trillian Trillian offers a transparent, append-only, and cryptographically verifiable data store. Trillian will store its records in the MariaDB database we just created. We can install Trillian with Go. ```sh go install github.com/google/trillian/cmd/trillian_log_server@latest go install github.com/google/trillian/cmd/trillian_log_signer@latest go install github.com/google/trillian/cmd/createtree@latest ``` We’ll start the Trillian log server, providing the API used by Rekor and the Certificate Transparency frontend. ```sh $(go env GOPATH)/bin/trillian_log_server --logtostderr \ -http_endpoint=localhost:8090 -rpc_endpoint=localhost:8091 ``` Your output will indicate that the server has started, and the session will hang. ``` I0629 18:11:27.222341 7395 quota_provider.go:46] Using MySQL QuotaManager I0629 18:11:27.222847 7395 main.go:141] HTTP server starting on localhost:8090 I0629 18:11:27.222851 7395 main.go:180] RPC server starting on localhost:8091 I0629 18:11:27.223757 7395 main.go:188] Deleted tree GC started ``` Next, let’s start the log signer in a new terminal session (while keeping the previous session running), which will sequence data into cryptographically verifiable Merkle trees and periodically check the database. ```sh $(go env GOPATH)/bin/trillian_log_signer --logtostderr --force_master --http_endpoint=localhost:8190 -rpc_endpoint=localhost:8191 ``` You’ll receive output that indicates that the log signer has started. This session will also hang. ``` I0629 18:13:42.226319 8513 main.go:98] **** Log Signer Starting **** W0629 18:13:42.227281 8513 main.go:129] **** Acting as master for all logs **** … ``` The Trillian system can support multiple independent Merkle trees. We’ll have Trillian send a request to create a tree and save the log ID for future use. Run the following command in a third terminal session (while keeping the previous two sessions running). ```sh $(go env GOPATH)/bin/createtree --admin_server localhost:8091 \ | tee $HOME/lf-sigstore/trillian.log_id ``` In the Trillian log server terminal, you should have output similar to the following: ``` Acting as master for 2 / 2 active logs: master for: ``` This log string will match the string output of the new terminal session. Trillian uses the gRPC API for requests, which is an open source Remote Procedure Call (RPC) framework that can run in any environment. We can now move onto the Rekor server. ## Install and set up Redis Rekor server also requires a Redis instance. If you are on Debian or Ubuntu, you can install it with the following command. ```sh sudo apt install -y redis-server ``` If you are on macOS, you can install it with Homebrew. If you don’t already have Homebrew installed, visit [brew.sh](https://brew.sh) to set it up. ```sh brew install redis ``` If you’re using another operating system, review the [official Redis documentation](https://redis.io/docs/latest/operate/oss_and_stack/install/). With Redis installed, start it. For Debian or Ubuntu, you can run: ```sh sudo systemctl start redis-server ``` For macOS, you can run: ```sh brew services start redis ``` Now you can proceed to the next step, where you will install the Rekor server itself. ## Install Rekor server Rekor provides a restful API-based server with a transparency log that allows for validating and storing. Let’s move into the main `rekor/` directory we set up. ```sh cd $HOME/lf-sigstore/rekor ``` Now we’ll install the Rekor server from source with Go. ```sh go install ./cmd/rekor-cli ./cmd/rekor-server ``` You can now start the Rekor server with Trillian and can leave this running. ```sh $(go env GOPATH)/bin/rekor-server serve --trillian_log_server.port=8091 \ --enable_retrieve_api=false ``` Next, we’ll ensure that Rekor is working correctly. ## Test Rekor Let’s upload a test artifact to Rekor. Open another terminal session and ensure that you are in your main Rekor directory. ```sh cd $HOME/lf-sigstore/rekor ``` Now, let’s upload a test artifact to our Rekor instance. ```sh $(go env GOPATH)/bin/rekor-cli upload --artifact tests/test_file.txt \ --public-key tests/test_public_key.key \ --signature tests/test_file.sig \ --rekor_server http://localhost:3000 ``` Your terminal will output that you have created a log entry, and where it’s available. Note that your string will be different than what is indicated below. You can input the URL in a browser of your choice to inspect the resulting JSON. Created entry at index 0, available at: http://localhost:3000/api/v1/log/entries/d2f305428d7c222d7b77f56453dd4b6e6851752ecacc78e5992779c8f9b61dd9 Next, we’ll upload the key to our Rekor instance and attach it to the container we built in the Cosign chapter. If you have not created a key or the container, you can do so now, or alternately use a key and software artifact of your choice. ```sh $HOME/go/bin/cosign sign \ --key $HOME/cosign.key \ --rekor-url=http://localhost:3000 \ docker-username/hello-container ``` Now you can verify the container against both the mutable OCI attestation and the immutable Rekor record. If you signed your container using Gmail account with Google as the OIDC issuer, you can verify the image with the following command: ```sh $HOME/go/bin/cosign verify \ --key $HOME/cosign.pub \ --rekor-url=http://localhost:3000 \ --certificate-identity username@gmail.com \ --certificate-oidc-issuer https://accounts.google.com \ docker-username/hello-container ``` If everything goes well, your resulting output after running the above command should be similar to this: ``` Verification for index.docker.io/docker-username/hello-container:latest -- The following checks were performed on each of these signatures: - The cosign claims were validated - The claims were present in the transparency log - The signatures were integrated into the transparency log when the certificate was valid - The signatures were verified against the specified public key - Any certificates were verified against the Fulcio roots. [{"critical":{"identity":{"docker-reference":"index.docker.io/docker-username/hello-container"},"image":{"docker-manifest-digest":"sha256:35b25714b56211d548b97a858a1485b254228fe9889607246e96ed03ed77017d"},"type":"cosign container image signature"},"optional":{"Bundle":{"SignedEntryTimestamp":"MEUCIG...yoIY=","Payload":{"body":"...","integratedTime":1643917737,"logIndex":1,"logID":"4d2e4...97291"}}}}] ``` You can now also review the logs of your Rekor server, which will give you a URL on your localhost for this second log entry (at log entry 1). Once you are done with your Rekor instance, it is safe to exit each of the terminal sessions. Congratulations, you have set up your own Rekor server! --- ### How to sign an SBOM with Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/how-to-sign-an-sbom-with-cosign.md Last Modified: October 8, 2026 Tags: Cosign, Procedural, SBOM _An earlier version of this material was published in the [Cosign chapter](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/block-v1:LinuxFoundationX+LFS182x+2T2022+type@sequential+block@204b98f35bca48c194d1868e0356bef1/block-v1:LinuxFoundationX+LFS182x+2T2022+type@vertical+block@2f0ad9cb8f124a39ab555ac8bf1a114c) of the Linux Foundation [Sigstore course](https://learning.edx.org/course/course-v1:LinuxFoundationX+LFS182x+2T2022/home)._ [Cosign](https://github.com/sigstore/cosign), developed as part of the [Sigstore project](https://www.sigstore.dev/), is a command line utility for signing, verifying, storing, and retrieving software artifacts through interface with an OCI (Open Container Initiative) registry. Cosign can be used to sign attestations, or a verifiable assertion or statement about a software artifact.
What is an Attestation?

An attestation is a cryptographically verifiable statement about a software artifact. Attestations include a subject, a software artifact or artifacts to which the attestation applies, and a predicate, a claim or proposition about the subject. For example, an attestation might assert that a specific container image was built on a specific date using a specific configuration, and that assertion could be cryptographically verified as issuing from a specific organization or entity.

Attestations are commonly used for associating useful metadata such as SBOMs or SLSA provenance with a specific artifact such as an OCI container image. In the case of an SBOM (Software Bill of Materials), the attestation proposes that the software artifact contains a specific set of packages. In the case of SLSA provenance, the attestation claims that a specific build environment was used to create the artifact.

Attestations verify that a specific trusted entity issued specific metadata about a generated software artifact, and can also indicate that software artifacts and associated documents have not been tampered with. The SLSA project page hosts further detailed information on the attestation model.

One common use case for attestations is associating a software artifact, such as an OCI container image, with a [software bill of materials (SBOM)](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom), an inventory of the components that make up a given software artifact. Increasingly, SBOMs are considered an essential component in maintaining a secure software supply chain.
What is a Software Bill of Materials (SBOM?

A Software Bill of Materials (SBOM) is a formally structured list of libraries, modules, licenses, and version information that make up any given piece of software. An SBOM provides a comprehensive view of the components contained in a software artifact, allowing for systematic analysis, investigation, and automation. SBOMs are most useful in identifying known vulnerabilities, surveying licenses for compliance, and making decisions based on project status or supplier trust.

Read more about SBOMs.

Including an SBOM with software you ship can help others trust the provenance and contents of the software. Including your SBOM as an attestation verifies that the SBOM was generated by the same trusted organization that created the software artifact, and that neither the artifact nor its associated metadata and documents have been tampered with. In the following, we'll generate an SBOM and associate it with a specific OCI container using an attestation generated by Cosign. ## Creating a demonstration image Since we'll be attaching an SBOM to a container image, we'll first need to create an example image. We'll base this image on Chainguard's [`wolfi-base`](https://images.chainguard.dev/directory/image/wolfi-base/overview), and add a single additional package, the venerable `cowsay` utility that prints a message along with some ASCII art. We then set the entrypoint so that, when the image is run, a message will be displayed. Create a new folder for our Dockerfile build and change your working directory to that folder: ```sh mkdir -p ~/example-image && cd ~/example-image ``` Create a new `Dockerfile` in the folder using Nano or your preferred text editor: ```sh nano Dockerfile ``` Paste the following commands into the file: ```Dockerfile FROM cgr.dev/chainguard/wolfi-base RUN apk add cowsay ENTRYPOINT ["cowsay", "-f", "tux", "I love FOSS!"] ``` Save the file and close Nano by pressing `CTRL + X`, `y`, and `ENTER` in succession. Since we'll be pushing to a repository on your Docker Hub account, let's set a variable to your Docker Hub username that we can use in further commands. ```sh DH_USERNAME= ``` You can find your username on your Docker Hub account page. If you're logged in on the command line, you can also run the following command to find your username: ```sh docker info | sed '/Username:/!d;s/.* //' 2> /dev/null ``` Finally, let's build and tag the image with: ```sh docker build . -t $DH_USERNAME/example-image ``` You can test out the image by running it: ```sh docker run $DH_USERNAME/example-image ``` This will display the "I love FOSS!" message along with some ASCII art: ```output ______________ < I love FOSS! > -------------- \ \ .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/ ``` ## Generating an SBOM with Syft [Syft](https://github.com/anchore/syft) is a tool that allows us to create SBOMs. If you don't already have Syft, use the following instructions to install this utility.
How to Install Syft

Syft is a CLI tool for generating a Software Bill of Materials (SBOM). It is created and maintained by Anchore.

The recommended way to install Syft is by running a provided installation script. We recommend inspecting this script before running it on your local machine.

The following command will download the installation script and install Syft to your /usr/local/bin folder. Depending on your machine’s configuration, you may need to add sudo at the beginning of the second line of the below command or otherwise use elevated permissions to complete the installation.

curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | \
 sh -s -- -b /usr/local/bin

Alternately, your can install Syft with Homebrew:

brew install syft

You can also use Syft directly by pulling and running the official container image from Docker Hub. The following command will pull the Syft container image and use it to scan the offical Python container image from Docker Hub:

docker run -it anchore/syft:latest python

For more on installing Syft, review the project’s installation instructions on GitHub.

Once you have Syft installed, you can generate an SBOM with the `syft` command, passing in the target image as an argument. For example, the following will use Syft to generate a list of packages present in the official `python` image on Docker Hub: ```sh syft python ``` Now let's generate an SBOM for `example-image`, the image we built in the previous section. Run the following Syft command to generate an SBOM in SPDX format: ```sh syft $DH_USERNAME/example-image:latest -o spdx-json > example-image.spdx.json ```
What is SPDX?

System Package Data Exchange (SPDX) is a file format for representing Software Bills of Materials (SBOMs), or itemized metadata on packages contained in a software artifact.

The SPDX specification is based on an open standard and is maintained by the SPDX Project, part of the Linux Foundation.

If you take a look at the contents of the file, you will find our installed package, `cowsay`, represented alongside the other packages already in our base image, `wolfi-base`. You can also check that `cowsay` is detected by Syft with the following: ```sh syft --quiet $DH_USERNAME/example-image:latest | grep cowsay ``` ```output cowsay 3.04-r4 apk ``` In the next section, we'll associate this SBOM with our container image using Cosign. ## Attesting to the SBOM Now that we have our SBOM file, let's associate it with our image using an attestation. In this attestation, the image will be our subject, and the generated SBOM will serve as our predicate (an assertion about the subject). In this case, we will be attesting that the image contains the packages listed in our SBOM file. Before proceeding, let's push our image to Docker Hub, since the following commands will refer to the image on the OCI repository. ```sh docker push $DH_USERNAME/example-image ``` Our `example-image` still has attestations derived from our base image, since all of Chainguard's container images come with SBOM and SLSA provenance attestations. Let's remove these attestations with the `cosign clean` command: ```sh cosign clean $DH_USERNAME/example-image ``` We're now ready to add our attestation. Sigstore recommends referring to the image by digest and not by tag to avoid attesting to the wrong image, and attesting by tag will be removed in a future version of Cosign. The following command will set a variable to the digest of our newly pushed image: ```sh DIGEST=$(docker inspect $DH_USERNAME/example-image |jq -c 'first'| jq .RepoDigests | jq -c 'first' | tr -d '"') ``` Alternatively, you can find the digest manually by visiting your repository on Docker Hub. We can now attest using our image as the subject and our generated SBOM as the predicate: ```sh cosign attest --type spdxjson \ --predicate example-image.spdx.json \ $DIGEST ``` You will receive the following prompt: ```output Generating ephemeral keys... Retrieving signed certificate... The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/. Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record. This may include the email address associated with the account with which you authenticate your contractual Agreement. This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/. By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above. Are you sure you would like to continue? [y/N] ``` Note the warnings — a record of the attestation will be recorded to an immutable log maintained by the Sigstore project. When you're ready, press `y` to agree and attest. ## Verifying an attestation Cosign can also be used to verify the identity of the person or entity issuing an attestation. The following assumes you used GitHub to authenticate when attesting in the previous step. To verify that an attestation was issued by a specific entity, we use the `cosign verify-attestation` command, specifying the email address of the issuer: ```sh cosign verify-attestation \ --certificate-oidc-issuer=https://github.com/login/oauth \ --type https://spdx.dev/Document \ --certificate-identity=emailaddress@emailprovider.com \ $DIGEST ``` If the identity is successfully verified, an initial message similar to the following is printed to `stderr`: ``` Verification for $DH_USERNAME/example-image@sha256:545a731e803b917daf44e292b03b427427f8090c4e6c4a704e4c18d56c38539f -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates Certificate subject: Certificate issuer URL: https://github.com/login/oauth ``` The remainder of the message consists of an `in-toto attestation` envelope encoded in base64. If you wish, you can retrieve the predicate from this response: ```sh cosign verify-attestation \ --certificate-oidc-issuer=https://github.com/login/oauth \ --type https://spdx.dev/Document \ --certificate-identity=emailaddress@emailprovider.com \ $DIGEST | \ jq -r .payload | \ base64 -d | jq .predicate ``` At this point, you have successfully created an image, generated an SBOM for that image, associated the SBOM with the image as an attestation, and verified the identity of the issuer of the attestation. Using this workflow, you can attest to the contents of images you create, allowing others to understand the provenance of software you ship and enabling others to verify that software artifacts and associated documents originate from you. --- ### How to verify file signatures with Cosign URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/how-to-verify-file-signatures-with-cosign.md Last Modified: October 2, 2026 Tags: Cosign, Procedural Cosign can be used to verify binary artifacts ("blobs") using provided signatures as long as they are published to an OCI registry. In this tutorial, we’ll verify a binary artifact — in this case, a release of [`apko`](/open-source/build-tools/apko/), a command-line tool for building container images using a declarative language based on YAML. The methods in this tutorial apply to any blob file Cosign has signed with a keyless signature. This tutorial assumes you [have Cosign installed](/open-source/sigstore/cosign/how-to-install-cosign/). ## Verifying a binary with Cosign keyless signatures All `apko` releases include [keyless signatures using Cosign](/open-source/sigstore/cosign/an-introduction-to-cosign/#keyless-signing). You can verify the signature for an apko release using the `cosign` tool directly, or by calculating the SHA256 hash of the release and finding the corresponding Rekor transparency log entry. If you would like to learn how to verify a binary using Rekor or `curl`, follow the steps in our guide on [How to verify file signatures with Rekor or curl](/open-source/sigstore/cosign/how-to-verify-file-signatures-with-cosign/). We'll use the `apko_0.19.9_linux_arm64.tar.gz` tar archive from the `apko` [GitHub Release v0.19.9 page](https://github.com/chainguard-dev/apko/releases/tag/v0.19.9) in this example. There are three URLs from the list of assets on that page that you will need to copy: 1. The release itself: `https://github.com/chainguard-dev/apko/releases/download/v0.30.31/apko_0.30.31_linux_arm64.tar.gz` 2. The signature file: `https://github.com/chainguard-dev/apko/releases/download/v0.30.31/apko_0.30.31_linux_arm64.tar.gz.sig` 3. The public certificate: `https://github.com/chainguard-dev/apko/releases/download/v0.30.31/apko_0.30.31_linux_arm64.tar.gz.crt` With these URLs, construct (or copy) the following command to verify the tar archive: ```sh cosign verify-blob \ --signature https://github.com/chainguard-dev/apko/releases/download/v0.30.31/apko_0.30.31_linux_arm64.tar.gz.sig \ --certificate https://github.com/chainguard-dev/apko/releases/download/v0.30.31/apko_0.30.31_linux_arm64.tar.gz.crt \ --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \ --certificate-identity "https://github.com/chainguard-dev/apko/.github/workflows/release.yaml@refs/heads/main" \ https://github.com/chainguard-dev/apko/releases/download/v0.30.31/apko_0.30.31_linux_arm64.tar.gz ``` Running the command may take a moment, but when it completes you will receive the following output: ```output Verified OK ``` If any of the URLs are incorrect, if there was a problem with the `apko` release file, if the signature or certificate identity don't match, or if the release file was not signed, you will receive an error like the following: ```output Error: searching log query: [POST /api/v1/log/entries/retrieve][400] searchLogQueryBadRequest {"code":400,"message":"verifying signature: invalid signature when validating ASN.1 encoded signature"} error during command execution: searching log query: [POST /api/v1/log/entries/retrieve][400] searchLogQueryBadRequest {"code":400,"message":"verifying signature: invalid signature when validating ASN.1 encoded signature"} ``` If you receive an error while verifying a binary with Cosign, then you know that there was a problem with creating the artifact, or that the file that you are verifying is corrupted or invalid. If that is the case, you should download a fresh copy and verify it again, or try a different version of the software with a working signature. --- ### Cosign: The manual way URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/cosign-manual-way.md Last Modified: September 28, 2026 Tags: Cosign, Overview > **Note:** This tutorial is no longer actively maintained and may reference outdated versions of Cosign and related tools. While the underlying cryptographic concepts remain relevant, we recommend consulting the [current Cosign documentation](https://docs.sigstore.dev/cosign/signing/overview/) for up-to-date usage guidance. This content is preserved for educational purposes and may still provide value for those interested in understanding the mechanics of software signing. When I first used [Cosign](https://github.com/sigstore/cosign), the software artifact signing CLI from the [Sigstore](https://www.sigstore.dev/) project, I was amazed at how painless signing and verifying could be. For example, in the three commands below we create a public/private key pair, sign the text file, upload it to the [Rekor](https://github.com/sigstore/rekor) transparency log, and verify the signature of the message. ```bash # create public/private keys $ cosign generate-key-pair Enter password for private key: Enter password for private key again: Private key written to cosign.key Public key written to cosign.pub # sign an artifact and output the signature $ cosign sign-blob --key cosign.key --output-signature sig message.txt Using payload from: message.txt Enter password for private key: tlog entry created with index: 2014997 Signature wrote in the file sig # verify the signature $ cosign verify-blob --key cosign.pub --signature sig message.txt tlog entry verified with uuid: 7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e index: 7155742 Verified OK ``` These quick commands show how easily Sigstore can be integrated into software security practices for any developer. I am the kind of person who always wants to know what's going on under the hood. In this tutorial, I will walk you through doing Cosign the manual way so you too can understand the ins and outs of Cosign. ## Tools If you want to follow along you’ll need the following installed from your package manager of choice. I’ve noted the version used in this post but different minor versions should be fine. However, keep in mind that OpenSSL can drastically vary per system. * [openssl](https://www.openssl.org/source/) (3.0.8) Note: macOS uses libressl but it should still work * [jq](https://stedolan.github.io/jq/download/) (jq-1.6) * [curl](https://curl.se/download.html) (7.88.1) * xxd (2022-01-14) Note: macOS requires [coreutils installed via Homebrew](https://formulae.brew.sh/formula/coreutils) * [go](https://go.dev/doc/install) (go1.20.2) * [cosign](/open-source/sigstore/cosign/how-to-install-cosign/) ([v2.0.0](https://github.com/sigstore/cosign/releases/tag/v2.0.0)) * [rekor-cli](/open-source/sigstore/rekor/how-to-install-rekor/) ([v1.0.1](https://github.com/sigstore/rekor/releases/tag/v1.0.1)) ## The blob A blob is an arbitrary collection of raw data like a picture or the executable binary that your source code produces. Cosign is capable of signing and verifying blobs. In our case, we’ll be signing a spooky message. Let’s write that message to a file. ```bash echo 'Beware The Blob!' > message.txt ``` We’ll be using Cosign to sign this `.txt` file blob. ## Keys Before we can sign our message we need to generate a key pair. A key pair is a set of two keys, one private and one public, that can both sign/verify and encrypt/decrypt data. The private key is generated by some algorithm and the public key is derived from the private. There are a handful of [different algorithms](https://youtu.be/T-Ncf628ZGQ) in use today, but the most common are RSA, ECDSA, and Ed25519. I fell deep down the rabbit hole learning about the differences between these algorithms, [potential NSA backdoors](https://youtu.be/nybVFJVXbww) and which versions of OpenSSL support a given algorithm. To keep things simple, we'll use RSA but I encourage you to chase the rabbit on your own. The RSA algorithm in use today is defined by the Public Key Cryptography Standards #1 (PKCS1) specification. Let's generate a 4096 bit RSA private key with OpenSSL. ```bash $ openssl genrsa -out key.pem 4096 Generating RSA private key, 4096 bit long modulus (2 primes) ...................................................................................................................... .............++++ .................++++ e is 65537 (0x010001) ``` And from that private key we can output the public key. ``` $ openssl rsa -in key.pem -pubout -out pub.pem writing RSA key ``` ```bash $ cat key.pem -----BEGIN RSA PRIVATE KEY----- MIIJKgIBAAKCAgEA… -----END RSA PRIVATE KEY----- $ cat pub.pem -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A… -----END PUBLIC KEY----- ``` We now have our keys but this is still a bit of magic. What is a PEM and what is this syntax? To answer that, let's look at doing the same thing we just did in Go (omitting errors for brevity). ```go // main.go package main import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "os" ) func main() { key, _ := rsa.GenerateKey(rand.Reader, 4096) b := x509.MarshalPKCS1PrivateKey(key) priv := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: b, }) os.WriteFile("key.pem", priv, 0600) b, _ = x509.MarshalPKIXPublicKey(key.Public()) pub := pem.EncodeToMemory(&pem.Block{ Type: "PUBLIC KEY", Bytes: b, }) os.WriteFile("pub.pem", pub, 0600) } ``` Go's crypto library is a joy to work with and the [implementation for `rsa.GenerateKey`](https://github.com/golang/go/blob/df08c9a82152fd6f2b2811db03b40fea8b6e5e9e/src/crypto/rsa/rsa.go#L239-L346) is worth peeking at. After calling it to generate our private key, we have a bunch of mathy bits that we need to represent somehow — enter Abstract Syntax Notation number One (ASN.1) and Distinguished Encoding Rules (DER). ASN.1 is the syntax we express our key in (think a .proto file) and DER is how we encode it (think Protobuf wire format). For a more detailed explanation, [Let's Encrypt has a great article](https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/). After calling `x509.MarshalPKCS1PrivateKey` our private key is an ASN.1 DER encoded PKCS1 byte array 😅. We still have one last step, however. Byte arrays aren't human readable and are difficult to copypasta. As we often do when transmitting binary data we need to Base64 encode our bytes. Slapping on a header (`-----BEGIN RSA PRIVATE KEY------`) and footer (`-----END RSA PRIVATE KEY-----`) to help parsers identify our content finally leaves us with our Privacy-Enhanced Mail (PEM) file. PEM is a standard container for certificates and keys and was created to send binary data over email without messing with their contents. Since PKCS1 is specific to RSA our public key is serialized to Public Key Infrastructure X.509 (PKIX) which is a generic public key representation that includes information like the key algorithm. X.509 is the standard that defines the format for public key certificates like those used in your web browser. You can see above that the header is the generic `-----BEGIN PUBLIC KEY-----`. ## Time to sign With our keys created, we are ready to use them to sign our message. We could sign the file in its entirety, but that would require the full file when verifying the signature. This is fine for small text files but when signing and uploading 3 gigabyte containers, it's much more efficient to hash the payload and sign the digest. That way all that's needed to verify a signature is the public key and the hash value. SHA-256 is most commonly used today for computing a message digest. ```bash openssl dgst -sha256 -sign key.pem -out message.txt.sig message.txt ``` We can confirm our signing was successful by verifying the signature with the public key. ```bash openssl dgst -sha256 -verify pub.pem -signature message.txt.sig message.txt Verified OK ``` ## Get transparent Now that we have our signature, we can upload everything to the Rekor transparency log so others can find and verify it. Rekor supports a handful of [different distinct](https://github.com/sigstore/rekor/tree/main/pkg/types) types including Java JARs and RPM packages. The basic type is known as a [Rekord](https://github.com/sigstore/rekor/blob/b14714fb19061381159abf1858af2f43aeb4112e/pkg/types/rekord/v0.0.1/rekord_v0_0_1_schema.json) but since we signed the hash of our file we'll use a [Hashed Rekord](https://github.com/sigstore/rekor/blob/b14714fb19061381159abf1858af2f43aeb4112e/pkg/types/hashedrekord/v0.0.1/hashedrekord_v0_0_1_schema.json). Most of the fields in Rekor's types require Base64 encoding, so let's store these values in environment variables to make writing the payload easier. ```bash SIGSTORE_SIG_CONTENT=$(cat message.txt.sig | base64 | tr -d '\n') SIGSTORE_PUBLIC_KEY=$(cat pub.pem | base64 | tr -d '\n') SIGSTORE_HASH_CONTENT=$(shasum -a 256 message.txt | cut -d " " -f 1) ``` Next, we'll write out the payload to a file to make it easier to inspect and use in a request. ```bash $ cat < hashedrekord.json { "apiVersion": "0.0.1", "kind": "hashedrekord", "spec": { "data": { "hash": { "algorithm": "sha256", "value": "$SIGSTORE_HASH_CONTENT" } }, "signature": { "content": "$SIGSTORE_SIG_CONTENT", "publicKey": { "content": "$SIGSTORE_PUBLIC_KEY" } } } } EOF ``` Do a quick sanity check that our payload was created successfully. ```bash $ cat hashedrekord.json { "apiVersion": "0.0.1", "kind": "hashedrekord", "spec": { "data": { "hash": { "algorithm": "sha256", "value": "d8d321 … ``` We're now ready to send our data off to Rekor. Let's save the response to a file so we can poke around. ```bash curl -X POST -H "Content-Type: application/json" --data-binary @hashedrekord.json https://rekor.sigstore.dev/api/v1/log/entries > response.json ``` The top-level key of the response will be the database shard ID (16 characters) + entry UUID (64 characters) in the transparency log. ```bash # top level key $ jq -r 'keys[0]' response.json 24296fb24b8ad77a7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e # shard id $ jq -r 'keys[0]' response.json | cut -c -16 24296fb24b8ad77a # entry uuid $ jq -r 'keys[0]' response.json | cut -c 17- 7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e ``` The entry UUID is actually the hash of the [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) leaf node for our entry. I won't attempt to explain Merkle Trees in depth, but the basic idea is that inclusion of a node can be cryptographically verified all the way up to the root hash of the tree. [RFC 6962](https://datatracker.ietf.org/doc/html/rfc6962) explains how this works and we'll step through this verification in a bit. For now, let's see where this hash comes from. The RFC states that the hash of a leaf node is SHA-256(0x00 || d(n)). That is, the SHA 256 sum of the hex byte 0x00 concatenated to the contents of the entry, which in our case is the Hashed Rekord. We can do this in bash with process substitution. ```bash $ shasum -a 256 <(cat <(printf '\x00') <(jq -rcj '.' hashedrekord.json )) | cut -d ' ' -f 1 7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e ``` Now let's retrieve the entry from Rekor using this ID and save it to a file. If you inspect the contents, you'll notice a handful of new fields. ```bash curl https://rekor.sigstore.dev/api/v1/log/entries/24296fb24b8ad77a7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e > entry.json ``` First, let's check that our Hashed Rekord is in the body. ```bash diff <(jq -rc '.[].body' entry.json | base64 -d) <(jq -rcj '.' hashedrekord.json) ``` The body matches, which confirms that our entry was created, but how would the recipient of our message go about verifying and proving that for themselves? That’s where our next step comes in. ## Trust but verify Well, there are a few important things they must check: * That the message digest matches the signature for the public key inside the Hashed Rekord. (We'll leave this as an exercise for you, dear reader: you can reverse the steps from earlier.) * That we were in possession of the key when the message was signed. (You could check AWS/GCP KMS access logs) * That the entry is indeed included in the transparency log. (RFC 6962) The next thing that we can verify in the entry is the Signed Entry Timestamp. I'll leave this explanation to our friend Hayden Blauzvern: > As a transparency log, Rekor provides cryptographic proofs of inclusion in a log. Fetching an inclusion proof requires querying the log. The log returns a checkpoint (signed tree head) as a commitment to the current state of the log and the inclusion proof. > > Requiring an online lookup for every entry that you're verifying could cause a lot of increased latency in a verifier, and requires that the log have very high availability. Ideally, Rekor could provide an inclusion proof that could be verified offline – Rekor does this with a "signed entry timestamp" (SET). > > An SET is a structure signed with the same private key that signs Rekor's checkpoints. It is a "promise" of inclusion. It does not contain cryptographic proof, but since it is signed by the log, the log is committing to including the entry. A verifier that trusts Rekor can verify the SET without needing to do an online lookup. Asynchronously, for additional assurances, a log monitor can verify that an entry is truly present in the log for each SET a verifier views. We can start by fetching Rekor's current public key. ```bash curl https://rekor.sigstore.dev/api/v1/log/publicKey > rekor.pub ``` Next we can pull the SET out of the entry into its own file. ```bash jq -r '.[].verification.signedEntryTimestamp' entry.json | base64 -d > set.sig ``` The attestation and verification fields in the entry are not included in what is signed by the timestamping authority, so let's remove them. ```bash jq -cj '.[] | del(.attestation, .verification)' entry.json > set.json ``` Finally, we can verify the SET. ```bash $ openssl dgst -sha256 -verify rekor.pub -signature set.sig set.json Verified OK ``` The last thing we need to verify is that our entry was actually included in the Merkle tree. As mentioned earlier, this is defined in [RFC 6962](https://www.rfc-editor.org/rfc/rfc6962). [RFC 9162](https://www.rfc-editor.org/rfc/rfc9162) will eventually replace 6962 and the [pseudocode](https://datatracker.ietf.org/doc/rfc9162/#:~:text=2.1.3.2.%20%20Verifying%20an%20Inclusion%20Proof) is easier to follow (read through it first). What follows is a bash implementation of this algorithm that I am equally proud of and upset by. It builds on everything we've covered so far with the addition of the `xxd` tool. `xxd` is used to convert between binary and hex and we use it to build up the binary representation of our tree nodes from the hashes in our entry. These hashes should eventually compute to the `rootHash`. ```bash #!/usr/bin/env bash set -euo pipefail # https://datatracker.ietf.org/doc/rfc9162/#:~:text=2.1.3.2.%20%20Verifying%20an%20Inclusion%20Proof entry=$(cat entry.json) mapfile -t hashes < <(jq -rc '.[].verification.inclusionProof.hashes | .[]' <<< "$entry") rootHash=$(jq -r '.[].verification.inclusionProof.rootHash' <<< "$entry") startHash=$(shasum -a 256 <(cat <(printf '\x00') <(jq -r '.[].body' <<< "$entry" | base64 -d)) | cut -d ' ' -f 1) logIndex=$(jq -r '.[].verification.inclusionProof.logIndex' <<< "$entry") treeSize=$(jq -r '.[].verification.inclusionProof.treeSize' <<< "$entry") if [[ $logIndex -ge $treeSize ]]; then echo "verification failed! log index larger than tree size" exit 1 fi echo -e "0x00 || leaf\nstart: ${startHash}\n\n" echo -e "want: ${rootHash}\n\n" r="${startHash}" fn="${logIndex}" sn=$(( treeSize - 1 )) for i in "${!hashes[@]}" do if [[ $sn -eq 0 ]]; then echo "verification failed! tree is incomplete" exit 1 fi lsb=$(( fn & 1 )) if [[ ($lsb -eq 1) || ($fn -eq $sn) ]]; then echo "0x01 || ${hashes[i]} || ${r}" r=$(shasum -a 256 <(cat <(printf '\x01') <(xxd -r -p <<< "${hashes[i]}") <(xxd -r -p <<< "${r}")) | cut -d ' ' -f 1) while [[ ($lsb -eq 0) || $fn -eq 0 ]]; do fn=$(( fn >> 1 )) sn=$(( sn >> 1)) lsb=$(( fn & 1 )) done else echo "0x01 || ${r} || ${hashes[i]}" r=$(shasum -a 256 <(cat <(printf '\x01') <(xxd -r -p <<< "${r}") <(xxd -r -p <<< "${hashes[i]}")) | cut -d ' ' -f 1) fi fn=$(( fn >> 1 )) sn=$(( sn >> 1)) echo -e "${r}\n\n" done if [[ "${r}" == "${rootHash}" ]]; then echo "verification successful! got: ${r} want: ${rootHash}" exit 0 else echo "verification failed! got: ${r} want: ${rootHash}" exit 1 fi ``` Running this monstrosity, we can see that verification was successful! ``` $ ./verify.sh ./verify.sh 0x00 || leaf start: 7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e want: b054e63c475d8751b14b51a54255918e4ae0aa26e0ee60c9c7ee3e333396c4ad 0x01 || 2fb6c414fe3710a0ca820e20a352d827e920ef94de1a62cf032f0b508583d343 || 7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e 16e4d477cd081846cd410daf07299d17056dc5b31f77c40a716e8520a0457b1c 0x01 || af8df402b3a2f95a2e11e693d0401237c23ab1ef29844ff5b122ef7b868eb802 || 16e4d477cd081846cd410daf07299d17056dc5b31f77c40a716e8520a0457b1c 6e76df3263704eccc1c047bbc1704bbf8071a817a0c5626a2308da7d9bf3cae9 0x01 || 54b4fb9c6af94d6c8c9361176de175cb26334f26cf0c2238c8dda23ca256f7b1 || 6e76df3263704eccc1c047bbc1704bbf8071a817a0c5626a2308da7d9bf3cae9 dff1cbc7728565c69328b8618b14fe7033baa5e8936098ec8933009c3988104e 0x01 || dff1cbc7728565c69328b8618b14fe7033baa5e8936098ec8933009c3988104e || 7e12ab8d4338713ca3a427db4eb41ecdbe3e6ae15fd490716409d8c89c63ae1d 6ad77f4dcac7a5d47d3087283129429d50cd2d9a4410f5d4900afd78e33ad40a 0x01 || 94cc41349e90789f7f80dce5df0339373749400539d41c8a3ecca485e3c92603 || 6ad77f4dcac7a5d47d3087283129429d50cd2d9a4410f5d4900afd78e33ad40a 4fdd278ae02e4cbae8dbc0650e4d3282e208ffec9603c68f73bcafe328488ac7 0x01 || a50a2b2e4a506dd075f59dab6db168164eb52e437756f53162f04be6ce0b5c3b || 4fdd278ae02e4cbae8dbc0650e4d3282e208ffec9603c68f73bcafe328488ac7 65cfed18ce41b642653912e86402b45d5df0109de29edbf0cb20c62f3444fdc0 0x01 || 65cfed18ce41b642653912e86402b45d5df0109de29edbf0cb20c62f3444fdc0 || 52f10e8d704883a11ed3f157d079afffd814f563d5c013d5752ea81744aac4b5 749a1795a60841356d1928b78af94814b127b7fea9aede093eb39b410e166f27 0x01 || a0441f106e6bb9ec4acc5f3126beea2ae60b721648c3c7ba741368458cef89c6 || 749a1795a60841356d1928b78af94814b127b7fea9aede093eb39b410e166f27 5d0ebe394bc76ddf46868b03466d3042b4efcb70760f997731ee6758c272208f 0x01 || 5d0ebe394bc76ddf46868b03466d3042b4efcb70760f997731ee6758c272208f || df2f19f10ed0def555c68cda80b4fdd8f535ba929c1cfc36d9c789732d20e305 9138f73ed31973bab8e005465ee04bec1cfb2a56e0f6eb9f316d964127c9877e 0x01 || 9138f73ed31973bab8e005465ee04bec1cfb2a56e0f6eb9f316d964127c9877e || 93663301e472c817961f75499d26844b246665d275538e5a24e16b065cda7afe 874589aee5388025c078a59e1f6e66ac65d185cdc766620860ba0680cfbd26bc 0x01 || c2ed30483aafe58267a6740c2fb76408eb6366ceb48c9f0509c0f2c04b101f05 || 874589aee5388025c078a59e1f6e66ac65d185cdc766620860ba0680cfbd26bc 0926480781b8d7bc2790d7e63972846bf98b79b47a1968a47f9c0992f319da44 0x01 || b593f43b0ca6048ca4e1564817b0381f25f66d6c6b205673d6e6bc1c582a02bd || 0926480781b8d7bc2790d7e63972846bf98b79b47a1968a47f9c0992f319da44 1af9b80f354a6c824fe9893f7278ce192dea39f4cb800707681947408b9f8c60 0x01 || b8f5ef19b43c82a6deee74a4b36c41a4b7f030d46f9862913ae5ab74177d7a34 || 1af9b80f354a6c824fe9893f7278ce192dea39f4cb800707681947408b9f8c60 b76bf4a5ee7dbf09372243c01c86d620fc3581e8c4b5659ce549b397ec9608bc 0x01 || 0b4e9c3dce66ce4f393b3b3ae5f141a0d20902538d5fc99bd030348672b69b81 || b76bf4a5ee7dbf09372243c01c86d620fc3581e8c4b5659ce549b397ec9608bc 31fcf995aaadf0cc7ce249b3ae06fb73653648c6279dd8401a75e59538a85718 0x01 || 9a991dc567e6dd4fa21d6235df691252ca81eab5e1301c849c8aa2151363e6a7 || 31fcf995aaadf0cc7ce249b3ae06fb73653648c6279dd8401a75e59538a85718 36a05fd84e2e4f497a41a3e6e633b59a49873d731e1d4e2e5c91ecb5ece28091 0x01 || 732af72ebcc0c8a16dbdba657c7b755d2097691d08882f308dbc5b133372c170 || 36a05fd84e2e4f497a41a3e6e633b59a49873d731e1d4e2e5c91ecb5ece28091 5219998b60322aa7e1aa3c02cf15bedc67d89876dfc46f1b89937c8e496aa947 0x01 || 2747468d0ed5e5b1138bba7b7968367a9842437d9004b3166391f115cb867d1e || 5219998b60322aa7e1aa3c02cf15bedc67d89876dfc46f1b89937c8e496aa947 b054e63c475d8751b14b51a54255918e4ae0aa26e0ee60c9c7ee3e333396c4ad verification successful! got: b054e63c475d8751b14b51a54255918e4ae0aa26e0ee60c9c7ee3e333396c4ad want: b054e63c475d8751b14b51a54255918e4ae0aa26e0ee60c9c7ee3e333396c4ad ``` This is the same flow that the Rekor CLI will step through. ``` $ rekor-cli verify --uuid 24296fb24b8ad77a7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e Current Root Hash: f0e0de7e6b03385bc086c46703b7a2abbbd16ae10fc28b3f480125b1898536fb Entry Hash: 24296fb24b8ad77a7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e Entry Index: 7155742 Current Tree Size: 2995354 Checkpoint: rekor.sigstore.dev - 2605736670972794746 2995354 8ODefmsDOFvAhsRnA7eiq7vRauEPwos/SAElsYmFNvs= Timestamp: 1668548431487913062 — rekor.sigstore.dev wNI9ajBEAiB/Lcxmn82//9QIwqVPbVSgzEAfACmAnZNLD9RuIH9QiAIgLToW3Bd8Y26Wwz3JuuZBsC1/IhUExSbu1NET/nzoajc= Inclusion Proof: SHA256(0x01 | 24296fb24b8ad77a7e53fd5d089af142b7909598d214e13ca76001cc575fddaad3210adbee86363e | 2fb6c414fe3710a0ca820e20a352d827e920ef94de1a62cf032f0b508583d343) = 768fdb04aac9d523a34adbfeecd3268f920ff250ffc809867bae28bd10eb5f15 … ``` Stay spooky… _Special thanks to [Appu Goundan](https://github.com/loosebazooka) and Hayden Blauzvern._ --- ### Verifying signatures in air-gapped environments URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/open-source/sigstore/cosign/verifying-in-air-gapped-environments.md Last Modified: September 28, 2026 Tags: Cosign, Procedural Cosign verifies keyless signatures against the public Sigstore infrastructure, which an air-gapped environment can't reach. You can still verify signatures and attestations there. You need to carry two things across the air gap ahead of time: the Sigstore trust root, and the signatures themselves. This guide uses Chainguard Containers as the example artifact, but the technique applies to any container image signed with Cosign. ## What Cosign needs from the network A keyless verification normally makes three kinds of network request. Only the first blocks you in a disconnected environment: - **The Sigstore trust root**, fetched over [The Update Framework](https://theupdateframework.io/) (TUF) from `tuf-repo-cdn.sigstore.dev`. Cosign refreshes this metadata on every verification, so an air-gapped run fails unless you supply the trust root from a file. - **The registry**, to fetch the image and its signature. Inside the air gap, this is your internal registry or a directory on disk. - **The Rekor transparency log**, to confirm the signature was logged. Cosign doesn't need this. A Chainguard signature carries a signed entry timestamp alongside it, which proves the log entry exists and which Cosign checks offline. The last point matters: you keep transparency-log verification in an air-gapped environment. You don't need `--insecure-ignore-tlog`, and you shouldn't use it, because it discards a check that still works. ## Prerequisites - [Cosign](/open-source/sigstore/cosign/how-to-install-cosign/) v3.0.3 or later, installed on both a connected machine and inside the air-gapped environment. Cosign v3.0.3 fixed several problems with offline verification. Earlier releases need different flags, so upgrade rather than work around them. - A connected machine that can reach `cgr.dev` and the public Sigstore infrastructure. - An approved way to move files into the air-gapped environment. ## Export the Sigstore trust root Run this on the connected machine. `cosign initialize` downloads the current TUF metadata and writes it to a local cache: ```sh cosign initialize ``` Copy the trust root out of that cache: ```sh cp ~/.sigstore/root/tuf-repo-cdn.sigstore.dev/targets/trusted_root.json . ``` The file is about 7 KB. It holds the Fulcio certificate authorities, the Rekor transparency log keys, the certificate transparency log keys, and the timestamp authority certificates that Cosign checks a signature against.

Note: Copying the whole ~/.sigstore directory into the air-gapped environment doesn’t work. Cosign refreshes its TUF metadata over the network on every verification, so a copied cache still fails. Pass the trust root as a file with --trusted-root instead.

## Move the images and their signatures A Cosign signature is a separate artifact in the registry, stored under a tag derived from the image digest. Copying an image alone leaves the signature behind, and verification inside the air gap then fails with `no signatures found`. To see what's attached to an image, run `cosign tree` on the connected machine: ```sh cosign tree cgr.dev/chainguard/go:latest ``` The output lists the signature and attestation tags attached to the image: ``` 📦 Supply Chain Security Related artifacts for an image: cgr.dev/chainguard/go:latest └── 💾 Attestations for an image tag: cgr.dev/chainguard/go:sha256-6be282d9e6dc....att ├── 🍒 sha256:475e95ecb3900fbf479c31b79527c8d1a8f85445dd44caaf21310fb8cd56fdad ├── 🍒 sha256:ae3c33f8f466481133b4826e165f09cdd11bb76a93f6a6af3429e41c887b0109 └── 🍒 sha256:4f3c43079eed9885ebe77b787604e98ebf0f8020f61002efb70f5fda091ed27b └── 🔐 Signatures for an image tag: cgr.dev/chainguard/go:sha256-6be282d9e6dc....sig └── 🍒 sha256:f0e210a4dfafc1188c7a9ab97b3e01082a7a9ebbbb98174292bedc8ddca71b61 ``` Choose one of the following two transports. ### Option 1: Copy into a mirrored registry Use `cosign copy`, which carries the image, its signatures, and its attestations together. For a multi-architecture image it also copies the per-platform signatures, which matters if the air-gapped environment pulls a single architecture: ```sh cosign copy cgr.dev/chainguard/go:latest registry.internal/chainguard/go:latest ``` Confirm the signature arrived before you rely on the mirror: ```sh cosign tree registry.internal/chainguard/go:latest ```

Note: General-purpose copy tools don’t carry Cosign signatures for Chainguard Containers. Chainguard stores signatures and attestations under .sig and .att tags rather than in the OCI referrers graph, so tools that walk referrers report success and copy the image bytes alone. Use cosign copy, or copy the .sig and .att tags explicitly.

### Option 2: Save to a directory If the air-gapped environment has no registry, `cosign save` writes the image and its attached artifacts to an OCI layout on disk: ```sh cosign save cgr.dev/chainguard/go:latest --dir ./transfer ``` Move the `transfer` directory and `trusted_root.json` across the air gap together. On the other side you can verify the directory in place, or load it into a registry with `cosign load`. ## Verify inside the air-gapped environment Pass the trust root with `--trusted-root`. Everything else matches a connected verification. ### Verify a mirrored image ```sh cosign verify \ --trusted-root ./trusted_root.json \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ registry.internal/chainguard/go:latest ``` A successful run reports the offline transparency-log check: ``` Verification for registry.internal/chainguard/go:latest -- The following checks were performed on each of these signatures: - The cosign claims were validated - Existence of the claims in the transparency log was verified offline - The code-signing certificate was verified using trusted certificate authority certificates ```

Note: The verified output still reports a docker-reference of cgr.dev/chainguard/go, even though you verified a mirrored copy. This is expected. The signature covers the image digest, not the registry it’s served from.

### Verify a saved directory Point Cosign at the directory with `--local-image`: ```sh cosign verify \ --local-image \ --trusted-root ./trusted_root.json \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ ./transfer ``` ### Verify attestations `cosign verify-attestation` works the same way. This example checks the SPDX SBOM attestation: ```sh cosign verify-attestation \ --trusted-root ./trusted_root.json \ --type https://spdx.dev/Document \ --certificate-oidc-issuer=https://token.actions.githubusercontent.com \ --certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \ registry.internal/chainguard/go:latest ``` ### Verify images from a private registry Images in your organization's registry are signed by your organization's `image-syncer` and `custom-image-builder` identities rather than by Chainguard's public signing identity. Resolve those identifiers on the connected machine, because `chainctl` needs to reach the Chainguard control plane: ```sh PARENT=your-organization IMAGE_SYNCER=$(chainctl iam account-associations describe $PARENT -o json | jq -r '.[].chainguard.service_bindings.CATALOG_SYNCER') CUSTOM_IMAGE_BUILDER=$(chainctl iam account-associations describe $PARENT -o json | jq -r '.[].chainguard.service_bindings.APKO_BUILDER') ``` Record both values and carry them across the air gap with the trust root. Verification then uses them in place of the public identity: ```sh cosign verify \ --trusted-root ./trusted_root.json \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp="https://issuer.enforce.dev/(${IMAGE_SYNCER}|${CUSTOM_IMAGE_BUILDER})" \ registry.internal/chainguard/go:latest ``` For more on these identities, see [Verifying Chainguard Containers and metadata signatures with Cosign](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/). ### Store signatures in a separate repository Some registry layouts keep signatures apart from the images they sign. Set `COSIGN_REPOSITORY` to tell Cosign where to look: ```sh export COSIGN_REPOSITORY=registry.internal/signatures ``` Without it, Cosign looks beside the image and reports `no signatures found`. ## Refresh the trust root Sigstore rotates the keys and certificate authorities in the trust root from time to time. A trust root you exported months ago can fail to verify a signature made after a rotation. Re-export `trusted_root.json` from a connected machine on the same schedule you use to refresh images, and move the two together. Treat the trust root as part of every transfer rather than as one-time setup. ## Re-signing with your own key Some organizations verify Chainguard signatures on the connected side, then re-sign the images with an internally managed key before import. Inside the air gap, admission policies check only that internal key. That's a decision about trust domains and key custody, not a technical requirement. The verification described earlier works with no outbound connectivity, so you can also keep verifying Chainguard's signatures directly. If you do re-sign, see [How to sign a container with Cosign](/open-source/sigstore/cosign/how-to-sign-a-container-with-cosign/). ## Troubleshooting | Message | Cause | |---------|-------| | `tuf: failed to download 13.root.json` | Cosign tried to refresh TUF metadata over the network. Pass `--trusted-root`. | | `no signatures found` | The signature wasn't copied with the image, or it lives in another repository. Check with `cosign tree` and set `COSIGN_REPOSITORY` if needed. | | `none of the expected identities matched what was in the certificate` | The `--certificate-identity` or `--certificate-oidc-issuer` value doesn't match the signer. The error lists the subject that was found. | | `Flag --offline has been deprecated` | Remove `--offline`. Supplying `--trusted-root` covers this case. | | `if any flags in the group [local-image new-bundle-format] are set none of the others can be` | Remove `--new-bundle-format`. Older guides pair it with `--local-image`, which Cosign v3.0.3 rejects. | ## Learn more For background on how Cosign verification works, read [An introduction to Cosign](/open-source/sigstore/cosign/an-introduction-to-cosign/). To verify Chainguard Containers in a connected environment, see [Verifying Chainguard Containers and metadata signatures with Cosign](/chainguard/containers/security-and-compliance/verifying-chainguard-images-and-metadata-signatures-with-cosign/). For mirroring Chainguard Containers into an internal registry, see the [pull-through guides](/chainguard/containers/registry/pull-through-guides/). --- ## Platform ### Chainguard MCP servers overview URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/mcp-servers/overview.md Last Modified: October 13, 2026 Tags: MCP, Overview Four of Chainguard's MCP servers give an AI tool direct access to live product data: the container images in `cgr.dev`, the Wolfi package index, upstream version and end-of-life history, and the Chainguard platform API. An MCP client connected to them can answer questions such as what a given image's SBOM contains, which Wolfi package replaces a Debian one, or whether an upstream release is still supported, using data from the product itself rather than from a model's training data. These servers back the [Chainguard Power for Kiro](/platform/integrations/kiro/) and the [Chainguard plugin for Cursor](/platform/integrations/cursor/), but you don't need either one. Any MCP-compatible client can connect to them directly. In a [Chainguard Workspaces](/chainguard/workspaces/overview/) session, Claude Code comes with the `cg-apk` and `cg-versions` servers already configured, with no browser sign-in. Refer to [MCP servers in a session](/chainguard/workspaces/coding-agents/#mcp-servers-in-a-session). ## What is MCP? [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open protocol that standardizes how AI applications access external data and tools. An MCP server exposes structured data and tools that AI clients can call to ground their responses in real information. ## Chainguard's MCP servers Chainguard's MCP servers fall into two groups. Four return live product data, and two serve specific content: documentation and agent skills. ### Product data servers | Server | Endpoint | What it gives you | | ----- | ----- | ----- | | [`cg-oci`](/platform/mcp-servers/cg-oci/) | `https://cgr.dev/mcp` | Manifests, image configs, SBOMs, apko configs, and provenance for a specific image reference | | [`cg-apk`](/platform/mcp-servers/cg-apk/) | `https://apk.cgr.dev/mcp` | Wolfi package lookup by name, plus package SBOMs and melange build configs | | [`cg-versions`](/platform/mcp-servers/cg-versions/) | `https://versions.cgr.dev/mcp` | Upstream release and end-of-life history for tracked projects | | [`cg-api`](/platform/mcp-servers/cg-api/) | `https://console-api.enforce.dev/mcp` | The Chainguard platform API, read-only: organizations, IAM, registry metadata, and security advisories | These servers return live product data, such as an image tag's manifest and SBOM as it exists in the registry right now, rather than documentation about it. All four use the Streamable HTTP transport and authenticate with OAuth 2.0 against the Chainguard issuer. ### AI Docs MCP server The [AI Docs MCP server](/platform/mcp-servers/ai-docs/), at `https://mcp.edu.chainguard.dev/mcp`, searches Chainguard documentation: guides, security references, and the Wolfi, apko, melange, and chainctl references. It also maps Debian and Fedora packages to their Wolfi equivalents, and upstream images to the Chainguard images that replace them. It has no live image data; for that, use `cg-oci`. You can also run it locally from a container image, or download its documentation as a single file. It needs no sign-in and doesn't paginate its results, so the Authentication and Pagination sections that follow don't apply to it. ### Public Skills MCP server The [Public Skills MCP server](/chainguard/agent-skills/public-skills-mcp/), at `https://skills.cgr.dev/mcp`, serves Chainguard's hardened agent skills. An AI tool can search the catalog, inspect a skill, and load one to run without installing it first. Signing in requires a Chainguard account, but no entitlement or organization membership. Like the product data servers, it authenticates with OAuth 2.0 against the Chainguard issuer and needs its own sign-in, including through `chainctl` with the audience `https://skills.cgr.dev/mcp`. Its `search_skills` and `list_skills` tools return results one page at a time. For connection steps, refer to its own page. ## Authentication Each product data server authenticates separately with OAuth 2.0 against the Chainguard issuer. On first use, your client opens a browser window for sign-in. Every tool call is scoped to the token it runs under. The servers return what your Chainguard account can already read and nothing more, so two people connected to the same endpoint can see different repositories, packages, and organizations. `cg-api` goes further and varies its *tool list* by token: tools your capabilities don't cover never appear in the client. ### Authenticate with chainctl instead of a browser On a remote or headless workstation, the browser redirect can't complete. Claude Code's OAuth path against `issuer.enforce.dev` also issues access tokens that last about an hour and carry no refresh token, so the browser flow repeats often. To avoid both problems, reuse your existing `chainctl` session. First, register an audience for each MCP server you plan to use. An OAuth audience names the resource a token is valid for, and for MCP servers it must be the full URL including the `/mcp` path — a bare hostname is rejected with `401 invalid token`. Repeat `--audience` to request several tokens at once, and add `--headless` to log in with a device code rather than a browser redirect: ```sh chainctl auth login \ --headless \ --audience=https://cgr.dev/mcp \ --audience=https://apk.cgr.dev/mcp \ --audience=https://versions.cgr.dev/mcp \ --audience=https://console-api.enforce.dev/mcp ``` `--headless` also makes headless your default login mode, so later `chainctl` logins print a URL instead of opening a browser. To return to browser login, run `chainctl config unset auth.mode`. See [Headless mode persists after you use it](/platform/chainctl-usage/authentication-options/#headless-mode-persists-after-you-use-it). The Public Skills MCP server accepts the same method. Add `--audience=https://skills.cgr.dev/mcp` to log in to it too. Once an audience is logged in, `chainctl auth token --audience=` returns from cache without prompting, and `chainctl` refreshes the underlying token as it nears expiry. Next, point your client at `chainctl` rather than at OAuth. Claude Code's MCP configuration accepts a `headersHelper` field naming a script that Claude Code runs (through `sh -c`, with a roughly 10-second budget) each time it opens a connection. The script reads `CLAUDE_CODE_MCP_SERVER_URL` from its environment and prints a JSON object of HTTP headers on stdout, which Claude Code merges into the connection in place of the OAuth flow. Save this helper as its own file and make it executable. It works for any Chainguard MCP server, so you need only one copy no matter how many you connect: ```shell mkdir -p ~/bin cat > ~/bin/cg-mcp-headers <<'EOF' #!/bin/sh set -e aud=${CLAUDE_CODE_MCP_SERVER_URL:?CLAUDE_CODE_MCP_SERVER_URL not set} t=$(command -v timeout || command -v gtimeout || true) tok=$(${t:+"$t" -s KILL 8} chainctl auth token --audience="$aud") || { echo "no chainctl token for audience $aud; run: chainctl auth login --audience=$aud" >&2 exit 1 } printf '{"Authorization":"Bearer %s"}\n' "$tok" EOF chmod +x ~/bin/cg-mcp-headers ``` The heredoc delimiter is quoted (`<<'EOF'`) so that the shell writes `$aud`, `$tok`, and `${CLAUDE_CODE_MCP_SERVER_URL}` into the file literally. With an unquoted delimiter, the shell expands them as it writes the file. `CLAUDE_CODE_MCP_SERVER_URL` isn't set in your shell, so the `:?` check aborts the write and leaves an empty file. The file must be executable because Claude Code runs its path as a command through `sh -c`. The `timeout` keeps the helper from stalling. If you haven't logged in to an audience, `chainctl auth token` can start an interactive login, which runs past Claude Code's time limit for the helper. With the timeout, the helper exits quickly and prints the command that fixes the problem. macOS doesn't include `timeout`. The script falls back to `gtimeout`, which `brew install coreutils` provides, and without either it runs `chainctl` with no time limit. Now reference the script from each server entry. The `headersHelper` value must be an absolute path, so write the file from the repository root and let the shell expand `$HOME` for you: ```shell cat > .mcp.json <
Edit ~/.claude.json rather than overwriting it. It holds all of your Claude Code configuration, so redirecting a heredoc over it discards everything else you have set up. Open it in an editor and add the projects entry alongside what’s already there.
Because this form is keyed by absolute path, a second clone or `git worktree` of the same repository needs its own entry. ## Pagination Every `list_*` and `search_*` tool across the product data servers and the Public Skills server returns one page at a time and caps the page size. The parameter names differ by server: | Server | Page size | Request the next page with | Response field to pass back | | ----- | ----- | ----- | ----- | | `cg-oci` | 50 default, 200 max | `cursor` | `next_cursor` (or the last item's name, for `list_repos`) | | `cg-apk` | 50 default, 200 max | `cursor` | `next_cursor` | | `cg-versions` (`search_projects`) | 25 default, 200 max | `page_token` | `next_page_token` | | `cg-versions` (`list_stream_versions`) | 50 default, 200 max | `cursor` | `next_cursor` | | `cg-api` | 50 default, 200 max | `page_token` | `nextPageToken` | | Public Skills | 50 default, 200 max | `page_token` | `next_page_token` | ## Next steps - [`cg-oci`](/platform/mcp-servers/cg-oci/) — look up manifests, configs, SBOMs, and provenance for a container image - [`cg-apk`](/platform/mcp-servers/cg-apk/) — find Wolfi packages and read their SBOMs and build configs - [`cg-versions`](/platform/mcp-servers/cg-versions/) — check upstream releases and end-of-life dates - [`cg-api`](/platform/mcp-servers/cg-api/) — query organizations, IAM, registry metadata, and security advisories through the platform API --- ### Provision Chainguard users with SCIM URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/scim-provisioning/scim-overview.md Last Modified: September 28, 2026 Tags: Conceptual, Procedural System for Cross-domain Identity Management (SCIM) is an open standard for automating the exchange of user identity information between systems. Chainguard uses SCIM to create and deactivate user accounts based on your identity provider (IdP). Connect your IdP's SCIM provisioning once; from then on, assigning a user to the application provisions them, and deactivating or unassigning them removes their Chainguard access. Accounts follow your IdP, so you manage access in one place. Chainguard's SCIM provisioning manages accounts, not roles. To grant roles from IdP group membership, use [group mappings](/platform/administration/custom-idps/grant-roles-from-groups/); the two are independent, and you can use either or both. Chainguard's SCIM endpoint accepts user provisioning only: group provisioning over SCIM is not yet available, so leave your IdP's SCIM group push turned off. This page explains how provisioning behaves and how to manage the connection over its lifetime. To connect a specific identity provider, follow its guide: * [Okta SCIM provisioning](/platform/administration/custom-idps/scim-provisioning/okta-scim/) * [Microsoft Entra ID SCIM provisioning](/platform/administration/custom-idps/scim-provisioning/ms-entra-id-scim/) ## How SCIM provisioning works Provisioning behaves the same way for every provider: * **Provisioning creates records, logins create accounts.** When your IdP provisions a user, Chainguard stores a provisioning record. Chainguard creates the user's account the first time they log in, exactly as it would without SCIM, and connects it to their provisioning record automatically. Users who have logged in before are connected at their next login, with no re-registration; their existing access is unaffected. * **Matching is by `externalId`, never by email.** A provisioned user connects to a login by matching the SCIM `externalId` your IdP sends against the subject of the login token. Each provider guide covers how to make the two align. Chainguard never uses email addresses for matching. * **Deactivation takes effect at the user's next login or token refresh.** When your IdP deactivates or unassigns a provisioned user, their current access token runs out on its own schedule (up to an hour), and Chainguard refuses every attempt to log in or refresh after the deactivation. Reactivating the user in your IdP restores their ability to log in; it does not restore any role bindings that were removed while they were deactivated. * **Provisioning does not assign roles.** A provisioned user who logs in gets the identity provider's default role, plus any group-mapped roles and role bindings they hold, same as any other user. * **Enabling SCIM does not restrict other logins.** Users who aren't provisioned log in exactly as before; provisioning adds lifecycle control only for the users your IdP sends. ## Prerequisites The provider guides share the following prerequisites: * A custom identity provider already configured for login to Chainguard. If you haven't set one up yet, refer to our guide on [using custom identity providers](/platform/administration/custom-idps/custom-idps/). * An IAM role that can manage identity providers in your organization, such as the owner role. * Two owners with directly assigned role bindings in your organization. Enabling SCIM requires this, so that access to your organization never depends entirely on the IdP that SCIM controls. Roles held through group mappings don't count toward the two: Chainguard grants them per session at login, which an IdP outage would interrupt. Organizations that manage access through groups often have only their creator as a directly assigned owner, so check this first. * `chainctl` installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't already have this installed. You must also authenticate with `chainctl auth login`. The commands on this page refer to your identity provider by its UIDP, stored in the `IDENTITY_PROVIDER` environment variable. Retrieve and set it with the following command: ```sh export IDENTITY_PROVIDER=$(chainctl iam identity-providers list -o json | jq -r '.items[0].id') ``` Organizations generally have one identity provider, so the list usually holds a single entry and the `.items[0].id` filter selects it. If the list holds more than one — because you belong to several organizations, or your organization has several providers — run `chainctl iam identity-providers list` on its own and set `IDENTITY_PROVIDER` to the UIDP of the provider you are configuring. ## The SCIM token Your IdP authenticates to Chainguard's SCIM endpoint with a bearer token. Generate it with the following command: ```sh chainctl iam identity-providers scim token generate $IDENTITY_PROVIDER ``` This command prints the token to standard output and the SCIM endpoint URL and expiry to standard error. Chainguard shows the token exactly once and stores only a digest of it, so paste it into your IdP right away. If you lose it, create a replacement with the `regenerate` command shown in [Manage provisioning](#manage-provisioning). Tokens expire after one year by default. Set a different lifetime with `--expires-in` (up to two years), or issue a non-expiring token with `--never-expires`. ## Enable and disable provisioning Generating a token does not start provisioning. Enable it with the following command: ```sh chainctl iam identity-providers scim enable $IDENTITY_PROVIDER ``` Chainguard now accepts provisioning requests from your IdP. If the command fails with a message about owner-tier identities, your organization doesn't yet have two directly assigned owners; refer to [Prerequisites](#prerequisites). Disabling stops Chainguard from accepting provisioning requests. It doesn't invalidate the token, and already provisioned users are unaffected: ```sh chainctl iam identity-providers scim disable $IDENTITY_PROVIDER ``` ## Manage provisioning * **Rotate the token.** Replace the token while your IdP keeps working through the change. The previous token keeps authenticating for the overlap window (one hour by default, up to 24 hours) while you paste the new one into your IdP: ```sh chainctl iam identity-providers scim token regenerate $IDENTITY_PROVIDER --overlap 1h ``` * **Revoke the token.** If you suspect the token is exposed, revoke it. This immediately invalidates the current token and any overlap token: ```sh chainctl iam identity-providers scim token revoke $IDENTITY_PROVIDER ``` Provisioning requests fail until you regenerate a replacement; revoking contains a compromise without turning provisioning off. If you want an immediate replacement instead of a stop, regenerate with `--overlap 0`. * **Delete a user in your IdP.** If your IdP sends a SCIM delete (distinct from deactivation), Chainguard removes the provisioning record entirely and deactivates the user's account. Token lifecycle and the on/off switch are independent: rotating or revoking a token never disables provisioning, and disabling never invalidates a token. ## Limits * Chainguard rate limits provisioning write requests (create, update, deactivate) per organization and per source address. Past the limit, requests receive HTTP `429` with a `Retry-After` header; IdPs retry on their own schedule. * Chainguard caps request bodies on writes at 256 KiB, which is larger than any standard SCIM user payload. ## Related resources * [How to provision users into Chainguard from Okta with SCIM](/platform/administration/custom-idps/scim-provisioning/okta-scim/) * [How to provision users into Chainguard from Microsoft Entra ID with SCIM](/platform/administration/custom-idps/scim-provisioning/ms-entra-id-scim/) * [Grant Chainguard roles from identity provider groups](/platform/administration/custom-idps/grant-roles-from-groups/) * [Using custom identity providers to authenticate to Chainguard](/platform/administration/custom-idps/custom-idps/) * [Overview of the Chainguard IAM model](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/) --- ### Using Chainguard with Cursor URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/integrations/cursor.md Last Modified: September 28, 2026 AI coding agents write code and install dependencies faster than any security team can review them manually. Every `pip install`, `npm install`, or `docker pull` an agent kicks off is a trust decision being made on your behalf against public registries. The [Chainguard and Cursor partnership](https://www.chainguard.dev/partners/cursor) follows a clear separation of responsibilities: Cursor is where your developers and agents plan, write, and review code. Chainguard is where developers reach for open source artifacts: Python, Java, and JavaScript libraries plus 2,300+ container images, all rebuilt from verifiable sources in the Chainguard Factory. This page explains how to start using Chainguard artifacts in Cursor. ## Prerequisites Before you begin, you'll need: * A [Cursor](https://cursor.com/) account * A [Chainguard account](https://console.enforce.dev/) and organization in domain format (for example, `acme-corp.com`). * [`chainctl` installed and authenticated](/platform/chainctl-usage/how-to-install-chainctl/) * Chainguard authentication should be configured in the environment where Cursor runs commands. * Your Chainguard pull token credentials should be injected via environment variables, and not hard-coded in source. ## Install the Chainguard plugin The Chainguard integration is delivered as an official plugin through the Cursor marketplace. To get started: 1. Open the Cursor marketplace and search for Chainguard. 1. On the Chainguard plugin screen, click **Add to Cursor**. 1. In your Cursor settings, navigate to the **Tools & MCPs** section. Connect all Chainguard MCP servers to your account. You will be prompted to authenticate Chainguard MCPs. After authenticating, Cursor displays an overview of how the plugin works. ## Migrate a project to use Chainguard After installing the plugin, open a project in Cursor. When writing prompts, tell Cursor to use Chainguard libraries and images: ```prompt I'd like to migrate this project to use Chainguard images and libraries. ``` Cursor will update your Dockerfiles to reference Chainguard container images and reconfigure your build files to pull dependencies from Chainguard Libraries. You can also reference specific Chainguard library skills in prompts. For example: ```prompt /chainguard-libraries-python set up my token ``` > **Note**: Cursor's output is prompt-driven and may vary depending on your project's structure and complexity. Review all changes before running a build or deploying to production. ### Using Chainguard container images Chainguard provides thousands of minimal, CVE-free container images. When Cursor updates a Dockerfile to use Chainguard images, it will reference the images like the following example: ```dockerfile FROM cgr.dev/chainguard/python:latest-dev AS builder FROM cgr.dev/chainguard/python:latest ``` You can browse available images and their tags in the [Chainguard Images directory](https://images.chainguard.dev/). This Dockerfile references both the `python` image's `latest` and `latest-dev` variants. Learn more about Chainguard image variants in [Chainguard’s container variants docs](/chainguard/containers/concepts/container-variants/). > **Note**: `latest` tags are not recommended for production workloads. For production, we recommend pinning to a specific version or digest to ensure reproducible builds. Learn more about Chainguard's container images in the [Containers documentation](/chainguard/containers/). ### Using Chainguard Libraries [Chainguard Libraries](/chainguard/libraries/) provide malware-resistant Python, Java, and JavaScript packages. When Cursor migrates a project, it reconfigures your build files to pull dependencies from Chainguard instead of public registries. You can browse available packages in the [Chainguard Console](https://console.chainguard.dev/), under the **Libraries** section. > **Note**: Chainguard Libraries does not mirror every package available on public registries. If a required package is unavailable, your build may fail. Refer to [Package not found / build fails after migration](#package-not-found--build-fails-after-migration) for ecosystem-specific fallback options. Learn more about configuring libraries in the documentation for each ecosystem: * [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/) * [Chainguard Libraries for Python](/chainguard/libraries/python/) * [Chainguard Libraries for Java](/chainguard/libraries/java/) ## Troubleshooting ### Authentication errors on install If you see a 401 or 403 when running `npm install`, `pip install`, or a Maven build after Cursor configures your project: * Confirm the pull token was created for the correct ecosystem (`java`, `python`, or `javascript`). * Check that the token hasn't expired. The default TTL is 720 hours (30 days). * For npm: verify your `.npmrc` has both the registry line and the auth line for the correct host. ### Image not found / tag doesn't exist If Cursor references an image tag that doesn't exist: * Check available tags for the image in the [Chainguard Images directory](https://images.chainguard.dev/). Ensure that you have access to the tag. * Use `latest` or `latest-dev` as a reliable starting point. Image digests are also available for production use. ### Package not found / build fails after migration If your build fails because a package is unavailable from Chainguard: * **JavaScript**: The Chainguard Repository includes a configurable upstream fallback for npm packages; [enable fallback](/chainguard/libraries/javascript/overview/#upstream-fallback-policy-and-controls) if you want to pull from upstream when a package isn't available from Chainguard. * **Python**: To enable fallback to PyPI for unavailable packages, add `extra-index-url = https://pypi.org/simple/` to your `pip.conf` or `pyproject.toml`. * **Java**: To enable upstream fallback, add `mavenCentral()` after the Chainguard Libraries for Java repository in your Gradle configuration, or add Maven Central as a second `repository` entry in your `pom.xml`. --- ### What is the Chainguard Factory? URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/what-is-factory.md Last Modified: September 28, 2026 Tags: Chainguard Factory, Overview, Video, Security [Watch the video on YouTube](https://www.youtube.com/watch?v=KyO4ppSR9Lo) ## Transcript **Interviewer**: So Dustin, can you explain what the Chainguard Factory is? **Dustin Kirkland**: Yeah, so the Chainguard Factory is the automation that's at the heart of what we do here at Chainguard. Essentially, we have this build system that's constantly monitoring over 10,000 open source projects, and the moment that any upstream maintainer tags a new release, our automation springs into action—fetching that source code, checking the checksums, applying our build rules, rebuilding and recompiling that software, retesting that software at the package and unit level. And then, if all of that works, publishing a new package with signatures of that package that's been rebuilt from source, bootstrapped from source with our toolchain and our testing. Once we have that package, we're then able to assemble that package into multiple different image formats. We can do container images, we can do virtual machine images, and we're now building libraries—Java, Python, and Node libraries—from source. --- ### Overview of roles and role-bindings in Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings.md Last Modified: October 13, 2026 Tags: chainctl, Overview In the context of Chainguard, an *identity* represents an individual user within an organization. Chainguard's IAM model allows administrators to assign identities to specialized *roles* which define the level of access that an identity has to the organization's resources. You assign a role by creating a *role-binding*, which is what ties an identity to a given role. This guide serves as an overview of what roles and role-bindings are within the context of Chainguard. It also outlines how you can manage roles and role-bindings with `chainctl`. ## Prerequisites This guide includes several examples of how you can manage roles and role-bindings with `chainctl`, the Chainguard command-line tool. To set up `chainctl`, follow our guide on [how to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you haven't done so already. ## Roles There are a number of built-in roles in Chainguard's IAM model that customers can assign to identities within their organization. Most users in your organization likely have one of the following roles with broadly-defined privileges: `owner`, `editor`, `viewer`, or `console_viewer`. `owner` is the role with the most privileges. An owner can create, delete, view (list), and modify (update) organizations, account associations, role-bindings, organization invitations, custom roles, role-bindings, and subscriptions. `editor` is the role with read access and limited creation and modification access. An editor has all the access of the `viewer` role. Beyond that, an editor can create, update, and delete container repositories, edit [Custom Assembly overlays](/chainguard/containers/custom-assembly/overlays/), draft library build requests, and manage event subscriptions. An editor can pull images and APK packages, but can't push them, can't access Chainguard Libraries, and can't grant roles or permissions. `viewer` is a role that has read-only access and **can** pull images. That is, a viewer can list images, policies, organizations (and organization invites), records, roles and role-bindings, subscriptions, and account associations. `console_viewer` is a role that has read-only access and **cannot** pull images. It is designed for teams that need visibility into the Chainguard Console without any image pull access. A console viewer can browse the Console — viewing organizations, roles, and group information — but has no `registry.pull` or `apk.pull` permissions. It also can't view legal terms or run Guardener conversions or migrations. This makes it a safe option for inviting developers, auditors, or stakeholders who need awareness of your organization's setup without the ability to pull images or APK packages. `limited_owner` is the role of a Starter organization's owner, such as a [Catalog Starter](/chainguard/containers/reference/catalog-starter/) user. It combines the capabilities of the `viewer` role with pull token creation. It can also invite people whose email address is in the organization's domain, remove members, manage billing, and turn on [Chainguard Checks](/chainguard/checks/overview/) and [Chainguard Workspaces](/chainguard/workspaces/overview/). It can't create identities other than pull tokens or use paid features like Custom Assembly. The remaining roles are for more specialized functions. For example, `registry.pull`, `registry.push`, and `registry.pull_token_creator` relate to administering a registry of Chainguard products. `sandbox.user` lets a member use Chainguard Checks and Chainguard Workspaces, and `mcp.tool_user` lets an identity call tools on [Chainguard MCP servers](/platform/mcp-servers/overview/). The `owner`, `editor`, and `viewer` roles are useful for user profiles that require broad, but clearly defined capabilities. The registry, container, and library roles have limited permissions, allowing them to manage only one specific Chainguard resource. These specialized, resource-specific roles grant minimal required access. Every role grants actions on at least one Chainguard resource. Most actions are `create`, `list`, `update`, or `delete`, and a few resources use others, such as `get`, `accept`, or `call`. For example, the `apk.pull` role grants `list` access for APK packages and groups, and `get` access to APK package content. This means identities with this role can pull the organization's APK packages and retrieve information about the organization, but won't have general access to the organization's [Chainguard registry](/chainguard/containers/registry/overview/) access. Chainguard's built-in default roles serve as building blocks and can be complemented with custom roles for specific use cases. Custom roles can extend or restrict default role capabilities, and offer your organization greater flexibility, allowing you to mix default and custom roles based on your team's structure and needs. When assigning a role, do so based on the principle of least privilege; assign only the role needed for the identity's function. For example, CI systems should have a role like `registry.pull`, not `editor`. You can run `chainctl iam roles list` to retrieve a list of all the roles available to your organization and review each of their specific capabilities. This command lists all the built-in roles as well as any custom roles created for your organization. The next section outlines how to create and manage such custom roles. ## Managing custom roles There are two options for managing custom roles: - with [`chainctl`](#manage-custom-roles-with-chainctl), or - directly [in the Chainguard Console](#manage-custom-roles-in-the-chainguard-console). ### Manage custom roles with chainctl You can use `chainctl` to create custom roles for teams or individuals in your organization, like in the following example. ```sh chainctl iam roles create my-role ``` After running this command, an interactive prompt asks you to select what capabilities the new role should have and the organization under which the role should be created. You can avoid the interactive prompt by including the `--capabilities` option in this command. ```sh chainctl iam roles create new-role --capabilities=roles.list ``` This example creates a new role named `new-role` in your organization. The new role can only list roles in the organization. `chainctl` selects your organization automatically when you belong to only one. If you have access to more than one organization or folder, it prompts you to choose; add `--parent=` to skip the prompt. You can also grant multiple capabilities to a custom role with one command, as in this example: ```sh chainctl iam roles create puller-role --capabilities=apk.list,groups.list,manifest.list,manifest.metadata.list,record_signatures.list,repo.list,sboms.list,tag.list,vuln.list ``` This example command creates a role named `puller-role` that has the following capabilities: - `apk.list` - `groups.list` - `manifest.list` - `manifest.metadata.list` - `record_signatures.list` - `repo.list` - `sboms.list` - `tag.list` - `vuln.list` This essentially combines the capabilities of the `registry.pull` and `apk.pull` roles, allowing any identities bound to this role to be able to pull both Chainguard Containers and APKs. You can also use `chainctl` to delete custom roles. ```sh chainctl iam roles delete new-role ``` Note that you cannot delete any of the built-in roles. Attempting to do so returns an error. ### Manage custom roles in the Chainguard Console In addition to using `chainctl` to manage roles, you can also create, view, and edit custom roles directly in the [Chainguard Console](https://console.chainguard.dev). #### Create a custom role 1. In the Chainguard Console, navigate to **Settings** > **Roles**. 1. Click **Create role**. 1. Enter a name and optional description for the new role. 1. Select the capabilities you want the role to include. 1. Click **Create**. After creating a role, you can attach it to a user. Learn more under [Managing role-bindings](#managing-role-bindings). #### Edit an existing custom role 1. Navigate to **Settings** > **Roles**. 2. Find the role you want to modify and click **Edit**. 3. Add or remove capabilities as needed. 4. Click **Update**.

Note: Changes to a custom role take effect immediately for all identities currently bound to that role. You can’t edit built-in roles.

Both the Console and `chainctl` operate on the same underlying roles and role-bindings; changes made in either place are immediately reflected everywhere. ## Managing role-bindings ### Manage role bindings with chainctl You can assign a role — and all of its capabilities — to a given user by creating a role-binding and tying it to that user's identity. You can run a command like the following example to create a role-binding. ```sh chainctl iam role-bindings create ``` This starts an interactive prompt where you can enter the appropriate details for this new role-binding. Specifically, you'll be prompted to specify the identity to bind, the role you want the identity bound to, and the organization that the role-binding should belong to. To avoid the interactive prompt, add these details to the command by including the `--identity` and `--role` options. ```sh chainctl iam role-bindings create --identity=example-id --role=viewer ``` This example creates a role-binding for the identity `example-id` with the built-in `viewer` role in your organization. Note that in order to use the `--identity` option like this, you need to know the identity's UIDP. You can find a list of all your identities' UIDPs by running `chainctl iam identities ls`. The UIDPs appear in the `ID` column of the output. ### Manage role bindings in the Chainguard Console 1. In the Chainguard Console, navigate to **Settings** > **Users**. 1. Click **Create role binding**. 1. Select a user and the role you want to attach to the user. 1. Click **Create**. ## Learn more You can create a Chainguard identity for an automation system (such as Buildkite or GitHub Actions) to assume. This process involves choosing a role for the assumable identity and then creating a role-binding for it. Check out our [overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/) — as well as our [collection of assumable identity examples](/platform/administration/assumable-ids/identity-examples/) — to learn more. If you'd like to learn more about Chainguard's IAM model and structures, we encourage you to read our [Overview of the Chainguard IAM model](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/). --- ### Using the Chainguard Console URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/console/images-directory.md Last Modified: October 8, 2026 Tags: Chainguard Containers This guide walks you through the Chainguard Console. Anyone can use the Console, but you first need to [create an account and log in](https://console.chainguard.dev/auth/login). If you're not ready to create a Chainguard account, you can follow along with the public [Chainguard Directory](/chainguard/containers/registry/chainguard-directory/). It offers similar information, but it isn't connected to an organization or account. The Directory's **Sign in** button opens the Console. ## Accessing the Chainguard Console Log in to access the [Chainguard Console](https://console.chainguard.dev/auth/login). To open the Console with your organization already selected, use (and bookmark) a link like this one, replacing `ORGANIZATION` with your organization's name: ```URL https://console.chainguard.dev/auth/login?org=ORGANIZATION ``` ## Browse Chainguard Libraries in the Console To learn about browsing Chainguard Libraries in the Console, refer to the [Libraries browsing page](/chainguard/libraries/introduction/browse/). ## Browse container images and details in the Console When you sign in to the [Chainguard Console](https://console.chainguard.dev), it opens to the **Overview** page:
Screenshot showing the Chainguard Console's Overview page.

If your organization and account have [Chainguard Notifications](/platform/console/use-chainguard-notifications/) enabled, this page also shows the **Activity Center**, where Chainguard posts occasional notifications. Click **Images** in the left-hand navigation. The **Images** page opens to the **Organization** tab. If you're part of an organization, this tab lists the private Chainguard Containers (also called *Production Containers*) your organization can access. Select the **Chainguard catalog** tab, which lists all of Chainguard's available images in a table with four columns: * **Name**: the name of the container image * **Latest tag**: the latest available version of the image * **Description**: a brief description of the image * **Updated**: how long ago the image was last updated If you belong to an organization, this tab also has an unlabeled column with an **Add to org** button for each image. For images already in your organization, the button has a different icon and adds another instance of the image. Depending on your organization's plan, some images might show a different option instead, such as **Contact us**. With catalog pricing, **Add to org** lets you provision Chainguard Containers yourself, without contacting Chainguard. For more information, refer to [Chainguard container catalog pricing](/chainguard/containers/reference/pricing/). If an image you need doesn't appear in your organization's catalog, or appears without the version you need, refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/). The **Organization** tab has no **Description** column, but it adds two others: * **Status**: whether your organization has access to the image. **Active** means your organization can download and use the image. **Expired** means your organization had access in the past but doesn't anymore. * **Pull URL**: the URL you use to pull the image, for example in a `docker pull` command. Click a column name to sort the list by that column, in ascending or descending order. Above the table, a search box finds images by name or latest version number. To its right, the **Filter by** menu filters the images by their [container image category](/chainguard/containers/concepts/container-categories/). ## Container image information Click any container image to open its details page:
Screenshot of the Container Details page for the go image, showing the 'Tags' tab.

This example shows the details page for `go` in the Console. Each image's details page has several tabs, each covering a different aspect of the image. ### Tags Each image opens on the **Tags** tab, which lists the image's version tags in a table with these columns: * **Tag**: each tag available for the image * **Pull URL**: the URL you can use to download a version, shown when your organization can pull that version. When it can't, this column shows a status label in place of the URL: **Add to organization for access**, **Add image for access**, **Request image for access**, **Available in organization**, **Unavailable to organization**, or **Contact us for access**. These labels describe the repository rather than the version on that row, so **Available in organization** can appear beside a version you can't pull. For what each label means and what to do about it, refer to [Troubleshoot container and version availability](/chainguard/containers/troubleshooting/container-version-troubleshooting/). * **Digest**: the digest of each version, shown when you're signed in to an organization * **Compressed size**: the size of the image, in megabytes * **Last changed**: when each version of the image was last updated Above the table, a search box filters the image's versions. A **Variant** drop-down menu filters for all images, only development variants, or only non-development variants. ### Overview The **Overview** tab contains the container image's README. READMEs typically explain how to download the image, note any compatibility issues, and describe how to get started with it. ### Comparison The **Comparison** tab compares the CVE count of a Chainguard Container with a non-Chainguard alternative, with charts that visualize the comparison. For more information, refer to [CVE visualizations](/chainguard/containers/security-and-compliance/vulnerability-management/cve-visualizations/). ### Provenance All of Chainguard's container images contain verifiable signatures and high-quality [software bills of materials](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom) (SBOMs). Signatures let you confirm each image's origin, and SBOMs list everything the image contains. The **Provenance** tab explains how to verify container signatures and how to download and verify image attestations, with examples that use [`cosign`](/open-source/sigstore/cosign/an-introduction-to-cosign/). ### Specifications The **Specifications** tab lists important details about the image, such as whether it ships with the `apk` package manager or a shell, its default user ID, its environment variables, and its entrypoint. It also shows the image's **Raw configuration**, which includes many of these details plus the image's OCI labels (similar to [annotations](/chainguard/containers/overview/#annotations)). ### SBOM The **SBOM** tab lists the packages in the image. Everything in a Chainguard Container is a package, so this list is a complete view of the image's contents. The package table has six columns: * **Type**: the type of package, such as `golang` or `apk` * **Namespace**: the source repository the package is based on (Chainguard builds every package in its container images) * **Name**: the name of each package included in the image's SBOM * **Version**: the version of the listed package * **Subpath**: if available, a subpath that points to a specific file or directory within the package * **License**: the license under which each package is published Above the table, a search box filters the package list. To its left, two drop-down menus select the image version and the architecture (x86_64 or arm64) whose SBOM you want. To the right of the search box, the **Download** button downloads the SBOM in SPDX or CycloneDX format. It's available for Free Containers and for images your organization has access to. Chainguard began generating SBOMs for its images on November 15, 2023, so image versions released before that date have no SBOM data. ### Vulnerabilities The **Vulnerabilities** tab lists every CVE found in the image. Like the **SBOM** tab, it has a search box for filtering the list and, to its left, a drop-down menu for selecting an image version. Most of Chainguard's container images show no vulnerabilities for the `latest` version. This isn't an error: Chainguard aims to remove vulnerabilities from images as soon as they arise. To check how the table appears when vulnerabilities are present, select different versions in the drop-down until you find one with a vulnerability. The table has five columns: * **CVE ID**: the official identifier of the vulnerability * **Severity**: **Critical**, **High**, **Medium**, **Low**, or **Unknown** * **Package**: the package that contains the vulnerability * **Version**: the version of the package containing the vulnerability * **Last detected**: the date and time when the vulnerability last appeared in a scan of the container image Click the down-pointing chevron (**˅**) to the left of a row to expand it. The expanded row shows the **Package** name and **Version**, the package's **Fixed version**, a brief **Description** of the vulnerability, and one or more **References** where you can learn more. As with SBOM data, Chainguard began generating vulnerability data for its images on November 15, 2023, so image versions released before that date have no vulnerability data. ### Advisories The **Advisories** tab lists the CVEs that have affected the image and what action Chainguard took on each one. It shows a timeline of the image's security advisories, newest first. Each entry specifies the date and time the advisory was released, the CVE in question, the affected package, and the [current status](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/#summary-of-advisory-statuses). To learn more about Chainguard security advisories, read [How Chainguard issues Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/) and [How to use Chainguard Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-to-use/). You can also browse every advisory published for Chainguard Containers on the self-service [Security Advisories page](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-directory). ### Builds In the Console, images also have a **Builds** tab, which lists recent builds of the image. The tab is available only for images with an **Active** status. It's especially useful for images customized with Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) tool. For more about the **Builds** tab and working with Custom Assembly in the Console, refer to [Using the Chainguard Console to manage Custom Assembly resources](/chainguard/containers/custom-assembly/custom-assembly-console/). ### Find Helm charts in the Chainguard Console For organizations that deploy Chainguard container images with Helm, Chainguard provides upstream-produced Helm charts and a set of Chainguard-created iamguarded charts, designed for organizations migrating off Bitnami. To find these charts in the Console, click **Helm charts** in the sidebar menu. The iamguarded charts have an **iamguarded** label. Click a chart name to open its details. ### Find packages in the Chainguard Console To find the packages available to your organization, click **Packages** in the sidebar menu. The **Packages** page lists the APK packages in your organization's private Chainguard APK repository, and you can search the list. Use the **Architecture** drop-down above the list to filter packages by architecture. Click a package to open its details in a side panel, and use the panel's **Version** drop-down to switch between versions. ## Learn more The Chainguard Console helps you find which Chainguard container images are available and learn details about each one. To work with an individual image, check whether a [getting started guide](/chainguard/containers/getting-started/) exists for it. You can also learn [how to use Security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-to-use/) on the [self-service public Security Advisories page](https://images.chainguard.dev/security?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-chainguard-images-working-with-images-images-directory). --- ### Overview of assumable identities in Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/assumable-ids.md Last Modified: September 30, 2026 Tags: Chainguard Containers Both [`chainctl`](/platform/chainctl/) and the [Chainguard Console](https://console.chainguard.dev/) are useful tools for interacting with Chainguard. However, there may be times that you want to hand off certain administrative tasks to an automation system, like Buildkite or GitHub Actions. In such cases, you can create a Chainguard identity for these systems to assume, allowing them to perform certain tasks within a specific scope. You can restrict access to an identity so that only workflows that present tokens matching a specific issuer and subject can assume it. Likewise, assumable identities can be tied to certain roles — like `viewer`, `owner`, or `editor` — letting you place strict limits on what a given identity is allowed to do. This guide provides a general overview of assumable identities in Chainguard, outlining how they work and how to create them.

Note: Chainguard Catalog Starter organizations can’t create assumable identities. A pull token is the only credential they can issue for automation. Refer to Authenticate CI pipelines with a pull token.

## About assumable identities Chainguard's *assumable identities* are identities that can be assumed by workflows in order to complete tasks without manual authorization. In many ways, these are similar to AWS roles or Google Service accounts, as Chainguard identities allow you to delegate access to your Chainguard resources to external applications or services. Chainguard originally only supported what are referred to as *literal identities*. These are identities that consist of a unique mapping of verified issuer and subject to refer to an individual user. Literal identities can work well for self-service enrollment in some cases. However, they start to run into problems in several scenarios, such as with systems that use variable `subject` claims (like Buildkite, which injects commit SHAs) or automation systems (like continuous integration systems), which can be difficult to register to literal identities on their first use. Assumable identities essentially reverse the lookup process of literal identities. Instead of Chainguard analyzing at a token's issuer and subject to determine their literal identity, the client presents an assumable identity's UIDP (unique identifier path). Chainguard then checks this UIDP against the client's token. If the token's issuer and subject match those required by the identity, then the client may assume the identity. This enables you to create identities that can only be assumed by specific automated workflows, providing greater security for your build and deployment processes. We have a number of examples of how to create assumable identities for specific providers. * [GitHub](/platform/administration/assumable-ids/identity-examples/github-identity/) * [GitLab](/platform/administration/assumable-ids/identity-examples/gitlab-identity/) * [AWS](/platform/administration/assumable-ids/identity-examples/aws-identity-oidc/) * [AWS (legacy)](/platform/administration/assumable-ids/identity-examples/aws-identity/) * [Jenkins with Terraform](/platform/administration/assumable-ids/identity-examples/jenkins-terraform/) or [Jenkins with chainctl](/platform/administration/assumable-ids/identity-examples/jenkins-chainctl/) * [Buildkite](/platform/administration/assumable-ids/identity-examples/buildkite-identity/) * [Bitbucket](/platform/administration/assumable-ids/identity-examples/bitbucket-identity/) A notable difference between registered users and identities in Chainguard's IAM model is that identities are tied to a specific [IAM organization](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/). When you create an identity, you must specify a Chainguard organization under which the identity will be created. However, an identity won't automatically have access to the other resources associated with that organization. In order for an identity to be able to interact with a organization's resources — including the containers, repositories, and users associated with the organization — it must be granted the permissions it needs to do so. To do this, you must also tie the identity to a role. Chainguard comes with a few built-in roles, including `viewer`, `editor`, and `owner`. You can also create custom role-bindings with `chainctl`. Check out the [`chainctl iam role-bindings` documentation](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings/) for more details. Now that you have a better understanding of what assumable identities are, let's go over how you can set up an assumable identity. There are currently two main ways you can create an identity: with Terraform and with `chainctl`. Let's first go over how to set up an identity with Terraform. ## Terraform To set up an assumable identity with Terraform, you will need to add a few specific blocks to your Terraform configuration. The following example `resource` block is the most important of these, as it is what creates the assumable identity. ```hcl resource "chainguard_identity" "" { parent_id = name = "" description = <-identity" { value = chainguard_identity..id } ``` The last two blocks you should include in a Terraform configuration are what apply a role-binding to the new identity. First, you need to include a `data` section to look up the role. In this example it looks up Chainguard's built-in `viewer` role. ```hcl data "chainguard_roles" "viewer" { name = "viewer" } ``` Then you need to include another `resource` block to create the role-binding using the determined role. The identity will have the permissions of that role over the organization specified within this block. ```hcl resource "chainguard_rolebinding" "view-stuff" { identity = chainguard_identity..id group = chainguard_group.user-group.id role = data.chainguard_roles.viewer.items[0].id } ``` This means that the identity this Terraform configuration will create will only be able to view the resources tied to the same organization the identity is tied to. Applying this configuration will create the assumable identity. You can follow any of our [identity examples](/platform/administration/assumable-ids/identity-examples/) to create an assumable identity that can be used by a continuous integration workflow to interact with Chainguard. The Terraform files used in the linked tutorials are based closely on the template outlined here. ## Managing identities with `chainctl` You can also set up an assumed identity using the `chainctl` command-line tool. Specifically, you can run the `chainctl iam identities create` subcommand, which uses the following syntax: ```sh chainctl iam identities create \ --identity-issuer= \ --issuer-keys= \ --subject= \ --group= \ --role= ``` As with Terraform, you must provide `chainctl` with certain information about the identity you want to create, including the issuer and subject of the identity, the role-bindings associated with the identity (if any), and the organization under which the identity should be created. Be aware that you can use regular expressions to create an identity that matches claims using a pattern. The following example passes pattern expressions to the `--identity-issuer-pattern` and `--subject-pattern` flags: ```shell chainctl iam identities create \ --identity-issuer-pattern="https://*.mycompany\.com" \ --subject-pattern="^\d{4}$" ``` You can use the following `chainctl` flags to match claims to patterns: * `--audience-pattern`: A pattern to match the audience of the identity. * `--claim-pattern`: A comma-separated list of `claim:pattern` pairs of custom claims to match the identity. * `--identity-issuer-pattern`: A pattern to match the issuer of the identity. * `--subject-pattern`: A pattern to match the subject of the identity. You can change an existing identity with the `update` command. The following example would update the identity's issuer. ```sh chainctl iam identities update --identity-issuer=https://new-issuer.mycompany.com ``` To delete an identity, use the `delete` subcommand. ```sh chainctl iam identities delete ``` For more detailed information on managing identities with `chainctl`, we encourage you to check out the [`chainctl` reference documentation](/platform/chainctl/chainctl-docs/chainctl_iam_identities/). ## Assuming an identity Whether you create an identity with `chainctl` or with Terraform, Chainguard will generate a UIDP (unique identifier path) tied to the identity. You can retrieve a list of all the identities you've created — along with their UIDPs — with the following command. ```sh chainctl iam identities ls -o table ``` ```output ID | NAME | TYPE | DESCRIPTION | ROLES | ISSUER | EXPIRES ------------------------------------------------------------+----------------+-------------+-------------+-----------------------+---------------------------------------------+---------- c95870ebffa72a258df087ea727ee92daf177e29/f067a9080d45a098 | sampleidentity | claim_match | | example-group: viewer | https://token.actions.githubusercontent.com | n/a ``` If a workflow is authorized to assume the identity — meaning that its token matches the `issuer` and `subject` specified for the identity — then it only needs to present this identification number and the token in order to assume it. There are a few different ways to do this. ### Using `chainctl` On some platforms, such as [GitHub](/platform/administration/assumable-ids/identity-examples/github-identity/) or Google Cloud Platform, `chainctl` is able to detect your credentials transparently. This means you only need to provide the identity's UIDP to log in, as in this example: ```sh chainctl auth login --identity ``` Otherwise, you will need to provide the OIDC token explicitly. This can either be the raw token value or a path to the token on disk: ```sh chainctl auth login --identity --identity-token ``` Once you're logged in, you will be able to perform the operations that the identity has permissions for. For instance, listing repositories. ```sh chainctl image repo list ``` Alternatively, you can use `chainctl auth token` to retrieve a bearer token that you can use directly with the Chainguard API: ```sh curl -sSf \ -H "Authorization: Bearer $(chainctl auth token)" \ https://console-api.enforce.dev/registry/v1/repos \ | jq -r .items[].name ``` ### Using the Chainguard API If you want to interact with the API directly without using `chainctl`, you can exchange your platform's OIDC token for a Chainguard API token like in the following example. Be sure to substitute `` and `` with the identity's UIDP and the OIDC token, respectively: ```sh curl -sSf \ -H "Authorization: Bearer " \ "https://issuer.enforce.dev/sts/exchange?aud=https://console-api.enforce.dev&identity=" \ | jq -r .token ``` This will return a token for the Chainguard API that you can use for subsequent requests to the API. For instance, listing repositories: ```sh curl -sSf \ -H "Authorization: Bearer " \ https://console-api.enforce.dev/registry/v1/repos \ | jq -r .items[].name ``` The API token issued by `/sts/exchange` will be valid for up to an hour. You will need to fetch a new token after it has expired. ## Learn more As mentioned previously, we've published a few tutorials that outline how you can [set up an identity for a CI/CD workflow to assume](/platform/administration/assumable-ids/identity-examples/). We strongly encourage you to follow these guides to better understand how assumable identities work in Chainguard. --- ### Create an assumable identity for a GitHub Actions workflow URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/github-identity.md Last Modified: September 30, 2026 Tags: Chainguard Containers Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. For instance, an assumable identity can be used to allow a GitHub Actions workflow to pull images from `cgr.dev` without a static pull token. This tutorial outlines how to create an identity, and then create a GitHub Actions workflow that will assume the identity to interact with Chainguard resources. ## Prerequisites To complete this guide, you will need the following. * One of: * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * `terraform` installed on your local machine. Terraform is an Infrastructure as Code tool which this guide will use to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. * A GitHub repository you can use for testing out GitHub identity federation. To complete this guide, you must have permissions to create GitHub Actions on this testing repo. * A Chainguard organization that can create assumable identities. [Chainguard Catalog Starter](/chainguard/containers/reference/catalog-starter/) organizations can't, so authenticate those pipelines with a pull token instead. Refer to [Authenticate CI pipelines with a pull token](/chainguard/containers/reference/catalog-starter/#authenticate-ci-pipelines-with-a-pull-token). ## Creating an identity

Note:

GitHub issues OIDC tokens whose sub claim embeds immutable numeric IDs for the repository owner and the repository. For example, the subject repo:my-org@123456/repo-name@654321:ref:refs/heads/main replaces the older name-only form repo:my-org/repo-name:ref:refs/heads/main. This format is the default for repositories created after July 15, 2026, and is available as an opt-in for older repositories through GitHub’s OIDC settings.

An identity that matches the older name-only subject will not match a token that carries the immutable subject, so the workflow fails to authenticate. The examples that follow use the immutable format. If your repository still sends the name-only subject, leave out the @<owner-id> and @<repo-id> portions.

### Finding your repository's numeric identifiers The immutable subject claim uses the numeric ID of the repository owner and the numeric ID of the repository. To retrieve the subject claim in the GitHub UI, modify and browse to this URL: ```http https://github.com///settings/actions/oidc-configuration ``` To retrieve the subject claim with the [GitHub CLI](https://cli.github.com/): ```sh gh api repos/OWNER/REPO --jq '{owner_id: .owner.id, repo_id: .id}' ``` You can also preview the exact subject your repository sends from its OIDC settings in the GitHub web interface. To confirm the subject a running workflow presents, inspect the `sub` claim of the OIDC token in the workflow logs. For background on the format, refer to GitHub's changelog announcing [immutable subject claims for GitHub Actions OIDC tokens](https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/). ### Chainctl This command creates an identity that can be assumed by workflows in the `my-org/repo-name` repository. Substitute the owner and repository IDs you retrieved earlier. The identity is bound to the `registry.pull` role. ```sh chainctl iam identities create github my-identity-name \ --github-repo=my-org@/repo-name@ \ --role registry.pull ``` You can also include the `--github-ref` flag to restrict it to workflows that run from a specific branch. For instance, the `main` branch. ```sh chainctl iam identities create github my-identity-name \ --github-repo=my-org@/repo-name@ \ --github-ref=refs/heads/main \ --role registry.pull ``` Regex is also available. For example, you can create an identity that can be assumed by multiple repositories and multiple branches. Because every repository in an organization shares the same owner ID, a single pattern can match all of them. ```sh chainctl iam identities create github my-identity-name \ --github-repo='my-org@/.*' \ --github-ref='refs/heads/main|master' \ --role registry.pull ``` This will return the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers). Note this value down, as you'll need it to set up the GitHub Actions workflow. If you need to retrieve the UIDP later on, you can always run the following `chainctl` command to list the identity. ```sh chainctl iam identities list --name=my-identity-name ``` ### Terraform Alternatively, you could create the identity with the [Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest). Substitute your Chainguard organization name, GitHub organization, and GitHub repository for ``, ``, and ``, respectively. Substitute the numeric owner and repository IDs for `` and ``, following the steps in [Finding your repository's numeric identifiers](#finding-your-repositorys-numeric-identifiers). ```hcl data "chainguard_group" "org" { name = "" } resource "chainguard_identity" "my_identity_name" { parent_id = data.chainguard_group.org.id name = "my-identity-name" claim_match { issuer = "https://token.actions.githubusercontent.com" subject = "repo:@/@:ref:refs/heads/main" } } data "chainguard_role" "registry_pull" { name = "registry.pull" } resource "chainguard_rolebinding" "my_identity_name_registry_pull" { identity = chainguard_identity.my_identity_name.id group = data.chainguard_group.org.id role = data.chainguard_role.registry_pull.items[0].id } output "my_identity_name_id" { value = chainguard_identity.my_identity_name.id } ``` In this example the `chainguard_identity.my_identity_name` resource defines an identity in your organization that can be assumed by a GitHub workflow that matches the claims in the `claim_match` block. The `chainguard_rolebinding.my_identity_name_registry_pull` resource binds the `registry.pull` role to the identity. The `my_identity_name_id` output provides the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers). You'll need this value to set up the GitHub Actions workflow. ## Creating and testing a GitHub Actions workflow This example workflow definition assumes an identity with the [`setup-chainctl`](https://github.com/chainguard-dev/setup-chainctl) GitHub Action, lists repositories with `chainctl` and pulls an image from `cgr.dev`. Commit this workflow to the `main` branch of your repository as `.github/workflows/assume-and-explore.yaml`, substituting `` with the UIDP of the assumable identity you created in the previous step and `` with the name of your Chainguard organization. ```yaml name: Assume and Explore on: workflow_dispatch: {} jobs: assume-and-explore: name: actions assume example permissions: id-token: write runs-on: ubuntu-latest steps: - uses: chainguard-dev/setup-chainctl@main with: identity: - run: | chainctl image repo list --parent= - run: | docker pull cgr.dev//example-image:latest ``` Once its committed, you can trigger the workflow by navigating to `Actions > Assume And Explore` from the home page of your repository and selecting `Run workflow`. ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, the Terraform documentation includes a section on [recommended best practices](https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices) which you can refer to if you'd like to build on the provided Terraform configuration for a production environment. Likewise, for more information on using GitHub Actions, we encourage you to check out the [official documentation on the subject](https://docs.github.com/en/actions). --- ### Using custom identity providers to authenticate to Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/custom-idps.md Last Modified: October 1, 2026 Tags: Chainguard Containers, Overview The Chainguard platform supports Single Sign-on (SSO) authentication for users. By default, users can log in with GitHub, GitLab, and Google, but SSO support allows users to bring their own identity provider for authentication. This is helpful when your organization mandates using a corporate identity provider — like Okta or Azure Active Directory — to authenticate to SaaS products. ## Usage Once an administrator has [configured an identity provider](#setup-and-administration) and set up their organization, users can authenticate at the command line and in the web console using the identity provider’s organization. ### Authenticate with `chainctl` [`chainctl`, the Chainguard command line interface (CLI)](/platform/chainctl/), supports SSO authentication by supplying the identity provider organization name as a flag or by setting it as a default in configuration. To use a flag to authenticate using SSO, pass the `--identity-provider` flag to `chainctl auth login`. ```sh export IDP_ID= chainctl auth login --identity-provider=$IDP_ID ``` You can retrieve all your identity provider's unique IDs by running `chainctl iam identity-providers list`. Note that you can also use the [`--headless` option](/platform/chainctl-usage/authentication-options/#headless-device-code-login) to log in with a custom IdP in an environment that doesn't have a browser installed, such as a container or a remote server. With this option, `chainctl` outputs a special URL. You can then navigate to the URL in another device's browser to log in with your custom IdP. To log in with a custom IdP using the `--headless` option, you would run a command like the following: ```sh chainctl auth login --headless --identity-provider=$IDP_ID ``` Then you can use the URL in this command's output to complete the login flow from another device's browser. ### Set a default identity provider As an alternative to remembering identity provider IDs, you can set the default identity provider by editing the `chainctl` configuration file. You can do so with the following command. ```sh chainctl config edit ``` This command opens your system's default text editor, where you can edit the local `chainctl` config. Add the following lines to this file. ```yaml default: identity-provider: ``` Then save and close the file. If your system's default editor is `nano`, for example, you can do so by pressing `CTRL + X`, `Y`, and then `ENTER`. You can also set this with a single command using the `chainctl config set` subcommand, as in this example. ```sh chainctl config set default.identity-provider ``` After you set it, `chainctl auth login` uses the configured identity provider automatically. ### Authenticate with `chainctl` using a verified organization If your organization is [verified](/platform/administration/iam-organizations/verified-orgs/), you can use your organization name instead of the ID of your identity provider to authenticate. ```sh chainctl auth login --org-name example.com ``` You can add your organization's name to your `chainctl` config to make this a default setting. ```yaml default: org-name: example.com ``` To learn more about working with your `chainctl` config, you can read our doc on [How to manage `chainctl` configuration](/platform/chainctl-usage/manage-chainctl-config/). ### Authenticate with the Chainguard Console To authenticate with the Chainguard Console, [open the login screen](https://console.chainguard.dev). Then, select one of the following options: - To use your organization's SSO, enter your Organization or email address and click **Continue**. - To use a third-party identity provider, click the corresponding option from the list. - To use your email and a password, enter your email and click **Continue**.
Screenshot showing an example Chainguard login box, with all described options shown.
In each of these cases, the Chainguard Console redirects you to an external identity provider to authenticate and then returns you to the Console. If you use your email and a password, [Auth0](https://auth0.com/) handles authentication and stores your credentials. Multi-factor authentication follows the same split. Your own identity provider manages it for users who sign in through your SSO integration, while Chainguard manages it for users who sign in with an email address and a password. See [Change or reset your MFA device](/get-started/mfa-devices/) for what each group should do to move MFA to a new device. ## Setup and administration Chainguard SSO supports OpenID Connect (OIDC) compatible identity providers. In addition, identity providers must support the following: - The `authorization code` grant type (sometimes called the `authorization code` *flow*). - The standard `openid`, `email`, and `profile` scopes. The Chainguard platform [partially functions](https://openid.net/specs/openid-connect-basic-1_0.html#Scopes) with only the `openid` scope, but full functionality requires the `email` and `profile` scopes as well. - A token that carries a populated, standard `email` claim, not only the requested scope. See [Required token claims](#required-token-claims). Customer-managed identity providers must also have a public, unauthenticated OIDC discovery endpoint. Typically, identity providers enable you to set up SSO by creating a specific resource on the provider's platform. For example, Ping Identity requires you to [add an application](https://docs.pingidentity.com/pingone/applications/p1_applications_add_applications.html), while Okta has you create [an app integration](https://help.okta.com/en-us/content/topics/apps/apps_apps.htm). To set up SSO for your identity provider, you must configure one of these resources to use OIDC so that the Chainguard platform can interact with the provider. Following that, you have to configure the Chainguard platform to use that application. ### Required token claims Chainguard reads the standard OIDC `email` claim to populate each user's email address. That address is used for display across the Console and for the [support portal](/get-started/get-support/) handoff; it is not used to authenticate users, grant access, or correlate identities. Even so, several things break without it, so the token your identity provider issues must carry a populated `email` claim — requesting the `email` scope alone isn't enough. Some identity providers store a user's email under a directory attribute, such as the LDAP `mail` attribute, that isn't mapped to the standard `email` claim by default. When that mapping is missing, sign-in still completes, but the identity is created without an email: the user appears in the Console by an internal ID rather than a name, and opening a support request fails. If your users' email lives in a non-standard attribute, configure your identity provider so the token carries it as the standard `email` claim. What that takes depends on the provider: some let you map any attribute to the claim, while others — notably Microsoft Entra ID — populate `email` only from a specific source attribute and don't let you re-point it. See the [Okta](/platform/administration/custom-idps/idp-providers/okta/#send-a-usable-email-claim) and [Microsoft Entra ID](/platform/administration/custom-idps/idp-providers/ms-entra-id/#send-a-usable-email-claim) guides for the specifics. Chainguard currently reads email from the standard `email` claim. Where your provider supports it, also send the `email_verified` companion claim: when it is absent or `false`, Chainguard stores the address as unverified and re-evaluates it on each sign-in. Set `email_verified` to `true` only when the address has actually been verified. ### Confidential and public applications OAuth classifies client applications as either confidential or public, and most identity providers use this terminology when you register an application. The classification determines whether your application authenticates with a client secret. - **Confidential applications** can store credentials securely, so they authenticate with a client secret. A web application backed by a server is the typical example, and identity providers usually label these as "Web" applications. - **Public applications** can't store credentials securely, because their code runs where the user can reach it. Native, mobile, desktop, and single-page applications fall here, and identity providers usually label these as "Native" or "SPA" applications. A public application has no secret to prove its identity, so it uses PKCE (Proof Key for Code Exchange) instead. PKCE binds the authorization code to the client that requested it, so an intercepted code is useless to anyone else. Chainguard supports both models: - **Confidential**: a client ID and client secret. The integration guides describe this setup, and you can also [add PKCE on top of the client secret](/platform/administration/custom-idps/enabling-pkce/). - **Public**: a client ID and PKCE, with no client secret. OAuth 2.1 requires this model for public clients going forward. Identity providers usually tie this classification to the application *type* you select at registration rather than to a separate setting, and some don't let you change the type afterward. If you want a public application, choose the matching type when you first register it. For the underlying definitions, refer to [section 2.1 of RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749#section-2.1); your identity provider's documentation names the application types it offers. ### Integration guides for supported identity providers We have [published guides for multiple platforms](/platform/administration/custom-idps/), including Okta and Ping Identity. If you aren’t using one of these identity providers, you can complete the following Generic Integration Guide to configure your provider to work with Chainguard. However, be aware that Chainguard does not actively support identity providers other than the ones listed previously. If you are using an alternate identity provider, we encourage you to contact us to learn more. ### Generic integration guide For a generic OIDC-compatible identity provider, start by creating an OIDC application. If possible, set as much metadata as possible for the application so that your users can identify this application as the Chainguard platform. The following assets and details can be helpful to include in the metadata: - The Console homepage is [console.chainguard.dev/](https://console.chainguard.dev) - Our terms of service can be found at [chainguard.dev/terms-of-service](https://www.chainguard.dev/terms-of-service) - Our terms of use can be found at [chainguard.dev/terms-of-use](https://www.chainguard.dev/terms-of-use) - Our privacy policy is located at [chainguard.dev/privacy-notice](https://www.chainguard.dev/privacy-notice) - You can also add a Chainguard logo icon here to help your users visually identify this integration. The icon from the [Chainguard Console](https://console.chainguard.dev/logo512.png) suits most platforms Next, configure your OIDC application as follows: - Set redirect URI to `https://issuer.enforce.dev/oauth/callback` - Restrict grant types to **authorization code** only. It is critical that your application does not support "client credentials", "device code", "implicit" or other grant types (sometimes called “flows”) - Restrict response types to only authorization codes (sometimes called just “code”) - Enable “openid”, “email” and “profile” scopes for application - Set PKCE to **optional**. Chainguard supports PKCE for the OAuth token exchange, so you can also set it to **required** if you [enable PKCE on the Chainguard side](/platform/administration/custom-idps/enabling-pkce/). Don't disable PKCE if you plan to enable it for Chainguard. Finally, configure a set of client credentials and make note of the following details to configure Chainguard: - The issuer URL - Client ID - Client Secret Next, use `chainctl` to log in to Chainguard with an OIDC provider (such as Google, GitHub, or GitLab) to bootstrap your account. ```sh chainctl auth login ``` The bootstrap account can use any supported IdP -- for example you may choose to temporarily use a personal Google account. You can leave this account active as a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) or, if you prefer, you can delete the account by removing the role-binding after configuring the custom IdP. Create a new identity provider using the details you noted from your OIDC application. Replace ``, ``, and `` with the values from your own application. ```sh export NAME=my-sso-identity-provider export CLIENT_ID= export CLIENT_SECRET= export ISSUER= chainctl iam identity-provider create \ --configuration-type=OIDC \ --oidc-client-id=${CLIENT_ID} \ --oidc-client-secret=${CLIENT_SECRET} \ --oidc-issuer=${ISSUER} \ --oidc-additional-scopes=email \ --oidc-additional-scopes=profile \ --default-role=viewer \ --name=${NAME} ``` `chainctl` installs the provider in your organization automatically when you belong to only one. If you have access to more than one, add `--parent=` to choose where it is installed. The `oidc-issuer`, `oidc-client-id`, and `oidc-client-secret` values are required when setting up an OIDC configuration with `chainctl`. You must also include a unique name for each custom IdP account. If you omit the `--default-role` option, `chainctl` prompts you to select a value interactively. This option defines the default role granted to users registering with this identity provider. The previous example specifies the `viewer` role, but depending on your needs you might choose `editor` or `owner`. For more information, refer to the [IAM and Security section](#iam-and-security). You can retrieve a list of all your Chainguard organizations — along with their UIDPs — with the following command. ```shell chainctl iam organizations ls -o table ``` ```output ID | NAME | DESCRIPTION --------------------------------------------------------+------------+--------------------- 59156e77fb23e1e5ebcb1bd9c5edae471dd85c43 | sample_org | . . . | . . . | ``` The organization you choose doesn't affect how your users authenticate, but it determines who has permission to modify the SSO configuration. ## Managing existing identity providers Identity providers can be managed via `chainctl` using the `chainctl iam identity-provider` subcommand. To create new providers, you can use the `create` subcommand. ```sh chainctl iam identity-provider create ``` To list out every configured identity provider, run the `list` subcommand. ```sh chainctl iam identity-provider list ``` This command returns details for each of your identity providers, including their names and unique IDs. To modify an existing identity provider, use the `update` subcommand. ```sh chainctl iam identity-provider update ``` This can be useful for rotating client credentials. Lastly, to delete an identity provider, run the `delete` subcommand. ```sh chainctl iam identity-provider delete ``` For more details, check out the [`chainctl` documentation for these commands](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/). ## IAM and security After you create an identity provider on the Chainguard platform, any user who can authenticate with that identity provider can use it to access the Chainguard platform. It’s important to note that users can do so even if they have no IAM capabilities with the IAM organization at which the identity provider is defined. Identity providers give access to the Chainguard platform, but not the specific IAM organization where the identity provider is defined. The IAM capabilities `identity_providers.create`, `identity_providers.update`, `identity_providers.list` and `identity_providers.delete` control which users can read and manipulate identity providers. The built-in roles `viewer`, `editor` and `owner` have the following capabilities related to identity providers. | **Role** | **Capabilities** | |----------|----------| | `viewer` | `identity_providers.list` | | `editor` | `identity_providers.list` | | `owner` | `identity_providers.create`, `identity_providers.list`, `identity_providers.update`, `identity_providers.delete` | ## Backup accounts In the case of an outage or misconfiguration of your identity provider, it can be helpful to have an authentication mechanism to the Chainguard platform outside of your SSO identity provider for recovery purposes. To this end, you can use one of our OIDC login providers (currently Google, GitHub, or GitLab) to create a backup account. As an OIDC login account needs to be set up to bootstrap the SSO identity provider initially, it’s possible to keep this account as a backup account in case you need it for recovery. However, the nature of these OIDC provider accounts is such that it is difficult to share them as a backup resource since they’re often tied to a single user. Instead of relying on an account with an OIDC login provider, you can alternatively set up an assumable identity to use as a backup account. Refer to our [conceptual guide on assumable identities](/platform/administration/assumable-ids/assumable-ids/) to learn more. ## Troubleshooting ### SSO users have no email, or the support portal redirect fails If SSO users sign in successfully but appear in the Console by an internal ID instead of a name or email — or see an error such as "There was an issue redirecting you to our support platform" when opening a support request — their token is most likely reaching Chainguard without an `email` claim. Sign-in itself still works; only the email-dependent parts break. Confirm it by decoding a user's ID token — with your identity provider's token preview, or [jwt.ms](https://jwt.ms) for Entra ID — and checking for a non-empty `email` claim. If it's missing, configure the provider to emit one as described in [Required token claims](#required-token-claims) and your [provider guide](/platform/administration/custom-idps/). This affects every user on the identity provider, so one corrected configuration fixes the whole organization. Existing identities pick up the email on the user's next sign-in, so have an affected user sign out and back in to confirm. --- ### How to integrate Okta SSO with Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/idp-providers/okta.md Last Modified: September 30, 2026 Tags: Chainguard Containers, Procedural The Chainguard platform supports single sign-on (SSO) authentication for users. By default, users can log in with GitHub, GitLab, and Google, but SSO support lets users bring their own identity provider for authentication. This guide outlines how to create an Okta application and integrate it with Chainguard. After completing this guide, you'll be able to log in to Chainguard using Okta and you'll no longer be limited to the default SSO options. ## Prerequisites To complete this guide, you need the following. * `chainctl` installed on your system. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't already have this installed. * An Okta account over which you have administrative access. ## Create an Okta app integration To integrate your Okta identity provider with the Chainguard platform, [log in to Okta](https://www.okta.com/login/) and navigate to the **Applications** landing page in the Admin console. There, click **Create App Integration**. Select **OIDC - OpenID Connect** as the sign-in method and **Web Application** as the application type. > **Note**: A Web Application is a confidential client and always expects a client secret. You can [add PKCE to the token exchange](/platform/administration/custom-idps/enabling-pkce/) on top of that secret. To use a secret-free public client instead, select **Native** or **SPA** here, since Okta doesn't let you change an application's type after you create it. Next, in the **General Settings** window, configure the application as follows: * **App integration name**: Enter a descriptive name (like "Chainguard") here. * **Grant type**: Ensure that the grant type is set to **Authorization Code** only. > **Warning**: Don't select other grant types, because doing so may compromise your security. * **Sign-in redirect URIs**: Set the redirect URI to the following. ```URI https://issuer.enforce.dev/oauth/callback ``` * **Sign-out redirect URIs**: This field has a URI set to `http://localhost:8080` by default. Click the **X** icon to remove the sign-out redirect entirely, leaving the field blank. * **Assignments**: You must select one of the options in this section: * **Allow everyone in your organization to access**: This option grants access to any [users](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-people.htm) you've added to your Okta organization. * **Limit access to selected groups**: If you select this option, you can select one or more Okta groups to have access to the identity provider. * **Skip group assignment for now**: You can select this option and configure group assignment later. However, note that users **will not** be able to log in unless they have been granted access to the application. > **Note**: For more information on Okta users and groups, refer to [the official documentation](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-main.htm). Click **Save**. Then, navigate to the **Sign On** tab. There, find the **OpenID Connect ID Token** section and click **Edit**. Set the **Issuer** option to **Okta URL**, then click **Save**. With that, you've configured the Okta application. Next, you need to configure the Chainguard platform to use it by creating a custom identity provider. ## Configure Chainguard to use Okta SSO Now that your Okta application is ready, you can create the custom identity provider. First, log in to Chainguard with `chainctl`, using an OIDC provider like Google, GitHub, or GitLab to bootstrap your account. ```shell chainctl auth login ``` Note that you can use this bootstrap account as a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) — that is, an account you can use to log in if you ever lose access to your primary account. However, if you prefer to remove this role-binding after configuring the custom IdP, you can do so. To configure the platform, make a note of the following settings from your Okta Application: * **Client ID**: You can find this on the **General** tab under **Client Credentials** * **Client Secret**: Find this on the **General** tab under **Client Credentials** * **Issuer URL**: You can find this under the **Sign On** tab in the **OpenID Connect ID Token** section With this information in hand, create a new identity provider with the following commands. Replace ``, ``, and `` with the values you just noted. ```shell export NAME=okta export CLIENT_ID= export CLIENT_SECRET= export ISSUER= chainctl iam identity-provider create \ --configuration-type=OIDC \ --oidc-client-id=${CLIENT_ID} \ --oidc-client-secret=${CLIENT_SECRET} \ --oidc-issuer=${ISSUER} \ --oidc-additional-scopes=email \ --oidc-additional-scopes=profile \ --default-role=viewer \ --name=${NAME} ``` Note the `--default-role` option. This defines the default role granted to users registering with this identity provider. This example specifies the `viewer` role, but depending on your needs you might choose `editor` or `owner`. If you don't include this option, you'll be prompted to specify the role interactively. For more information, refer to the [IAM and security section](/platform/administration/custom-idps/custom-idps/#iam-and-security) of our Introduction to Custom Identity Providers in Chainguard tutorial. You can refer to our [Generic integration guide](/platform/administration/custom-idps/custom-idps/#generic-integration-guide) in our Introduction to Custom Identity Providers article for more information about the `chainctl iam identity-provider create` command and its required options. ### Send a usable email claim Chainguard reads the standard `email` claim from the token Okta issues, which Okta populates from the user's Okta profile email once the `email` scope is requested. If your users' email is sourced from a directory attribute (for example an AD or LDAP `mail` attribute) that isn't mapped into that profile field, the token carries an empty address and the user has no email in Chainguard. Fix it in Okta's [profile attribute mappings](https://help.okta.com/en-us/content/topics/users-groups-profiles/usgp-about-attribute-mappings.htm) — map the source attribute to the Okta user profile's `email` — rather than by adding a custom token claim, which the org authorization server this integration uses does not support. See [Required token claims](/platform/administration/custom-idps/custom-idps/#required-token-claims) for the full requirement. ## Log in to Chainguard with the Okta identity provider To log in to the Chainguard Console with the new identity provider you just created, navigate to [console.chainguard.dev/auth/login](https://console.chainguard.dev/auth/login), enter your Chainguard organization's name into the **Email or organization** box, and click **Continue**. This opens a new window with the Okta login flow, where you can complete the login process. You can also use the custom identity provider to log in through `chainctl`. To do this, run the `chainctl auth login` command and add the `--identity-provider` option followed by the identity provider's ID value: ```shell chainctl auth login --identity-provider ``` The ID value appears in the `ID` column of the table returned by the `chainctl iam identity-provider create` command you ran previously. You can also retrieve this table at any time by running `chainctl iam identity-provider ls -o table` when logged in. To have Okta create and deactivate Chainguard user accounts automatically as you assign and unassign them, check out our guide on [how to provision users into Chainguard from Okta with SCIM](/platform/administration/custom-idps/scim-provisioning/okta-scim/). --- ### Chainguard Events URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/cloudevents/events-reference.md Last Modified: October 15, 2026 Tags: Platform, Reference, Product Chainguard generates and emits [CloudEvents](https://cloudevents.io/) based on actions that occur within a Chainguard account, such as registering a Kubernetes cluster or creating an IAM invitation. Chainguard also emits events when workloads or policies are changed in a cluster. Check out [this GitHub repository](https://github.com/chainguard-dev/enforce-events) for some sample applications that demonstrate how to use events to create Slack notifications, open GitHub issues, and mirror images. To subscribe to Chainguard events for your account, use the `chainctl` command like this: ```shell chainctl events subscriptions create –parent $YOUR_ORGANIZATION_OR_FOLDER https:// ``` Once you are subscribed to Chainguard events, you will start receiving HTTP POST requests. Each request has a common set of CloudEvent header fields, denoted by the `Ce-` prefix. The event body is encoded using JSON and will have two top-level keys, `actor` and `body`. The `actor` field is the identity of the actor in your Chainguard account that triggered the event, such as a team member or a Kubernetes cluster. The `body` field contains the specific data about the event, for example the response status for an invite creation request, or a cluster delete request. ## UIDP Identifiers Each Chainguard event includes a `Ce-Subject` header that contains a UIDP (UID Path) identifier. Identifiers follow POSIX directory semantics and components are separated by `/` delimiters. A UIDP is comprised of: * A globally unique identifier (UID), consisting of 20 bytes, that are URL safe hex encoded. For example, account identities like `0475f6baca584a8964a6bce6b74dbe78dd8805b6`. * One, or multiple `/` separated, scoped unique identifiers (SUID). An SUID is 8 bytes that are unique within a scope (like a group), and are URL safe hex encoded. The following is an example SUID: `b74ce966caf448d1`. SUIDs are used to identify every entity in Chainguard, from groups, policies, Kubernetes cluster IDs, event subscriptions, to IAM invitations, roles and role-bindings. Since Chainguard groups can contain child groups, events in a child group will propagate to the parent and thus the UIDP will contain multiple group SUIDs, along with the entity SUID itself. For example, assuming the following components: * An account UID of `0475f6baca584a8964a6bce6b74dbe78dd8805b6` * A group SUID of `b74ce966caf448d1` * A child of group `b74ce966caf448d1` with its own SUID of `dda9aab2d2d90f9e` The complete UIDP in the event's `Ce-Subject` header would be: ``` 0475f6baca584a8964a6bce6b74dbe78dd8805b6/b74ce966caf448d1/dda9aab2d2d90f9e/1a4b29ca6df80013 ``` ## Authorization Header Every Chainguard event has a JWT formatted [OIDC ID token](https://openid.net/specs/openid-connect-basic-1_0.html#IDToken) in its `Authorization` header. For authorization purposes, there are two important fields to validate: 1. Use the `iss` field to ensure that the issuer is Chainguard, specifically `https://issuer.enforce.dev`. 2. Use the `sub` field to check that the event matches your configured Chainguard identity. For example, assuming a UIDP ID of `0475f6baca584a8964a6bce6b74dbe78dd8805b6`, the value will resemble the following: `webhook:0475f6baca584a8964a6bce6b74dbe78dd8805b6`. If the subscription is in a sub-group, then the value will have the corresponding group SUID appended to the path. Validating these fields before processing the JWT token using a verification library can save resources, as well as alert about suspicious traffic, or misconfigured Chainguard group settings. ## CloudEvents Sources Chainguard CloudEvents are delivered from a stable set of egress IP addresses. These are also published as A records on `egress.enforce.dev`, so you can allowlist that name instead of hard-coding the individual addresses: * `34.132.193.40` * `35.237.242.37` * `35.230.121.20` * `34.85.183.217` These addresses apply to sinks reached over the public internet. A sink hosted on Google Cloud, such as a Cloud Run `.run.app` URL, may instead be reached over Google's internal network. In that case the delivery does not originate from any of these addresses, and no source-IP allowlist will match it. Verify those deliveries using the OIDC token in the `Authorization` header, as described above, rather than by source IP. ## Events Reference The following list of services and methods show example HTTP headers and bodies for public facing Chainguard events. ## Service: Registry - Pull ### Method: Pulled #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: cgr.dev Ce-Specversion: 1.0 Ce-Subject: The identifier of the repository being pulled from Ce-Time: 2026-10-15T09:15:08.777503713Z Ce-Type: dev.chainguard.registry.pull.v1 Content-Length: 789 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "digest": "The digest of the image being pulled", "error": { "code": "The OCI distribution-spec error code", "message": "The error message", "status": 0 }, "location": "Location holds the detected approximate location of the client who pulled. For example, \"ColumbusOHUS\" or \"Minato City13JP", "method": "The method used to pull the image. One of: HEAD or GET", "remote_address": "", "repo_id": "The identifier of the repository being pulled from", "repository": "The identifier of the repository being pulled from", "size": 1024, "tag": "The tag of the image being pulled", "type": "Type determines whether the object being pulled is a manifest or blob", "user_agent": "The user-agent of the client who pulled", "when": "2026-10-15T09:15:08.775540" } } ``` ## Service: Registry - Push ### Method: Pushed #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: cgr.dev Ce-Specversion: 1.0 Ce-Subject: The identifier of the repository being pushed to Ce-Time: 2026-10-15T09:15:08.775802559Z Ce-Type: dev.chainguard.registry.push.v1 Content-Length: 707 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "digest": "The digest of the image being pushed", "error": { "code": "The OCI distribution-spec error code", "message": "The error message", "status": 0 }, "location": "Location holds the detected approximate location of the client who pushed. For example, \"ColumbusOHUS\" or \"Minato City13JP", "remote_address": "", "repo_id": "The identifier of the repository being pushed to", "repository": "The identifier of the repository being pushed to", "tag": "The tag of the image being pushed", "type": "Type determines whether the object being pushed is a manifest or blob", "user_agent": "The user-agent of the client who pushed", "when": "2026-10-15T09:15:08.775512" } } ``` ## Service: auth - Auth ### Method: Register #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/auth/v1/register Ce-Specversion: 1.0 Ce-Subject: Chainguard UIDP Ce-Time: 2026-10-15T09:15:08.783544421Z Ce-Type: dev.chainguard.api.auth.registered.v1 Content-Length: 154 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "group": "the group this identity has joined by invitation", "identity": "Chainguard UIDP" } } ``` ## Service: events - Subscriptions ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/events/v1/subscriptions Ce-Specversion: 1.0 Ce-Subject: UIDP identifier of the subscription Ce-Time: 2026-10-15T09:15:08.792656264Z Ce-Type: dev.chainguard.api.events.subscription.created.v1 Content-Length: 152 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP identifier of the subscription", "sink": "Webhook endpoint (http/https URL)" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/events/v1/subscriptions Ce-Specversion: 1.0 Ce-Subject: UIDP identifier of the subscription to delete Ce-Time: 2026-10-15T09:15:08.792844557Z Ce-Type: dev.chainguard.api.events.subscription.deleted.v1 Content-Length: 119 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP identifier of the subscription to delete" } } ``` ## Service: iam - ExternalGroupRoleMappings ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/externalGroupRoleMappings Ce-Specversion: 1.0 Ce-Subject: UIDP of the mapping Ce-Time: 2026-10-15T09:15:08.785951864Z Ce-Type: dev.chainguard.api.iam.external_group_role_mappings.created.v1 Content-Length: 290 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "external_group_id": "The IdP group identifier", "id": "UIDP of the mapping", "identity_provider_uidp": "UIDP of the identity provider", "role_uidp": "UIDP of the Chainguard role", "scope": "UIDP of the group where the role applies" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/externalGroupRoleMappings Ce-Specversion: 1.0 Ce-Subject: UIDP of the mapping Ce-Time: 2026-10-15T09:15:08.7861829Z Ce-Type: dev.chainguard.api.iam.external_group_role_mappings.deleted.v1 Content-Length: 93 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the mapping" } } ``` ### Method: BatchDelete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/externalGroupRoleMappings:batchDelete Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.786344395Z Ce-Type: dev.chainguard.api.iam.external_group_role_mappings.deleted.batch.v1 Content-Length: 346 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "items": [ { "external_group_id": "The IdP group identifier", "id": "UIDP of the mapping", "identity_provider_uidp": "UIDP of the identity provider", "role_uidp": "UIDP of the Chainguard role", "scope": "UIDP of the group where the role applies" } ], "parent_id": "UIDP of the identity provider" } } ``` ## Service: iam - GroupAccountAssociations ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/account_associations Ce-Specversion: 1.0 Ce-Subject: UIDP with which this account information is associated Ce-Time: 2026-10-15T09:15:08.79374754Z Ce-Type: dev.chainguard.api.iam.account_associations.created.v1 Content-Length: 385 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "amazon": { "account": "Amazon account ID (if applicable)" }, "description": "description of this association", "google": { "project_id": "Google Cloud Project ID (if applicable)", "project_number": "Google Cloud Project Number (if applicable)" }, "group": "UIDP with which this account information is associated", "name": "group name" } } ``` ### Method: Update #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/account_associations Ce-Specversion: 1.0 Ce-Subject: UIDP with which this account information is associated Ce-Time: 2026-10-15T09:15:08.793872549Z Ce-Type: dev.chainguard.api.iam.account_associations.updated.v1 Content-Length: 336 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "amazon": { "account": "amazon account if applicable" }, "description": "group description", "google": { "project_id": "project id if applicable", "project_number": "project number if applicable" }, "group": "UIDP with which this account information is associated", "name": "group name" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/account_associations Ce-Specversion: 1.0 Ce-Subject: UIDP of the group whose associations will be deleted Ce-Time: 2026-10-15T09:15:08.793975319Z Ce-Type: dev.chainguard.api.iam.account_associations.deleted.v1 Content-Length: 129 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "group": "UIDP of the group whose associations will be deleted" } } ``` ## Service: iam - GroupInvites ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/group_invites Ce-Specversion: 1.0 Ce-Subject: group UIDP under which this invite resides Ce-Time: 2026-10-15T09:15:08.796135064Z Ce-Type: dev.chainguard.api.iam.group_invite.created.v1 Content-Length: 145 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "expiration": { "seconds": 100 }, "id": "group UIDP under which this invite resides" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/group_invites Ce-Specversion: 1.0 Ce-Subject: UIDP of the record Ce-Time: 2026-10-15T09:15:08.796324598Z Ce-Type: dev.chainguard.api.iam.group_invite.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the record" } } ``` ## Service: iam - Groups ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/groups Ce-Specversion: 1.0 Ce-Subject: group UIDP under which this group resides Ce-Time: 2026-10-15T09:15:08.797331742Z Ce-Type: dev.chainguard.api.iam.group.created.v1 Content-Length: 169 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "group description", "id": "group UIDP under which this group resides", "name": "group name" } } ``` ### Method: Update #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/groups Ce-Specversion: 1.0 Ce-Subject: group UIDP under which this group resides Ce-Time: 2026-10-15T09:15:08.797474694Z Ce-Type: dev.chainguard.api.iam.group.updated.v1 Content-Length: 169 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "group description", "id": "group UIDP under which this group resides", "name": "group name" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/groups Ce-Specversion: 1.0 Ce-Subject: UIDP of the record Ce-Time: 2026-10-15T09:15:08.797614159Z Ce-Type: dev.chainguard.api.iam.group.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the record" } } ``` ## Service: iam - Identities ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identities Ce-Specversion: 1.0 Ce-Subject: UIDP of identity Ce-Time: 2026-10-15T09:15:08.779621165Z Ce-Type: dev.chainguard.api.iam.identity.created.v1 Content-Length: 329 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "identity": { "Relationship": null, "description": "The human readable description of identity", "id": "The unique identifier of this specific identity", "name": "The human readable name of identity" }, "parent_id": "The Group UIDP path under which the new Identity resides" } } ``` ### Method: Update #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identities Ce-Specversion: 1.0 Ce-Subject: The unique identifier of this specific identity Ce-Time: 2026-10-15T09:15:08.779796739Z Ce-Type: dev.chainguard.api.iam.identity.updated.v1 Content-Length: 245 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "Relationship": null, "description": "The human readable description of identity", "id": "The unique identifier of this specific identity", "name": "The human readable name of identity" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identities Ce-Specversion: 1.0 Ce-Subject: UIDP of the record Ce-Time: 2026-10-15T09:15:08.779919308Z Ce-Type: dev.chainguard.api.iam.identity.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the record" } } ``` ## Service: iam - IdentityProviders ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of identity provider Ce-Time: 2026-10-15T09:15:08.786602085Z Ce-Type: dev.chainguard.api.iam.identity_providers.created.v1 Content-Length: 378 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "identity_provider": { "Configuration": null, "description": "The human readable description of identity provider", "id": "The UIDP of the IAM group to nest this identity provider under", "name": "The human readable name of identity provider" }, "parent_id": "The UIDP of the IAM group to nest this identity provider under" } } ``` ### Method: Update #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: The UIDP of the IAM group to nest this identity provider under Ce-Time: 2026-10-15T09:15:08.786842711Z Ce-Type: dev.chainguard.api.iam.identity_providers.updated.v1 Content-Length: 279 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "Configuration": null, "description": "The human readable description of identity provider", "id": "The UIDP of the IAM group to nest this identity provider under", "name": "The human readable name of identity provider" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the IdP Ce-Time: 2026-10-15T09:15:08.787040205Z Ce-Type: dev.chainguard.api.iam.identity_providers.deleted.v1 Content-Length: 89 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the IdP" } } ``` ### Method: GenerateScimToken #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.787194924Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_token.generated.v1 Content-Length: 250 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "endpoint_url": "SCIM endpoint URL for the identity provider", "etag": "Opaque version of the SCIM configuration", "expire_time": {}, "identity_provider_uid": "UIDP of the identity provider" } } ``` ### Method: RegenerateScimToken #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.787323389Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_token.regenerated.v1 Content-Length: 319 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "endpoint_url": "SCIM endpoint URL for the identity provider", "etag": "Opaque version of the SCIM configuration", "expire_time": {}, "identity_provider_uid": "UIDP of the identity provider", "previous_token_expire_time": {}, "requested_overlap": { "seconds": 3600 } } } ``` ### Method: RevokeScimToken #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.787471741Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_token.revoked.v1 Content-Length: 189 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "etag": "Opaque version of the SCIM configuration", "identity_provider_uid": "UIDP of the identity provider", "revoke_time": {} } } ``` ### Method: SetScimEnabled #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.787592422Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_enabled.updated.v1 Content-Length: 187 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "enabled": true, "etag": "Opaque version of the SCIM configuration", "identity_provider_uid": "UIDP of the identity provider" } } ``` ## Service: iam - RoleBindings ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/rolebindings Ce-Specversion: 1.0 Ce-Subject: UIDP of the Role to bind Ce-Time: 2026-10-15T09:15:08.787978497Z Ce-Type: dev.chainguard.api.iam.rolebindings.created.v1 Content-Length: 261 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "parent": "The Group UIDP path under which the new RoleBinding resides", "role_binding": { "id": "UID of this role binding", "identity": "UID of the Identity to bind", "role": "UIDP of the Role to bind" } } } ``` ### Method: CreateBatch #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/rolebindings/batch Ce-Specversion: 1.0 Ce-Subject: UID of this role binding, under a parent group UIDP Ce-Time: 2026-10-15T09:15:08.788437832Z Ce-Type: dev.chainguard.api.iam.rolebindings.created.batch.v1 Content-Length: 220 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "role_bindings": [ { "id": "UID of this role binding, under a parent group UIDP", "identity": "UID of the Identity to bind", "role": "UIDP of the Role to bind" } ] } } ``` ### Method: Update #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/rolebindings Ce-Specversion: 1.0 Ce-Subject: UID of this role binding Ce-Time: 2026-10-15T09:15:08.788567184Z Ce-Type: dev.chainguard.api.iam.rolebindings.updated.v1 Content-Length: 173 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UID of this role binding", "identity": "UID of the Identity to bind", "role": "UIDP of the Role to bind" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/rolebindings Ce-Specversion: 1.0 Ce-Subject: UID of the record Ce-Time: 2026-10-15T09:15:08.788664811Z Ce-Type: dev.chainguard.api.iam.rolebindings.deleted.v1 Content-Length: 91 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UID of the record" } } ``` ## Service: iam - Roles ### Method: Create #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/roles Ce-Specversion: 1.0 Ce-Subject: UIDP of the role under the group Ce-Time: 2026-10-15T09:15:08.778938658Z Ce-Type: dev.chainguard.api.iam.roles.created.v1 Content-Length: 159 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "role description", "name": "role name", "uid": "UIDP of the role under the group" } } ``` ### Method: Update #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/roles Ce-Specversion: 1.0 Ce-Subject: UIDP of the role under the group Ce-Time: 2026-10-15T09:15:08.779186572Z Ce-Type: dev.chainguard.api.iam.roles.updated.v1 Content-Length: 159 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "role description", "name": "role name", "uid": "UIDP of the role under the group" } } ``` ### Method: Delete #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/roles Ce-Specversion: 1.0 Ce-Subject: UIDP of the role to delete Ce-Time: 2026-10-15T09:15:08.779299062Z Ce-Type: dev.chainguard.api.iam.roles.deleted.v1 Content-Length: 101 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "uid": "UIDP of the role to delete" } } ``` ## Service: iam - Terms ### Method: AcceptTerms #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v1/terms Ce-Specversion: 1.0 Ce-Subject: Chainguard UIDP of the organization Ce-Time: 2026-10-15T09:15:08.797796005Z Ce-Type: dev.chainguard.api.iam.terms.accepted.v1 Content-Length: 159 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "document_ids": [ "guardener-tos.v1", "sfdpa.v1" ], "group": "Chainguard UIDP of the organization" } } ``` ## Service: registry - Charts ### Method: AddChart #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/repos Ce-Specversion: 1.0 Ce-Subject: UIDP of the destination organization Ce-Time: 2026-10-15T09:15:08.794128071Z Ce-Type: dev.chainguard.api.platform.registry.chart.added.v1 Content-Length: 208 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "repos": [ { "created": true, "id": "The UIDP of the created repo", "name": "The path of the created repo relative to the destination organization" } ] } } ``` ## Service: registry - Registry ### Method: CreateRepo #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.78031539Z Ce-Type: dev.chainguard.api.platform.registry.repo.created.v1 Content-Length: 243 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository", "name": "The name is the human-readable name of the repository", "sync_config": { "expiration": {}, "source": "Repo ID to sync from" } } } ``` ### Method: UpdateRepo #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.78071372Z Ce-Type: dev.chainguard.api.platform.registry.repo.updated.v1 Content-Length: 243 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository", "name": "The name is the human-readable name of the repository", "sync_config": { "expiration": {}, "source": "Repo ID to sync from" } } } ``` ### Method: DeleteRepo #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.780923701Z Ce-Type: dev.chainguard.api.platform.registry.repo.deleted.v1 Content-Length: 116 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository" } } ``` ### Method: CreateTag #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/tags Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific tag Ce-Time: 2026-10-15T09:15:08.781164872Z Ce-Type: dev.chainguard.api.platform.registry.tag.created.v1 Content-Length: 197 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "digest": "The digest of the manifest with this tag", "id": "The identifier of this specific tag", "name": "The unique name of the tag" } } ``` ### Method: UpdateTag #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/tags Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific tag Ce-Time: 2026-10-15T09:15:08.781430153Z Ce-Type: dev.chainguard.api.platform.registry.tag.updated.v1 Content-Length: 197 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "digest": "The digest of the manifest with this tag", "id": "The identifier of this specific tag", "name": "The unique name of the tag" } } ``` ### Method: DeleteTag #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v1/tags Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific tag Ce-Time: 2026-10-15T09:15:08.781643069Z Ce-Type: dev.chainguard.api.platform.registry.tag.deleted.v1 Content-Length: 109 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific tag" } } ``` ## Service: v1 - Bindings ### Method: CreateBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/bindings Ce-Specversion: 1.0 Ce-Subject: UIDP of the binding Ce-Time: 2026-10-15T09:15:08.789365029Z Ce-Type: dev.chainguard.api.policies.bindings.created.v1 Content-Length: 245 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "created_at": {}, "id": "UIDP of the binding", "mode": 1, "policy": "UIDP of the policy bound to the parent", "resource_types": [ "Resource type(s) the binding applies to" ], "updated_at": {} } } ``` ### Method: UpdateBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/bindings Ce-Specversion: 1.0 Ce-Subject: UIDP of the binding Ce-Time: 2026-10-15T09:15:08.789532203Z Ce-Type: dev.chainguard.api.policies.bindings.updated.v1 Content-Length: 245 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "created_at": {}, "id": "UIDP of the binding", "mode": 1, "policy": "UIDP of the policy bound to the parent", "resource_types": [ "Resource type(s) the binding applies to" ], "updated_at": {} } } ``` ### Method: DeleteBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/bindings Ce-Specversion: 1.0 Ce-Subject: UIDP of the binding Ce-Time: 2026-10-15T09:15:08.789639869Z Ce-Type: dev.chainguard.api.policies.bindings.deleted.v1 Content-Length: 93 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the binding" } } ``` ## Service: v1 - Overrides ### Method: CreateOverride #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/overrides Ce-Specversion: 1.0 Ce-Subject: UIDP of the override Ce-Time: 2026-10-15T09:15:08.789754183Z Ce-Type: dev.chainguard.api.policies.overrides.created.v1 Content-Length: 303 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "created_at": {}, "created_by": "Identity of the actor that created the override", "digest": "Digest of the image being waived", "id": "UIDP of the override", "policy_id": "UIDP of the policy being waived", "reason": "Justification for the waiver" } } ``` ### Method: DeleteOverride #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/overrides Ce-Specversion: 1.0 Ce-Subject: UIDP of the override Ce-Time: 2026-10-15T09:15:08.789915334Z Ce-Type: dev.chainguard.api.policies.overrides.deleted.v1 Content-Length: 94 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the override" } } ``` ## Service: v1 - Policies ### Method: CreatePolicy #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/policies Ce-Specversion: 1.0 Ce-Subject: UIDP of the policy Ce-Time: 2026-10-15T09:15:08.788885703Z Ce-Type: dev.chainguard.api.policies.policies.created.v1 Content-Length: 337 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "created_at": {}, "description": "Description of the policy", "expression": "Rego expression that defines the policy", "id": "UIDP of the policy", "name": "Name of the policy", "policy_type": 2, "supported_resource_type": "Versioned resource type the policy supports", "updated_at": {} } } ``` ### Method: UpdatePolicy #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/policies Ce-Specversion: 1.0 Ce-Subject: UIDP of the policy Ce-Time: 2026-10-15T09:15:08.789110875Z Ce-Type: dev.chainguard.api.policies.policies.updated.v1 Content-Length: 337 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "created_at": {}, "description": "Description of the policy", "expression": "Rego expression that defines the policy", "id": "UIDP of the policy", "name": "Name of the policy", "policy_type": 2, "supported_resource_type": "Versioned resource type the policy supports", "updated_at": {} } } ``` ### Method: DeletePolicy #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/policies/v1/policies Ce-Specversion: 1.0 Ce-Subject: UIDP of the policy Ce-Time: 2026-10-15T09:15:08.789248843Z Ce-Type: dev.chainguard.api.policies.policies.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the policy" } } ``` ## Service: v2beta1 - AccountAssociationsService ### Method: CreateAccountAssociation #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/accountAssociations Ce-Specversion: 1.0 Ce-Subject: UIDP with which this account information is associated Ce-Time: 2026-10-15T09:15:08.78427714Z Ce-Type: dev.chainguard.api.iam.account_associations.created.v1 Content-Length: 385 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "amazon": { "account": "Amazon account ID (if applicable)" }, "description": "description of this association", "google": { "project_id": "Google Cloud Project ID (if applicable)", "project_number": "Google Cloud Project Number (if applicable)" }, "group": "UIDP with which this account information is associated", "name": "group name" } } ``` ### Method: DeleteAccountAssociation #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/accountAssociations Ce-Specversion: 1.0 Ce-Subject: UIDP of the group whose associations will be deleted Ce-Time: 2026-10-15T09:15:08.785532631Z Ce-Type: dev.chainguard.api.iam.account_associations.deleted.v1 Content-Length: 129 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "group": "UIDP of the group whose associations will be deleted" } } ``` ### Method: UpdateAccountAssociation #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/accountAssociations Ce-Specversion: 1.0 Ce-Subject: UIDP with which this account information is associated Ce-Time: 2026-10-15T09:15:08.785726357Z Ce-Type: dev.chainguard.api.iam.account_associations.updated.v1 Content-Length: 336 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "amazon": { "account": "amazon account if applicable" }, "description": "group description", "google": { "project_id": "project id if applicable", "project_number": "project number if applicable" }, "group": "UIDP with which this account information is associated", "name": "group name" } } ``` ## Service: v2beta1 - ExternalGroupRoleMappingsService ### Method: CreateExternalGroupRoleMapping #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/externalGroupRoleMappings Ce-Specversion: 1.0 Ce-Subject: UIDP of the mapping Ce-Time: 2026-10-15T09:15:08.794352994Z Ce-Type: dev.chainguard.api.iam.external_group_role_mappings.created.v1 Content-Length: 290 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "external_group_id": "The IdP group identifier", "id": "UIDP of the mapping", "identity_provider_uidp": "UIDP of the identity provider", "role_uidp": "UIDP of the Chainguard role", "scope": "UIDP of the group where the role applies" } } ``` ### Method: DeleteExternalGroupRoleMapping #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/externalGroupRoleMappings Ce-Specversion: 1.0 Ce-Subject: UIDP of the mapping Ce-Time: 2026-10-15T09:15:08.79447338Z Ce-Type: dev.chainguard.api.iam.external_group_role_mappings.deleted.v1 Content-Length: 93 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the mapping" } } ``` ### Method: BatchDeleteExternalGroupRoleMappings #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/externalGroupRoleMappings:batchDelete Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.794583278Z Ce-Type: dev.chainguard.api.iam.external_group_role_mappings.deleted.batch.v1 Content-Length: 346 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "items": [ { "external_group_id": "The IdP group identifier", "id": "UIDP of the mapping", "identity_provider_uidp": "UIDP of the identity provider", "role_uidp": "UIDP of the Chainguard role", "scope": "UIDP of the group where the role applies" } ], "parent_id": "UIDP of the identity provider" } } ``` ## Service: v2beta1 - GroupInvitesService ### Method: CreateGroupInvite #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/groupInvites Ce-Specversion: 1.0 Ce-Subject: group UIDP under which this invite resides Ce-Time: 2026-10-15T09:15:08.790125747Z Ce-Type: dev.chainguard.api.iam.group_invite.created.v1 Content-Length: 145 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "expiration": { "seconds": 100 }, "id": "group UIDP under which this invite resides" } } ``` ### Method: DeleteGroupInvite #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/groupInvites Ce-Specversion: 1.0 Ce-Subject: UIDP of the record Ce-Time: 2026-10-15T09:15:08.790526597Z Ce-Type: dev.chainguard.api.iam.group_invite.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the record" } } ``` ## Service: v2beta1 - GroupsService ### Method: DeleteGroup #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/groups Ce-Specversion: 1.0 Ce-Subject: UIDP of the record Ce-Time: 2026-10-15T09:15:08.7781845Z Ce-Type: dev.chainguard.api.iam.group.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the record" } } ``` ### Method: CreateGroup #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/groups Ce-Specversion: 1.0 Ce-Subject: group UIDP under which this group resides Ce-Time: 2026-10-15T09:15:08.77842811Z Ce-Type: dev.chainguard.api.iam.group.created.v1 Content-Length: 169 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "group description", "id": "group UIDP under which this group resides", "name": "group name" } } ``` ### Method: UpdateGroup #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/groups Ce-Specversion: 1.0 Ce-Subject: group UIDP under which this group resides Ce-Time: 2026-10-15T09:15:08.778672825Z Ce-Type: dev.chainguard.api.iam.group.updated.v1 Content-Length: 169 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "group description", "id": "group UIDP under which this group resides", "name": "group name" } } ``` ## Service: v2beta1 - IdentitiesService ### Method: CreateIdentity #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identities Ce-Specversion: 1.0 Ce-Subject: UIDP of identity Ce-Time: 2026-10-15T09:15:08.796561361Z Ce-Type: dev.chainguard.api.iam.identity.created.v1 Content-Length: 329 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "identity": { "Relationship": null, "description": "The human readable description of identity", "id": "The unique identifier of this specific identity", "name": "The human readable name of identity" }, "parent_id": "The Group UIDP path under which the new Identity resides" } } ``` ### Method: DeleteIdentity #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identities Ce-Specversion: 1.0 Ce-Subject: UIDP of the record Ce-Time: 2026-10-15T09:15:08.796773525Z Ce-Type: dev.chainguard.api.iam.identity.deleted.v1 Content-Length: 92 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the record" } } ``` ### Method: UpdateIdentity #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identities Ce-Specversion: 1.0 Ce-Subject: The unique identifier of this specific identity Ce-Time: 2026-10-15T09:15:08.796938028Z Ce-Type: dev.chainguard.api.iam.identity.updated.v1 Content-Length: 245 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "Relationship": null, "description": "The human readable description of identity", "id": "The unique identifier of this specific identity", "name": "The human readable name of identity" } } ``` ### Method: UpdateIdentityMetadata #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identities:updateIdentityMetadata Ce-Specversion: 1.0 Ce-Subject: The caller's identity UID Ce-Time: 2026-10-15T09:15:08.797112682Z Ce-Type: dev.chainguard.api.iam.identity.metadata.updated.v1 Content-Length: 135 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "name": "The caller's display name", "uid": "The caller's identity UID" } } ``` ## Service: v2beta1 - IdentityProvidersService ### Method: CreateIdentityProvider #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of identity provider Ce-Time: 2026-10-15T09:15:08.781979371Z Ce-Type: dev.chainguard.api.iam.identity_providers.created.v1 Content-Length: 378 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "identity_provider": { "Configuration": null, "description": "The human readable description of identity provider", "id": "The UIDP of the IAM group to nest this identity provider under", "name": "The human readable name of identity provider" }, "parent_id": "The UIDP of the IAM group to nest this identity provider under" } } ``` ### Method: UpdateIdentityProvider #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: The UIDP of the IAM group to nest this identity provider under Ce-Time: 2026-10-15T09:15:08.782270115Z Ce-Type: dev.chainguard.api.iam.identity_providers.updated.v1 Content-Length: 279 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "Configuration": null, "description": "The human readable description of identity provider", "id": "The UIDP of the IAM group to nest this identity provider under", "name": "The human readable name of identity provider" } } ``` ### Method: DeleteIdentityProvider #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the IdP Ce-Time: 2026-10-15T09:15:08.78247404Z Ce-Type: dev.chainguard.api.iam.identity_providers.deleted.v1 Content-Length: 89 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UIDP of the IdP" } } ``` ### Method: GenerateScimToken #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.782674533Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_token.generated.v1 Content-Length: 250 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "endpoint_url": "SCIM endpoint URL for the identity provider", "etag": "Opaque version of the SCIM configuration", "expire_time": {}, "identity_provider_uid": "UIDP of the identity provider" } } ``` ### Method: RegenerateScimToken #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.782916015Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_token.regenerated.v1 Content-Length: 319 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "endpoint_url": "SCIM endpoint URL for the identity provider", "etag": "Opaque version of the SCIM configuration", "expire_time": {}, "identity_provider_uid": "UIDP of the identity provider", "previous_token_expire_time": {}, "requested_overlap": { "seconds": 3600 } } } ``` ### Method: RevokeScimToken #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.78315161Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_token.revoked.v1 Content-Length: 189 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "etag": "Opaque version of the SCIM configuration", "identity_provider_uid": "UIDP of the identity provider", "revoke_time": {} } } ``` ### Method: SetScimEnabled #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/identityProviders Ce-Specversion: 1.0 Ce-Subject: UIDP of the identity provider Ce-Time: 2026-10-15T09:15:08.78333192Z Ce-Type: dev.chainguard.api.iam.identity_providers.scim_enabled.updated.v1 Content-Length: 187 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "enabled": true, "etag": "Opaque version of the SCIM configuration", "identity_provider_uid": "UIDP of the identity provider" } } ``` ## Service: v2beta1 - OverlayBindingsService ### Method: CreateOverlayBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/overlayBindings Ce-Specversion: 1.0 Ce-Subject: The identifier of this overlay binding Ce-Time: 2026-10-15T09:15:08.790800758Z Ce-Type: dev.chainguard.api.platform.registry.overlay_binding.created.v1 Content-Length: 449 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "overlay": { "config": { "contents": { "packages": [ "The APK package names the attached overlay adds" ] } }, "name": "The unique name of the attached overlay", "uid": "The identifier of the attached overlay" }, "repo": "The identifier of the repo this binding applies to", "tag_selector": { "kind": 1, "tags": [ "The exact tag names this binding applies to" ] }, "uid": "The identifier of this overlay binding" } } ``` ### Method: UpdateOverlayBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/overlayBindings Ce-Specversion: 1.0 Ce-Subject: The identifier of this overlay binding Ce-Time: 2026-10-15T09:15:08.791103125Z Ce-Type: dev.chainguard.api.platform.registry.overlay_binding.updated.v1 Content-Length: 449 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "overlay": { "config": { "contents": { "packages": [ "The APK package names the attached overlay adds" ] } }, "name": "The unique name of the attached overlay", "uid": "The identifier of the attached overlay" }, "repo": "The identifier of the repo this binding applies to", "tag_selector": { "kind": 1, "tags": [ "The exact tag names this binding applies to" ] }, "uid": "The identifier of this overlay binding" } } ``` ### Method: DeleteOverlayBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/overlayBindings Ce-Specversion: 1.0 Ce-Subject: The identifier of the deleted overlay binding Ce-Time: 2026-10-15T09:15:08.791298514Z Ce-Type: dev.chainguard.api.platform.registry.overlay_binding.deleted.v1 Content-Length: 120 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "uid": "The identifier of the deleted overlay binding" } } ``` ## Service: v2beta1 - OverlaysService ### Method: CreateOverlay #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/overlays Ce-Specversion: 1.0 Ce-Subject: The identifier of this overlay Ce-Time: 2026-10-15T09:15:08.793187626Z Ce-Type: dev.chainguard.api.platform.registry.overlay.created.v1 Content-Length: 224 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "config": { "contents": { "packages": [ "The APK package names this overlay adds" ] } }, "name": "The unique name of the overlay", "uid": "The identifier of this overlay" } } ``` ### Method: UpdateOverlay #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/overlays Ce-Specversion: 1.0 Ce-Subject: The identifier of this overlay Ce-Time: 2026-10-15T09:15:08.793378056Z Ce-Type: dev.chainguard.api.platform.registry.overlay.updated.v1 Content-Length: 224 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "config": { "contents": { "packages": [ "The APK package names this overlay adds" ] } }, "name": "The unique name of the overlay", "uid": "The identifier of this overlay" } } ``` ### Method: DeleteOverlay #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/overlays Ce-Specversion: 1.0 Ce-Subject: The identifier of the deleted overlay Ce-Time: 2026-10-15T09:15:08.793544495Z Ce-Type: dev.chainguard.api.platform.registry.overlay.deleted.v1 Content-Length: 112 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "uid": "The identifier of the deleted overlay" } } ``` ## Service: v2beta1 - ReposService ### Method: CreateRepo #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.795413424Z Ce-Type: dev.chainguard.api.platform.registry.repo.created.v1 Content-Length: 243 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository", "name": "The name is the human-readable name of the repository", "sync_config": { "expiration": {}, "source": "Repo ID to sync from" } } } ``` ### Method: UpdateRepo #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.795602533Z Ce-Type: dev.chainguard.api.platform.registry.repo.updated.v1 Content-Length: 243 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository", "name": "The name is the human-readable name of the repository", "sync_config": { "expiration": {}, "source": "Repo ID to sync from" } } } ``` ### Method: DeleteRepo #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.795769116Z Ce-Type: dev.chainguard.api.platform.registry.repo.deleted.v1 Content-Length: 116 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository" } } ``` ### Method: UpdateRepoReadme #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/repos Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific repository Ce-Time: 2026-10-15T09:15:08.795900221Z Ce-Type: dev.chainguard.api.platform.registry.repo.updated.v1 Content-Length: 243 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific repository", "name": "The name is the human-readable name of the repository", "sync_config": { "expiration": {}, "source": "Repo ID to sync from" } } } ``` ## Service: v2beta1 - RoleBindingsService ### Method: CreateRoleBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roleBindings Ce-Specversion: 1.0 Ce-Subject: UIDP of the Role to bind Ce-Time: 2026-10-15T09:15:08.791519014Z Ce-Type: dev.chainguard.api.iam.rolebindings.created.v1 Content-Length: 261 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "parent": "The Group UIDP path under which the new RoleBinding resides", "role_binding": { "id": "UID of this role binding", "identity": "UID of the Identity to bind", "role": "UIDP of the Role to bind" } } } ``` ### Method: DeleteRoleBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roleBindings Ce-Specversion: 1.0 Ce-Subject: UID of the record Ce-Time: 2026-10-15T09:15:08.791705652Z Ce-Type: dev.chainguard.api.iam.rolebindings.deleted.v1 Content-Length: 91 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UID of the record" } } ``` ### Method: BatchCreateRoleBindings #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roleBindings:batchCreate Ce-Specversion: 1.0 Ce-Subject: UID of this role binding, under a parent group UIDP Ce-Time: 2026-10-15T09:15:08.791834525Z Ce-Type: dev.chainguard.api.iam.rolebindings.created.batch.v1 Content-Length: 220 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "role_bindings": [ { "id": "UID of this role binding, under a parent group UIDP", "identity": "UID of the Identity to bind", "role": "UIDP of the Role to bind" } ] } } ``` ### Method: UpdateRoleBinding #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roleBindings Ce-Specversion: 1.0 Ce-Subject: UID of this role binding Ce-Time: 2026-10-15T09:15:08.792075087Z Ce-Type: dev.chainguard.api.iam.rolebindings.updated.v1 Content-Length: 173 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "UID of this role binding", "identity": "UID of the Identity to bind", "role": "UIDP of the Role to bind" } } ``` ## Service: v2beta1 - RolesService ### Method: CreateRole #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roles Ce-Specversion: 1.0 Ce-Subject: UIDP of the role under the group Ce-Time: 2026-10-15T09:15:08.794861686Z Ce-Type: dev.chainguard.api.iam.roles.created.v1 Content-Length: 159 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "role description", "name": "role name", "uid": "UIDP of the role under the group" } } ``` ### Method: UpdateRole #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roles Ce-Specversion: 1.0 Ce-Subject: UIDP of the role under the group Ce-Time: 2026-10-15T09:15:08.794982936Z Ce-Type: dev.chainguard.api.iam.roles.updated.v1 Content-Length: 159 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "description": "role description", "name": "role name", "uid": "UIDP of the role under the group" } } ``` ### Method: DeleteRole #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/roles Ce-Specversion: 1.0 Ce-Subject: UIDP of the role to delete Ce-Time: 2026-10-15T09:15:08.795165205Z Ce-Type: dev.chainguard.api.iam.roles.deleted.v1 Content-Length: 101 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "uid": "UIDP of the role to delete" } } ``` ## Service: v2beta1 - TagsService ### Method: CreateTag #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/tags Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific tag Ce-Time: 2026-10-15T09:15:08.792306907Z Ce-Type: dev.chainguard.api.platform.registry.tag.created.v1 Content-Length: 197 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "digest": "The digest of the manifest with this tag", "id": "The identifier of this specific tag", "name": "The unique name of the tag" } } ``` ### Method: DeleteTag #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/registry/v2beta1/tags Ce-Specversion: 1.0 Ce-Subject: The identifier of this specific tag Ce-Time: 2026-10-15T09:15:08.792486769Z Ce-Type: dev.chainguard.api.platform.registry.tag.deleted.v1 Content-Length: 109 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "id": "The identifier of this specific tag" } } ``` ## Service: v2beta1 - TermsService ### Method: AcceptTerms #### Example HTTP Headers ``` POST / HTTP/1.1 Host: console-api.enforce.dev Accept-Encoding: gzip Authorization: Bearer oidctoken Ce-Audience: customer Ce-Group: UID of parent group Ce-Id: cloudevent generated UUID Ce-Source: https://console-api.enforce.dev/iam/v2beta1/terms Ce-Specversion: 1.0 Ce-Subject: Chainguard UIDP of the organization Ce-Time: 2026-10-15T09:15:08.787755037Z Ce-Type: dev.chainguard.api.iam.terms.accepted.v1 Content-Length: 159 Content-Type: application/json User-Agent: Chainguard Enforce ``` #### Example HTTP Body ```json { "actor": { "subject": "identity that triggered the event" }, "body": { "document_ids": [ "guardener-tos.v1", "sfdpa.v1" ], "group": "Chainguard UIDP of the organization" } } ``` --- ### How to install chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/how-to-install-chainctl.md Last Modified: October 7, 2026 Tags: chainctl Chainguard's `chainctl` command-line interface provides essential tools for managing your container security infrastructure, including image management, identity and access control, and resource monitoring. This CLI enables automation of Chainguard operations and integration with CI/CD pipelines. The tool uses the familiar ` ` style of CLI interactions. For example, to retrieve a list of all the private Chainguard Containers available to your organization, you can run `chainctl images list`. Before we begin, let’s move into a temporary directory that we can work in. Be sure you have [curl](https://curl.se/download.html) and [jq](https://jqlang.org/download/) installed. The `curl` installation and the Cosign verification steps use jq to find the latest release version. ```sh mkdir -p ~/tmp && cd $_ ``` There are currently two ways to install `chainctl`, depending on your operating system and preferences. ## Install `chainctl` with Homebrew You can install `chainctl` for macOS and Linux with the package manager [Homebrew](https://brew.sh/). Note that you will need to have the Xcode Command Line Tools installed prior to installing `chainctl` with Homebrew on macOS. Without these installed, you won't be able to use Homebrew to install `chainctl` on your macOS device. If you haven't already done so, you can install the Xcode Command Line Tools with the following command. ```sh xcode-select --install ``` Before installing `chainctl` with Homebrew, use `brew tap` to bring in Chainguard's repositories and trust them. ```sh brew tap chainguard-dev/tap brew trust --tap chainguard-dev/tap ``` Next, install `chainctl` with Homebrew. ```sh brew install chainctl ``` You are now ready to use the `chainctl` command. You can verify that it works correctly in the final section of this guide. ## Install with `curl` A platform-agnostic approach to installing `chainctl` is through using `curl`. We have [specific instructions for Windows users](/platform/chainctl-usage/how-to-install-chainctl/#installing-with-curl-in-windows-powershell) on installing `chainctl` with `curl`, but all others can run the following command: ```bash PLATFORM="$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/')" VERSION=$(curl -sS "https://dl.enforce.dev/chainctl/latest/metadata.json" | jq -r .version) curl -o chainctl "https://dl.enforce.dev/chainctl/${VERSION}/chainctl_${PLATFORM}" ``` We resolve the release version once from `metadata.json` and then download the binary from that explicit version path (rather than from `/latest/` directly). This ensures the binary and the signature you verify it against in the next step come from the same release. Downloading individual artifacts from `/latest/` in separate steps can occasionally pull mismatched files if a new release is published in between, causing signature verification to fail. Keep this same terminal session for the verification step below so that the `PLATFORM` and `VERSION` values are reused. Move `chainctl` into your `/usr/local/bin` directory and elevate its permissions so that it can execute as needed. ```sh sudo install -o $UID -g $(id -g) -m 0755 chainctl /usr/local/bin/ ``` At this point, you'll be able to use the `chainctl` command. ### Installing with `curl` in Windows PowerShell As stated previously, you can also use `curl` install `chainctl` on Windows systems. Running the following command in PowerShell will download the appropriate `.exe` file. ```PowerShell $VERSION = (curl -UseBasicParsing https://dl.enforce.dev/chainctl/latest/metadata.json | ConvertFrom-Json).version curl -o chainctl.exe https://dl.enforce.dev/chainctl/$VERSION/chainctl_windows_x86_64.exe ``` It may take several minutes for this operation to complete. Following that you can use `chainctl`. Be aware that Windows PowerShell does not load commands from the working directory by default so you will need to include `.\` before any `chainctl` commands you run, as in this example. ```PowerShell .\chainctl auth login ``` Also, please note that while [`chainctl` commands](/platform/chainctl/) will generally work, some are not as thoroughly tested on Windows and may not behave as expected. ## Install with Scoop in Windows Scoop is a command-line installer for Windows that has a community-contributed and -maintained way of [installing `chainctl`](https://scoop.sh/#/apps?q=chainctl). If you have Scoop installed, then installing `chainctl` only requires two commands. First, add the bucket. ```shell scoop bucket add main ``` And then install `chainctl` from there. ```shell scoop install main/chainctl ``` ## Verifying the `chainctl` binary with Cosign Before running `chainctl` for the first time, you should verify the integrity of the downloaded binary using Cosign. This ensures the binary has not been tampered with. Ensure that you have the latest version of Cosign installed by following our [How to install Cosign guide](/open-source/sigstore/cosign/how-to-install-cosign/). If you are continuing in the same terminal session as the previous installation step, `PLATFORM` and `VERSION` are already set. Otherwise, resolve them again first. Then download the release's Sigstore bundle, which holds the binary's signature and certificate, from the same pinned version, and use it to verify the binary: ```sh PLATFORM="$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/')" VERSION=$(curl -sS "https://dl.enforce.dev/chainctl/latest/metadata.json" | jq -r .version) curl -sS -o chainctl.sigstore.json "https://dl.enforce.dev/chainctl/${VERSION}/chainctl_${PLATFORM}.sigstore.json" cosign verify-blob \ --bundle chainctl.sigstore.json \ --certificate-identity "https://github.com/chainguard-dev/mono/.github/workflows/.release-drop.yaml@refs/tags/v${VERSION}" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ $(which chainctl) ```

Note: Always pin the binary and bundle downloads to the same explicit ${VERSION} rather than fetching each from /latest/ independently. If a new release is published between the individual downloads, you can end up with a binary and a bundle from different releases, and verification will fail with an error such as invalid signature when validating ASN.1 encoded signature.

You should receive the following output: ```output Verified OK ``` If you do not see the line `Verified OK` then there is a problem with your `chainctl` binary and you should reinstall it using the instructions at the beginning of this page. ## Verifying installation Once verified, you can confirm that everything was set up correctly by checking the `chainctl` version. ```sh chainctl version ``` You should receive output similar to the following. ```output ____ _ _ _ ___ _ _ ____ _____ _ / ___| | | | | / \ |_ _| | \ | | / ___| |_ _| | | | | | |_| | / _ \ | | | \| | | | | | | | | |___ | _ | / ___ \ | | | |\ | | |___ | | | |___ \____| |_| |_| /_/ \_\ |___| |_| \_| \____| |_| |_____| chainctl: Chainguard Control GitVersion: GitCommit: GitTreeState: clean BuildDate: GoVersion: Compiler: gc Platform: ``` If you received output that you did not expect, check your bash profile to make sure that your system is using the expected PATH. ## Authenticating With chainctl installed, you can authenticate into Chainguard with the following command: ```sh chainctl auth login ``` This will open your browser window and take you through a workflow to login with your OIDC provider.

Note: chainctl auth login supports several login flows, including device-code login for machines without a browser and assumable identities for CI. If the default browser flow doesn't work in your environment, see Authentication options.

### Authentication tokens If you ever need to retrieve your local Chainguard token, you can do so with the following command: ```shell chainctl auth token ``` Following the [XDG standard](https://specifications.freedesktop.org/basedir/latest/), chainctl stores tokens in the local OS's cache directory: * **On Linux systems**, this is typically `~/.cache/` * **On MacOS**, this is `~/Library/Caches/` * **On Windows**, this is `%LocalAppData%` (typically `C:\Users\\AppData\Local`) The token's path is the local cache directory appended with `/chainguard//`. The `oidc-token` is a short-lived access token used for authentication, while the `refresh-token` is a longer-lived credential that allows chainctl to obtain new access tokens without requiring you to log in again. For example, on Linux you can find the `oidc-token` for the `https://console-api.enforce.dev` audience in the following location: ``` /home/$USER/.cache/chainguard/https:--console-api.enforce.dev/ ``` Note that for this audience, chainctl replaces `/` with `-` in order to ensure a valid file path. On Windows, the same `oidc-token` is stored at: ``` C:\Users\\AppData\Local\chainguard\https---console-api.enforce.dev\oidc-token ``` Windows doesn't allow `:` in file paths either, so on Windows chainctl replaces both `/` and `:` with `-`. The `https://console-api.enforce.dev` audience becomes `https---console-api.enforce.dev` (three hyphens), rather than the `https:--console-api.enforce.dev` form used on Linux and macOS. ## Configure a Docker credential helper You can configure a Docker credential helper with chainctl by running: ```sh chainctl auth configure-docker ``` This will: * Create a link for `docker-credential-cgr` in the same directory as `chainctl`. * Update your Docker config file to call `docker-credential-cgr` when an auth token is needed. A browser window will open when the token needs to be refreshed.

Note: If you installed chainctl to a directory that needs root permissions and run as root (eg with sudo) when creating the docker-credential-cgr symlink, and then complete login in that same session, the Docker credentials will be written to root’s .docker/config.json instead of your user’s Docker config. In that case, run configure-docker again as the correct user.

### Docker credential helper on Windows On Windows, Docker expects a `docker-credential-cgr.exe` helper to be available on your `PATH`. `chainctl auth configure-docker` creates this helper for you by copying `chainctl.exe` to `docker-credential-cgr.exe` in the same directory. Because it copies the binary rather than creating a symbolic link, a standard, non-administrator account can run it without enabling Developer Mode or running PowerShell as an administrator. To make Docker pulls work, perform the following steps: #### 1. Add `chainctl.exe` to the `PATH` (replace `C:\Tools` with the actual directory you used): ```PowerShell $env:Path += ";C:\Tools" ``` For a persistent setup, add that directory to your user or system `PATH` via the Windows Environment Variables settings. #### 2. Authenticate and configure Docker credentials: ```PowerShell chainctl auth configure-docker ``` `chainctl auth configure-docker` creates `docker-credential-cgr.exe` next to `chainctl.exe` and updates your Docker configuration to use it. You don't need to run `chainctl auth login` first; `configure-docker` authenticates you when no valid token is available. Following this, you can verify Docker pulls from Chainguard by pulling a private image. ## Updating `chainctl` When your version of `chainctl` is a few weeks old or older, you may consider updating it to make sure that your version is the most up to date. You can update `chainctl` by running the `update` command. ```sh sudo chainctl update ``` The command shows your current version and the latest version, then asks `Do you want to continue? [Y,n]`. Enter `Y` to update. To skip the prompt, run `sudo chainctl update --yes` instead. The update command verifies the signature of the downloaded binary in-process before replacing your existing installation, so you don't need a separate `cosign` binary on your `PATH`. Verification needs network access to the download host (`dl.enforce.dev`) and, at least on first use, to the Sigstore trust root at `tuf-repo-cdn.sigstore.dev`. If verification fails or those hosts are unreachable, the update stops, the unverified binary is removed, and your current installation is left in place. When the update finishes, `chainctl` keeps a backup of your previous binary and prints a command to delete it, such as `rm -f "/root/.cache/chainctl/chainctl.bak"`. Once the new version works, you can run that command to remove the backup. Keeping `chainctl` up to date will ensure that you are using the most up to date version. --- ### Overview of the Chainguard IAM model URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/iam-organizations/overview-of-chainguard-iam-model.md Last Modified: September 30, 2026 Tags: Console, Reference Chainguard's identity and access management (IAM) model enables more secure, fine-grained control over container registries and security resources, using familiar concepts from cloud providers like AWS and GCP. This enterprise-grade IAM system allows organizations to implement least-privilege access, delegate permissions, and integrate with existing identity providers for seamless authentication and authorization. ## Organizations and folders Chainguard's IAM model consists of two structures: **Organizations** and **Folders**. An organization is a customer or group of customers working with the same Chainguard resources, while a folder is a collection of resources within a Chainguard organization. Organizations have a unique domain as their identifier and a user can belong to more than one organization. It's possible for organizations to become [verified organizations](/platform/administration/iam-organizations/verified-orgs/). Verification modifies some aspects of the Chainguard platform user experience to help large organizations guide their user base to the correct resource. This optional process is performed manually by Chainguard, so if you're interested in verifying your organization, please reach out to your customer support contact. ## Identities In the context of Chainguard, an identity represents an individual user within an organization. Users typically join an organization after [being sent an invitation](/platform/administration/iam-organizations/how-to-manage-iam-organizations-in-chainguard/#inviting-others-to-an-organization). After receiving an invitation, the user can sign up with a Google, GitHub, or Gitlab account. In cases like this, the user's identity is the email address associated with the account they used to log in. > Note: If their organization has configured one, a user can sign up with a [custom identity provider](/platform/administration/custom-idps/custom-idps/). In order to create an invitation for a new user, you must choose a role for that user and then create a role-binding to tie that user to the chosen role. Our [overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/) has more information. You can also create assumable identities. These are typically used to allow automation tools like GitHub Actions or Amazon Lambda to connect to and manage Chainguard resources. Refer to our [guide on assumable identities](/platform/administration/assumable-ids/assumable-ids/) to learn more. ## Logging in to the Chainguard platform You can authenticate to the Chainguard platform with `chainctl` in several ways, including interactive browser login, headless device-code login, social login providers, and assumable identities for CI/CD. For the full list of login flows and guidance on when to use each, see [Authentication options](/platform/chainctl-usage/authentication-options/). --- ### Chainguard OpenAPI specification URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/api/spec.md Last Modified: September 28, 2026
--- ### Chainguard API v1 specification URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/api/spec-api-v1.md Last Modified: September 28, 2026
--- ### Chainguard API v2 specification URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/api/spec-api-v2.md Last Modified: September 28, 2026
--- ### cg-api: the Chainguard platform API MCP server URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/mcp-servers/cg-api.md Last Modified: September 28, 2026 Tags: MCP, Platform `cg-api` exposes the Chainguard platform API — the same API behind `chainctl` and the Chainguard Console — to an MCP client. Through it, a client can resolve which organizations and folders you belong to, list the image repositories and tags your account holds, inspect roles, role bindings, identity providers, and cloud account associations, and read security advisories. It is the broadest of Chainguard's four product-data MCP servers, and the only one that reaches organization and IAM data. The server uses the Streamable HTTP transport, at this endpoint: ``` https://console-api.enforce.dev/mcp ``` Like the other three servers, `cg-api` is read-only. It exposes none of the platform API's create, update, or delete operations, so use `chainctl` or the Console to change platform resources. ## Prerequisites - An MCP-compatible client such as Claude Code, Claude Desktop, or Cursor - A [Chainguard account](https://console.chainguard.dev/) and an organization - [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) installed, if you plan to narrow your token's scope or use the headless authentication path ## Connect to the server ### Claude Code Add the server with `claude mcp add`, using the HTTP transport: ```shell claude mcp add --transport http cg-api https://console-api.enforce.dev/mcp ``` Pick the scope that fits how you want to use it: `local` (the default — only you, in the current directory), `project` (writes a shared `.mcp.json` at the repository root, checked in for teammates), or `user` (only you, across every project). The server is added unauthenticated. To complete OAuth, start a session and run the `/mcp` command: ```Prompt /mcp ``` Select **cg-api**, choose **Authenticate**, and approve the connection in the browser window that opens. Check the status any time with: ```shell claude mcp list ``` ```output cg-api: https://console-api.enforce.dev/mcp (HTTP) - ✓ Connected ``` ### Cursor Cursor supports HTTP transport natively. Add the server to your MCP configuration: ```json { "mcpServers": { "cg-api": { "url": "https://console-api.enforce.dev/mcp" } } } ``` Restart Cursor, then connect the server from **Tools & MCPs** in settings and complete the browser sign-in. ### Claude Desktop Claude Desktop reads MCP servers from a JSON file but does not yet support HTTP transport directly. Use [`mcp-remote`](https://github.com/geelen/mcp-remote) to bridge to the hosted server: ```json { "mcpServers": { "cg-api": { "command": "npx", "args": [ "mcp-remote", "https://console-api.enforce.dev/mcp" ] } } } ``` The configuration file lives at: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` `npx` downloads and runs `mcp-remote` on demand, so you need Node.js installed on the host. Restart Claude Desktop after saving the file. ### Other MCP clients Any client that supports a remote Streamable HTTP MCP server with OAuth can connect to the same endpoint. Point it at `https://console-api.enforce.dev/mcp` and complete the browser sign-in when prompted. ## Authentication Authentication is OAuth 2.0 against the Chainguard issuer, and every call runs with your own platform permissions. The server grants no access beyond what your role already allows. On a remote or headless workstation, you can supply a token from `chainctl` instead of completing the browser flow. Refer to [Authenticate with chainctl instead of a browser](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser) for the full recipe; `cg-api`'s audience is `https://console-api.enforce.dev/mcp`. ### The tool list depends on your token `cg-api` filters its advertised tools against the capabilities of the token you connected with. Two people connected to the same endpoint can see different tool lists, and a missing tool means your token can't call it, not that the server doesn't offer it. Run `/mcp` in Claude Code to see what your own token receives. ### Narrow what an AI tool can do `chainctl auth token` accepts two flags that reduce a token's reach: - `--capabilities` requests a token narrowed to the capabilities you name. List the ones a token currently carries with `chainctl auth token capabilities`. - `--scope` reduces a token's scope to the groups you name, which confines it to one organization or folder. The browser OAuth flow offers no equivalent, so narrowing means authenticating through `chainctl`. Add the flags to the `chainctl auth token` call inside the headers helper described in the [overview](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser), and every connection that helper opens inherits the narrowed token. A helper that scopes its token to one organization produces a `cg-api` session that can't read data from your other organizations. ## Common parameters Most tools share the following conventions. ### UIDPs Platform resources are addressed by **UIDP**, a slash-delimited path that encodes the resource's position in the group hierarchy. A root organization has a bare UIDP such as `0ac7ff905850c35723a7f376e10d007c958c45c8`; a folder beneath it appends a segment, as in `0ac7ff905850c35723a7f376e10d007c958c45c8/014da1131bcc7f51`. Every `*_get` tool takes a `uid` of this shape, and you normally obtain it from a `*_list` call rather than constructing it. ### The uidp filter `list` tools accept a `uidp` object that scopes results to part of the hierarchy: | Field | Type | Description | | ----- | ----- | ----- | | `ids` | array of strings | Restrict to these exact UIDPs | | `children_of` | string | Direct children of this UIDP | | `descendants_of` | string | Every descendant of this UIDP, at any depth | | `ancestors_of` | string | The chain of groups above this UIDP | | `in_root` | boolean | Restrict to root-level groups | ### Pagination Every `list` tool returns at most 200 results per page and 50 by default; a larger `page_size` is reduced rather than rejected. Continue by passing the response's `nextPageToken` as the next call's `page_token`. Note the casing difference: the parameter is snake_case, and the response field is camelCase. Responses also carry `totalCount`, so an MCP client can report a total without paging through the results. Most `list` tools additionally accept `order_by` and `skip`. ## Tool reference The server advertises 30 tools across 15 services, grouped by the service they belong to. Run `api_list` to enumerate the services your own token reaches, and `api_read` to see an individual RPC's metadata. ### API discovery | Tool | Parameters | Returns | | ----- | ----- | ----- | | `api_list` | `path` | Services at the root, or the RPCs under a service such as `iam.v2beta1.GroupsService` | | `api_read` | `path` | The metadata payload for one RPC, such as `iam.v2beta1.GroupsService/ListGroups` | | `api_callers` | `type` | The RPCs you can call that use a given proto message type | *Example prompt:* "What parts of the Chainguard API can I reach?" ### Organizations and folders | Tool | Parameters | Returns | | ----- | ----- | ----- | | `iam_groups_list` | `name`, `uidp`, `order_by`, `page_size`, `page_token`, `skip` | Groups you can access | | `iam_groups_get` | `uid` | One group | *Example prompt:* "What Chainguard organizations am I a member of?" ### Roles and role bindings | Tool | Parameters | Returns | | ----- | ----- | ----- | | `iam_roles_list` | `name`, `uidp`, `order_by`, `page_size`, `page_token`, `skip` | Roles available to you | | `iam_roles_get` | `uid` | One role | | `iam_role_bindings_get` | `uid` | One role binding | *Example prompt:* "What roles exist in my organization?" ### Identities and identity providers | Tool | Parameters | Returns | | ----- | ----- | ----- | | `iam_identities_get` | `uid` | One identity | | `iam_identity_providers_list` | `name`, `uidp`, pagination | Configured identity providers | | `iam_identity_providers_get` | `uid` | One identity provider | | `iam_external_group_role_mappings_list` | `uidp`, pagination | Mappings from IdP groups to Chainguard roles | | `iam_external_group_role_mappings_get` | `uid` | One mapping | *Example prompt:* "Which identity providers are configured for my org?" ### Invitations | Tool | Parameters | Returns | | ----- | ----- | ----- | | `iam_group_invites_get` | `uid` | One invitation | ### Cloud account associations | Tool | Parameters | Returns | | ----- | ----- | ----- | | `iam_account_associations_list` | `uidp`, pagination | Configured cloud account associations | | `iam_account_associations_get` | `uid` | One association | | `iam_account_associations_check` | `uid`, `provider_type` | Verifies an association by performing a live credential exchange against Google, Amazon, or Azure | *Example prompt:* "Is my AWS account association working?" ### Event subscriptions | Tool | Parameters | Returns | | ----- | ----- | ----- | | `iam_subscriptions_list` | `uidp`, pagination | Event subscriptions you can access | | `iam_subscriptions_get` | `uid` | One subscription | Despite the `iam_` prefix, these tools address the events service rather than IAM. Refer to the [events reference](/platform/administration/cloudevents/events-reference/) for the event types available. ### Registry metadata | Tool | Parameters | Returns | | ----- | ----- | ----- | | `registry_repos_list` | `name`, `uidp`, `order_by`, `page_size`, `page_token`, `skip` | Repositories you can access | | `registry_repos_get` | `uid` | One repository | | `registry_tags_list` | `name`, `digest`, `updated_since`, `include_dates`, `include_epochs`, `include_referrers`, `include_vcs_snapshots`, `uidp`, pagination | Tags you can access | | `registry_tags_get` | `uid` | One tag | | `registry_images_get_architectures` | image identifier | The architectures an image provides | | `registry_images_get_size` | image identifier | An image's size | | `registry_overlays_list` / `registry_overlays_get` | `uidp`, pagination, `uid` | Custom Assembly overlays | | `registry_overlay_bindings_list` / `registry_overlay_bindings_get` | `uidp`, pagination, `uid` | Which overlays are bound to which repositories | *Example prompt:* "Which repositories in my org have been updated this week?" ### Security advisories | Tool | Parameters | Returns | | ----- | ----- | ----- | | `vulnerabilities_advisories_get` | `uid` | One security advisory | ### Tools that are deliberately withheld The server exposes no write tools. Every create, update, and delete operation in the platform API, and `iam_terms_accept`, is absent from the MCP tool list. Six collection-listing tools are also withheld, because enumerating them wholesale would pull sensitive organization data into a model's context. Four of the six have a per-resource `*_get` form, so you can still read a specific record when you have its UIDP: | Withheld tool | Read one instead with | | ----- | ----- | | `iam_scim_users_list` | — | | `iam_identities_list` | `iam_identities_get` | | `iam_group_invites_list` | `iam_group_invites_get` | | `iam_role_bindings_list` | `iam_role_bindings_get` | | `iam_terms_list_terms_acceptances` | — | | `vulnerabilities_advisories_list` | `vulnerabilities_advisories_get` | To enumerate any of these, or to change platform resources, use `chainctl` or the Console rather than an MCP client. ## Core tools in detail ### iam_groups_list Lists the groups — organizations and folders — that the caller can access. An AI tool usually calls this first, because it returns the UIDPs that almost every other call needs. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name` | string | no | Filter by group name | | `uidp` | object | no | Scope to part of the hierarchy | | `order_by` | string | no | Sort order | | `page_size` | integer | no | Results per page (default 50, max 200) | | `page_token` | string | no | The `nextPageToken` from a previous call | | `skip` | integer | no | Skip this many results | Each group returns `name`, `uid`, `description`, `createTime`, `updateTime`, and `verified`. The response carries `totalCount` and, when more pages remain, `nextPageToken`. *Example prompt:* "What Chainguard organizations and folders can I see?" ### registry_repos_list Lists the container image repositories the caller can access, with the same filtering and pagination conventions. The response key is `repos`. Each repository carries its full `activeTags` list, so pages get large quickly. A page of 50 repositories can exceed an MCP client's result limit; use a smaller `page_size` when listing a large organization. *Example prompt:* "List the repositories in my organization." This overlaps with `cg-oci`'s `list_repos` but answers a different question. `registry_repos_list` returns the platform's record of a repository — its UIDP, its parent group, its configuration. `cg-oci`'s `list_repos` returns what the registry serves you over the OCI protocol. Use this one for organization and configuration questions, and `cg-oci` for pulling content. ### registry_tags_list Lists tags. It has more filters than any other tool on this server. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name` | string | no | Filter by tag name | | `digest` | string | no | Find the tags pointing at a digest | | `updated_since` | date-time | no | Only tags updated after this timestamp | | `include_dates` | boolean | no | Include tag timestamps | | `include_epochs` | boolean | no | Include epoch information | | `include_referrers` | boolean | no | Include referring artifacts | | `include_vcs_snapshots` | boolean | no | Include version control snapshot information | | `uidp` | object | no | Scope to part of the hierarchy | `include_dates` combined with `updated_since` is how you answer freshness questions across a whole organization, rather than one image at a time. *Example prompt:* "Which tags in my org have been rebuilt since Monday?" ### api_list, api_read, and api_callers These three describe the API rather than query it, which makes them the reliable way to find out what your token can do. `api_list` with an empty `path` returns the services available to you. On a broadly capable token that is 15 services, all at `v2beta1`: the `iam`, `registry`, `vulnerabilities`, and `events` families. Pass a fully qualified service name to list that service's RPCs. Use the name exactly as the root listing returns it — `iam.v2beta1.GroupsService`, not `iam.Groups`: ``` api_list(path: "iam.v2beta1.GroupsService") → GetGroup, ListGroups ``` A path that doesn't resolve returns an empty `entries` list rather than an error, so an empty result usually means the name was abbreviated rather than that you lack access. `api_read` takes a single RPC in `Service/Rpc` form and returns its metadata: ``` api_read(path: "iam.v2beta1.GroupsService/ListGroups") → capabilities: ["groups.list"] method: GET path: /iam/v2beta1/groups request: chainguard.platform.iam.v2beta1.ListGroupsRequest response: chainguard.platform.iam.v2beta1.ListGroupsResponse ``` The `capabilities` field names the exact capability an RPC requires, which is what you need when [narrowing a token](#narrow-what-an-ai-tool-can-do) to the smallest useful set. `api_callers` works backwards from a proto message type, such as `chainguard.platform.iam.Group`, to the RPCs that use it. *Example prompt:* "What RPCs can I call on the Groups service, and what capability does each need?" ## Example usage Sample exchanges from a Claude Code session with `cg-api` connected. ```Prompt What Chainguard organizations can I see? ``` ```output Called cg-api (ctrl+o to expand) ● Your token reaches 24 groups in total. The root organization is acme-corp.com, which holds folders including platform, data-services, and a sandbox per team. Want me to page through the rest? ``` ```Prompt Which repositories in my org were updated in the last week? ``` ```output Called cg-api (ctrl+o to expand) ● 132 of the 142 repositories in your organization have at least one tag that moved in the last seven days, including gitlab-base, keycloak, python, and most of your Helm charts. The 10 that didn't change are mostly untagged test repositories, plus one expired entitlement (rabbitmq). ``` ```Prompt What parts of the API can I actually reach? ``` ```output Called cg-api (ctrl+o to expand) ● Your token reaches 15 services, all at v2beta1: eight under iam (Groups, Roles, RoleBindings, Identities, IdentityProviders, GroupInvites, AccountAssociations, ExternalGroupRoleMappings), five under registry (Repos, Tags, Images, Overlays, OverlayBindings), plus vulnerabilities.Advisories and events.Subscriptions. ``` ## Troubleshooting | Symptom | What it means | What to do | | ----- | ----- | ----- | | A tool you expected isn't listed | `cg-api` filters its tool list by your token's capabilities, and it withholds every write tool and six collection-listing tools from everyone. | Run `/mcp` to see your session's tools, and `api_list` to see the services your token reaches. For a write operation or a withheld list tool, use `chainctl` or the Console. | | `PERMISSION_DENIED` on a call | Your role does not carry the capability that RPC requires. The server enforces your existing permissions and adds nothing. | Run `api_read` on the RPC to see the capability it requires, then check your role bindings with `chainctl iam role-bindings list` or ask an organization administrator. | | A `*_get` call fails on a UIDP you typed by hand | UIDPs are slash-delimited hierarchy paths, not names, and a folder's UIDP includes its parent's. | Get the UIDP from the matching `*_list` call rather than composing it. | | A list call returns fewer results than `totalCount` | The response is one page. The default page size is 50 and the maximum is 200. | Pass the response's `nextPageToken` as `page_token` to continue. | | Results look like another team's data | The API returns everything your token reaches across every organization you belong to, not just your current one. | Scope the call with the `uidp` filter — `children_of` or `descendants_of` your own organization's UIDP. | | Server shows as not connected in `claude mcp list` | OAuth was never completed, or the token expired. Claude Code's tokens against the Chainguard issuer last about an hour and carry no refresh token. | Run `/mcp`, select **cg-api**, and authenticate again, or switch to the [`chainctl` helper](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser). | | `401 invalid token` when using the `chainctl` helper | The audience was registered as a bare hostname. MCP audiences must include the `/mcp` path. | Run `chainctl auth login --audience=https://console-api.enforce.dev/mcp` and try again. | ## Next steps - [`cg-oci`](/platform/mcp-servers/cg-oci/) — read manifests, SBOMs, and provenance from the registry itself - [`cg-apk`](/platform/mcp-servers/cg-apk/) — search the Wolfi package index - [`cg-versions`](/platform/mcp-servers/cg-versions/) — check upstream releases and end-of-life dates - [Chainguard MCP servers overview](/platform/mcp-servers/overview/) — the full set, and the `chainctl` authentication recipe - [Chainguard API documentation](/platform/api/) — the same API over plain HTTP --- ### Authentication options for chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/authentication-options.md Last Modified: October 1, 2026 Tags: chainctl There are several ways to authenticate to the Chainguard platform with `chainctl`, each suited to a different environment: * **Interactive login**: Good for everyday interactive use, but needs a browser that the current shell can launch. * **Headless login**: Also good for interactive use. It still requires a browser, but not from within the current shell or even the current device. * **Social login**: Lets you choose which default identity provider (email, Google, GitHub, or GitLab) to authenticate with. * **Assumable identities**: Designed for CI/CD and require no interaction, but they need more setup and aren't ideal outside automation. * **Pull tokens**: Ideal for pulling images and libraries, and they can be long-lived. ## Interactive browser login To authenticate to the Chainguard platform, run the following command: ```sh chainctl auth login ``` A browser window opens and prompts you to log in through your chosen OIDC flow. Select the account you want to log in as, and then you can begin managing your Chainguard resources. ## Headless device-code login If the shell can't launch a browser—for example, on a container or a remote server—use the `--headless` option to log in through a device-code flow: ```sh chainctl auth login --headless ``` `chainctl` prints a single URL with a one-time code embedded in it: ```output Visit this URL on any device with a browser to authenticate: https://issuer.enforce.dev/oauth?headless_code= ``` Open the URL in a browser on any device and complete the login. You don't type the code anywhere. If you also pass `--social-login`, the URL includes a `connection` parameter that names the provider. `chainctl` waits about 10 minutes for the browser login to finish, and then you can use Chainguard from the headless device. If time runs out, `chainctl` exits with a `timed out waiting` error. Run the command again to get a new URL. ### Headless mode persists after you use it When you pass `--headless`, `chainctl` saves headless as your default login mode and tells you so: ```output Saving "headless" as default auth mode to chainctl configuration. To disable: chainctl config unset auth.mode ``` From then on, `chainctl auth login` uses the device flow even without `--headless`, and so does any command that logs you in again after your token expires. Instead of opening a browser, the command prints a URL and waits for you to complete the login. If you miss the URL, the command can look like it has stopped responding. To return to browser login, remove the setting: ```sh chainctl config unset auth.mode ``` ## Select an identity provider with --social-login To authenticate with a specific default identity provider, pass the `--social-login` flag. The value must be one of `email`, `google`, `github`, or `gitlab`: ```sh chainctl auth login --social-login github ``` You can also set a default provider in your configuration with the `default.social-login` setting. See [Manage your chainctl configuration](/platform/chainctl-usage/manage-chainctl-config/). > Note: If your organization has configured a custom identity provider, authenticate with `--org-name` or `--identity-provider` instead. See [custom identity providers](/platform/administration/custom-idps/custom-idps/). Which provider you authenticate with also determines who manages your multi-factor authentication. To move it to a new device, see [Change or reset your MFA device](/get-started/mfa-devices/). ## Assumable identities for CI/CD Assumable identities let automation tools like GitHub Actions or AWS Lambda connect to and manage Chainguard resources without interactive login. See the [guide on assumable identities](/platform/administration/assumable-ids/assumable-ids/). ## Pull tokens Pull tokens are ideal for pulling images and libraries and can be long-lived. You can create them in the Chainguard Console or with `chainctl`. See [authenticating to the Chainguard registry](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token). A pull token is a pair of values that most tools consume as a username and a password. `chainctl` labels that pair differently in each output format, so refer to [pull token output formats and credential names](/platform/chainctl-usage/pull-token-output/) to map the labels to each other. ## Troubleshoot chainctl login The following sections cover the most common reasons `chainctl auth login` fails or stalls. After you apply a fix, confirm that you're logged in: ```sh chainctl auth status ``` ### Login prints a URL instead of opening a browser If `chainctl auth login` prints a URL and waits, your configuration is probably set to headless mode. Check the `auth` section of your configuration: ```sh chainctl config view ``` If it shows `mode: headless`, either complete the login at the printed URL or [return to browser login](#headless-mode-persists-after-you-use-it). ### Login hangs or fails behind a TLS-inspecting proxy Corporate proxies that decrypt and inspect TLS traffic, such as Netskope or Zscaler, can break `chainctl` login. The login might wait indefinitely, or fail with an error such as `context deadline exceeded`, `missing selected ALPN property`, or `timed out validating the new token`. Run the command with `--log-level=debug` to see which request fails. Ask your network administrator to exempt these hosts from TLS inspection: * `issuer.enforce.dev` and `console-api.enforce.dev`, which every login needs * `auth.chainguard.dev` and `chainguard.us.auth0.com`, which social login also needs For the full list of hosts that Chainguard tools use, see [Network requirements](/chainguard/containers/registry/network-requirements/). If the proxy mishandles HTTP/2, downgrade the login's STS requests to HTTP/1.x: ```sh chainctl auth login --sts-http1-downgrade ``` This flag affects only STS requests. Other `chainctl` commands still need encrypted HTTP/2 through the proxy, so treat the flag as a workaround until your network administrator adds the exemption. ### Organization not found or not verified If login fails with an error that the organization is "not found, is not verified, or does not have an IDP configured," `chainctl` couldn't match the organization name you entered. Enter your organization's verified domain, such as `example.com`, rather than its display name. To skip the prompt, pass the domain with `--org-name`: ```sh chainctl auth login --org-name=example.com ``` If your organization uses a custom identity provider, you can pass its ID with `--identity-provider` instead. See [custom identity providers](/platform/administration/custom-idps/custom-idps/). --- ### How to provision users into Chainguard from Okta with SCIM URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/scim-provisioning/okta-scim.md Last Modified: September 28, 2026 Tags: Procedural The Chainguard platform supports SCIM 2.0 provisioning from Okta. With SCIM enabled, Okta creates, updates, and deactivates Chainguard users as you assign and unassign them, and Chainguard links each provisioned user to their single sign-on (SSO) login automatically. This guide covers the Okta-specific setup. For provisioning behavior, token lifecycle, and limits, refer to [Provision Chainguard users with SCIM](/platform/administration/custom-idps/scim-provisioning/scim-overview/). ## Prerequisites To complete this guide, you need the following: * Okta already configured for login to Chainguard through an OIDC app integration. If you haven't set that up yet, refer to our guide on [How to integrate Okta SSO with Chainguard](/platform/administration/custom-idps/idp-providers/okta/). * The [prerequisites for SCIM provisioning](/platform/administration/custom-idps/scim-provisioning/scim-overview/#prerequisites): an IAM role that can manage identity providers, two owners with directly assigned role bindings, and an authenticated `chainctl`. The rest of this guide refers to your identity provider by its UIDP, stored in the `IDENTITY_PROVIDER` environment variable: ```sh export IDENTITY_PROVIDER=$(chainctl iam identity-providers list -o json | jq -r '.items[0].id') ``` Organizations generally have one identity provider, so the list usually holds a single entry and the `.items[0].id` filter selects it. If the list holds more than one — because you belong to several organizations, or your organization has several providers — run `chainctl iam identity-providers list` on its own and set `IDENTITY_PROVIDER` to the UIDP of the provider you are configuring. ## Step 1: Enable provisioning and generate the SCIM token First, generate the bearer token Okta authenticates with: ```sh chainctl iam identity-providers scim token generate $IDENTITY_PROVIDER ``` The `token generate` command prints the token to standard output and the SCIM endpoint URL to standard error. Chainguard shows the token exactly once, so keep both values for Step 2. Our guide [Provision Chainguard users with SCIM](/platform/administration/custom-idps/scim-provisioning/scim-overview/#the-scim-token) outlines lifetime options and rotation. Next, enable SCIM for your identity provider: ```sh chainctl iam identity-providers scim enable $IDENTITY_PROVIDER ``` Generate the token first: `scim enable` fails with `etag: etag is required` if the identity provider has no token issued yet. If it instead fails with a message about owner-tier identities, your organization doesn't yet have two directly assigned owners; refer to the [prerequisites for SCIM provisioning](/platform/administration/custom-idps/scim-provisioning/scim-overview/#prerequisites). ## Step 2: Create the Okta provisioning app Okta hosts SCIM provisioning on an app integration, but you cannot add SCIM to the OIDC app integration you use for login. This means you must create a second app integration that exists only to carry provisioning. In the Okta Admin Console, navigate to **Applications and Resources** > **Applications**, click **Create App Integration**, and choose **SAML 2.0**. Complete the wizard with placeholder values; Chainguard does not consume the SAML assertion, and sign-in stays on your existing OIDC app. On the new app's **General** tab, set **Provisioning** to **SCIM**. Then open the app's **Provisioning** tab, select **Integration**, and click **Edit** to configure the SCIM connection: * **SCIM connector base URL**: the endpoint printed in Step 1. * **Unique identifier field for users**: `userName`. * **Supported provisioning actions**: check **Push New Users** and **Push Profile Updates**. Leave **Push Groups** unchecked; Chainguard's SCIM endpoint accepts user provisioning only. * **Authentication Mode**: select **HTTP Header**, then paste the bare `cgscim_...` token into the **Bearer** field. Do not add a `Bearer` prefix — Okta supplies the scheme itself, and a prefixed value fails authentication. Use **Test API Credentials** to confirm the URL and token, then save. ### Error authenticating: No results for users returned Okta's connection test reads the SCIM endpoint and expects to find a user it recognizes there. A newly enabled Chainguard endpoint has no users yet, so on a first-time setup the test fails with this message. Despite the wording, authentication succeeded; the missing user is what blocks the save. A workaround is to create the first user directly, then run the test again. Replace `` and `` with the bearer token and endpoint URL for your identity provider, and `` and `` with the details of a real user you intend to provision. ```sh curl -X POST \ -H "Authorization: Bearer " \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "", "externalId": "", "active": true }' \ "/Users" ``` Set `externalId` to the user's Okta ID — the `00u...` value in the page URL when you open that user under **Directory** > **People**. Okta's test rejects an arbitrary value even though the endpoint returns the user, so this is not a field you can fill with a placeholder. `externalId` is also what Chainguard matches against at login, and a wrong value leaves the user unlinked with no error, so seed a real user you intend to provision. ## Step 3: Enable provisioning actions With the integration saved, select **To App** in the **Provisioning** tab, click **Edit**, and enable **Create Users**, **Update User Attributes**, and **Deactivate Users**. These options appear only after you save the connection, and only for the actions you checked under **Supported provisioning actions** in Step 2. ## Step 4: Assign users and verify In Okta, assign a user to the provisioning app integration you created in Step 2. Okta provisions them immediately. Have that user log in to Chainguard through Okta and confirm they have access. A provisioned user connects to their login by matching the SCIM `externalId` against the login token's subject; with Okta, both default to the Okta user ID, so they align without configuration. To confirm the rest of the lifecycle, deactivate that user in Okta or remove their assignment, and confirm that Chainguard refuses their next login. Then reactivate them and confirm that they can log in again. ## Troubleshooting ### A user logged in but wasn't linked A provisioning mismatch does not produce an error; the login proceeds as a normal, unlinked user. Confirm that you assigned the user to the provisioning app and that Okta provisioned them (check the app's assignment and Okta's provisioning logs), and that the app sends the default Okta user ID as `externalId`. ### Test API Credentials fails If the message is "No results for users returned," refer to [Error authenticating: No results for users returned](#error-authenticating-no-results-for-users-returned) in Step 2. Otherwise, confirm the base URL is the endpoint printed by `token generate` and that the **Bearer** field holds the correct `cgscim_...` token. The URL and token must come from the same `token generate` run. ### Provisioning requests receive HTTP `429` Chainguard rate limits provisioning writes; Okta retries on its own schedule, so bulk assignments complete without intervention. For more information, refer to the [Limits section](/platform/administration/custom-idps/scim-provisioning/scim-overview/#limits). ## Related resources * [Provision Chainguard users with SCIM](/platform/administration/custom-idps/scim-provisioning/scim-overview/) * [Grant Chainguard roles from identity provider groups](/platform/administration/custom-idps/grant-roles-from-groups/) * [How to integrate Okta SSO with Chainguard](/platform/administration/custom-idps/idp-providers/okta/) * [Overview of the Chainguard IAM model](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/) --- ### Using Chainguard with Kiro URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/integrations/kiro.md Last Modified: September 28, 2026 [Kiro](https://kiro.dev/) helps developers move quickly, but speed alone does not reduce supply chain risk. Every `docker pull`, `pip install`, `npm install`, or Maven dependency resolution an AI assistant initiates is still a trust decision against a software source. The Chainguard Power for Kiro brings Chainguard context directly into that workflow. Kiro remains the place where developers and agents plan, write, and review code. Chainguard provides the trusted artifacts those workflows depend on: hardened container images, malware-resistant language packages, and related supply chain guidance. This page explains how to install the Chainguard Power for Kiro and use it to start migrating projects to Chainguard. ## Prerequisites Before you begin, you'll need: * A [Kiro](https://kiro.dev/) account, with [Kiro IDE](https://kiro.dev/downloads/) downloaded. * A [Chainguard account](https://console.chainguard.dev/) and organization in domain format (for example, `acme-corp.com`). * [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) installed and authenticated * Access to the Chainguard products you plan to use * [Chainguard Containers](/chainguard/containers/overview/) for container image migration * [Chainguard Libraries](/chainguard/libraries/introduction/overview/) for Java, JavaScript, or Python dependency migration. Learn how to create an entitlement in the [Libraries access docs](/chainguard/libraries/introduction/access/#manage-library-entitlements). The Chainguard Power uses your existing `chainctl` session for image and package lookups in Kiro. If you want Kiro to help reconfigure a project to use Chainguard Libraries, make sure your organization also has access to the relevant Libraries ecosystem. Note: Chainguard MCP authentication depends on the Chainguard authentication flows available to your organization. If your users do not sign in through one of the currently supported social identity providers (Google, GitHub, or GitLab), then MCP-based IDE workflows may not work as expected. If your organization uses a different IdP, confirm the authentication path before rolling out the Kiro integration broadly. ## Install the Chainguard Power The Kiro integration is delivered through the Kiro Powers panel. To install it: 1. Open Kiro IDE and navigate to the **Powers** panel in the sidebar. 1. Scroll down to the Chainguard Power. 1. Select the Chainguard Power and click **Install**. After installation, Kiro automatically registers the Chainguard MCP servers included with the power. You do not need to manually edit JSON configuration files or perform separate CLI setup for the power itself. ## Using the Chainguard Power Kiro can use Chainguard context to help with tasks such as: * Migrating Dockerfiles to [Chainguard Containers](https://images.chainguard.dev/) * Reconfiguring Java, JavaScript, and Python projects to use [Chainguard Libraries](/chainguard/libraries/introduction/overview/) * Looking up Chainguard image tags * Finding Wolfi package equivalents for packages currently installed with `apt` or similar package managers * Guiding Chainguard platform tasks such as policy, cluster, and IAM workflows The power includes the following MCP servers: * [`cg-api`](/platform/mcp-servers/cg-api/) for Chainguard platform and organization workflows * [`cg-apk`](/platform/mcp-servers/cg-apk/) for Wolfi package discovery * [`cg-oci`](/platform/mcp-servers/cg-oci/) for container image discovery and tag lookup * [`cg-versions`](/platform/mcp-servers/cg-versions/) for version and upgrade-path lookup You can also connect these servers to any MCP-compatible client without installing the Kiro power. Refer to the [Chainguard MCP servers overview](/platform/mcp-servers/overview/) for each server's endpoint, tools, and authentication options. ### Migrate a project to use Chainguard After installing the power, open your project in Kiro and describe the migration you want. For example: ```Prompt Can you help me migrate this project to use Chainguard container images and libraries? ``` Kiro can then inspect the relevant files in your project and suggest changes such as: * Replacing public base images with Chainguard container images * Translating OS packages to Wolfi equivalents * Updating package manager configuration to use Chainguard Libraries * Explaining the changes it made so you can review them before building or deploying As with any AI-assisted code change, review the output before you run a build or ship it to production. ### Example: Migrate a Dockerfile One of the most direct ways to get started is to paste a Dockerfile into Kiro and ask for help migrating it. For example: ```dockerfile FROM python:3.11-slim WORKDIR /app RUN apt-get update && apt-get install -y curl git libpq-dev COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"] ``` Prompt Kiro with a request such as: ```Prompt Can you help me migrate this Dockerfile to use Chainguard Containers? ``` Kiro can then look up a Chainguard replacement image, translate system package installation to the appropriate Wolfi packages, rewrite the Dockerfile, and explain any important tradeoffs. A resulting Dockerfile might look similar to this: ```dockerfile FROM cgr.dev/chainguard/python:3.11-dev WORKDIR /app RUN apk add --no-cache curl git libpq-dev COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["python", "app.py"] ``` In this example, Kiro may also point out that: * a `-dev` image is useful when build-time tooling is required * a production runtime image may use a different tag * Chainguard images run as non-root by default To browse available images and tags, refer to the [Chainguard Containers directory](https://images.chainguard.dev/). ### Use Chainguard Libraries with Kiro The Chainguard Power can also help migrate language dependencies to Chainguard Libraries. For example, you can ask Kiro to help with prompts such as: * `Help me configure npm to use Chainguard Libraries` * `How do I set up my Maven project to use Chainguard Libraries?` * `Help me migrate this Python project to Chainguard Libraries` Kiro can help update build files and package manager configuration, but your build environment still needs valid Chainguard credentials for package installation. For ecosystem-specific instructions, see: * [Chainguard Libraries for JavaScript](/chainguard/libraries/javascript/overview/) * [Chainguard Libraries for Python](/chainguard/libraries/python/overview/) * [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) ## Learn more * [Chainguard Images directory](https://images.chainguard.dev/) * [Chainguard Libraries](https://www.chainguard.dev/libraries) * [Kiro Powers directory](https://kiro.dev/powers/) --- ### Chainguard API v2 tutorial URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/api/api-v2-tutorial.md Last Modified: September 28, 2026 Tags: Chainguard Console, Procedural The v2 API is now Generally Available (GA) and introduces cursor-based pagination, server-side ordering, consistent resource patterns, and structured error responses across all endpoints. This guide walks through the v2 API using real `curl` commands. If you're migrating an existing v1 or beta (`v2beta1`) integration, see [Migrating from API v1 to API v2](/platform/api/api-v2-migration/) instead. > **Note:** The example output in this guide was captured from a development environment. Your organization's resource names, UIDs, timestamps, and counts will differ. The response structure and field names are the same across all environments. ## What's the same - **Authentication** — same OIDC token model as v1 - **Authorization** — same identity-based access control - **Scoping** — same `uidp.descendants_of` / `uidp.children_of` hierarchy filters ## What's new in v2 - **Cursor-based pagination** with `page_size`, `page_token`, `total_count` - **Server-side ordering** with `order_by` (ascending/descending on any sortable field) - **Random-access pagination** with `skip` for UI page jumping - **Structured errors** with typed detail payloads (AIP-193) - **Consistent resource patterns** — every resource has `uid`, `createTime`, `updateTime` - **Hydrated references** — role binding responses include full identity, group, and role objects - **FieldMask updates** — partial updates via `updateMask` instead of sending the full resource ## Available endpoints | Domain | Resources | Operations | | -------- | ----------- | ------------ | | **IAM** | Groups, Identities, Roles, RoleBindings, IdentityProviders, AccountAssociations, GroupInvites, ExternalGroupRoleMappings, Terms | List, Get, Create, Update, Delete (Terms: accept + list acceptances) | | **Registry** | Repos (incl. readme), Tags, Images | Repos: List, Get, Create, Update, Delete; Tags: List, Get, Create, Delete; Images: architectures & size (read-only) | | **Vulnerabilities** | Advisories | List, Get | | **Libraries** | Artifacts | List artifacts, list versions, artifact count (read-only) | | **Advisory** | Security advisory documents, vulnerability metadata, resolved-vulnerability reports | List (read-only) | | **Events** | Subscriptions | List, Get, Create, Delete | | **Ping** | Ping (connectivity check) | Ping | All endpoints live under a versioned path per domain: `/iam/v2/`, `/registry/v2/`, `/vulnerabilities/v2/`, `/libraries/v2/`, `/advisory/v2/`, `/events/v2/`, and `/ping/v2/`. The worked examples in this guide focus on IAM, Registry, and Vulnerabilities. The other domains follow the same request and response conventions. For an IAM resource used end to end in an administrative task, refer to [Automate IdP group mappings with the API](/platform/administration/custom-idps/grant-roles-from-groups/#automate-mappings-with-the-api), which maps identity provider groups to Chainguard roles in bulk with the `ExternalGroupRoleMappings` endpoints. ## Prerequisites Get an API token and set your organization ID: ```shell export TOKEN=$(chainctl auth token) export API=https://console-api.enforce.dev # ORG_ID is the UID of your root organization group export ORG_ID=YOUR_ORG_ID ``` The following examples use `$TOKEN`, `$API`, and `$ORG_ID` for brevity. ## Operational notes Keep the following in mind as you work through this guide. - **Page tokens expire after 3 days** ([AIP-158](https://google.aip.dev/158)). If a token expires, the query restarts from the beginning — no error is returned. - **gRPC** — all endpoints are also available via gRPC at the same host. Proto definitions are at `chainguard.dev/sdk/proto/chainguard/platform/`, and the Go SDK clients live under `chainguard.dev/sdk/proto/chainguard/platform/clients/v2`. --- ## 1. Your first v2 request List the first 3 repos in your organization: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . ``` ```json { "repos": [ { "uid": "d9e2f1a0.../06626efd8c6b3fb7", "name": "nginx", "createTime": "2026-01-28T12:54:21.189Z", "updateTime": "2026-01-28T12:54:21.189Z" }, { "uid": "d9e2f1a0.../0ed18f0f929f4c60", "name": "python", "createTime": "2026-01-23T14:54:42.774Z", "updateTime": "2026-01-23T14:54:42.774Z" }, { "uid": "d9e2f1a0.../12b4208b23740c37", "name": "static", "createTime": "2026-01-23T14:54:39.021Z", "updateTime": "2026-01-23T14:54:39.021Z" } ], "nextPageToken": "CqQBV3lK...", "totalCount": "12", "skipped": 0 } ``` Every v2 response follows the same shape: - **`uid`** — unique resource identifier (replaces `id` in v1) - **`createTime` / `updateTime`** — timestamps on every resource - **`nextPageToken`** — cursor for the next page (empty when no more results) - **`totalCount`** — total matching results across all pages ### Get a single resource New in v2: fetch a resource directly by UID. In v1, this required a List call with an ID filter. ```shell # REPO_UID is a uid value from the List repos response above export REPO_UID=YOUR_REPO_UID curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos/$REPO_UID" | jq '{uid, name, createTime}' ``` ```json { "uid": "d9e2f1a0.../06626efd8c6b3fb7", "name": "nginx", "createTime": "2026-01-28T12:54:21.189Z" } ``` Use direct UID lookups when you already know the resource identifier — they are faster than a List call with an ID filter. ### Filter by name Find a specific repo without knowing its UID: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&name=nginx" \ | jq '[.repos[] | {uid, name, createTime}]' ``` ```json [ { "uid": "d9e2f1a0.../06626efd8c6b3fb7", "name": "nginx", "createTime": "2026-01-28T12:54:21.189Z" } ] ``` Name filtering returns exact matches. Combine with `uidp.descendants_of` to scope the search to your organization. --- ## 2. Set up access for a new team A common workflow: create a CI identity at your organization and bind a role to it. > **Note:** Create identities under your root organization group (`$ORG_ID`) so they can reach the resources that live there, including your registry. An identity created under a subgroup is scoped to that subgroup and won't see your registry — so it can't pull images. ### Create an identity ```shell curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/identities/$ORG_ID" \ -d '{ "name": "ci-bot", "description": "CI/CD pipeline identity", "claimMatch": { "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:my-org@123456/my-repo@654321:ref:refs/heads/main" } }' | jq . ``` ```json { "uid": "d9e2f1a0.../f462d354ca32ca9f", "name": "ci-bot", "description": "CI/CD pipeline identity", "lastSeenTime": "2026-03-27T13:55:00.783Z", "createTime": "2026-03-27T13:55:00.785Z", "updateTime": "2026-03-27T13:55:00.785Z", "claimMatch": { "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:my-org@123456/my-repo@654321:ref:refs/heads/main" } } ```

Note: The subject shown here uses GitHub’s immutable format, which embeds the numeric owner ID (123456) and repository ID (654321). Match the exact subject your repository’s token carries. For how to find these IDs and when the format applies, see Create an Assumable Identity for a GitHub Actions Workflow.

Note the identity `uid` in the response — you will use it in the next step when binding a role. ### Bind a role First, find the viewer role: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/iam/v2/roles" | jq '.roles[] | select(.name == "viewer") | {uid, name, description}' ``` ```json { "uid": "63921b2c44617e3f2603851537be0123af4a57d7", "name": "viewer", "description": "Viewer Role (built-in)" } ``` Then bind it: ```shell # ROLE_UID is the uid of the viewer role, retrieved above ROLE_UID="63921b2c44617e3f2603851537be0123af4a57d7" # IDENTITY_UID is the uid value returned in the Create an identity response above export IDENTITY_UID=YOUR_IDENTITY_UID curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/roleBindings/$ORG_ID" \ -d "{\"identityUid\": \"$IDENTITY_UID\", \"roleUid\": \"$ROLE_UID\"}" | jq . ``` ```json { "uid": "d9e2f1a0.../9b822036a7075d75", "identity": { "uid": "d9e2f1a0.../f462d354ca32ca9f", "name": "ci-bot", "description": "CI/CD pipeline identity", "subject": "repo:my-org@123456/my-repo@654321:ref:refs/heads/main", "issuer": "https://token.actions.githubusercontent.com" }, "group": { "uid": "d9e2f1a0...", "name": "my-org", "description": "Root organization group" }, "role": { "uid": "63921b2c44617e3f2603851537be0123af4a57d7", "name": "viewer", "description": "Viewer Role (built-in)" }, "createTime": "2026-03-27T13:55:01.475Z" } ``` The response includes fully hydrated identity, group, and role objects — no need for follow-up lookups. --- ## 3. Pagination Every List endpoint supports cursor-based pagination with consistent parameters. ### Basic pagination ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=5" \ | jq '{totalCount, repos: [.repos[].name], nextPageToken: .nextPageToken[:20]}' ``` ```json { "totalCount": "12", "repos": ["apko", "busybox", "go", "jdk", "nginx"], "nextPageToken": "CqQBV3lKbE16Z3dPVE0y" } ``` Follow the cursor for the next page: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=5&page_token=CqQBV3lK..." \ | jq '{repos: [.repos[].name]}' ``` ```json { "repos": ["node", "php", "postgres", "python", "redis"] } ``` When `nextPageToken` is absent from the response, you have reached the last page. ### Server-side ordering Sort by name: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name" \ | jq '[.repos[].name]' ``` ```json ["apko", "busybox", "go", "jdk", "nginx"] ``` Reverse the order: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name%20desc" \ | jq '[.repos[].name]' ``` ```json ["static", "ruby", "redis", "python", "postgres"] ``` Sort by creation time (newest first): ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=5&order_by=created_at%20desc" \ | jq '[.repos[] | {name, createTime}]' ``` ```json [ {"name": "redis", "createTime": "2026-02-14T09:11:05.488Z"}, {"name": "postgres", "createTime": "2026-02-10T17:02:05.135Z"}, {"name": "node", "createTime": "2026-02-03T11:48:04.814Z"}, {"name": "nginx", "createTime": "2026-01-28T12:54:21.189Z"}, {"name": "python", "createTime": "2026-01-23T14:54:42.774Z"} ] ``` Pagination and ordering combine: pages maintain sort order across cursors. ### Random-access with `skip` Jump directly to page 3 (skip the first 10 results): ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=5&order_by=name&skip=10" \ | jq '{skipped: .skipped, repos: [.repos[].name]}' ``` ```json { "skipped": 10, "repos": ["ruby", "static"] } ``` The `skipped` field in the response confirms how many results were skipped, useful for building UI page controls. ### Pagination parameters | Parameter | Description | | ----------- | ------------- | | `page_size` | Number of results per page (default 50, max 200) | | `page_token` | Opaque cursor from previous response's `nextPageToken` | | `order_by` | Sort field and direction, for example `name` or `created_at desc` | | `skip` | Number of results to skip (for random-access / UI page jumping) | --- ## 4. Tags and end-of-life Tags live under a repo. List them with the same patterns you used for repos, scoped to a single repo with `uidp.children_of`. ### List tags in a repo Each tag carries its `digest` and a `deprecated` flag: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/tags?uidp.children_of=$REPO_UID&page_size=3" \ | jq '[.tags[] | {name, digest, deprecated, updateTime}]' ``` ```json [ {"name": "latest", "digest": "sha256:6b3f...", "deprecated": false, "updateTime": "2026-07-14T09:12:44.501Z"}, {"name": "1.27", "digest": "sha256:8c1a...", "deprecated": false, "updateTime": "2026-07-14T09:12:44.502Z"}, {"name": "1.26", "digest": "sha256:a90d...", "deprecated": true, "updateTime": "2026-05-02T18:30:10.114Z"} ] ``` ### Check for deprecated tags In v1, a dedicated `ListEolTags` call surfaced end-of-life tags. v2 has no separate end-of-life endpoint or server-side filter. Instead, each tag carries a `deprecated` boolean, which you filter on client-side: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/tags?uidp.children_of=$REPO_UID&page_size=200" \ | jq '[.tags[] | select(.deprecated) | .name]' ``` > **Note:** Client-side filtering on `deprecated` is the intended approach in v2. A v2 equivalent of `ListEolTags` is on the backlog with no committed date; until it ships, the v1 `ListEolTags` endpoint remains available. --- ## 5. Querying vulnerabilities The Vulnerabilities domain exposes advisory data. In v2 it covers advisories with List and Get. ### List advisories Advisories are scoped and paginated like every other List endpoint, with extra filters such as `artifactNames` and `advisoryIds`: ```shell curl -s -H "Authorization: Bearer $TOKEN" \ "$API/vulnerabilities/v2/advisories?uidp.descendants_of=$ORG_ID&page_size=3" \ | jq '[.advisories[] | {uid, advisoryId, artifactName, updateTime}]' ``` ```json [ {"uid": "d9e2f1a0.../3b1c", "advisoryId": "CGA-abcd-1234-wxyz", "artifactName": "nginx", "updateTime": "2026-07-18T21:04:11.220Z"}, {"uid": "d9e2f1a0.../7f2a", "advisoryId": "CGA-efgh-5678-stuv", "artifactName": "python", "updateTime": "2026-07-18T21:04:11.221Z"}, {"uid": "d9e2f1a0.../a1b2", "advisoryId": "CGA-ijkl-9012-mnop", "artifactName": "openssl", "updateTime": "2026-07-18T21:04:11.222Z"} ] ``` Fetch a single advisory by UID at `/vulnerabilities/v2/advisories/{uid}`. ### Vulnerability reports The v1 `GetVulnReport` and `ListVulnCountReports` calls have no v2 equivalent today, and advisories are not a replacement — they serve a different, advisory-feed purpose. The `ListResolvedVulnsReports` endpoint under `/advisory/v2/` is also advisory-feed oriented, not a vulnerability-report replacement. If your integration reads vulnerability reports, continue using the v1 endpoints, which remain fully supported. v2 coverage arrives with the Vulnerabilities domain's migration. --- ## 6. Structured errors API v2 returns structured error responses with machine-parseable codes and details. ### Validation error ```shell curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/identities/$ORG_ID" \ -d '{}' | jq . ``` ```json { "code": 3, "message": "Invalid argument: name: name must match \"^[a-z0-9 ._-]{1,}$\"", "details": [ { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "INVALID_ARGUMENT", "domain": "iam.chainguard.dev" }, { "@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [ { "field": "name", "description": "name must match \"^[a-z0-9 ._-]{1,}$\"" } ] } ] } ``` The `fieldViolations` array identifies exactly which fields failed validation and why. ### Precondition failure Attempting to delete a group that still contains child resources returns a precondition failure: ```json { "code": 9, "message": "Precondition failed: cannot delete group with child repos", "details": [ { "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "FAILED_PRECONDITION", "domain": "iam.chainguard.dev" }, { "@type": "type.googleapis.com/google.rpc.PreconditionFailure", "violations": [ { "type": "RESOURCE_NOT_EMPTY", "description": "cannot delete group with child repos" } ] } ] } ``` Error responses follow [Google AIP-193](https://google.aip.dev/193) with typed detail payloads you can switch on programmatically. --- ## 7. Partial updates with FieldMask Update specific fields without sending the full resource. Only the fields listed in `updateMask` are changed: ```shell curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/registry/v2/repos/$REPO_UID" \ -d '{ "description": "Updated description — only this field changes" }' | jq '{uid, name, description}' ``` ```json { "uid": "d9e2f1a0.../06626efd8c6b3fb7", "name": "nginx", "description": "Updated description — only this field changes" } ``` The `name` field was not in the request body, so it's unchanged. In v1, updates required sending the entire resource — any omitted field would be reset to its zero value. To be explicit about which fields to update, pass `updateMask`: ```shell curl -s -X PATCH -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/registry/v2/repos/$REPO_UID?updateMask=description" \ -d '{ "description": "Only this field is updated", "name": "this-is-ignored" }' | jq '{uid, name, description}' ``` ```json { "uid": "d9e2f1a0.../06626efd8c6b3fb7", "name": "nginx", "description": "Only this field is updated" } ``` The `name` in the body is ignored because `updateMask` only includes `description`. This PATCH-with-field-mask pattern applies to every updatable resource. --- ## Migration from v1 v2 is additive — v1 endpoints remain available during a transition period, so you can migrate at your own pace. For the full field-by-field mapping and migration timeline, see [Migrating from API v1 to API v2](/platform/api/api-v2-migration/). --- ## Cleanup Delete resources you created during this walkthrough: ```shell # Delete in reverse order: role binding, then identity # BINDING_UID is the uid value returned in the Bind a role response above curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2/roleBindings/$BINDING_UID" curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$API/iam/v2/identities/$IDENTITY_UID" ``` Each DELETE returns an empty response body on success. --- ### Built-in roles and capabilities reference URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference.md Last Modified: October 13, 2026 Tags: IAM, Reference, Product Chainguard provides customers with a set of built-in roles as part of its Identity and Access Management (IAM) system. These roles have different permissions and capabilities that allow them to serve specialized purposes, from general administrative access to access for specific resources like registries, APK packages, and programming language libraries. This reference provides an overview of all Chainguard IAM capabilities and shows which built-in roles include each capability. Each capability represents a specific permission or action that can be performed within the Chainguard platform. For more information on roles and role-bindings within Chainguard's IAM model, please refer to our [Overview of roles and role-bindings](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). ## Built-in roles summary This guide outlines the built-in Chainguard IAM roles available to most customer organizations. You can find more info about specific roles in your organization with the following `chainctl` command: ```shell chainctl iam roles list ``` Every role grants actions on at least one Chainguard resource. Most actions are `create`, `list`, `update`, or `delete`, and a few resources use others, such as `get`, `accept`, or `call`. For example, the `owner` role can create, delete, list, and update custom roles within Chainguard, while the `viewer` role can only list them. This guide outlines the following built-in roles provided by Chainguard: * **Administrative Roles:** * `owner`: Full administrative access with all capabilities * `editor`: `viewer` access plus creating, updating, and deleting container repositories, editing Custom Assembly overlays, drafting library build requests, and managing event subscriptions * `viewer`: Read-only access across all resources, **can** pull images, and can run Guardener sessions * `limited_owner`: Owner of a Starter organization, with `viewer` access plus pull tokens, invites limited to the organization's email domain, removing members, billing, and turning on Chainguard Checks and Chainguard Workspaces * `console_viewer`: Read-only access across all resources and **cannot** pull images or APK packages, run Guardener sessions, or view legal terms * **Registry and Container Roles:** * `registry.pull`: Container image access * `registry.pull_token_creator`: Chainguard registry token management with additional repository capabilities * `registry.push`: `registry.pull` access plus pushing images, and creating, updating, and deleting repositories and tags * `registry.editor`: `registry.pull` access plus creating, updating, and deleting repositories, and editing Custom Assembly overlays * `apk.pull`: Access to the organization's APK packages, including the private APK repository * `apk.push`: `apk.pull` access plus uploading and deleting APK packages * **Library Roles:** * `libraries.java.pull`: Java library access * `libraries.java.pull_token_creator`: Java token management * `libraries.java.push`: `libraries.java.pull` access plus Java library uploads * `libraries.python.pull`: Python library access * `libraries.python.pull_token_creator`: Python library token management * `libraries.python.push`: `libraries.python.pull` access plus Python library uploads * `libraries.javascript.pull`: JavaScript library access * `libraries.javascript.pull_token_creator`: JavaScript library token management * `libraries.javascript.push`: `libraries.javascript.pull` access plus JavaScript library uploads * **Guardener Roles**: * `guardener.user`: Minimum role for running Guardener sessions, including Guardener conversion, terms lookup, and registry pull access * `guardener.admin`: Extends `guardener.user` with permission to accept the legal terms required before Guardener sessions can start * **Checks and Workspaces Roles**: * `sandbox.user`: Start, view, and cancel Chainguard Checks runs, and open Chainguard Workspaces sessions * `sandbox.identity.user`: Use shared identities in Chainguard Workspaces sessions * **MCP Roles**: * `mcp.tool_user`: Call tools on Chainguard MCP servers * **Actions Roles**: * `actions.viewer`: View the Chainguard Actions catalog The administrative roles are useful for user profiles that require broad, but clearly defined capabilities. The registry, container, and library roles have limited permissions, allowing them to manage only one specific Chainguard resource. These specialized, resource-specific roles grant minimal required access. For example, the `apk.pull` role grants `list` access for APK packages and groups, and `get` access to APK package content. This means identities with this role can pull the organization's APK packages and retrieve information about the organization, but won't have general access to the organization's [Chainguard registry](/chainguard/containers/registry/overview/) resources. ## Chainguard role capabilities The following table maps Chainguard resources to the built-in roles that have permissions for them. Each row represents a specific resource type (like `apk`, `repo`, `identity`, etc.), describes its purpose, and lists which actions each built-in role has on that resource.
| Resource | Purpose | Roles with access to this resource | |------------|---------|---------------------------| | `account_associations` | Link cloud provider accounts to organization |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `actions` | View the [Chainguard Actions](/chainguard/actions/overview/) catalog |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `actions.viewer` (list)
| | `advisories` | View [security advisories](/chainguard/containers/security-and-compliance/security-advisories/) for the packages in Chainguard images |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `apk` | Manage APK packages in the registry |
  • `owner` (create, delete, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull_token_creator` (list)
  • `apk.pull` (list)
  • `apk.push` (create, delete, list)
| | `apk.blobs` | Download APK package binary content |
  • `owner` (get)
  • `editor` (get)
  • `viewer` (get)
  • `limited_owner` (get)
  • `registry.pull_token_creator` (get)
  • `apk.pull` (get)
  • `apk.push` (get)
| | `billing` | Manage the organization's billing and payment method |
  • `owner` (manage)
  • `limited_owner` (manage)
| | `build_report` | Access detailed build and scan reports for images and packages |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `group_invites` | Send and manage invitations to join Chainguard organization |
  • `owner` (create, delete, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (create, delete, list)
  • `console_viewer` (list)
| | `groups` | Manage organization and hierarchical structures |
  • `owner` (create, delete, list, rename, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list, rename)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (create, list)
  • `registry.editor` (list)
  • `apk.pull` (list)
  • `apk.push` (list)
  • `libraries.java.pull_token_creator` (list)
  • `libraries.python.pull_token_creator` (list)
  • `libraries.javascript.pull_token_creator` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `guardener.actions` | Have Guardener migrate a repository's GitHub Actions to Chainguard Actions by opening a pull request |
  • `owner` (migrate)
  • `editor` (migrate)
  • `viewer` (migrate)
  • `limited_owner` (migrate)
  • `guardener.user` (migrate)
  • `guardener.admin` (migrate)
| | `guardener.association` | View and manage the GitHub organizations connected through [the Chainguard App](/chainguard/guardener/github/app-connections/) |
  • `owner` (list, manage)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list, manage)
  • `guardener.user` (list)
  • `guardener.admin` (list, manage)
| | `guardener.dfc` | Convert Dockerfiles to use Chainguard images with [Guardener](/chainguard/guardener/dockerfile-migration/) |
  • `owner` (convert)
  • `editor` (convert)
  • `viewer` (convert)
  • `limited_owner` (convert)
  • `guardener.user` (convert)
  • `guardener.admin` (convert)
| | `guardener.entitlement` | View the organization's Guardener settings, such as which repositories Guardener can access |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `guardener.images` | Request and view [container image migrations](/chainguard/guardener/github/image-migration/) for a repository |
  • `owner` (migrate)
  • `editor` (migrate)
  • `viewer` (migrate)
  • `limited_owner` (migrate)
  • `guardener.user` (migrate)
  • `guardener.admin` (migrate)
| | `guardener.scan` | View the dependency scans Guardener runs on connected repositories |
  • `owner` (get, list)
  • `editor` (get, list)
  • `viewer` (get, list)
  • `limited_owner` (get, list)
  • `console_viewer` (get, list)
  • `guardener.user` (get, list)
  • `guardener.admin` (get, list)
| | `identity` | Create and manage user identities, service accounts, and pull tokens |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (create, list)
  • `console_viewer` (list)
  • `registry.pull_token_creator` (create)
  • `libraries.java.pull_token_creator` (create)
  • `libraries.python.pull_token_creator` (create)
  • `libraries.javascript.pull_token_creator` (create)
| | `identity_providers` | Configure [custom identity providers](/platform/administration/custom-idps/custom-idps/) (OIDC, SAML) for authentication |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `identity_providers.scim` | Turn on [SCIM provisioning](/platform/administration/custom-idps/scim-provisioning/scim-overview/) for a custom identity provider and manage its tokens |
  • `owner` (manage)
| | `identity_providers.scim.users` | View the users a custom identity provider provisioned through SCIM |
  • `owner` (list)
| | `libraries.artifacts` | View Chainguard Library artifact metadata and information |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.aws_marketplace.subscriptions` | Manage Chainguard Libraries subscriptions bought through AWS Marketplace |
  • `owner` (create, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.cache` | View and clear the organization's Chainguard Libraries resolution cache |
  • `owner` (invalidate, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.entitlements` | Manage access permissions for Chainguard Libraries |
  • `owner` (create, delete, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `libraries.java.pull` (list)
  • `libraries.java.pull_token_creator` (list)
  • `libraries.java.push` (list)
  • `libraries.python.pull` (list)
  • `libraries.python.pull_token_creator` (list)
  • `libraries.python.push` (list)
  • `libraries.javascript.pull` (list)
  • `libraries.javascript.pull_token_creator` (list)
  • `libraries.javascript.push` (list)
| | `libraries.java` | Access [Chainguard Libraries for Java](/chainguard/libraries/java/overview/) |
  • `owner` (create, list)
  • `libraries.java.pull` (list)
  • `libraries.java.pull_token_creator` (list)
  • `libraries.java.push` (create, list)
| | `libraries.javascript` | Access Chainguard Libraries for JavaScript |
  • `owner` (create, list)
  • `libraries.javascript.pull` (list)
  • `libraries.javascript.pull_token_creator` (list)
  • `libraries.javascript.push` (create, list)
| | `libraries.policy` | Create and manage the cooldown and malware policies applied to upstream library pulls |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.policy.binding` | Apply Chainguard Libraries policies to the organization |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.policy.block_event` | View the library pulls a policy blocked |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.python` | Access [Chainguard Libraries for Python](/chainguard/libraries/python/overview/) |
  • `owner` (create, list)
  • `libraries.python.pull` (list)
  • `libraries.python.pull_token_creator` (list)
  • `libraries.python.push` (create, list)
| | `libraries.remote_registries` | Configure the upstream registries the organization's virtual repositories pull from |
  • `owner` (create, delete, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.request_groups` | Request library versions for Chainguard to build, and track those requests |
  • `owner` (create, delete, list, submit, update)
  • `editor` (create, delete, list, update)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `libraries.request_groups.items` | Edit the individual libraries in a draft build request |
  • `owner` (update)
  • `editor` (update)
| | `libraries.storage_usage` | View how much storage the organization's uploaded libraries use in each ecosystem |
  • `owner` (get)
| | `manifest` | Access and manage container image manifests |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (create, delete, list, update)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `manifest.metadata` | View container image manifest metadata and attestations |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (list)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `mcp.tool` | Call tools on [Chainguard MCP servers](/platform/mcp-servers/overview/) |
  • `owner` (call)
  • `mcp.tool_user` (call)
| | `packages.entitlements` | View which tier of Chainguard OS Packages the organization is entitled to, base or FIPS |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `policies.binding` | Turn image pull policies on for the organization |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `policies.decision` | View the allow and block decisions image pull policies made |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `policies.override` | Exempt a specific image from policy enforcement |
  • `owner` (create, delete, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `policies.policy` | View and manage image pull policies, such as blocking end-of-life images |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `record_signatures` | View cryptographic signature verification records |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (list)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `referrers` | View the signatures, attestations, and other artifacts attached to an image |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (list)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `registry.deployments` | View the Helm charts associated with a container repository |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `registry.entitlements` | View registry access entitlements and permissions |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `registry.entitlements.images` | Add, remove, and swap the images in the organization's registry entitlement |
  • `owner` (add, remove, swap)
  • `limited_owner` (add)
| | `registry.overlays` | View and edit [Custom Assembly overlays](/chainguard/containers/custom-assembly/overlays/) |
  • `owner` (edit, list)
  • `editor` (edit, list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.editor` (edit, list)
| | `registry.settings` | View the organization's registry settings, such as whether unique tags and the End-of-Life Grace Period are on by default |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `repo` | Create and manage container repositories (including [Custom Assembly](/chainguard/containers/custom-assembly/) resources) |
  • `owner` (check_policies, create, delete, list, update)
  • `editor` (create, delete, list, update)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (create, delete, list, update)
  • `registry.editor` (create, delete, list, update)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `repo.blobs` | Download container image binary content |
  • `owner` (get)
  • `editor` (get)
  • `viewer` (get)
  • `limited_owner` (get)
  • `registry.pull` (get)
  • `registry.pull_token_creator` (get)
  • `registry.push` (get)
  • `registry.editor` (get)
  • `guardener.user` (get)
  • `guardener.admin` (get)
| | `role_bindings` | [Assign roles to identities](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/#managing-role-bindings) (users and service accounts) |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (create, delete, list)
  • `console_viewer` (list)
  • `registry.pull_token_creator` (create)
  • `libraries.java.pull_token_creator` (create)
  • `libraries.python.pull_token_creator` (create)
  • `libraries.javascript.pull_token_creator` (create)
| | `roles` | Create, modify, and manage [custom Chainguard IAM roles](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/) |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull_token_creator` (list)
  • `libraries.java.pull_token_creator` (list)
  • `libraries.python.pull_token_creator` (list)
  • `libraries.javascript.pull_token_creator` (list)
| | `sandbox.identity` | Create the [Chainguard identity a Workspaces session acts through](/chainguard/workspaces/credentials/#your-chainguard-identity-in-a-session), and define and use shared session identities |
  • `owner` (admin, create, use)
  • `sandbox.user` (create)
  • `sandbox.identity.user` (use)
| | `sandbox.run` | Start, view, and cancel [Chainguard Checks](/chainguard/checks/overview/) runs |
  • `owner` (cancel, create, list)
  • `limited_owner` (cancel, create)
  • `sandbox.user` (cancel, create, list)
| | `sandbox.session` | Open [Chainguard Workspaces](/chainguard/workspaces/overview/) sessions, and list every user's sessions in the organization |
  • `owner` (create, list)
  • `limited_owner` (create)
  • `sandbox.user` (create)
| | `sboms` | Access Software Bill of Materials for packages and images |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (list)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `skills` | View the [Chainguard Agent Skills](/chainguard/agent-skills/overview/) catalog and publish skills |
  • `owner` (list, publish)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `skills.entitlements` | Manage which Chainguard Agent Skills the organization can access |
  • `owner` (create, delete, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `skills.harden` | Submit, track, and cancel [skill hardening](/chainguard/agent-skills/skill-hardening/) jobs |
  • `owner` (cancel, read, write)
| | `subscriptions` | Manage [CloudEvent](/platform/administration/cloudevents/events-reference/) subscriptions for notifications and automation |
  • `owner` (create, delete, list, update)
  • `editor` (create, delete, list, update)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `tag` | Manage Chainguard container image tags |
  • `owner` (create, delete, list, update)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (create, delete, list, update)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `telemetry.containers` | View the container images that connected repositories use, as Guardener observes them |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
| | `terms` | View and accept the legal terms a Chainguard product requires before you can use it |
  • `owner` (accept, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (accept, list)
  • `guardener.user` (list)
  • `guardener.admin` (accept, list)
| | `version` | View version information across all resources and assets |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `vuln` | Create vulnerability reports and assessments |
  • `owner` (create)
| | `vuln_report` | Manage detailed vulnerability assessments for specific resources |
  • `owner` (create, list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
| | `vuln_reports` | View high-level vulnerability report summaries |
  • `owner` (list)
  • `editor` (list)
  • `viewer` (list)
  • `limited_owner` (list)
  • `console_viewer` (list)
  • `registry.pull` (list)
  • `registry.pull_token_creator` (list)
  • `registry.push` (list)
  • `registry.editor` (list)
  • `guardener.user` (list)
  • `guardener.admin` (list)
|
## Role capabilities comparison The following table compares the general abilities of the built-in roles described in the [previous summary](#built-in-roles-summary):
| Role | Pull Images | List Tags/Repos | View SBOMs/Diffs | Create IAM Resources | Create Pull Tokens | Libraries Access | |------|-------------|-----------------|------------------|---------------------|--------------------|-----------------| | `owner` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | `editor` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `viewer` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `limited_owner` | ✅ | ✅ | ✅ | ✅ | ✅ | ✕ | | `console_viewer` | ✕ | ✅ | ✅ | ✕ | ✕ | ✕ | | `registry.pull` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `registry.pull_token_creator` | ✅ | ✅ | ✅ | ✅ | ✅ | ✕ | | `registry.push` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `registry.editor` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `apk.pull` | ✕ | ✕ | ✕ | ✕ | ✕ | ✕ | | `apk.push` | ✕ | ✕ | ✕ | ✕ | ✕ | ✕ | | `libraries.java.pull` | ✕ | ✕ | ✕ | ✕ | ✕ | ✅ | | `libraries.java.pull_token_creator` | ✕ | ✕ | ✕ | ✅ | ✅ | ✅ | | `libraries.java.push` | ✕ | ✕ | ✕ | ✕ | ✕ | ✅ | | `libraries.python.pull` | ✕ | ✕ | ✕ | ✕ | ✕ | ✅ | | `libraries.python.pull_token_creator` | ✕ | ✕ | ✕ | ✅ | ✅ | ✅ | | `libraries.python.push` | ✕ | ✕ | ✕ | ✕ | ✕ | ✅ | | `libraries.javascript.pull` | ✕ | ✕ | ✕ | ✕ | ✕ | ✅ | | `libraries.javascript.pull_token_creator` | ✕ | ✕ | ✕ | ✅ | ✅ | ✅ | | `libraries.javascript.push` | ✕ | ✕ | ✕ | ✕ | ✕ | ✅ | | `guardener.user` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `guardener.admin` | ✅ | ✅ | ✅ | ✕ | ✕ | ✕ | | `sandbox.user` | ✕ | ✕ | ✕ | ✕ | ✕ | ✕ | | `sandbox.identity.user` | ✕ | ✕ | ✕ | ✕ | ✕ | ✕ | | `mcp.tool_user` | ✕ | ✕ | ✕ | ✕ | ✕ | ✕ | | `actions.viewer` | ✕ | ✕ | ✕ | ✕ | ✕ | ✕ |
**Notes** * **Pull Images/List Tags/Repos/View SBOMs**: These capabilities refer to container registry operations relating to the `manifest`, `repo`, `tag`, and `sboms` resources * **APK Pull**: The `apk.pull` role is specialized for APK package management, not container operations * **Console Viewer**: Has the same broad `list` access as `viewer`, but can't pull blobs (`apk.blobs`, `repo.blobs`), view legal terms (`terms`), run Guardener conversions or migrations (`guardener.dfc`, `guardener.actions`, `guardener.images`), or list the GitHub organizations connected through the Chainguard App (`guardener.association`). This makes it a safe role for inviting team members who need Console visibility without the ability to pull images or APKs. ### Pull token creator roles The following roles are used for managing pull tokens for certain resources: * `registry.pull_token_creator` * `libraries.java.pull_token_creator` * `libraries.python.pull_token_creator` * `libraries.javascript.pull_token_creator` For example, the `libraries.*.pull_token_creator` roles are focused on their respective library ecosystems and don't have container registry access. These roles are able to create pull tokens because of the `identity.create` capability. However, none of these roles have the `identity.list` capability, meaning that they aren't able to view the pull tokens they've created. The reason for this is that Chainguard doesn't distinguish pull token identities from other [assumable identities](/platform/administration/assumable-ids/assumable-ids/) at the IAM level. If these roles also had the `identity.list` capability, they would be able to view **all** the identities in that scope. By not including `identity.list` among their capabilities, the pull token creator roles have a more limited scope, as intended. ## Learn more * [Overview of roles and role-bindings in Chainguard](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/) - Conceptual overview and basic management * [Overview of Chainguard IAM model](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/) - Complete IAM architecture --- ### Overview of the Chainguard Factory URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/overview.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Chainguard VMs Chainguard Factory is the automated build infrastructure that continuously monitors, builds, and updates thousands of open source projects to deliver containers, libraries, and VMs with a strong security posture and the latest patches. This massive automation system tackles one of the industry's biggest challenges: keeping software dependencies current at scale while maintaining security and compatibility across the entire open source ecosystem. ![Diagram of a factory](factory.png) It is hard to overstate the sheer scale of Chainguard’s task of being the secure source for open source. There are **thousands** of projects that we constantly monitor, build, test, and update. Some of these are foundational projects like [LLVM](https://llvm.org/) and [glibc](https://www.gnu.org/software/libc/). Others are applications central to modern infrastructure like PostgreSQL and nginx. Still more are the small utilities and libraries that we use all the time and almost forget about, such as sed and jq. These projects are then built into our 1,500 containers, plus our VMs and libraries, each of which are updated daily. If you look inside the factory, you will find a complex build system running parallel builds at high speed, constantly being updated and refined by engineers supported by AI. ## The pace of updates Open source projects are constantly evolving and updating. The factory keeps up through vigilant and automated monitoring that leverages the [GitHub API](https://docs.github.com/en/rest), the [Release Monitoring project](https://release-monitoring.org/), [PyPI](https://pypi.org/), [Maven Central](https://mvnrepository.com/repos/central) and the [npm registry](https://docs.npmjs.com/about-the-public-npm-registry). To give you an idea of how many updates happen in a short window of time, here is a screenshot of 5 hours of our automation finding updates to projects. ![Screenshot showing package updates committed by Factory automation](octo-prs.png) Automation opens a Pull Request (PR) when a new release is detected which updates the builds to the latest version. After building the new version, a suite of tests will then run to ensure it works as expected. If a test fails, AI analyzes the logs and suggests updates. Human engineers then step in to approve the update or fix any issues. Once approved, the package is built and is added to one of our package repositories. The process combines fast, automated updates from AI, with reviews and guidance from engineers to ensure all changes meet our high standards of quality and security. As every software engineer knows, not all updates are equal. Some are simple updates to applications which build cleanly and are not used in other packages. Some updates are a lot more complicated however, with other packages being dependent on them, either at buildtime or runtime. Consider a bump to the Go compiler. Not only do we need to update the Go package and release a new Go compiler image, we also need to rebuild all the packages that are dependent on Go and their dependencies. Some of these builds will fail due to changes caused by the update, and will require extra attention from engineers. Updates to core packages like the Go compiler, OpenSSL, or glibc are major events that require considerable engineering effort to successfully conclude. They are also happening all the time. Once an update passes through the package stage, we still need to update all container images that are dependent on it. For applications like PostgreSQL, this affects a handful of images, but an update to OpenSSL or glibc requires practically all our images to be rebuilt. After new versions of images are built, they are tested, often as part of a Helm chart deployed to a Kubernetes cluster (sometimes even a specially provisioned EKS cluster for images such as [eks-distro-kube-proxy!](https://images.chainguard.dev/directory/image/eks-distro-kube-proxy/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement)). On top of this, software is constantly going End-of-Life (EOL). When software is no longer supported upstream, we will cease updating it in Wolfi (refer to [Wolfi Packages in chainguard Containers](/chainguard/containers/concepts/lifecycle-and-eol/versions/#wolfi-packages-in-chainguard-containers)). Customers benefit from an extended [EOL grace Period](/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period/) with Chainguard OS where we continue to build old versions). The result is a relentless, orchestrated flow of updates — ensuring our software remains secure, current, and reliable. ## Handling CVEs Projects like [NVD](https://nvd.nist.gov/) and [GHSA](https://github.com/advisories) are constantly issuing security advisories, for example in 2024 an [average of 108 CVEs were issued every day](https://jerrygamblin.com/2025/01/05/2024-cve-data-review/). These advisories are ingested by [scanners](https://www.chainguard.dev/scanners?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) such as grype and snyk. We use these scanners internally to alert whenever a CVE is identified as affecting one of our packages. Automation adds the CVE to an internal queue for further investigation and an advisory is issued with the status of `Under Investigation`. After an engineer investigates the issue, the most likely outcomes are: - We determine the advisory to be a false positive and change the status to `Not Affected`. - We determine it to be a real issue and take further action to resolve it. This could mean pulling in a patch or bumping a dependency in the project, before rebuilding and updating the status to `Fixed`. Refer to [How Chainguard issues security Advisories](/chainguard/containers/security-and-compliance/security-advisories/how-chainguard-issues/) for full information on the lifecycle of advisories. ## Identifying malware and malicious updates It is essential that the factory is secure and we do not inadvertently publish malware. There have been multiple cases of supply chain attacks where reputable open source projects have been subverted or taken over and used for the distribution of malware, the XZ utils attack being a case-in-point. Avoiding the propagation of such malware is a key goal of the factory and there are several steps that we take to ensure security. One of the most important is analyzing code for known malware, but we also automatically identify any unexpected changes in the functionality of software from an update. For example: an update would be flagged as suspicious if it makes new network connections but there are no references to this in the project's changelog. ## Infrastructure and build security We use GitHub for source code management, but our builds themselves run on Kubernetes clusters to provide the scaling and observability we require. As you can imagine, there is a considerable cost associated with building at the pace described. We take security in our build environment seriously and adhere to the [SLSA](https://slsa.dev/) guidelines for ensuring build integrity and provenance. The builds themselves follow [OpenSSF guidance for security hardening via compiler flag settings](https://www.chainguard.dev/unchained/enhanced-compiler-flags-for-building-chainguards-guarded-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement). ## On automation, AI and human engineers To keep the factory running at the pace it does, we leverage automation and AI to streamline operations. But the vast number of changes results in constant exceptions and unexpected events that require domain specific knowledge to resolve. For this reason, we rely on a large and experienced engineering team with backgrounds in a wide range of technologies, from low-level kernel and VM engineers to those who know their way around higher level ecosystems like Node, Java and PHP. ## Conclusion The Chainguard Factory operates at an enormous scale. The number of builds and workflows that are happening every second is staggering. The factory is a critical component in Chainguard and effectively what our customers are paying for — the ability to turn open source projects into easily consumable containers, VMs, and libraries quickly and securely. --- ### How to use Chainguard Notifications URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/console/use-chainguard-notifications.md Last Modified: September 28, 2026 Tags: Chainguard Containers You can use the [Chainguard Console](/platform/console/images-directory/) to configure how **Chainguard** is permitted to send notifications about things like breaking changes to users in your organization. The feature includes options to allow notifications to be sent in-app to the **Activity Center** on the user’s Overview page in the Chainguard Console, via Slack, and for customers who are opted in, via email. These notifications are different from [Chainguard Events](/platform/administration/cloudevents/) as Chainguard Notifications are sent by Chainguard’s customer success representatives. ## Prerequisites and limitations

Note: Chainguard Notifications is in beta.

Notifications are currently limited to messages related to a small set of topics like breaking changes, incidents, and product lifecycle changes like end-of-life (EOL) and new releases. The in-app notifications are set up automatically and currently have no configuration options. Slack requires you to establish the connection between our Chainguard Notifications Slack app and your company Slack workspace by completing the Slack OAuth flow that initiates when you connect Slack the first time. Some customers have access to email notifications. When enabled, you have control over which email notifications are sent. You are also able to set one or more additional email addresses to receive notifications for your organizations; these are called forwarding addresses. ## Set up Slack for Chainguard Notifications Make sure you are logged into both the console and to your Slack workspace on the same machine before you begin. To perform this task, you must use a user account for the Chainguard Console that is configured with the *owner* role for the organization and a Slack account that can add apps to your Slack workspace and which has access to the Slack channels you plan to use. > For example, when you are trying to add the Chainguard Notifications app to Slack channels, the Slack account you are using will only be shown a list of public and private Slack channels the app has access to and your Slack user account is a member of. If you have a specific channel in mind for these notifications, make sure the Slack user establishing the connection has access to and is a member of that channel. To set this up, follow these steps: 1. In the Chainguard Console, open **Settings \> Activity Center**. 1. Next to the **Integrations** section heading, click **Edit**. 1. Under the **Slack** subheading, click **Connect Slack**. A pop-up window from Slack will appear asking you to allow the **Chainguard Notifications** app to access Slack. 1. In the **Workspace** dropdown, select the Slack workspace in which you want to have your users receive Chainguard Notifications. 1. Click **Allow**. Follow the directions in one or more of the following sections to enable notifications in public and private channels. ### Preliminary step to set up notifications in a private channel If you want to provide Chainguard Notifications in a private Slack channel, then at this point you must add the app to the private channel. In Slack: 1. Enter the private channel with your Slack user. 1. Click on the channel members icon to open the list. 1. Click to open the **Integrations** tab. 1. Click the **Add an App** button to display a list of available apps in your workspace. 1. Find **Chainguard Notifications** in the list and click the **Add** button. Include the private channel in your actions in the next section. ### Set up notifications in a public or private channel 1. Click into the **Slack Channels** box under the **Slack** subheading on the **Notifications** page in the console. You will be shown the list of channels available for the Chainguard Notifications app to use. Select the channel(s) where you want these notifications to be posted. > NOTE: Private channels will not appear here unless you first complete the preliminary step in the previous section. ## Manage email notifications Email notifications are not yet available to all customers. Use this procedure to change which notification topics are sent, change who receives them, or unsubscribe an address from them entirely. To perform this task, you must use a user account for the Chainguard Console that is configured with the *owner* role for the organization. Then, follow these steps: 1. In the Chainguard Console, open **Settings \> Activity Center**. 1. Next to the **Email** section heading, click **Edit**. You can then change settings for what notification topics will be sent and to whom, either an individual or perhaps to an email alias for a group. To unsubscribe an address, remove it here. 1. When you are done, click **Save changes**. If you don't have the *owner* role, ask an owner in your organization to make the change for you. If you can't reach an owner, see [Get support](/get-started/get-support/). ## Notification categories Notifications fall into one of multiple categories based on content. Here's a list of the currently available categories: - **Breaking changes** - Changes that could disrupt regular use - **Incidents** - Reports and updates - **Product lifecycle** - Has two sub-categories: - **End of life** - **New releases** ## Troubleshooting FAQ - setting up Slack ### Channel seems to be missing — check if it is private **Q:** I connected Slack successfully, but the channel I want to use doesn't appear in the "Slack Channels" dropdown. Other channels show up fine — why is this one missing? The most common cause is that the channel is private. The Chainguard Notifications Slack app can only see public channels by default. To make a private channel selectable: 1. In Slack, open the private channel. 1. Click the channel members icon, then open the **Integrations** tab. 1. Click **Add an App**, find **Chainguard Notifications**, and click **Add**. 1. Return to the Chainguard Console and refresh the **Settings > Activity Center** page. The private channel should now appear in the dropdown. ### Channel still seems to be missing — check which email address you are using **Q:** My channel is public, I'm a member of it, and I even added the Chainguard Notifications app to it — but it still doesn't show up. What's wrong? This usually means the email address you used to sign in to the Chainguard Console doesn't match the email address associated with your account in your company's Slack workspace. The Slack app uses your Console email to look up your Slack user ID — if it can't find a matching user in your workspace, it can't determine which channels you have access to, and the channel list comes back empty (or partial). A common version of this scenario is signing in to the Console with your Github profile linked to your personal Gmail (e.g. you@gmail.com) while your corporate Slack workspace knows you as you@company.com. To fix it: 1. In the Console, disconnect the Slack integration in **Settings > Activity Center > Integrations > Edit**. 1. Sign out of the Chainguard Console. 1. Sign back in using the email address that matches your corporate Slack workspace identity. 1. Re-connect Slack and complete the OAuth flow again. ### I still cannot find the channel — refresh the Console cache **Q:** I disconnected and reconnected Slack with the correct email, but the channel I want still isn't appearing. Now what? After reconnecting, the channel list can get stuck on a stale cached version from the previous session. A hard refresh of the Console tab almost always resolves it. On Mac use `Cmd + Shift + R` or on Windows and Linux use `Ctrl + Shift + R` to refresh. Then reopen **Settings > Activity Center** and check the channel dropdown again. If the channel still doesn't appear after a hard refresh, contact your Chainguard Customer Success representative — they can verify on the backend that your Slack user ID was successfully linked to the Notifications service. --- ### Subscribing to Chainguard CloudEvents URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/cloudevents/events-example.md Last Modified: September 28, 2026 Tags: Chainguard Console, CloudEvents, Procedural Chainguard implements [CloudEvents](/platform/administration/cloudevents/events-reference/), a specification for a standard format for events data. This means developers can use events (generated based on interactions with Chainguard resources) to initiate processes and thus automate certain actions. For example, you could set up infrastructure to listen for push events to an organization's private registry and [mirror any new Chainguard Containers in the registry to a third-party repository](/platform/administration/cloudevents/image-copy-gcr/). This article includes an example of how to use `chainctl` to create an event subscription. It also includes details on how to validate events from Chainguard and highlights some potential use cases for them. This article is primarily focused on Registry `push` and `pull` events. *Push* events occur when an image in your entitlement is added or updated. *Pull* events occur when an image is pulled from your Chainguard repository. Be aware, though, that there are also events related to IAM, such as user creation and adding identity providers. ## Subscribing to events To subscribe to Chainguard events for your account, use the following `chainctl` command: ```shell chainctl events subscriptions create https:// ``` The webhook URL should connect to a service that you use to process the requests. This can use whatever infrastructure works for your team, but a common choice is to use a serverless service such as [AWS Lambda](https://aws.amazon.com/lambda/) or [Google Cloud Run](https://cloud.google.com/run/docs/overview/what-is-cloud-run). As an example, this guide uses [Webhook.site](https://github.com/webhooksite/webhook.site). This is an open-source application that will generate a URL you can use to receive, send, and transform webhooks, among other actions. Webhook.site can be self-hosted, but for the purposes of this guide you can just use the [cloud version of the application](https://webhook.site). > **Note**: You can also try this out with alternative webhook testing sites, like [smee.io](https://smee.io/). Once you have the webhook URL, use a `chainctl` command similar to the following to set up a subscription to events for your account: ```shell chainctl events subscriptions create https://webhook.site/aEXAMPLE-b689-49a5-94df-f1dEXAMPLE29 ``` Select the organization whose events you want to subscribe to when prompted. If successful, this returns an ID for the subscription: ```output ✔ Selected folder chainguard.edu. ID | SINK ------------------------------------------------------------+------------------------------------------------------------ 45a0cEXAMPLE977f050c5fb9EXAMPLEeed764595/91b3cdEXAMPLE7a6 | https://webhook.site/aEXAMPLE-b689-49a5-94df-f1dEXAMPLE29 ``` In order to generate an event, try pulling an image from your organization's repository within the Chainguard registry: ```shell docker pull cgr.dev/chainguard.edu/istio-pilot:1-dev ``` Be sure to change this command to use your own organization's repository and an image you have access to. ```output 7.0.10: Pulling from chainguard.edu/istio-pilot Digest: sha256:5a4583fb12ee4b33306a2c23ff33c9f2d04e6d1c7580703850928abf50de5dcf Status: Image is up to date for cgr.dev/chainguard.edu/istio-pilot:1-dev cgr.dev/chainguard.edu/istio-pilot:1-dev ``` Then, navigate back to Webhook.site. It may take a few moments, but in time an event will appear with request content like the following: ``` { "actor": { "subject": "261ea43771f4d962f17c1d206155a38b9b17ff18", "act": { "aud": "Hvqy7yoEhI8TY1zX9rPrsdcIntDz9yh2", "aws-acct": "", "aws-arn": "", "email": "", "iss": "https://auth.chainguard.dev/", "sub": "google-oauth2|117799788801679028930" } }, "body": { "repository": "chainguard.edu/istio-pilot", "repo_id": "45a0cEXAMPLE977f050c5fb9EXAMPLEeed764595/e2fca7026fbaa243", "tag": "1-dev", "digest": "sha256:043446cbda630e5071e4f72736b38b5249c859d07bb14886cd93b4e36fc3402c", "method": "HEAD", "type": "manifest", "when": "2025-04-23T00:37:47.810899", "location": "", "remote_address": "76.169.101.202", "user_agent": "docker/27.3.1 go/go1.22.7 git-commit/41ca978 kernel/6.8.0-57-generic os/linux arch/amd64 UpstreamClient(Docker-Client/27.3.1 \\(linux\\))" } } ``` This sample request has the following headers: | Header | Value | | ---------- | ---------- | | accept-encoding | `gzip` | | traceparent | `00-6f9aceb3276e8c60676cf04099d8818d-dd75e26d099ef057-00` | | original-traceparent | `00-6f9aceb3276e8c60676cf04099d8818d-dd75e26d099ef057-00` | | content-type | `application/json` | | ce-type | `dev.chainguard.registry.pull.v1` | | ce-time | `2025-04-23T00:37:47Z` | | ce-subject | `` | | ce-specversion | `1.0` | | ce-source | `cgr.dev` | | ce-id | `188888b6-27d2-4a80-8ad5-c7450ab89c0c` | | ce-group | `` | | ce-audience | `customer` | | ce-actor | `enforce-prod-registry-jzjewxe4@prod-enforce-fabc.iam.gserviceaccount.com` | | authorization | `Bearer …` | | content-length | `721` | | user-agent | `Chainguard Enforce bd6a3e9-dirty` | | host | `webhook.site` | This shows that you have successfully subscribed the test service to Chainguard Events. ## Filtering events The webhook will get all events for your organization. You will need to filter them to only the events you are interested in, which can be done using the `ce-type` header. For pull events the type is `dev.chainguard.registry.pull.v1` and push events are of type `dev.chainguard.registry.push.v1`. A full description of all events and their types is [available on Chainguard Academy](/platform/administration/cloudevents/events-reference/). ## Validating events Before processing an event from Chainguard, you should ensure that it is valid. Every Chainguard event has a JSON Web Token (JWT) formatted OIDC ID token in its Authorization header. For authorization purposes, validate the first two fields; then, verify the third: * Use the `iss` field to ensure that the issuer is Chainguard, specifically `https://issuer.enforce.dev`. * Use the `sub` field to check that the event matches your configured Chainguard identity. For example, assuming a UIDP ID of `0475f6baca584a8964a6bce6b74dbe78dd8805b6`, the `sub` field's value will resemble the following: `webhook:0475f6baca584a8964a6bce6b74dbe78dd8805b6`. If the subscription is in a sub-group, then the value will have the corresponding group SUID appended to the path. * Verify the JWT digest against the content of the event. Validating these fields before processing the JWT token using a verification library can save resources, as well as alert you about suspicious traffic or misconfigured Chainguard organization settings. ## Use cases Events can be used to drive a wide range of processing scenarios, including the following: * Copying images to another registry when an image is updated * Kicking off an image rebuild process when a base image is updated * Logging new image availability to a Slack channel * Logging pulls and user creation to check for unexpected or unauthorized usage Chainguard's [platform-examples](https://github.com/chainguard-dev/platform-examples) repository contains example code for implementing workflows based on events. Most examples are based on Terraform and work with the Google Cloud Platform, but should be portable to other environments. ## Learn more This article outlined details on what Chainguard Events are, how to use them, and some common use cases. For more information on CloudEvents, you can refer to [cloudevents.io](http://cloudevents.io/). You can also find more details in [Chainguard's CloudEvents reference documentation](/platform/administration/cloudevents/). --- ### Chainguard FIPS containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/fips-images.md Last Modified: September 28, 2026 Tags: Chainguard Containers, FIPS ## What is FIPS? [Federal Information Processing Standards](https://www.nist.gov/itl/publications-0/federal-information-processing-standards-fips) (FIPS) are standards developed by the National Institute of Standards and Technology (NIST) in accordance with the Federal Information Security Management Act (FISMA). FIPS compliance ensures that cryptographic security services within applications meet strict security and integrity standards, and are implemented and configured correctly. Chainguard provides FIPS-validated container images to help organizations meet federal compliance requirements, including [FedRAMP](https://www.fedramp.gov/) and Department of Defense security frameworks. These FIPS-enabled containers feature a kernel-independent design that simplifies deployment while maintaining compliance. ## What to expect from Chainguard FIPS containers Chainguard offers 700+ FIPS image variants covering language runtimes (Go, Java, Python, Node.js, .NET, PHP, C/C++), databases, web servers, and Kubernetes components. These images use NIST-validated cryptographic modules including the OpenSSL FIPS provider and Bouncy Castle FIPS for Java. All FIPS images include STIG hardening, daily builds with zero-to-minimal CVEs under SLA, and build-time SBOMs. Chainguard's warranties and certification details are on the [FIPS Commitment](https://www.chainguard.dev/legal/fips-commitment) page. ## Kernel-independent FIPS architecture Traditionally, FIPS-compliant containers required the host kernel to be configured in FIPS mode to provide a validated entropy source. This limited deployment options and prevented testing on developer machines. Chainguard FIPS Containers use a userspace entropy source (the Jitterentropy library with SP 800-90B validation) instead of relying on the kernel. This allows FIPS containers to run on any recent Linux kernel, including developer workstations, standard CI/CD environments, and cloud platforms like GKE, AWS Bottlerocket, and Azure Linux. **Limitations**: Some workloads (certain Kubernetes CNI plugins, LUKS2 encryption, StrongSwan VPN) still require a kernel configured in FIPS mode. As of August 2025, Java-based FIPS images support kernel-independent operation. For technical details, refer to [Kernel-Independent FIPS Containers](https://www.chainguard.dev/unchained/kernel-independent-fips-images). ## Using FIPS containers FIPS images are available for language runtimes (Go, Java, Node.js, Python, .NET, PHP), databases (PostgreSQL, Elasticsearch, Redis), web servers (nginx, HAProxy), Kubernetes components, and monitoring tools. View the complete catalog at [images.chainguard.dev/?category=fips](https://images.chainguard.dev/?category=fips). All Chainguard FIPS Containers include [STIG hardening](/chainguard/containers/security-and-compliance/stigs/) in addition to FIPS validation. For images not currently available with FIPS, [contact Chainguard](https://www.chainguard.dev/contact) to discuss custom requirements. ## Additional resources - [FIPS Commitment](https://www.chainguard.dev/legal/fips-commitment) - Warranties and certifications - [Frequently asked questions](/platform/fips/faqs/) - Common FIPS questions - [OpenSSL FIPS module documentation](https://www.openssl.org/docs/manmaster/man7/fips_module.html) - [Bouncy Castle FIPS Crypto package](https://www.bouncycastle.org/about/bouncy-castle-fips-faq/) Chainguard FIPS Containers are not included in the free tier. To request access, [contact Chainguard](https://www.chainguard.dev/contact). --- ### Create an assumable identity for a GitLab CI/CD pipeline URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/gitlab-identity.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This procedural tutorial outlines two methods for how to create a Chainguard identity: `chainctl` and Terraform. It then walks through how to create a GitLab CI/CD pipeline that will assume the identity to interact with Chainguard resources. ## Prerequisites To complete this guide, you will need the following. Both methods outlined in this guide require you to have the following: * Access to a GitLab project and CI/CD pipeline you can use to test out the identity you'll create. GitLab provides a [quickstart tutorial on creating your first pipeline](https://docs.gitlab.com/ee/ci/quick_start/) which can be useful for getting a testing pipeline up and running. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. Additionally, the Terraform method requires you to have `terraform` installed on your local machine. Terraform is an open source Infrastructure as Code tool which this guide will use to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. ## Create an assumable identity with `chainctl` You can create a new Chainguard identity that a GitLab CI/CD pipeline can assume by running the following command. Be sure to replace `` and `` with your actual GitLab group and project names. If you have access to more than one Chainguard organization, add `--parent=` to choose where the identity is created. For example, if your GitLab project URL is `https://gitlab.com/mycompany/myproject`, then: * `` should be `mycompany` * `` should be `myproject` ```shell chainctl iam identities create cg-gitlab-id \ --identity-issuer="https://gitlab.com" \ --subject="project_path:/:ref_type:branch:ref:main" \ --audience="https://gitlab.com" \ --role=viewer ``` Take note of the identity's full UIDP (unique identity path) as you'll need it to test this identity out in a [GitLab CI/CD configuration](/platform/administration/assumable-ids/identity-examples/gitlab-identity/#testing-the-identity-with-a-gitlab-cicd-pipeline). This command creates an identity named `cg-gitlab-id` with the following claim matching rules: * Issuer: `https://gitlab.com` (GitLab's OIDC token issuer) * Subject: Restricts access to a specific GitLab project running on the main branch * Audience: `https://gitlab.com` (the intended recipient of the token) These options' values are string literals; you can use `--identity-issuer-pattern`, `--subject-pattern`, and `--audience-pattern` to use regular expressions instead. This command also binds the `viewer` role to the new identity. The `viewer` role provides read-only access to Chainguard resources, which is appropriate for most CI/CD use cases that need to pull images or inspect resources. You can also chain together multiple roles, as in `--role=registry.push,registry.pull`. To see all available roles and their permissions, run `chainctl iam roles list`. You can also learn more by reviewing our [Overview of roles and role-bindings in Chainguard](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/). If you ever need to retrieve information about it in the future, you can run the following command: ```shell chainctl iam identities ls ``` This will return `cg-gitlab-id` listed among all your Chainguard identities. ### Self-managed GitLab instances The previous example works for GitLab SaaS (`gitlab.com`) and for self-managed instances whose OIDC issuer is reachable from the public internet. To validate a pipeline's token, Chainguard's Security Token Service (STS) fetches the signing keys from the issuer's JWKS endpoint (`https:///oauth/discovery/keys`). If your instance isn't reachable from the public internet, this fetch fails and the pipeline can't assume the identity. For a private instance, pin the signing keys when you create the identity by passing them to `--issuer-keys`. Export the JWKS from a host that can reach your instance and provide it inline: ```shell chainctl iam identities create cg-gitlab-id \ --identity-issuer="https://" \ --issuer-keys="$(curl -s https:///oauth/discovery/keys)" \ --subject="project_path:/:ref_type:branch:ref:main" \ --audience="https://" \ --role=viewer ``` Set `--identity-issuer` and `--audience` to your instance's URL instead of `https://gitlab.com`. Because the keys are pinned rather than fetched on demand, the identity doesn't pick up key rotations automatically. GitLab rotates its signing keys, so `chainctl` creates a static identity that expires after a set period, 30 days by default. Recreate the identity with the current JWKS before it expires or whenever the keys rotate. > **Note**: You don't need to register your self-managed GitLab as a [custom identity provider](/platform/administration/custom-idps/custom-idps/) to assume an identity from a pipeline. Custom identity providers federate human sign-in to Chainguard, whereas a pipeline assumes an identity through the OIDC token exchange described above. Registering a private instance as an identity provider won't work in any case, because Chainguard can't reach its issuer to validate it — the same reachability limit that requires you to pin the keys with `--issuer-keys`. ## Create an assumable identity with Terraform This section outlines how to use Terraform to create an identity for a GitLab pipeline to assume. This involves creating two Terraform configuration files that, together, will produce such an identity. To help explain each configuration file's purpose, we will go over what they do and how to create each file one by one. First, though, create a directory to hold the Terraform configuration and navigate into it. ```shell mkdir ~/gitlab-tf && cd $_ ``` This will help make it easier to clean up your system at the end of this guide. ### `main.tf` The first file, which we will call `main.tf`, will serve as the scaffolding for our Terraform infrastructure. The file will consist of the following content. ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } } } ``` This is a fairly barebones Terraform configuration file, but we will define the rest of the resources in the other two files. In `main.tf`, we declare and initialize the Chainguard Terraform provider. ### `gitlab.tf` The `gitlab.tf` file is what will actually create the identity for your GitLab CI pipeline workflow to assume. The file will consist of five sections, which we'll go over one by one. The first section section looks up a Chainguard IAM organization named `myorg.biz`: ```hcl data "chainguard_group" "group" { name = "myorg.biz" } ``` Be sure to change `myorg.biz` to the name of your own Chainguard organization. The next section creates the identity itself. ```hcl resource "chainguard_identity" "gitlab" { parent_id = data.chainguard_group.group.id name = "gitlab-ci" description = <` and `` with the name of your GitLab group and project names, respectively. The next section will output the new identity's `id` value. This is a unique value that represents the identity itself. ```hcl output "gitlab-identity" { value = chainguard_identity.gitlab.id } ``` The section after that looks up the `viewer` role. ```hcl data "chainguard_role" "viewer" { name = "viewer" } ``` The final section grants this role to the identity. ```hcl resource "chainguard_rolebinding" "view-stuff" { identity = chainguard_identity.gitlab.id group = data.chainguard_group.group.id role = data.chainguard_role.viewer.items[0].id } ``` Following that, your Terraform configuration will be ready. Now you can run a few `terraform` commands to create the resources defined in your `.tf` files. ### Creating your resources First, run `terraform init` to initialize Terraform's working directory. ```shell terraform init ``` Then run `terraform plan`. This will produce a speculative execution plan that outlines what steps Terraform will take to create the resources defined in the files you set up in the last section. ```shell terraform plan ``` Then apply the configuration. ```shell terraform apply ``` Before going through with applying the Terraform configuration, this command will prompt you to confirm that you want it to do so. Enter `yes` to apply the configuration. ``` ... Plan: 4 to add, 0 to change, 0 to destroy. Changes to Outputs: + gitlab-identity = (known after apply) Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: ``` After typing `yes` and pressing `ENTER`, the command will complete and will output a `gitlab-ci` value. ``` ... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: gitlab-identity = "" ``` This is the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers), which you configured the `gitlab.tf` file to emit in the previous section. Note this value down, as you'll need it to set up the GitLab CI pipeline you'll use to test the identity. If you need to retrieve this UIDP later on, though, you can always run the following `chainctl` command to obtain a list of the UIDPs of all your existing identities. ```shell chainctl iam identities ls ``` Note that you may receive a `PermissionDenied` error part way through the apply step. If so, run `chainctl auth login` once more, and then `terraform apply` again to resume creating the identity and resources. You're now ready to create a GitLab CI pipeline which you'll use to test out this identity. ## Testing the identity with a GitLab CI/CD pipeline From the GitLab Dashboard, select **Projects** in the left-hand sidebar menu. From there, click on the project you specified in the `subject` claim to be taken to the project overview. In the list of the repository's contents, there will be a file named `.gitlab-ci.yml`. This is a special file that's required when using GitLab CI/CD, as it contains the CI/CD configuration. Click on the `.gitlab-ci.yml` file, then click the **Edit** button and select an option for editing the file. For the purpose of this guide, delete whatever content is in this file to start and replace it with the following. ``` image: cgr.dev/chainguard/wolfi-base stages: - assume-and-explore assume-and-explore: id_tokens: ID_TOKEN_1: aud: https://gitlab.com stage: assume-and-explore script: - | # Install wget apk add wget # Install chainctl. wget -O chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_linux_$(uname -m)" chmod +x chainctl mv chainctl /usr/bin # Assume the identity. chainctl auth login \ --identity-token $ID_TOKEN_1 \ --identity chainctl auth configure-docker \ --identity-token $ID_TOKEN_1 \ --identity # Explore available images. chainctl images repos list ``` Let's go over what this configuration does. First, GitLab requires that pipelines have a shell. To this end, this configuration uses the [`cgr.dev/chainguard/wolfi-base` image](https://images.chainguard.dev/directory/image/wolfi-base/overview?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-administration-iam-organizations-identity-examples-gitlab-identity) since it includes the `sh` shell. Next, this configuration creates a JSON Web Token (JWT) with an [`id_tokens`](https://docs.gitlab.com/ee/ci/yaml/index.html#id_tokens) block that will allow the job to be able to fetch an OIDC token and authenticate with Chainguard. GitLab requires that any JWTs created in this manner must include an `aud` keyword. In this case, it should align with the `audience` associated with the Chainguard identity you created: `https://gitlab.com`. Following that, the job runs a few commands to download and install `chainctl`. It then uses `chainctl`, the JWT, and the Chainguard identity's `id` value to assume the Chainguard identity and log in. > **Note**: Be sure to replace `` with the identity UIDP you noted down in the previous section. After logging in, the pipeline is able to run any `chainctl` command under the assumed identity. To test out this ability, this configuration runs the `chainctl images repos list` command to list all available image repos associated with the organization. After updating the configuration, commit the changes and the pipeline will run automatically. A status box in the dashboard will let you know whether the pipeline runs successfully. Click **Build** and then **Pipelines** to view the pipeline, and then click the **assume-and-explore** job button to open the job's output from the last run. There you should see a list of container repositories accessible to your organization: ```Output . . . Successfully exchanged token. Valid! Id: $ORGANIZATION/$IDENTITY Updated auth config for cgr.dev [cgr.dev/example.edu] ├ [buildkit] ├ [mongodb] ├ [nginx] ├ [node] ├ [postgresql] └ [python] ``` This indicates that the GitLab CI/CD pipeline did indeed assume the identity and run the `chainctl images repos list` command. ## Next steps If you'd like to experiment further with this identity and what the workflow can do with it, there are a few parts of this setup that you can tweak. For instance, if you'd like to give this identity different permissions you can change the role data source to the role you would like to grant. ``` data "chainguard_roles" "editor" { name = "editor" } ``` To retrieve a list of all the available roles — including any custom roles — you can run the following command. ```shell chainctl iam roles list ``` You can also edit the pipeline itself to change its behavior. For example, instead of inspecting the image repos the identity has access to, you could have the workflow inspect the organization like in the following example. ```shell chainctl iam orgs ls ``` The GitLab pipeline will only be able to perform certain actions on certain resources, depending on what kind of access you grant it. ## Removing sample resources To delete the identity created directly with `chainctl`, run the following: ```shell chainctl iam identities delete cg-gitlab-id ``` This will also remove the identity's role-binding. To remove the resources Terraform created, you can run the `terraform destroy` command. ```shell terraform destroy ``` This will destroy identity and the role-binding created in this guide. It will not delete the organization. You can then remove the working directory to clean up your system. ```shell rm -r ~/gitlab-tf/ ``` Following that, all of the example resources created in this guide's Terraform instructions will be removed from your system. ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, the Terraform documentation includes a section on [recommended best practices](https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices) which you can refer to if you'd like to build on this Terraform configuration for a production environment. Likewise, for more information on using GitLab CI/CD pipelines, we encourage you to check out the [official documentation on the subject](https://docs.gitlab.com/ee/ci/pipelines/). --- ### Create role-bindings for a GitHub team using Terraform URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/iam-organizations/roles-role-bindings/rolebinding-terraform-gh.md Last Modified: October 8, 2026 Tags: Chainguard Console, Procedural There may be cases where an organization will want multiple users to have access to the same Chainguard organization. Chainguard allows you to grant other users access to Chainguard by [generating an invite link or code](/platform/administration/iam-organizations/how-to-manage-iam-organizations-in-chainguard/#inviting-others-to-an-organization). In addition, you can now grant access to users using Terraform and identity providers like GitHub, GitLab, and Google. You can also manage access through these providers' existing group structures, like GitHub Teams or GitLab Groups. Granting access through Terraform helps to reduce the risk of unwanted users gaining access to Chainguard. This guide outlines one method of using Terraform to grant members of a GitHub team access to the resources managed by a Chainguard organization. It also highlights a few other Terraform configurations you can use to manage role-bindings in the Chainguard platform. Although this guide is specific to GitHub, the same approach can be used for other systems. ## Prerequisites To complete this guide, you will need the following. * `terraform` installed on your local machine. Terraform is an open source Infrastructure as Code tool which this guide will use to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * Access to a GitHub team. If you'd like, you can create a new GitHub organization and team for testing purposes. Check out [GitHub's documentation](https://docs.github.com/en/organizations/organizing-members-into-teams/creating-a-team) for details on how to do this. * A GitHub Personal Access Token, with a minimum of **read.org** access. Follow [GitHub's documentation on the subject](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) to learn how to set one up. Additionally, you will need to [configure SSO for your personal access token](https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on) if required by your organization. ## Setting up your environment There are a few things you must have in place in order to follow this guide. First, create a testing directory to hold the Terraform configuration and navigate into it. ```sh mkdir ~/github-team && cd $_ ``` This will help make it easier to clean up your system at the end of this guide. Next, you'll need to set up a few environment variables that the Terraform configuration in this guide assumes you will have in place. Start by creating an environment variable named `GITHUB_ORG` that points to the name of your GitHub organization. Run the following command to create this variable, but be sure to replace `` with actual name of your organization as it appears in URLs. For example, if your organization owns a repository at the URL `htttps://github.com/orgName-example/repository-name`, then the value you would pass here would be `orgName-example`. ```sh export GITHUB_ORG= ``` Next, create a variable named `GITHUB_TEAM` set to the slug of the GitHub team for which you want to create a set of role-bindings. The Terraform configuration will use this detail to find and retrieve information about your GitHub team. If you aren't sure of what your team's slug is, you can find it with `gh`, the GitHub command line interface. You can use a command like the following to retrieve a list of all your organization's teams. ```sh gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" /orgs/$GITHUB_ORG/teams ``` Scroll through this command's output to find the `slug` value for the team in question. ```json [ . . . { "name": "Team Name", "id": 9999999, "node_id": "T_kwDOBTYtm84AbTQX", "slug": "team-slug", . . . }, . . . ] ``` With the team's slug in hand, run the following command to create the `GITHUB_TEAM` environment variable. ```sh export GITHUB_TEAM= ``` Following that, you will need to provide Terraform with your GitHub personal access token so it can access information related to your GitHub organization. Rather than hard coding your token into the Terraform configuration or having Terraform prompt you to enter it manually, you can create an environment variable named `GITHUB_TOKEN` which Terraform will automatically use. Create this variable with the following command. ```sh export GITHUB_TOKEN= ``` Lastly, the Chainguard role-bindings that this guide's Terraform configuration will create must all be tied to an organization. Create another variable named `CHAINGUARD_ORG` with the following command, replacing `` with the UIDP of the Chainguard IAM organization you want to tie the role-bindings to. You can find the UIDP for your Chainguard IAM organization by running `chainctl iam organizations ls -o table`. ```sh export CHAINGUARD_ORG="" ``` Following that, you will have everything you need in place to set up the Terraform configuration. ## Creating your Terraform configuration As mentioned previously, we will be using Terraform to create role-bindings for each user in a GitHub team, giving them access to resources associated with a given Chainguard organization. This guide outlines how to create two Terraform configuration files that, together, will produce a set of such role-bindings. To help explain both files' purposes, we will go over what they do and how to create each one individually. ### `main.tf` First, we will create a `main.tf` file which will set up the necessary Terraform providers. This file will consist of the following lines. ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } github = { source = "integrations/github" } } } provider "github" { owner = "$GITHUB_ORG" } ``` The `terraform` block defines the sources for the `chainguard` and `github` providers. The `provider` block sets up the `github` provider with one argument — `owner` — that points to the `GITHUB_ORG` variable you set previously. Create the `main.tf` file with the following command. ```sh cat < main.tf terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } github = { source = "integrations/github" } } } provider "github" { owner = "$GITHUB_ORG" } EOF ``` Next, you will create the other configuration file that will actually create the role-bindings for your GitHub team. ### `rolebindings.tf` The `rolebindings.tf` file will contain a few separate blocks that retrieve information about the GitHub team members and create Chainguard role-bindings for each one. The first block creates a `github_team` data source named `team`. ```hcl data "github_team" "team" { slug = "$GITHUB_TEAM" } ``` Using the arguments you provided in the `github` provider block in `main.tf`, Terraform will search for any GitHub teams matching the slug specified within this block. If Terraform can find a team with a matching slug in the specified GitHub organization, then you will be able to pull more data about this team down from GitHub. After the first block retrieves information about the team, we need to retrieve information about each member of the team in order to create an identity for each of them. To this end, the next block creates a `github_user` data source named `team_members`. ```hcl data "github_user" "team_members" { for_each = toset(data.github_team.team.members) username = each.key } ``` Because, we want this source to represent **every** member of the team, this block starts with a `for_each` meta-argument which accepts a `toset` map of the GitHub team members derived from the `github_team` source created previously. Additionally, the `github_user` data source [requires you](https://registry.terraform.io/providers/integrations/github/latest/docs/data-sources/user#username) to set the `username` argument to whichever user you want the data source to represent. In the Terraform Language, `each.key` is the map key corresponding to the objects referenced in the `for_each` argument. In this case, it means the `username` variable will be tied to a mapping of each member of the GitHub team within this Terraform configuration. The next block retrieves Chainguard identities for each member of the GitHub team. ``` data "chainguard_identity" "team_ids" { for_each = toset([for x in data.github_user.team_members : x.id]) issuer = "https://auth.chainguard.dev/" subject = "github|${each.key}" } ``` This block's `for_each` meta-argument iterates through each member of the team. For each iteration, it retrieves that user's GitHub ID and then retrieves a Chainguard identity that it derives using that GitHub ID. If there are members of the GitHub team who have not yet registered with Chainguard, this method will still assign them the correct permissions when they log in for the first time. The next block retrieves the predefined `viewer` role from Chainguard. ``` data "chainguard_role" "viewer" { name = "viewer" } ``` The final block puts all this information together to create the role-bindings for each member of the team. ``` resource "chainguard_rolebinding" "cg-binding" { for_each = data.chainguard_identity.team_ids identity = each.value.id group = "$CHAINGUARD_ORG" role = data.chainguard_role.viewer.items[0].id } ``` This `resource` block iterates through the list of Chainguard identities, assigns each one to the IAM organization specified by the `group` argument, and binds each identity to the `viewer` role. Here, the `group` argument is set to the `CHAINGUARD_ORG` variable you created at the start of this guide. Create the `rolebindings.tf` file with the following command. ```sh cat < rolebindings.tf data "github_team" "team" { slug = "$GITHUB_TEAM" } data "github_user" "team_members" { for_each = toset(data.github_team.team.members) username = each.key } data "chainguard_identity" "team_ids" { for_each = toset([for x in data.github_user.team_members : x.id]) issuer = "https://auth.chainguard.dev/" subject = "github|\${each.key}" } data "chainguard_role" "viewer" { name = "viewer" } resource "chainguard_rolebinding" "cg-binding" { for_each = data.chainguard_identity.team_ids identity = each.value.id group = "$CHAINGUARD_ORG" role = data.chainguard_role.viewer.items[0].id } EOF ``` Note that the fourteenth line of this file contains a backslash (`\`). ``` subject = "github|\${each.key}" ``` This is an escape character which will prevent the dollar sign in that line from causing a `bad substitution` error. Now that your Terraform configuration is in place, you're ready to apply it and create role-bindings for each member of your GitHub team. ## Applying your Terraform configuration First, run `terraform init` to initialize Terraform's working directory. ```sh terraform init ``` Then run `terraform plan`. This will produce a speculative execution plan that outlines what steps Terraform will take to create the resources defined in the files you set up in the last section. ```sh terraform plan ``` If the plan worked successfully and you're satisfied that it will produce the resources you expect, you can apply it. ```sh terraform apply ``` Before going through with applying the Terraform configuration, this command will prompt you to confirm that you want it to do so. Enter `yes` to apply the configuration. ```output . . . Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: ``` After pressing `ENTER`, the command will complete. ``` . . . Apply complete! Resources: 2 added, 0 changed, 0 destroyed. ``` Following this, any members of your GitHub team for whom you've created role-bindings will be able to view the resources associated with the Chainguard organization you specified. To do so, they need to log in to the Chainguard platform, either by logging into the [Chainguard Console](https://console.chainguard.dev/) or with the following command. ```sh chainctl auth login ``` After navigating to the Console or running the login command, they will be presented with the following login flow. ![Screenshot of the default Chainguard login flow. It includes the Linky logo above the words "Welcome. Log in to Chainguard to continue to Chainguard." Below this are three buttons, one reading "Continue with Google", one reading "Continue with GitHUb", and a third reading "Continue with GitLab".](login-flow.png) There, they must click the **Continue with GitHub** button to continue logging in under their GitHub account. Chainguard will immediately recognize their GitHub account because it is tied to the role-binding you created in the previous step, and they will be able to view the resources associated with the Chainguard organization specified in your Terraform configuration. ## Optional configurations The Terraform configuration used in this guide is meant to serve as a starting point, and we encourage you to tweak and expand on it to suit your organization's needs. This section contains a few alternative configurations that you may find useful. For example, rather than applying the `viewer` role to your team's role-bindings, you can apply one of the other built-in roles, or any custom roles created within your Chainguard organization. The following `data` and `resource` block examples could be used in place of the ones used in this guide's `rolebindings.tf` file. Instead of granting the identities the `viewer` role, this grants them the `editor` role. ```hcl data "chainguard_roles" "editor" { name = "editor" } resource "chainguard_rolebinding" "cg-binding" { for_each = toset(data.chainguard_identity.team_ids) identity = each.value.id group = "$CHAINGUARD_ORG" role = data.chainguard_roles.editor.items[0].id } ``` The Terraform configuration language is quite flexible. You can update your Terraform configuration to retrieve information about a single user, rather than an entire team. ``` data "github_user" "user" { username = "$USERNAME" } ``` Likewise, you can retrieve a GitHub user's Chainguard identity without having to include the `for_each` meta-argument. ``` data "chainguard_identity" "gh-user-chainguard-rb" { issuer = "https://auth.chainguard.dev/" subject = "github|${data.github_user.$USERNAME.id}" } ``` You can refer to the [Terraform language documentation](https://developer.hashicorp.com/terraform/language) for more information on extending the configuration outlined in this guide to suit your own needs. ## Removing sample resources To remove the resources Terraform created, you can run the `terraform destroy` command. ```sh terraform destroy ``` This will destroy the Chainguard role-bindings created for your GitHub team. You can then remove the working directory to clean up your system. ```sh rm -r ~/github-team/ ``` Following that, all of the example resources created in this guide will be removed from your system. ## Learn more The procedure outlined in this tutorial can be tweaked to work with other identity providers, including Google and GitLab. --- ### How to integrate Ping Identity SSO with Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/idp-providers/ping-id.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural The Chainguard platform supports Single sign-on (SSO) authentication for users. By default, users can log in with GitHub, GitLab and Google, but SSO support allows users to bring their own identity provider for authentication. This guide outlines how to create a Ping Identity application and integrate it with Chainguard. After completing this guide, you'll be able to log in to Chainguard using Ping and will no longer be limited to the default SSO options. ## Prerequisites To complete this guide, you will need the following. * `chainctl` installed on your system. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't already have this installed. * A Ping Identity account over which you have administrative access. ## Create a Ping Identity application To integrate the Ping identity provider with the Chainguard platform, [sign on to Ping Identity](https://www.pingidentity.com/en.html) and navigate to the Dashboard. Click on the **Applications** tab in the left-hand sidebar menu, and then click on **Applications** in the resulting dropdown menu. From the Applications landing page, click the plus sign (**➕**) to set up a new application.
Screenshot of the Ping Identity Dashboard, showing the applications landing page. The Applications tab in the left hand sidebar and the 'add application' plus sign icon are highlighted in yellow.

Configure the application as follows: * **Application Name**: Set a descriptive name (such as "Chainguard") and optional description to ensure users recognize this application is for authentication to the Chainguard platform. * **Icon**: You can optionally add a Chainguard logo icon here to help your users visually identify this integration. If you'd like, you can use the icon from the [Chainguard Console](https://console.chainguard.dev/logo512.png). * **Application Type**: Select **OIDC Web App**. After setting these details, click the **Save** button. Next, configure scopes for the application. In the **Overview** tab, click the **Resource Access** scope button.
Screenshot of the Overview tab, with the Resource Access scope button highlighted in a yellow box.

Add **email** and **profile** scopes, then save. Next, configure the OIDC application. Navigate to the **Configuration** tab and click the pencil-shaped "edit" icon. To configure the application, add the following settings. * **Response Type**: Select the **Code** checkbox. * **Grant Type**: Select the **Authorization Code** checkbox, and set PKCE Enforcement to "Optional." You can instead set this to "Required" if you [enable PKCE for the token exchange](/platform/administration/custom-idps/enabling-pkce/) on the Chainguard side. > Warning: Setting a grant type other than **Authorization Code** may compromise your security posture. * **Redirect URIs**: Set the Redirect URI to [`https://issuer.enforce.dev/oauth/callback`](https://issuer.enforce.dev/oauth/callback). Click the **Save** button to save your configuration. Finally, enable the Chainguard application by toggling the button in the top right corner. This completes configuration of the Ping application. You're now ready to configure the Chainguard platform to use it. ## Configure Chainguard to use Ping SSO Now that your Okta application is ready, you can create the custom identity provider. First, log in to Chainguard with `chainctl`, using an OIDC provider like Google, GitHub, or GitLab to bootstrap your account. ```sh chainctl auth login ``` Note that this bootstrap account can be used as a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) (that is, a backup account you can use to log in if you ever lose access to your primary account). However, if you prefer to remove this role-binding after configuring the custom IdP, you may also do so. To configure Chainguard make a note of the following settings from your Ping application. These can be found in the Ping console under the **Configuration** tab of the **Application** page. * Client ID * Client Secret * Issuer URL If you belong to more than one Chainguard organization, you will also need the UIDP of the one under which you want to install the identity provider. Your selection won’t affect how your users authenticate but will have implications on who has permission to modify the SSO configuration. You can retrieve a list of all the Chainguard organizations you belong to — along with their UIDPs — with the following command. ```shell chainctl iam organizations ls -o table ``` ```output ID | NAME | STATUS | DESCRIPTION ------------------------------------------|------------|--------|---------------------------------- 591**********************************c43 | sample_org | ready | A sample Chainguard organization . . . | . . . | . . . | . . . ``` Note down the `ID` value for your chosen organization. With this information in hand, create a new identity provider with the following commands. Replace ``, ``, and `` with the values from your Ping Identity application. ```sh export NAME=ping-id export CLIENT_ID= export CLIENT_SECRET= export ISSUER= chainctl iam identity-provider create \ --configuration-type=OIDC \ --oidc-client-id=${CLIENT_ID} \ --oidc-client-secret=${CLIENT_SECRET} \ --oidc-issuer=${ISSUER} \ --oidc-additional-scopes=email \ --oidc-additional-scopes=profile \ --default-role=viewer \ --name=${NAME} ``` `chainctl` installs the provider in your organization automatically when you belong to only one. If you have access to more than one, add `--parent=` to choose where it is installed. Note the `--default-role` option. This defines the default role granted to users registering with this identity provider. This example specifies the `viewer` role, but depending on your needs you might choose `editor` or `owner`. If you don't include this option, you'll be prompted to specify the role interactively. For more information, refer to the [IAM and security section](/platform/administration/custom-idps/custom-idps/#iam-and-security) of our Introduction to Custom Identity Providers in Chainguard tutorial. You can refer to our [Generic integration guide](/platform/administration/custom-idps/custom-idps/#generic-integration-guide) in our Introduction to Custom Identity Providers guide for more information about the `chainctl iam identity-provider create` command and its required options. To log in to the Chainguard Console with the new identity provider you just created, navigate to [console.chainguard.dev](https://console.chainguard.dev) and click **Use Your Identity Provider**. Next, click **Use Your Organization Name** and enter the name of the organization associated with the new identity provider. Finally, click the **Login with Provider** button. This will open up a new window with the Ping Identity login flow, allowing you to complete the login process through there. You can also use the custom identity provider to log in through `chainctl`. To do this, run the `chainctl auth login` command and add the `--identity-provider` option followed by the identity provider's ID value: ```sh chainctl auth login --identity-provider ``` The ID value appears in the `ID` column of the table returned by the `chainctl iam identity-provider create` command you ran previously. You can also retrieve this table at any time by running `chainctl iam identity-provider ls -o table` when logged in. --- ### How to manage Chainguard IAM organizations URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/iam-organizations/how-to-manage-iam-organizations-in-chainguard.md Last Modified: September 30, 2026 Tags: Chainguard Console, Procedural Chainguard provides a rich Identity and Access Management (IAM) model similar to those used by AWS and GCP. This guide outlines how to manage Chainguard's IAM structures with the [`chainctl` command line tool](/platform/chainctl/). > **Note**: You should work with Chainguard's Customer Success team to create or delete organizations. This will help to ensure that no users lose access to resources and that your IAM structure is configured correctly. ## Logging in To authenticate into the Chainguard platform, run the following login command. ```sh chainctl auth login ``` A web browser window will open to prompt you to log in via your chosen OIDC flow. Select an account with which you wish to register. Once authenticated, you can set up an organization. ## Listing organizations At any time, you can list the organizations your account has access to by using the `list` subcommand. To make it more human readable, you can output the information as a table by passing `-o table` to the end of the command. ```sh chainctl iam organizations list ``` You’ll get output regarding each of the organizations the user you're logged in as belongs to, including a description of each organization, if available. ```output [demo-org] This is a shared IAM organization for running demos [tutorial-org] This is a shared IAM organization for tutorials. ``` You can retrieve your organizations' UIDPs by adding the `-o table` option to the previous `list` command. ```sh chainctl iam organizations list -o table ``` ```output ID | NAME | DESCRIPTION ----------------------+--------------+--------------------------------- | tutorial-org | This is a shared IAM | | organization for tutorials. | demo-org | This is a shared IAM | | organization for running demos ``` Some other `chainctl` functions require you to know an organization's UIDP, making this a useful option to remember. ## Inviting others to an organization You can use `chainctl` to generate invite codes in order to invite others to a specific organization. To do so, run the following command, making sure to replace `$ORGANIZATION` with the name of your chosen organization. ```sh chainctl iam invite create $ORGANIZATION ``` You will be prompted for the scope that the invite code will be granted. After selecting the [role-bindings](/platform/administration/iam-organizations/roles-role-bindings/), this command will generate both an invite code and an invite link. If you ever lose the invite code, you can retrieve a list of active invite codes with the following `chainctl` command: ```sh chainctl iam invite list ``` This will provide output in the form of a table with the organization ID, a timestamp indicating when the invitation to the organization will expire, the invite code's key ID, and the selected role. ```output ID | EXPIRATION | KEYID | ROLE ----------------------+--------------------------+---------------+--------------------------------- | 2024-03-23T00:55:04.813Z | | [editor] Editor ``` Note that this invite code found under the `KEYID` column will be shorter than the one returned in the output of the `chainctl iam invite create` command, but they are effectively the same. To invite team members, auditors, or others to your desired organizations, securely distribute the invite code and have them log in with chainctl as follows. ```sh chainctl auth login --invite-code $INVITE_CODE ``` You can also securely distribute the invite link and have users open it in their web browser to log in and join the organization. Note that you can tighten the scope of an invite code by including the `--email` and `--ttl` flags when creating the invite. ```sh chainctl iam invite create $ORGANIZATION --email linky@example.com --ttl 24h ``` In this example, the invitation is scoped to a user with the email address `linky@example.com`. If someone with a different email address tries to use the code it will not work. The `--ttl` option in this example means that the code will expire in 24 hours. ## Learn more In addition to inviting other users to your organization, you can set up [assumable identities](/platform/administration/assumable-ids/assumable-ids/) to allow automation systems — like Buildkite or GitHub Actions — to perform certain administrative tasks for your organization. To learn more, we encourage you to check out our [Overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/) as well as our collection of [Assumable identity examples](/platform/administration/assumable-ids/identity-examples/). You may also be interested in setting up a [Custom identity provider](/platform/administration/custom-idps/custom-idps/) for your organization. By default, users can log in with GitHub GitLab, and Google, but a Custom IdP can allow members of your organization to log in to Chainguard with a corporate identity provider like [Okta](/platform/administration/custom-idps/idp-providers/okta/), [Microsoft Entra ID](/platform/administration/custom-idps/idp-providers/ms-entra-id/), or [Ping Identity](/platform/administration/custom-idps/idp-providers/ping-id/). --- ### cg-apk: the Chainguard APK registry MCP server URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/mcp-servers/cg-apk.md Last Modified: September 28, 2026 Tags: MCP, Wolfi `cg-apk` gives an AI tool read-only access to the Chainguard APK registry — the Wolfi package index that Chainguard container images are built from. This lets a client look up a package by name to find its current version, license, and origin, then read the SPDX SBOM and the melange build recipe embedded in the APK itself. That combination answers questions the package index alone cannot: not just which version of `openssl` ships today, but which upstream commit it was built from and what build definition produced it. The server uses the Streamable HTTP transport, at this endpoint: ``` https://apk.cgr.dev/mcp ``` ## Prerequisites - An MCP-compatible client such as Claude Code, Claude Desktop, or Cursor - A [Chainguard account](https://console.chainguard.dev/) ## Connect to the server ### Claude Code Add the server with `claude mcp add`, using the HTTP transport: ```shell claude mcp add --transport http cg-apk https://apk.cgr.dev/mcp ``` Pick the scope that fits how you want to use it: `local` (the default — only you, in the current directory), `project` (writes a shared `.mcp.json` at the repository root, checked in for teammates), or `user` (only you, across every project). Package lookups are useful in any repository, so `--scope user` is usually the right choice: ```shell claude mcp add --transport http --scope user cg-apk https://apk.cgr.dev/mcp ``` The server is added unauthenticated. To complete OAuth, start a session and run the `/mcp` command: ```Prompt /mcp ``` Select **cg-apk**, choose **Authenticate**, and approve the connection in the browser window that opens. Check the status any time with: ```shell claude mcp list ``` ```output cg-apk: https://apk.cgr.dev/mcp (HTTP) - ✓ Connected ``` ### Cursor Cursor supports HTTP transport natively. Add the server to your MCP configuration: ```json { "mcpServers": { "cg-apk": { "url": "https://apk.cgr.dev/mcp" } } } ``` Restart Cursor, then connect the server from **Tools & MCPs** in settings and complete the browser sign-in. ### Claude Desktop Claude Desktop reads MCP servers from a JSON file but does not yet support HTTP transport directly. Use [`mcp-remote`](https://github.com/geelen/mcp-remote) to bridge to the hosted server: ```json { "mcpServers": { "cg-apk": { "command": "npx", "args": [ "mcp-remote", "https://apk.cgr.dev/mcp" ] } } } ``` The configuration file lives at: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` `npx` downloads and runs `mcp-remote` on demand, so you need Node.js installed on the host. Restart Claude Desktop after saving the file. ### Other MCP clients Any client that supports a remote Streamable HTTP MCP server with OAuth can connect to the same endpoint. Point it at `https://apk.cgr.dev/mcp` and complete the browser sign-in when prompted. ## Authentication Authentication is OAuth 2.0 against the Chainguard issuer. Results are scoped to the APK repositories your account can read. On a remote or headless workstation, you can supply a token from `chainctl` instead of completing the browser flow. Refer to [Authenticate with chainctl instead of a browser](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser) for the full recipe; `cg-apk`'s audience is `https://apk.cgr.dev/mcp`. ## Tool reference The server exposes three tools. `search_packages` is the entry point, and its results include the four values the other two tools require, so a session normally starts with a search. | Tool | Parameters | Returns | Example prompt | | ----- | ----- | ----- | ----- | | `search_packages` | `name`, `exact`, `origin`, `all_versions`, `arch`, `page_size`, `cursor` | `{name, count, matches, page_size, next_cursor}` | "What version of openssl is in Wolfi?" | | `get_sbom` | `package_name`, `version`, `architecture`, `scope` | `{sbom}` — raw SPDX JSON | "What upstream source was the jq package built from?" | | `get_melange_config` | `package_name`, `version`, `architecture`, `scope` | `{melange_configuration}` — raw YAML | "Show me the melange build recipe for jq" | ### search_packages Looks up APK packages by name, one bounded page at a time. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name` | string | yes | Package name to search for. A case-insensitive substring match by default. | | `exact` | boolean | no | Require an exact name match (`-e`/`--exact`). Uses an indexed lookup; prefer it whenever you know the name. | | `origin` | boolean | no | Treat `name` as an origin (`-o`/`--origin`), returning the main package and all its subpackages | | `all_versions` | boolean | no | Return every version rather than only the latest (`-a`/`--all`) | | `arch` | string | no | Filter by architecture, such as `x86_64` or `aarch64` (default `x86_64`) | | `page_size` | integer | no | Results per page (default 50, max 200) | | `cursor` | string | no | The `next_cursor` from a previous call | Each match returns `package_name`, `version`, `architectures`, `description`, `license`, `origin`, and `scope`. Keep the following in mind when you search: - **Substring matching is the default, and it is the expensive path.** A search with only `name` set scans the whole index, like `apk search`. When you already know the package name, pass `exact=true` for an indexed lookup instead. Searching `libxml` returns every package whose name contains that string; searching `libxml2` with `exact=true` returns only that package. - **In exact and substring searches, the same package appears once per repository scope you can read.** `scope` is the UIDP of the APK repository a given copy lives in, and a package built into several repositories returns one match per repository. An exact search for `openssl` can return several identical rows that differ only in `scope`. This is not duplication in the index; it reflects the repositories your token reaches. `origin` is the most useful flag for migration work. Given `origin=true` and the name of a source package, it returns the main package and every subpackage built from it, such as `-dev`, `-doc`, and `-static`, which helps when you replace a Debian development package such as `libssl-dev` with its Wolfi equivalent. An `origin=true` search also collapses the per-scope duplicates, returning one row per package with a combined architecture list. ### get_sbom Fetches the SPDX SBOM document embedded in an APK's filesystem, at `var/lib/db/sbom/-.spdx.json`. The response is `{sbom}`, the raw SPDX JSON as a string. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `package_name` | string | yes | The APK package name, matching `package_name` from a search result | | `version` | string | yes | The package version, matching `version` from a search result | | `architecture` | string | yes | A single architecture, such as `x86_64`. Search results return `architectures` as a comma-joined list; pick one. | | `scope` | string | yes | The repository UIDP, matching `scope` from a search result | All four values come from `search_packages`, so this chains directly off a search. The document records the upstream source the package was generated from — for `jq` 1.8.2-r2, a `GENERATED_FROM` relationship pointing at the exact `github.com/jqlang/jq` commit — alongside the melange definition that built it and the APK's own metadata. The SBOM is the last file in the package, so the server downloads the whole APK to read it. It refuses a package larger than 64 MB before downloading, and a response over 512 KB rather than truncating it. For an oversized package, run `apk fetch` locally and read the file under `var/lib/db/sbom/`. ### get_melange_config Fetches the `.melange.yaml` build recipe embedded in an APK's control section. The response is `{melange_configuration}`, the raw YAML as a string. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `package_name` | string | yes | The APK package name, matching `package_name` from a search result | | `version` | string | yes | The package version, matching `version` from a search result | | `architecture` | string | yes | A single architecture, such as `x86_64` | | `scope` | string | yes | The repository UIDP, matching `scope` from a search result | APKs built before melange configs were embedded return an error rather than an empty result. The same size limits apply as for `get_sbom`: 64 MB for the package and 512 KB for the response. Some recipes are still large — `openssl`'s runs past 60,000 characters — so expect a client to truncate or summarize rather than print one in full. ## Example usage Sample exchanges from a Claude Code session with `cg-apk` connected. Package versions change often, so your answers will differ. ```Prompt What version of openssl is in Wolfi right now? ``` ```output Called cg-apk (ctrl+o to expand) ● Wolfi currently ships openssl 3.6.4 for x86_64, licensed Apache-2.0. It appears in five repository scopes your account can read, all at the same version. ``` ```Prompt I need the Wolfi equivalents of libxml2-dev. What's available? ``` ```output Called cg-apk (ctrl+o to expand) ● The libxml2 origin builds seven packages for aarch64 and x86_64: libxml2, libxml2-16, libxml2-dev, libxml2-doc, libxml2-py3, libxml2-static, and libxml2-utils. libxml2-dev is the direct equivalent. ``` ```Prompt What upstream commit was the jq package built from? ``` ```output Called cg-apk (ctrl+o to expand) ● jq 1.8.2 was generated from jqlang/jq at commit 34f7186b, fetched as the jq-1.8.2 archive. The package is licensed MIT. ``` ## Troubleshooting | Symptom | What it means | What to do | | ----- | ----- | ----- | | A search returns nothing | The substring matched no package name. Names in Wolfi often differ from their Debian or Fedora equivalents. | Try a shorter substring, or look up the mapping with the [AI Docs MCP server](/platform/mcp-servers/ai-docs/)'s `find_package_equivalent` tool, or the [package comparison tool](https://images.chainguard.dev/). | | A search is slow | A bare `name` runs a wildcard scan of the whole index. | Pass `exact=true` when you know the name. | | The same package appears several times | One match per repository scope your token can read. | Use whichever `scope` you intend to pull from; if you only consume the public index, any of them resolves to the same content. | | `get_sbom` or `get_melange_config` returns HTTP 401 or 403 | The `scope` you passed names a repository your token cannot read. | Re-run `search_packages` and use a `scope` from its results rather than one carried over from another session or account. | | `get_sbom` or `get_melange_config` is refused for size | The package exceeds 64 MB, or the response exceeds 512 KB. Responses are never truncated. | Run `apk fetch` locally and read the SBOM under `var/lib/db/sbom/`, or the `.melange.yaml` in the package's control section. | | `get_melange_config` returns an error for a package that exists | The APK predates embedded melange configs. | Call `get_sbom` instead. The SBOM still records the upstream source the package was built from. | | Server shows as not connected in `claude mcp list` | OAuth was never completed, or the token expired. Claude Code's tokens against the Chainguard issuer last about an hour and carry no refresh token. | Run `/mcp`, select **cg-apk**, and authenticate again, or switch to the [`chainctl` helper](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser). | | `401 invalid token` when using the `chainctl` helper | The audience was registered as a bare hostname. MCP audiences must include the `/mcp` path. | Run `chainctl auth login --audience=https://apk.cgr.dev/mcp` and try again. | ## Next steps - [`cg-oci`](/platform/mcp-servers/cg-oci/) — read manifests, SBOMs, and provenance for container images - [`cg-versions`](/platform/mcp-servers/cg-versions/) — check upstream releases and end-of-life dates - [`cg-api`](/platform/mcp-servers/cg-api/) — query organizations, IAM, and registry metadata through the platform API - [Chainguard MCP servers overview](/platform/mcp-servers/overview/) — the full set, and the `chainctl` authentication recipe --- ### Pull token output formats and credential names URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/pull-token-output.md Last Modified: September 28, 2026 Tags: chainctl, Reference `chainctl auth pull-token create` returns two values: * An **identity ID**, the identifier of the pull token identity that `chainctl` just created. It takes the form `ORGANIZATION_ID/TOKEN_ID`, where both parts are hexadecimal strings. * A **token**, a JSON Web Token that authenticates as that identity until the token's time to live expires. Every tool that consumes a pull token takes that pair as a username and a password for [HTTP basic authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication#basic_authentication_scheme): the identity ID is the username, and the token is the password. What changes between output formats is only the label. ## Terminology mapping | Value | Default output | `--output=json` | `--output=env` | | --- | --- | --- | --- | | Identity ID | `Username` | `identity_id` | `CHAINGUARD_IDENTITY_ID` or `CHAINGUARD__IDENTITY_ID` | | Token | `Password` | `token` | `CHAINGUARD_TOKEN` or `CHAINGUARD__TOKEN` | The Chainguard Console labels the same two values **Username** and **Password** when it displays a new access token. `chainctl auth pull-token` without a subcommand is equivalent to `chainctl auth pull-token create`, so the formats described here apply to both. `chainctl auth pull-token create` supports two output formats, `env` and `json`, plus the default output you get when you pass no `--output` flag at all. ## Default output With no `--output` flag, `chainctl` prints instructions for the repository type you asked for. For `--repository=oci`, the default, it prints a ready-to-run `docker login` command: ```sh chainctl auth pull-token create ``` ```output To use this pull token in another environment, run this command: docker login "cgr.dev" --username "" --password "" ``` The `--username` value is the identity ID and the `--password` value is the token. Both work with any tool that logs in to an OCI registry, including Podman, Helm, and registry mirroring tools. Refer to [Authenticate to Chainguard's Registry](/chainguard/containers/registry/authenticating/#using-a-pull-token-with-podman-helm-and-other-tools) for examples. For every other repository type, `chainctl` prints the pair as a username and a password: ```sh chainctl auth pull-token create --repository=java ``` ```output To use this pull token in another environment, supply the following for Basic authorization: Username: Password: ``` ## JSON output `--output=json` prints one compact object with an `identity_id` field and a `token` field: ```sh chainctl auth pull-token create --repository=java --output=json ``` ```output {"identity_id":"","token":""} ``` The field names stay the same for every repository type. Pipe the object to `jq` or another JSON processor to extract either value: ```sh TOKEN_JSON=$(chainctl auth pull-token create --repository=java --output=json) USERNAME=$(echo "$TOKEN_JSON" | jq -r '.identity_id') PASSWORD=$(echo "$TOKEN_JSON" | jq -r '.token') ``` ## Environment output `--output=env` prints two `export` statements, one per value: ```sh chainctl auth pull-token create --repository=java --output=env ``` ```output export CHAINGUARD_JAVA_IDENTITY_ID= export CHAINGUARD_JAVA_TOKEN= ``` Wrap the command in `eval` to run those `export` statements, which sets both variables in your current session: ```sh eval $(chainctl auth pull-token create --repository=java --output=env) ``` The variable names depend on the repository type. For a library ecosystem, `chainctl` uppercases the `--repository` value and inserts it into the name; for `oci` and `apk` it uses the unqualified names. | `--repository` | Identity ID variable | Token variable | | --- | --- | --- | | `oci` (default) | `CHAINGUARD_IDENTITY_ID` | `CHAINGUARD_TOKEN` | | `apk` | `CHAINGUARD_IDENTITY_ID` | `CHAINGUARD_TOKEN` | | `java` | `CHAINGUARD_JAVA_IDENTITY_ID` | `CHAINGUARD_JAVA_TOKEN` | | `javascript` | `CHAINGUARD_JAVASCRIPT_IDENTITY_ID` | `CHAINGUARD_JAVASCRIPT_TOKEN` | | `python` | `CHAINGUARD_PYTHON_IDENTITY_ID` | `CHAINGUARD_PYTHON_TOKEN` | Because the ecosystem name is part of the variable, credentials for two ecosystems can coexist in one shell session or one secrets file: ```sh eval $(chainctl auth pull-token create --repository=java --output=env) eval $(chainctl auth pull-token create --repository=python --output=env) ``` Each invocation creates a new pull token identity. Write the `export` statements to a file or a secrets manager rather than rerunning the command whenever you need the values again, because `chainctl` displays the token only once. ### Chainguard and tool-specific variables `chainctl` emits variables starting with `CHAINGUARD_`. However, build tools that read credentials from the environment typically use their own names, and don't read the `CHAINGUARD_*` variables directly. In such cases, you must map one to the other explicitly. For example, `uv` reads index-scoped credentials from `UV_INDEX__USERNAME` and `UV_INDEX__PASSWORD`, where `` is the index name in uppercase, with underscores replacing hyphens. For an index named `chainguard`: ```sh export UV_INDEX_CHAINGUARD_USERNAME="${CHAINGUARD_PYTHON_IDENTITY_ID}" export UV_INDEX_CHAINGUARD_PASSWORD="${CHAINGUARD_PYTHON_TOKEN}" ``` The same pattern applies wherever a tool defines its own variable, such as the `HTTP_AUTH` variable used for [private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/#pull-token-automation): ```sh export HTTP_AUTH="basic::${CHAINGUARD_IDENTITY_ID}:${CHAINGUARD_TOKEN}" ``` Names you choose yourself, such as GitHub Actions secrets, are also independent of the `CHAINGUARD_*` convention. Whatever you call them, the identity ID is the username and the token is the password. ## Why the `--output` help text lists more formats If you pass any other value, `chainctl` prints a warning to standard error, then falls back to default output. Requesting `csv`, for example, produces a warning along these lines: ```output "csv" is not a supported output. Supported: [ env json]. Using print to command line ``` Even when a command returns this warning, it still creates the pull token. It refuses only the formatting request, so a script that expects machine-readable output on standard output receives prose instead. This matters most for `eval`: `eval $(chainctl auth pull-token --output=csv)` creates a token, sends the warning to your terminal, and then tries to run the human-readable text as shell commands. The `--output` flag is global, so `chainctl --help` and every reference page describe it the same way: ```output -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] ``` That list is the union of every format any `chainctl` command supports, not a list of formats that all commands support. Each command declares its own subset. Table-shaped commands such as `chainctl iam identities list` accept `csv` and `markdown`; `pull-token create` returns a single credential pair and accepts only `env` and `json`. ## Related pages * [Authenticate to Chainguard's Registry](/chainguard/containers/registry/authenticating/#authenticating-with-a-pull-token) for container pull tokens * [Chainguard Libraries access](/chainguard/libraries/introduction/access/#pull-token) for library pull tokens * [Private APK repositories](/chainguard/containers/building-and-modifying/packages/private-apk-repos/#pull-token-automation) for APK pull tokens * [chainctl auth pull-token create](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token_create/) for the generated flag reference * [Automating with chainctl](/platform/chainctl-usage/automating-chainctl/) for other scripting patterns --- ### How to provision users into Chainguard from Microsoft Entra ID with SCIM URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/scim-provisioning/ms-entra-id-scim.md Last Modified: October 2, 2026 Tags: Procedural The Chainguard platform supports SCIM 2.0 provisioning from Microsoft Entra ID. With SCIM enabled, Entra ID creates, updates, and deactivates Chainguard users as you assign and unassign them in your directory, and Chainguard links each provisioned user to their single sign-on (SSO) identity the first time they log in. This guide outlines how to register an Entra ID application for SSO, create a Chainguard identity provider for it, and configure an Entra ID enterprise application for SCIM provisioning. Set up SSO first, since provisioning links users to their SSO logins. For provisioning behavior, token lifecycle, and limits, refer to [Provision Chainguard users with SCIM](/platform/administration/custom-idps/scim-provisioning/scim-overview/). ## Prerequisites To complete this guide, you need the following: * `chainctl` installed on your system. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't already have this installed. * Owner permissions on the Chainguard organization where you want to install the identity provider. * The [prerequisites for SCIM provisioning](/platform/administration/custom-idps/scim-provisioning/scim-overview/#prerequisites), including two owners with directly assigned role bindings in your organization. * An Entra ID account with Global Administrator or Application Administrator permissions. Without these, you cannot register applications or assign users to them. * An Entra ID P1 or P2 license. The **Automatic** provisioning mode this guide uses requires one. * A workforce Entra ID tenant. External ID (CIAM) tenants use a different application model, and this guide doesn't cover them. ## Register the Entra ID application for SSO Log in to the [Microsoft Entra admin center](https://entra.microsoft.com), navigate to **Entra ID** > **App registrations**, and click **New registration**. Configure the application as follows. * **Name**: Set the name to "Chainguard" (or similar) so users recognize this application is for authentication to Chainguard. * **Supported account types**: Select **Single tenant** so that only your organization can use this application to authenticate to Chainguard. * **Redirect URI**: Set the platform to **Web** and the URI to `https://issuer.enforce.dev/oauth/callback`. Click **Register**. From the application's **Overview** tab, note the **Application (client) ID** and the **Directory (tenant) ID**. Then open **Certificates & secrets**, click **New client secret**, set a description and expiration, and note the secret **Value**. Entra ID shows the Value only once, and SSO stops working when the secret expires. You'll need all three values in the next step. ## Create the Chainguard identity provider Log in to Chainguard with `chainctl`, using an OIDC provider like Google, GitHub, or GitLab to bootstrap your account. ```sh chainctl auth login ``` This bootstrap account can serve as a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) if you ever lose access to your primary login. If you belong to more than one organization, retrieve the ID of the one where you want to install the identity provider. ```sh chainctl iam organizations ls -o table ``` Then create the identity provider. Replace ``, ``, and `` with the three values from the previous step. ```sh export NAME=entra-id export CLIENT_ID= export CLIENT_SECRET= export TENANT_ID= export ISSUER="https://login.microsoftonline.com/${TENANT_ID}/v2.0" chainctl iam identity-providers create \ --configuration-type=OIDC \ --name=${NAME} \ --oidc-issuer=${ISSUER} \ --oidc-client-id=${CLIENT_ID} \ --oidc-client-secret=${CLIENT_SECRET} \ --oidc-correlation-rule=oid_equals_external_id \ --oidc-additional-scopes=email \ --oidc-additional-scopes=profile \ --default-role=viewer \ -o json ``` `chainctl` installs the provider in your organization automatically when you belong to only one. If you have access to more than one, add `--parent=` to choose where it is installed. This `create` command includes two options specific to SCIM linking with Entra ID: * `--oidc-correlation-rule=oid_equals_external_id` links each login to its provisioned user by the Entra ID `oid` claim. It is immutable — to change it, delete and recreate the identity provider. Confirm it appears in the command's JSON output, and keep that output as your record of the setting. * `--oidc-additional-scopes=profile` is required: Entra ID includes the `oid` claim only when you request `profile`. Pass `--oidc-additional-scopes` once per scope. Set an environment variable to the identity provider's `id` from the previous command's output: ```sh export IDP= ``` Then generate a provisioning token: ```sh chainctl iam identity-providers scim token generate ${IDP} ``` The `token generate` command prints the **SCIM base URL** for the identity provider and a **bearer token** beginning with `cgscim_`. Note both now — Chainguard shows the token only once. To replace or revoke it later, use the `scim token regenerate` and `scim token revoke` commands described in [Provision Chainguard users with SCIM](/platform/administration/custom-idps/scim-provisioning/scim-overview/#manage-provisioning). Following that, enable SCIM for the IdP: ```sh chainctl iam identity-providers scim enable ${IDP} ``` The order of these commands is important: you must generate the token first, as `scim enable` fails with `etag: etag is required` if the identity provider has no token issued yet. ## Configure the Entra ID enterprise application for SCIM Provisioning runs from a separate non-gallery enterprise application. In the Entra admin center, navigate to **Entra ID** > **Overview**. Click **+ Add** and select **Enterprise applications**. Click **Create your own application**, name it (for example, "Chainguard SCIM"), select **Integrate any other application you don't find in the gallery (Non-gallery)**, and click **Create**. ### Connect to the SCIM endpoint Open the enterprise application's **Overview** page, navigate to the **Provisioning** tab, and click **+ New configuration**. From there, configure the connection: * **Select authentication method**: **Bearer authentication**. * **Tenant URL**: The SCIM base URL from the previous step with `?aadOptscim062020` appended, which opts Entra ID into SCIM 2.0-compliant behavior. * To illustrate, the URL should have the following structure: ```url https://scim.enforce.dev/scim/v2//?aadOptscim062020 ``` * **Secret token**: The bare `cgscim_...` token. Do not add a `Bearer` prefix — Entra ID adds the scheme itself. Click **Test connection** to confirm the URL and token — the test must succeed — then **Save**. ### Map externalId to objectId Navigate to the **Attribute mapping** tab and select **Users**. Edit the mapping whose **Target attribute** is **`externalId`**, and change its **Source attribute** from the default (`mailNickname`) to **`objectId`** (`objectId` is in the source-attribute dropdown). Leave the mapping type as **Direct**, then click **Apply**.

Note: Make this change before provisioning any user, including with Provision on demand, and do not change it afterward. objectId is the value the identity provider matches against at login; changing it after users exist leaves those users permanently unlinked. Map externalId only to objectId — a user-editable or non-unique attribute could link a login to the wrong identity.

On the same page, delete every mapping whose **Target attribute** contains a value filter, such as `addresses[type eq "work"].streetAddress` or `phoneNumbers[type eq "work"].value`. Chainguard's SCIM endpoint doesn't support value-filter paths. One is enough to make it reject the entire request with `400 invalidPath`, so nothing in that update reaches Chainguard — deactivations included. Chainguard also doesn't store these attributes, so Entra ID finds the same gap on every cycle and retries the failing update until you delete the mappings. Keep the plain-path mappings. `userName` maps from `userPrincipalName`, which is the attribute Chainguard matches on. `active` maps through the default expression `Switch([IsSoftDeleted], , "False", "True", "True", "False")`, which inverts `IsSoftDeleted` so that a user Entra ID soft-deletes or unassigns arrives at Chainguard as inactive — this is what drives deactivation. If the application shows a **Provision Microsoft Entra ID Groups** mapping, disable it; Chainguard's SCIM endpoint accepts user provisioning only. ### Assign users and turn on provisioning Under the application's **Users and groups**, assign the users or groups to provision. On the **Provisioning** page, set **Provisioning Mode** to **Automatic**, and under **Settings** set **Scope** to **Sync only assigned users and groups**. Then set **Provisioning Status** to **On** and click **Save** to begin. Entra ID runs its provisioning cycle about every 40 minutes. While testing, use **Provision on demand** to push a user immediately. ## Verify the integration Provision a user, then query the SCIM API to confirm the user's `externalId` is their `objectId` — a lowercase GUID, not a mail nickname. Replace `` and `` with the bearer token and SCIM base URL from the `token generate` command. ```sh curl -s -H "Authorization: Bearer " "/Users?count=5" ``` Then have that user log in at [console.chainguard.dev](https://console.chainguard.dev): click **Use Your Identity Provider**, then **Use Your Organization Name**, enter your organization name, and click **Login with Provider**. The first login must go through this browser flow for linking to complete. Afterward the user appears in your organization with the identity provider's default role. ## Map Entra ID groups to Chainguard roles Chainguard's SCIM endpoint provisions users only. To grant Chainguard roles based on a user's Entra ID group membership, follow [Grant Chainguard roles from identity provider groups](/platform/administration/custom-idps/grant-roles-from-groups/). Group membership reaches Chainguard only in tokens issued for the SSO application you registered in [Register the Entra ID application for SSO](#register-the-entra-id-application-for-sso), not the SCIM enterprise application. Assigning a group to the SCIM application provisions its members but doesn't add the group to their login tokens. If you limit the groups claim to groups assigned to the application, assign each mapped group to the SSO application as well. For the full list of Entra ID settings, refer to [Microsoft Entra ID](/platform/administration/custom-idps/grant-roles-from-groups/#microsoft-entra-id) in the group mapping guide. Entra ID emits group Object IDs (GUIDs) rather than display names in the groups claim. As a result, a group-to-role mapping displays the group as a GUID, such as `aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee`, in both `chainctl` and the console's group-mapping tab. To find which group an Object ID refers to, look it up in the Entra admin center under **Groups**. To display readable names instead, configure the groups claim to emit cloud-group display names. ## Deactivation and deletion Unassigning a user from the application, disabling their account, or deleting them causes Entra ID to deactivate or delete the corresponding Chainguard identity on its next provisioning cycle. Chainguard rejects the login of a user deactivated before they ever logged in. For immediate offboarding, use **Provision on demand** on the affected user rather than waiting for the cycle. ## Troubleshooting ### A user logged in but wasn't linked A correlation mismatch does not error — the login proceeds as a normal, unlinked user. Confirm that you created the identity provider with `--oidc-correlation-rule=oid_equals_external_id` and `profile` in its scopes (check the saved `create` output), that the user's `externalId` is their `objectId` GUID, and that the user has logged in through the browser flow described earlier. ### Login fails, or the identity provider isn't offered This is an SSO problem, not SCIM. Recheck the redirect URI, issuer, and client ID from the SSO application. ### Test connection fails Confirm the Tenant URL is the SCIM base URL with `?aadOptscim062020` appended and the Secret token is the bare `cgscim_...` value. The URL and token must come from the same `token generate` run. **Provisioning is quarantined.** Entra ID pauses provisioning after repeated errors. Fix the error shown in the provisioning logs and restart provisioning. ## Related resources * [Provision Chainguard users with SCIM](/platform/administration/custom-idps/scim-provisioning/scim-overview/) * [Grant Chainguard roles from identity provider groups](/platform/administration/custom-idps/grant-roles-from-groups/) * [Overview of the Chainguard IAM model](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/) --- ### Migrating from API v1 to API v2 URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/api/api-v2-migration.md Last Modified: September 28, 2026 Tags: Chainguard Console, Procedural Chainguard's API v2 is now Generally Available (GA) across every product domain: Customer Platform (IAM), Containers, Ecosystems, and Integrations. At GA, the "beta" designation is dropped: endpoints move from `/v2beta1/` to `/v2/`. This guide covers what changed from v1, and the steps to migrate an existing direct integration. If you only use `chainctl`, the Chainguard Console, or the Terraform provider, you don't need this guide — those clients handle versioning for you, and nothing changes on your end. This guide is for anyone calling the API directly: `curl`, gRPC, or a custom SDK integration. v1 continues to work during a transition period, so nothing breaks today. Migrate at your own pace; see [Timeline](#timeline) for what happens next. ## Prerequisites You'll need: - A valid Chainguard identity with access to the resources you're calling. - [`chainctl` installed](/platform/chainctl-usage/how-to-install-chainctl/), to mint a token for testing (`chainctl auth token`). Set up your environment for the following examples: ```shell export TOKEN=$(chainctl auth token) export API=https://console-api.enforce.dev # ORG_ID is the UID of your root organization group export ORG_ID=YOUR_ORG_ID ``` ## What's not changing - **Authentication**: same OIDC token model as v1. - **Authorization**: same identity-based access control. - **Scoping**: same `uidp.descendants_of` / `uidp.children_of` hierarchy filters. - **Host**: same API host (`console-api.enforce.dev` in production). - **CREATE shape**: the parent resource stays in the URL path (`/groups/{parent}`), and the request body carries the resource fields directly. This is unchanged from v1. If your integration already authenticates against v1 successfully, no credential or auth-flow changes are needed to call v2. ## Quick reference | Operation | v1 pattern | v2 pattern | | --- | --- | --- | | Path versioning | Versioned (`/iam/v1/`, `/registry/v1/`, `/vulnerabilities/v1/`, `/libraries/v1/`, `/advisory/v1/`) | Same domains with the `/v2/` segment | | Field naming | Inconsistent (`id`, `created_at`, `group_id`) | Consistent camelCase (`uid`, `createTime`/`updateTime`) | | CREATE | Parent resource identified in the URL path | No change — parent stays in the URL path | | GET (single resource) | No dedicated endpoint; filter a List call | Dedicated Get endpoint, fetch by `uid` | | LIST | Offset/page-number params, full result set, no total count | `page_size`, `page_token`, `skip`, `order_by`, `total_count` | | UPDATE | PUT, full-resource replacement | PATCH, partial update via a field mask | | DELETE | Same path pattern, ambiguous failure on blocked deletes | Same path pattern, returns a `PreconditionFailure` detail when blocked | | Errors | Ambiguous — a missing resource can return an empty HTTP 200 result | Structured: `ErrorInfo`, `BadRequest`, `ResourceInfo`, `PreconditionFailure`, with proper status codes | ### Path prefixes by domain | Domain | v1 path prefix | v2 path prefix (GA) | | --- | --- | --- | | IAM (Customer Platform: groups, roles, identities, role bindings) | `/iam/v1/` | `/iam/v2/` | | Registry (Containers) | `/registry/v1/` | `/registry/v2/` | | Vulnerabilities | `/vulnerabilities/v1/` | `/vulnerabilities/v2/` | | Libraries (Ecosystems) | `/libraries/v1/` | `/libraries/v2/` | | Advisory (Integrations) | `/advisory/v1/` | `/advisory/v2/` | If you built against the beta endpoints, updating the version segment from `v2beta1` to `v2` is the only path change — the request and response shapes don't change as part of that rename. ## Migration steps ### Step 1: Update your endpoint paths Change the version segment from `v1` to `v2` for each domain you call, using the [path prefix table](#path-prefixes-by-domain). If you were already on `/v2beta1/`, rename it to `/v2/`. ### Step 2: Update field references v2 standardizes field naming across every service: - Resource identifiers are always `uid`, never `id`. - Timestamps are `createTime` / `updateTime`, not `created_at` / `updated_at`. - All fields use camelCase. Update any code that reads specific field names out of API responses — these differ from v1 even for the same resource. ### Step 3: Switch updates to PATCH with a field mask v1 uses PUT semantics: changing one field means sending the entire resource back, which forces a fetch-before-write pattern and creates race conditions on concurrent updates. v2 uses PATCH with a field mask, so you specify only the fields you're changing. ```shell curl -X PATCH -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos/$REPO_UID?updateMask=description" \ -d '{"description": "updated description"}' ``` Replace fetch-modify-write update logic with a PATCH call that sets only the fields you're changing. A wildcard `*` is available if you genuinely want full replacement. Repository updates (`UpdateRepo`) are a common case to migrate here. For a worked repo PATCH example, see [Partial updates with FieldMask](/platform/api/api-v2-tutorial/#7-partial-updates-with-fieldmask) in the tutorial. ### Step 4: Switch pagination to cursor-based v1 pagination relies on page numbers or offsets, and loads the full result set into memory. v2 List endpoints add: - `page_size`: how many items to return per page. - `page_token`: the opaque cursor from the previous response's `next_page_token`, used to fetch the next page. Omit it for the first page. - `skip`: number of items to skip, for jumping directly to a specific page without walking the cursor sequentially. - `order_by`: server-side ordering, instead of sorting client-side after fetching. - `total_count`: total matching items, so you don't have to infer it from page math. ```shell curl -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . ``` Replace any page-number or offset logic with a loop that follows `page_token` until the response stops returning one. Use `skip` instead if you need to jump to an arbitrary page. Adopt `order_by` and drop any client-side sort you were doing to compensate. ### Step 5: Use dedicated Get endpoints v1 typically requires filtering a List call to fetch a single resource. v2 adds a dedicated Get endpoint per resource, fetched by `uid` directly. ```shell curl -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos/$REPO_UID" ``` Replace any "list and filter to one item" pattern with the dedicated Get endpoint for that resource. ### Step 6: Update your error handling In v1, an empty or ambiguous result can mean several different things — no matches, no permission, or a missing parent resource — with no way for a client to tell them apart. For example, requesting a resource that doesn't exist in v1 returns an empty result with HTTP 200. v2 returns proper status codes (for example, `NOT_FOUND` / HTTP 404) with structured error details, following the same error model used across Google Cloud APIs: - **ErrorInfo**: a machine-readable reason code and error domain. - **BadRequest**: per-field validation errors. - **ResourceInfo**: which specific resource type and name wasn't found. - **PreconditionFailure**: why a request was blocked (for example, a delete blocked by a dependent resource). If your integration currently treats an empty v1 result as ambiguous, or works around that ambiguity with extra calls, update it to branch on the v2 status code and structured error details instead. Re-test your error-handling paths, not just the happy path. ### Step 7: Update gRPC and SDK clients All v2 endpoints are also available directly via gRPC, at the same host. Proto definitions are at `chainguard.dev/sdk/proto/chainguard/platform/`. At GA the `v2beta1` designation is dropped across REST paths, gRPC proto packages, and the Go SDK. If you call the API via generated gRPC clients, regenerate them against the `v2` packages; the Go SDK clients aggregation moves to `chainguard.dev/sdk/proto/chainguard/platform/clients/v2`. ## Full example: listing and deleting repos **Before (v1):** ```shell curl -H "Authorization: Bearer $TOKEN" \ "$API/registry/v1/repos?uidp.descendants_of=$ORG_ID" ``` **After (v2):** ```shell curl -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos?uidp.descendants_of=$ORG_ID&page_size=3&order_by=name" | jq . ``` Delete follows the same path-prefix change, with no other behavior change: ```shell curl -X DELETE -H "Authorization: Bearer $TOKEN" \ "$API/registry/v2/repos/$REPO_UID" ``` A `DELETE` returns an empty response body on success, same as v1. A delete blocked by a dependent resource now returns a `PreconditionFailure` error detail explaining why, instead of an ambiguous failure. ## Timeline v1 remains fully supported alongside v2. This release sets no sunset date and adds no deprecation or sunset response headers. When a deprecation timeline is set, Chainguard will announce it separately, with generous advance notice. You don't need to wait for a deadline to start: both versions are available today, and you can migrate incrementally. ## Troubleshooting **Will my existing integration stop working today?** No. v1 keeps working during the parallel availability period. Nothing changes for existing integrations until the sunset date, which will be communicated well in advance. **Do I need new credentials for v2?** No. Authentication and authorization are unchanged — the same tokens and identities that work with v1 work with v2. **Why did an API call that used to return an empty list now return an error?** This is expected, and it's an improvement. In v1, a missing resource and an empty result look the same, so there was no way to tell "nothing matched" apart from "that resource doesn't exist" or "you don't have permission." v2 returns a specific, structured error in those cases instead. **Where do I send a customer who asks about migrating?** This guide. If you're on the account or support team and hit something this guide doesn't cover, reach out to Engineering rather than guessing at specifics. ## Next steps - [Chainguard API v2 Tutorial](/platform/api/api-v2-tutorial/): worked examples of the v2 endpoints you'll be migrating to. - [Chainguard API v2 Specification](/platform/api/spec-api-v2/): full OpenAPI reference. - If you run into issues migrating, reach out to your Chainguard account team or refer to [Get support](/get-started/get-support/). --- ### Grant Chainguard roles from identity provider groups URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/grant-roles-from-groups.md Last Modified: October 2, 2026 Tags: Chainguard Containers, Procedural Chainguard can grant roles based on a user's groups in your identity provider (IdP). You map an IdP group to a Chainguard role once, and from then on any user who logs in with that group in their token receives the role for that session. Access follows group membership, so you manage who gets what in your IdP instead of assigning roles to each user in Chainguard. This guide covers Okta and Microsoft Entra ID. The Chainguard-side steps (2 through 4) are the same for both providers; only how you emit group membership in Step 1 differs. ## Prerequisites To complete this guide, you need the following: - A custom identity provider (such as [Okta](/platform/administration/custom-idps/idp-providers/okta/) or [Microsoft Entra ID](/platform/administration/custom-idps/idp-providers/ms-entra-id/)) already configured for login to Chainguard. If you haven't set one up yet, refer to our guide [Using custom identity providers to authenticate to Chainguard](/platform/administration/custom-idps/custom-idps/). - An IAM role that can manage identity providers and role-bindings in your organization, such as the `owner` role. - [`chainctl` installed](/platform/chainctl-usage/how-to-install-chainctl/) on your local machine. You must also authenticate with `chainctl auth login`. The rest of this guide refers to your organization and identity provider by their UIDPs, stored in the `ORGANIZATION` and `IDENTITY_PROVIDER` environment variables. Retrieve and set them with the following commands. Store your organization's UIDP in the `ORGANIZATION` variable: ```sh export ORGANIZATION=$(chainctl iam organizations list -o json | jq -r '.items[0].id') ``` Store your identity provider's UIDP in the `IDENTITY_PROVIDER` variable: ```sh export IDENTITY_PROVIDER=$(chainctl iam identity-providers list -o json | jq -r '.items[0].id') ``` These commands select the first result each list returns. If your account can access more than one organization or identity provider, replace `.items[0]` with a filter that matches the one you want, or set each variable to the UIDP directly. ## How IdP group mappings work Before you configure any mappings, it helps to understand how group-derived roles behave: - **Mappings are additive.** A mapping grants a role on top of whatever access a user already has. It never removes existing access. - **Group-derived roles are session-scoped, not standing grants.** They apply to the logged-in session and are re-evaluated at each login. They do not appear in `chainctl iam role-bindings list` or the Console's role-bindings view. Instead, each login that resolves group-derived roles emits a Chainguard CloudEvent recording the roles granted, along with the identity provider and groups they came from. To observe effective group-derived access, [subscribe to Chainguard's CloudEvents stream](/platform/administration/cloudevents/events-example/). As a result, no single command or view answers "who holds a group-derived role right now?" The CloudEvents stream is a log of each login as it happens, not a current-state table. During an access review or an incident, reconstruct effective access by combining that event history with current group membership in your IdP and the mappings from `chainctl iam external-group-role-mappings list`. - **Changes take effect within an hour.** A session that includes group-derived roles is short-lived by design. Its access token lasts at most one hour, and no refresh token is issued, so when the token expires the user logs in again and that login re-reads their current group membership. As a result: - Removing a user from a group takes effect at their next login, within one hour. - Deleting or changing a mapping takes effect within one hour, because Chainguard reads mappings fresh on every login. - A user whose access is only group-derived re-authenticates at least hourly. ## Step 1: Emit group membership from your IdP Configure your identity provider to include the user's group memberships in the OIDC token it issues to Chainguard. The exact Console steps differ by provider and change over time, so follow your provider's own documentation. This guide relies only on the result: - The token carries a claim listing the user's groups, named whatever you set in Step 2. That value format is what you supply as `--external-group-id` in Step 3. This guide uses `groups`. - The token carries only the groups you map, not every group a user belongs to. The IdP drops oversized group sets from the token, as described in [Limits](#limits). | Provider | Configuring the groups claim | Values you map on | | :---- | :---- | :---- | | Okta | [Customize tokens with a groups claim](https://developer.okta.com/docs/guides/customize-tokens-groups-claim/main/) | Group names (for example, `app-admins`) | | Microsoft Entra ID (Group Claim) | [Configure group claims](https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims) | Group Object IDs (GUIDs), by default | | Microsoft Entra ID (App Roles) | [Configure app roles](https://learn.microsoft.com/en-us/entra/identity-platform/howto-add-app-roles-in-apps) | App Role Value, by default | ### Microsoft Entra ID Entra ID shows the application you registered for Chainguard in two places: under **App registrations**, and under **Enterprise applications**, where you assign users and groups to it. Chainguard reads groups only from tokens that Entra ID issues for that application. Check the following: - **Configure the groups claim on the application your users sign in through.** That's the application whose **Application (client) ID** you passed to `--oidc-client-id`. In **App registrations**, open that application, select **Token configuration**, and click **Add groups claim**. If your tenant has more than one Chainguard application, a groups claim configured on any other one doesn't reach Chainguard. To check, compare the client ID on the application you configured with the one in your Chainguard identity provider. - **Emit only groups assigned to the application, and assign each mapped group to it.** When you add the groups claim, select **Groups assigned to the application**. Then, in **Enterprise applications**, open the same application, select **Users and groups**, and assign each mapped group. With this option, a group appears in the token only when it's assigned to the application and the user is a direct member of it. Membership in an unassigned group, or through a nested group, doesn't put the group in the token. This option is required to emit cloud-group display names instead of GUIDs, and it's the recommended way to stay under the [group limit](#limits). - **If you provision users with SCIM, assign groups to the SSO application as well.** The [Entra ID SCIM guide](/platform/administration/custom-idps/scim-provisioning/ms-entra-id-scim/) creates a separate enterprise application for provisioning. Assigning a group to the SCIM application provisions its members but doesn't add the group to their login tokens. Verify the claim with a real login to Chainguard, as described in [Step 4](#step-4-verify-the-mapping). The **Test** button in **Token configuration** (under **App registrations**) uses a test flow rather than a real sign-in, so it can show a groups claim that real logins don't carry. The same is true of any token you request outside a Chainguard login, such as a test sign-in through jwt.ms. ## Step 2: Point Chainguard at the groups claim Update the identity provider you use to log in so Chainguard requests the groups claim and knows which claim carries group membership: ```sh chainctl iam identity-providers update $IDENTITY_PROVIDER \ --oidc-additional-scopes=groups \ --oidc-groups-claim=groups ``` > `--oidc-additional-scopes=groups` should not be set when using Entra ID. - `--oidc-additional-scopes=groups` tells Chainguard to request the groups claim. - `--oidc-groups-claim=groups` tells Chainguard which claim carries group membership, using the name from Step 1. An empty value turns group mapping off for this provider. ## Step 3: Map a group to a role Create a mapping from an IdP group to a Chainguard role: ```sh chainctl iam external-group-role-mappings create \ --external-group-id "GROUP" \ --role editor \ --scope $ORGANIZATION \ --idp $IDENTITY_PROVIDER ``` - `--external-group-id` is the value the IdP emits: the group name for Okta (for example, `app-admins`), or the group Object ID (GUID) for Entra ID. - `--role` is the role to grant, by name or UIDP, such as `viewer` or `editor`. - `--scope` is the UIDP of the organization where the role applies. - `--idp` is the identity provider that owns the mapping. To review the mappings you've configured, run the `list` subcommand: ```sh chainctl iam external-group-role-mappings list --parent $ORGANIZATION ``` Each command creates one mapping. To map many groups at once, see [Automate mappings with the API](#automate-mappings-with-the-api). ## Step 4: Verify the mapping 1. Have a user who belongs to the mapped group log in to Chainguard through your IdP. Use a real login; a token from your IdP's test tools doesn't confirm what Chainguard receives. If the user is already logged in, have them log out first. Chainguard applies group-derived roles at login, so refreshing an existing session doesn't pick up a new mapping. 2. Confirm that the user received the granted role. This access doesn't appear in `chainctl iam role-bindings list`, because it's session-scoped. Instead, have the user log in with `chainctl` and inspect their session: ```sh chainctl auth logout chainctl auth login --org-name chainctl auth status ``` The **Capabilities** row lists each organization and role in the session, such as `example.com: owner`. In the Console, the user can confirm the role by performing an action it allows. 3. Have a user who doesn't belong to a mapped group log in and confirm that they receive no additional access. If the user logs in but doesn't receive the mapped role, check that their token carries the group value you mapped in Step 3. For Entra ID, start with the application settings in [Microsoft Entra ID](#microsoft-entra-id). ## Manage access Once mappings are in place, you can adjust access by changing group membership within your identity provider, or by changing the mappings themselves: - **Grant more than one role to a group.** Create one mapping per role for the same group. The capabilities combine. - **Remove one user's group-derived access.** Remove that user from the group in your IdP, or unassign them from the application. Only that user is affected; everyone else in the group keeps the mapped role. - **Remove a role from everyone in a group.** Delete the mapping. This revokes the mapped role for all users in that group and leaves their other access intact: ```sh chainctl iam external-group-role-mappings delete $MAPPING_ID ``` Be sure to replace `$MAPPING_ID` with the mapping's UIDP, which you can find by running the `chainctl iam external-group-role-mappings list` command shown in Step 3. - **Remove every mapping for an identity provider.** When offboarding a provider, delete all of its mappings in one command. `chainctl` asks you to confirm first; include the `--yes` flag to skip the prompt: ```sh chainctl iam external-group-role-mappings delete --all --idp $IDENTITY_PROVIDER ``` This revokes the mapped roles for all users across that IdP's groups and leaves their other access intact. Note that this cleanup step is optional; deleting the identity provider also deletes any mappings that still exist. Running it first is still worth doing — the confirmation shows exactly how many mappings you're revoking before the provider itself is removed. Each of these changes takes effect at the affected user's next login, within one hour. ### Revoke access urgently When you need to revoke a user's access immediately, as with a compromised account or a departing employee, deleting the mapping alone is not enough. A session that already resolved the group-derived role keeps it until the access token expires, up to an hour, and that token can't be revoked mid-session. The fix is to stop the user from re-deriving the role at their next login: 1. In your identity provider, disable the user's account or remove them from the mapped group. This prevents their next login from granting the role. 2. Delete the mapping if you also want to revoke the role for everyone else in the group. The existing session still expires on its own within the hour, and the IdP change blocks re-authorization after that. ## Automate mappings with the API Each `chainctl` command in this guide handles one mapping. When your IdP has dozens or hundreds of groups to map, or when you manage Chainguard access from a pipeline, use the [Chainguard API](/platform/api/api-v2-tutorial/) instead. Its `ExternalGroupRoleMappings` endpoints create, list, and delete the same mappings `chainctl` does, and the results behave identically: additive, session-scoped, and re-evaluated at each login. These examples reuse the `ORGANIZATION` and `IDENTITY_PROVIDER` variables from the [prerequisites](#prerequisites). Add a token and the API host: ```sh export TOKEN=$(chainctl auth token) export API=https://console-api.enforce.dev ``` That token is your own and expires within an hour. To run these calls from a pipeline, authenticate as an [assumable identity](/platform/administration/assumable-ids/assumable-ids/) that holds the access described in [Permissions for the calling identity](#permissions-for-the-calling-identity). ### Look up the role UIDP `chainctl` accepts a role name, but the API takes the role's UIDP. Retrieve it by name: ```sh export ROLE=$(curl -s -H "Authorization: Bearer $TOKEN" \ "$API/iam/v2/roles?name=editor" | jq -r '.roles[0].uid') ``` ### Create one mapping The identity provider owns the mapping, so its UIDP goes in the request path and the body carries the rest. The fields correspond to the flags in [Step 3](#step-3-map-a-group-to-a-role): | `chainctl` flag | API equivalent | | :---- | :---- | | `--idp` | The identity provider UIDP in the request path | | `--external-group-id` | `externalGroupId` | | `--role` | `roleUid`, which takes the role's UIDP rather than its name | | `--scope` | `scope` | ```sh curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/externalGroupRoleMappings/$IDENTITY_PROVIDER" \ -d "{\"externalGroupId\": \"app-admins\", \"roleUid\": \"$ROLE\", \"scope\": \"$ORGANIZATION\"}" | jq . ``` ```json { "uid": "d9e2f1a0.../4b0a7c19c3e2f8d1/e54a7ea6f02e5dff", "identityProviderUid": "d9e2f1a0.../4b0a7c19c3e2f8d1", "externalGroupId": "app-admins", "roleUid": "0e4b93c2...", "scope": "d9e2f1a0...", "createTime": "2026-09-10T18:04:21.968Z" } ``` Keep the `uid` from the response. It's the mapping's own UIDP, rooted under the identity provider, and deleting the mapping later requires it. ### Create many mappings The API has no batch create, so loop over your groups. This example maps every group listed in `groups.txt` to one role and reports the result of each call: ```sh while read -r group; do status=$(curl -s -o response.json -w '%{http_code}' \ -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/externalGroupRoleMappings/$IDENTITY_PROVIDER" \ -d "{\"externalGroupId\": \"$group\", \"roleUid\": \"$ROLE\", \"scope\": \"$ORGANIZATION\"}") case "$status" in 200) echo "created: $group" ;; 409) echo "exists: $group" ;; *) echo "failed: $group (HTTP $status)"; jq -c . response.json ;; esac done < groups.txt ``` A mapping that already exists returns HTTP 409 rather than a second record, so you can re-run the loop after fixing a failure without creating anything twice. To grant more than one role, run the loop once per role with its own group list. ### List mappings Read back every mapping under an identity provider: ```sh curl -s -H "Authorization: Bearer $TOKEN" \ "$API/iam/v2/externalGroupRoleMappings?identity_provider_uid=$IDENTITY_PROVIDER" \ | jq '{totalCount, mappings: [.externalGroupRoleMappings[] | {uid, externalGroupId, roleUid}]}' ``` `totalCount` reports how many mappings match. Results are paginated, so a `nextPageToken` in the response means more pages remain, as described in [Pagination](/platform/api/api-v2-tutorial/#3-pagination). ### Delete mappings Delete a single mapping by its UIDP: ```sh curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \ "$API/iam/v2/externalGroupRoleMappings/$MAPPING_UID" ``` A successful delete returns an empty JSON object, and a mapping that's already gone returns HTTP 404. To remove several mappings in one call, pass their UIDPs to the `:batchDelete` endpoint along with the identity provider they belong to: ```sh curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/externalGroupRoleMappings:batchDelete" \ -d "{\"parent\": \"$IDENTITY_PROVIDER\", \"names\": [\"$MAPPING_UID_1\", \"$MAPPING_UID_2\"]}" | jq . ``` The response lists the mappings it deleted. Names that no longer exist are skipped without error, so repeating a teardown is safe. Every name must belong to the identity provider named in `parent`; one that doesn't fails the whole call with `INVALID_ARGUMENT` and deletes nothing. The API has no equivalent of the `--all` flag. To clear every mapping for a provider you're offboarding, list them first and feed their UIDPs to `:batchDelete`: ```sh MAPPINGS=$(curl -s -H "Authorization: Bearer $TOKEN" \ "$API/iam/v2/externalGroupRoleMappings?identity_provider_uid=$IDENTITY_PROVIDER" \ | jq -c '[.externalGroupRoleMappings[].uid]') curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ "$API/iam/v2/externalGroupRoleMappings:batchDelete" \ -d "{\"parent\": \"$IDENTITY_PROVIDER\", \"names\": $MAPPINGS}" | jq . ``` This revokes the mapped roles for every group under that provider, so review the list output before you run the delete. ### Permissions for the calling identity Creating a mapping grants a role, so the API enforces an anti-escalation rule. The identity making the call needs permission to create identity providers and role bindings, and it must already hold every capability the mapped role grants. An identity can't create a mapping that grants access it doesn't have itself. The `owner` role satisfies all three requirements. ## Limits Identity providers cap how many groups a token can carry. Past that limit, the IdP stops sending the inline `groups` claim. This means Chainguard no longer receives the user's groups, and their mappings don't resolve. Keep the emitted set small by sending only the groups you map: - **Okta:** Filter the groups claim in Step 1 so the token carries only the groups you map rather than every group a user belongs to. - **Microsoft Entra ID:** Entra ID omits the `groups` claim once a user belongs to more than 200 groups (the JWT and OIDC limit; the SAML limit is 150). Past the limit, Entra ID emits an overage claim (`_claim_names` and `_claim_sources`) that points to Microsoft Graph instead of the inline list, and Chainguard doesn't follow it. Avoid the overage by emitting only groups assigned to the application, as described in [Microsoft Entra ID](#microsoft-entra-id), or by using fewer, coarser groups for access. ## Related resources - [Using custom identity providers to authenticate to Chainguard](/platform/administration/custom-idps/custom-idps/) - [How to integrate Okta SSO with Chainguard](/platform/administration/custom-idps/idp-providers/okta/) - [How to integrate Microsoft Entra ID SSO with Chainguard](/platform/administration/custom-idps/idp-providers/ms-entra-id/) - [Overview of the Chainguard IAM model](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/) - [Manage identity and access with chainctl](/platform/chainctl-usage/chainctl-iam/) - [Subscribe to Chainguard Events](/platform/administration/cloudevents/events-example/) - [Chainguard API v2 tutorial](/platform/api/api-v2-tutorial/) - [ExternalGroupRoleMappingsService in the API v2 specification](/platform/api/spec-api-v2/#tag/externalgrouprolemappingsservice) --- ### Create an assumable identity to authenticate from Azure URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/azure-identity.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural > **Note:** If you're authenticating from a workload running in Azure > Kubernetes Service (AKS), refer to the > [Kubernetes identity guide](/platform/administration/assumable-ids/identity-examples/kubernetes-identity/) > instead. Chainguard's [_assumable identities_](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This procedural tutorial outlines how to create an identity that can be assumed by an Azure workload — such as a VM, Container App, or Function — using an [Azure managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) and then used to interact with the Chainguard API. ## Prerequisites To complete this guide, you will need the following. - `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. - The [Azure CLI (`az`)](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli), authenticated with `az login`. - An Azure subscription, and permission to register applications in your Entra ID tenant. App registration is enabled for all users in most tenants. If it's disabled in yours, ask an administrator to pre-register the application and share its client ID with you. - To follow the Terraform example, [`terraform`](https://developer.hashicorp.com/terraform/install) installed locally. ## Find your tenant ID Each Entra ID tenant has its own OIDC issuer URL, which has the following format: ``` https://login.microsoftonline.com//v2.0 ``` Retrieve the tenant ID of your current Azure session with: ```shell az account show --query tenantId --output tsv ``` ## Create the Entra ID application Register an Entra ID application to serve as the audience for tokens issued to managed identities in your tenant. We'll grant it no API permissions and expose no scopes — its only purpose is to provide a unique, tenant-specific `aud` claim that Chainguard's STS can match on. ```shell az ad app create \ --display-name chainguard-audience \ --sign-in-audience AzureADMyOrg ``` Note down the `appId` from the output — this is the client ID we'll use as the token audience. Substitute it for `` in the following commands. Configure the application to issue v2.0 access tokens and to register `api://` as an identifier URI, so that Entra ID will resolve it as a valid scope when requesting tokens. ```shell az ad app update \ --id \ --requested-access-token-version 2 \ --identifier-uris "api://" ``` Then create a service principal for the application. Entra ID requires this in order to recognise the application as a valid token resource when a managed identity requests a token for it. ```shell az ad sp create --id ``` ## Create the managed identity If you don't already have a [user-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities) to bind the Chainguard identity to, create one now. Substitute `` and `` (e.g. `eastus`) for your own values. ```shell az identity create \ --name chainguard-identity \ --resource-group \ --location ``` Note down the `principalId` from the output — we'll use this as the subject of the Chainguard identity in the next step. Note the `clientId` too, as the workload will need it later to request a token. ## Create the assumable identity This guide outlines two methods for creating the Chainguard identity: one using `chainctl` over a command-line interface, and another using Terraform. ### CLI Run this `chainctl` command to create the identity and bind it to the `registry.pull` role. Substitute `` with the tenant ID you retrieved earlier, `` with the managed identity's principal ID, and `` with the application's client ID. ```shell chainctl iam id create azure-identity \ --identity-issuer="https://login.microsoftonline.com//v2.0" \ --subject="" \ --audience="" \ --role=registry.pull ``` This will return the [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers) of the identity, which we'll use when assuming it in the next section. If you need to retrieve the UIDP later, list the identity with this command: ```shell chainctl iam identities list --name=azure-identity ``` ### Terraform You can also create the application registration, managed identity, and Chainguard identity together with the [Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest) and the [AzureRM](https://registry.terraform.io/providers/hashicorp/azurerm/latest) and [AzureAD](https://registry.terraform.io/providers/hashicorp/azuread/latest) providers. Substitute `` with your Chainguard organization name, `` with your Azure resource group, and `` with the Azure region you want to deploy into. ```hcl terraform { required_providers { azurerm = { source = "hashicorp/azurerm" } azuread = { source = "hashicorp/azuread" } chainguard = { source = "chainguard-dev/chainguard" } } } provider "azurerm" { features {} } data "azurerm_client_config" "current" {} data "azuread_client_config" "current" {} # A permission-free Entra ID application registration. Its sole purpose is # to provide a unique audience for tokens exchanged with Chainguard's STS. resource "azuread_application" "chainguard_audience" { display_name = "chainguard-audience" owners = [data.azuread_client_config.current.object_id] api { requested_access_token_version = 2 } lifecycle { ignore_changes = [identifier_uris] } } # A service principal — required for Entra ID to recognise the application # as a valid token audience when a managed identity requests a token. resource "azuread_service_principal" "chainguard_audience" { client_id = azuread_application.chainguard_audience.client_id owners = [data.azuread_client_config.current.object_id] } # Register api:// as the identifier URI so Entra ID resolves # it as a valid resource for token requests. resource "azuread_application_identifier_uri" "chainguard_audience" { application_id = azuread_application.chainguard_audience.id identifier_uri = "api://${azuread_application.chainguard_audience.client_id}" } # A user-assigned managed identity that will assume the Chainguard identity. # Attach this to any Azure workload that supports managed identities # (VMs, Container Apps, Functions, etc). resource "azurerm_user_assigned_identity" "chainguard" { name = "chainguard-identity" resource_group_name = "" location = "" } data "chainguard_group" "org" { name = "" } resource "chainguard_identity" "azure" { parent_id = data.chainguard_group.org.id name = "azure-identity" description = "Assumed by Azure workloads via the chainguard-identity managed identity." claim_match { issuer = "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}/v2.0" subject = azurerm_user_assigned_identity.chainguard.principal_id audience = azuread_application.chainguard_audience.client_id } } data "chainguard_role" "registry_pull" { name = "registry.pull" } resource "chainguard_rolebinding" "registry_pull" { identity = chainguard_identity.azure.id role = data.chainguard_role.registry_pull.items[0].id group = data.chainguard_group.org.id } output "chainguard_identity_id" { value = chainguard_identity.azure.id } output "token_audience" { value = "api://${azuread_application.chainguard_audience.client_id}" } output "managed_identity_client_id" { value = azurerm_user_assigned_identity.chainguard.client_id } ``` The `chainguard_identity_id` output provides the identity's UIDP. The `token_audience` and `managed_identity_client_id` outputs provide the values your workload will need to request a managed identity token and exchange it with Chainguard. For a full end-to-end example of using this pattern from an Azure Container App, see the [`image-copy-acr` example](https://github.com/chainguard-demo/platform-examples/tree/main/image-copy-acr) in Chainguard's public `platform-examples` repository. ## Assume the identity Attach the managed identity to the Azure workload you want to authenticate from. For example, to attach it to an existing VM: ```shell az vm identity assign \ --name \ --resource-group \ --identities ``` From inside the workload, request a token from the [Azure Instance Metadata Service (IMDS)](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token) for the audience you configured on the application. Substitute `` with the application's client ID, and `` with the `clientId` of the managed identity. ```shell AZURE_TOKEN=$(curl -sSf \ -H "Metadata: true" \ "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://&client_id=" \ | jq -r .access_token) ``` The following sections outline two methods for using the Azure token to interact with Chainguard: one using `chainctl`, and another by exchanging the token directly with the Chainguard STS over the API. ### CLI Use the Azure token to log in with `chainctl`. Substitute `` with the UIDP of the Chainguard identity created earlier. ```shell chainctl auth login \ --identity \ --identity-token "${AZURE_TOKEN}" ``` Now you can issue `chainctl` commands under this assumed identity. For instance, you could list the container image repositories available to your organization: ```shell chainctl image repo list ``` To pull images from the Chainguard registry with Docker, configure a credential helper with `chainctl`. Provide the same identity and token: ```shell chainctl auth configure-docker \ --identity \ --identity-token "${AZURE_TOKEN}" ``` You can now pull images with `docker`. Substitute `` with your Chainguard organization name and `` with an image available to your organization. ```shell docker pull cgr.dev//:latest ``` ### API If `chainctl` isn't installed in the workload, you can exchange the Azure token for a Chainguard API token directly. Substitute `` with the UIDP of the Chainguard identity created earlier. ```shell API_TOKEN=$(curl -sSf \ -H "Authorization: Bearer ${AZURE_TOKEN}" \ "https://issuer.enforce.dev/sts/exchange?aud=https://console-api.enforce.dev&identity=" \ | jq -r .token) ``` Use the API token to interact with the Chainguard API. For instance, to list the container image repositories available to your organization: ```shell curl -sSf -H "Authorization: Bearer ${API_TOKEN}" \ https://console-api.enforce.dev/registry/v1/repos | jq -r .items[].name ``` If you're authenticating from Go, the [Chainguard SDK](https://pkg.go.dev/chainguard.dev/sdk) and the [Azure SDK for Go](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go) can do the token exchange for you. The [`image-copy-acr` example](https://github.com/chainguard-demo/platform-examples/tree/main/image-copy-acr) demonstrates this pattern. ## Clean up Delete the Chainguard identity. ```shell chainctl iam id delete ``` Delete the managed identity. ```shell az identity delete \ --name chainguard-identity \ --resource-group ``` Delete the Entra ID application registration. This will also remove the associated service principal. ```shell az ad app delete --id ``` ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). --- ### Create an assumable identity to authenticate from AWS URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/aws-identity-oidc.md Last Modified: September 28, 2026 Tags: Chainguard Containers Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to access Chainguard resources or perform certain actions. This tutorial outlines how to create a Chainguard identity that can be assumed by an AWS IAM user or IAM role using [AWS IAM outbound identity federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound.html). ## Prerequisites To complete this guide, outbound identity federation must be enabled for your AWS account. Follow [the official AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_getting_started.html#enable-outbound-federation) to set this up. You will also need the following tools. * The AWS CLI. Review the official documentation for information on [how to install or update to the latest version of the tool](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). * To create the assumable identity, you will need one of the following tools: * [`chainctl`](/get-started/getting-started-with-chainctl/) — the Chainguard command line interface tool. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * [`terraform`](https://developer.hashicorp.com/terraform) — an Infrastructure as Code tool developed by Hashicorp. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. ## Retrieve token issuer URL Each AWS account has a different issuer URL for outbound identity federation. You can retrieve it from the AWS Console UI by navigating to `IAM > Account settings > STS` as described in [the official getting started guide](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound_getting_started.html). The token issuer URL will align with the following format: ``` https://.tokens.sts.global.api.aws ``` ## Create the identity This guide outlines two methods for creating an identity that can be assumed by an AWS user or IAM role: one using `chainctl` over a command-line interface, and another using Terraform. ### CLI Firstly, attach a policy to your AWS role or user that allows it to call `sts:GetWebIdentityToken` for the `https://issuer.enforce.dev` audience. The following example is a minimal policy that allows this: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:GetWebIdentityToken", "Resource": "*", "Condition": { "ForAnyValue:StringEquals": { "sts:IdentityTokenAudience": "https://issuer.enforce.dev" }, "NumericLessThanEquals": { "sts:DurationSeconds": 300 } } } ] } ``` Then, run this command which uses `chainctl` to create a Chainguard identity and assign it the `registry.pull` role. Substitute ``, `` and `` with the name you want to give the identity, the issuer URL you just retrieved and the ARN of the AWS user or role you want to assume the identity with, respectively. ```shell chainctl iam id create --identity-issuer= --subject= --role=registry.pull ``` This command should return the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers). Note this value down, as you'll need it to assume the identity later. If you need to retrieve the UIDP later on, you can always run the following `chainctl` command to list the identity. ```sh chainctl iam identities list --name= ``` If you're unsure which ARN or issuer URL to provide to `chainctl iam id create`, you can issue a token with `aws sts get-web-identity-token` and inspect the claims with `jwt`. To complete this command, you must install `jwt` as described on [this page](https://github.com/mike-engel/jwt-cli#installation). ```shell aws sts get-web-identity-token \ --audience=https://issuer.enforce.dev \ --signing-algorithm=ES384 \ --query WebIdentityToken \ --output text \ | jwt decode -j - \ ``` The output will look like this: ```json { "header": { "typ": "JWT", "alg": "ES384", "kid": "EC384_0" }, "payload": { "aud": "https://issuer.enforce.dev", "exp": 1766915543, "https://sts.amazonaws.com/": { "aws_account": "123456789012", "org_id": "o-anih79mrvn", "original_session_exp": "2025-12-28T10:45:29Z", "ou_path": [ "o-anih79mrvn/r-amv9/ou-amv9-ndi0nlgq/" ], "principal_id": "arn:aws:iam::123456789012:role/example", "source_region": "us-west-2" }, "iat": 1766915243, "iss": "https://00000000-0000-0000-0000-000000000000.tokens.sts.global.api.aws", "jti": "00000000-0000-0000-0000-000000000000", "sub": "arn:aws:iam::123456789012:role/example" } } ``` The `sub` and `iss` claims are the values you should provide to `--subject` and `--identity-issuer`, respectively. ### Terraform You can also create an assumable identity with the [Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest). The following example demonstrates creating an identity that can be assumed by an IAM role. It binds the `registry.pull` role to the identity. Substitute your Chainguard organization name for `` and the issuer URL for ``. ```hcl terraform { required_providers { aws = { source = "hashicorp/aws" } chainguard = { source = "chainguard-dev/chainguard" } } } data "aws_caller_identity" "current" {} resource "aws_iam_role" "example" { name = "example-role" # Configuration omitted for brevity } resource "aws_iam_role_policy" "example_policy" { name = "web-identity-token-policy" role = aws_iam_role.example.id policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "sts:GetWebIdentityToken" Resource = "*" Condition = { "ForAnyValue:StringEquals" = { "sts:IdentityTokenAudience" = "https://issuer.enforce.dev" } "NumericLessThanEquals" : { "sts:DurationSeconds" : 300 } } } ] }) } data "chainguard_group" "org" { name = "" } resource "chainguard_identity" "my_identity_name" { parent_id = data.chainguard_group.org.id name = "my-identity-name" claim_match { issuer = "" subject = aws_iam_role.example.arn } } data "chainguard_role" "registry_pull" { name = "registry.pull" } resource "chainguard_rolebinding" "my_identity_name_registry_pull" { identity = chainguard_identity.my_identity_name.id role = data.chainguard_role.registry_pull.items[0].id group = data.chainguard_group.org.id } output "my_identity_name_id" { value = chainguard_identity.my_identity_name.id } ``` The `my_identity_name_id` output provides the identity’s [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers). You’ll need this value to assume the identity later. ## Assume the identity After creating an identity with either method outlined previously, generate a token with `aws sts get-web-identity-token`: ```shell TOK=$(aws sts get-web-identity-token --audience=https://issuer.enforce.dev --signing-algorithm=ES384 --query WebIdentityToken --output text) ``` Then use the token to log in with `chainctl`. Provide the UIDP of the identity as ``: ```shell chainctl auth login \ --identity \ --identity-token "${TOK}" ``` Now you will be able to issue `chainctl` commands under this assumed identity. For instance, you could list the container image repositories available to your organization: ```shell chainctl image repo list ``` ## Learn more By following this guide, you will have created a Chainguard identity that you can use to authenticate to Chainguard from AWS. For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, we encourage you to read through the rest of our documentation on [Administering Chainguard resources](/platform/administration/). --- ### Create an assumable identity to authenticate from AWS (legacy) URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/aws-identity.md Last Modified: September 30, 2026 Tags: Chainguard Containers > **Note:** This page describes a custom implementation of assumable identities for AWS that was > developed before AWS natively supported issuing OIDC tokens with [IAM outbound > identity federation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_outbound.html). > If possible, you should follow the instructions on > [this page](/platform/administration/assumable-ids/identity-examples/aws-identity-oidc/) > instead. Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to access Chainguard resources or perform certain actions. This tutorial outlines how to create a Chainguard identity that can be assumed by an AWS user or IAM role and used to authorize requests from AWS services and workloads hosted on platforms like EC2, ECS, Lambda, and EKS. ## Prerequisites To complete this guide, you will need the following tools. * The AWS CLI. Review the official documentation for information on [how to install or update to the latest version of the tool](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). * To create the assumable identity, you will need one of the following tools: * [`chainctl`](/get-started/getting-started-with-chainctl/) — the Chainguard command line interface tool. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * [`terraform`](https://developer.hashicorp.com/terraform) — an Infrastructure as Code tool developed by Hashicorp. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. ## Create the assumable identity This guide outlines two methods for creating an identity that can be assumed by an AWS user or IAM role: one using `chainctl` over a command-line interface, and another using Terraform. ### CLI This example creates an identity for an IAM role and binds it to the `registry.pull` role. Replace `` with the name you'd like to give the identity, `` with your AWS account ID, and `` with the name of your IAM role. ```shell chainctl iam id create aws role --aws-account-id= --aws-role-name= --role=registry.pull ``` For an IAM user, use this command. Replace `` with the name you'd like to give the identity, `` with your AWS account ID and `` with the name of your IAM user. ```shell chainctl iam id create aws user --aws-account-id= --aws-user-name= --role=registry.pull ``` If your IAM resources are in the `aws-cn` or `aws-us-gov` [partitions](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/partitions.html), you must specify that with the `--aws-partition` flag. For instance, for a role: ```shell chainctl iam id create aws role --aws-partition=aws-us-gov --aws-account-id= --aws-role-name= --role=registry.pull ``` These commands will return the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers). Note this value down, as you'll need it to assume the identity later. If you need to retrieve the UIDP later on, you can always run the following `chainctl` command to list the identity. ```sh chainctl iam identities list --name= ``` If you're unsure which IAM identity your AWS resource is using, you can run `aws sts get-caller-identity` to get the details of the current AWS session. You must run this from the AWS environment you are trying to assume the identity from. For example, if you run this command from an EC2 instance with an assumed role, you might see output like this: ```sh aws sts get-caller-identity ``` ```output { "UserId": "AROAEXAMPLEC2UL7LUB:i-05a6373examplehd2", "Account": "452example43", "Arn": "arn:aws:sts::452example43:assumed-role/AmazonSSMRoleForInstancesQuickSetup/i-05a6373examplehd2" } ``` In this example, the role name is `AmazonSSMRoleForInstancesQuickSetup`. ### Terraform You can also create an assumable identity with the [Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest). The following example demonstrates creating an identity that can be assumed by an IAM role. It binds the `registry.pull` role to the identity. Substitute your Chainguard organization name for ``. ```hcl data "aws_caller_identity" "current" {} resource "aws_iam_role" "example" { # Configuration omitted for brevity } data "chainguard_group" "org" { name = "" } resource "chainguard_identity" "my_identity_name" { parent_id = data.chainguard_group.org.id name = "my-identity-name" aws_identity { aws_account = data.aws_caller_identity.current.account_id aws_user_id_pattern = "^${aws_iam_role.example.unique_id}:(.+)$" aws_arn_pattern = "^arn:aws:sts::${data.aws_caller_identity.current.account_id}:assumed-role/${aws_iam_role.example.name}/(.+)$" } } data "chainguard_roles" "registry_pull" { name = "registry.pull" } resource "chainguard_rolebinding" "my_identity_name_registry_pull" { identity = chainguard_identity.my_identity_name.id role = data.chainguard_roles.registry_pull.items[0].id group = data.chainguard_group.org.id } output "my_identity_name_id" { value = chainguard_identity.my_identity_name.id } ``` The `my_identity_name_id` output provides the identity’s [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers). You’ll need this value to assume the identity later. For a full example, refer to the [`aws-auth` example](https://github.com/chainguard-dev/platform-examples/tree/main/aws-auth) in Chainguard's public `platform-examples` repository. ## Assume the identity Unlike other supported platforms, AWS does not issue OIDC tokens for its IAM entities. To facilitate assumable identities from AWS workloads, Chainguard accepts a custom token format that is constructed by Base64 encoding a signed AWS API request to [`GetCallerIdentity`](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html). Chainguard can then use that request and verify that it returns a caller identity that is permitted to assume the Chainguard identity. To better understand the verification logic, refer to the [`aws.VerifyToken`](https://pkg.go.dev/chainguard.dev/sdk@v0.1.44/auth/aws#VerifyToken) function from the [Chainguard SDK For Go](https://pkg.go.dev/chainguard.dev/sdk). The following sections outline different methods of generating the token, assuming the identity, and using it to interact with the Chainguard API from your AWS resources. ### CLI You should be able to follow these instructions from any environment, including AWS services like EC2 and EKS, as long as the AWS CLI [can fetch credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) and both `curl` and `chainctl` are installed. First, use the AWS CLI to retrieve credentials and export them as the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` environment variables: ```shell eval "$(aws configure export-credentials --format env)" ``` Next, use `curl` to sign a HTTP request with the credentials and base64 encode it. Store the result in the variable `${TOK}`. Substitute `` with the UIDP of your Chainguard identity which you created previously: ```shell TOK=$(curl -X POST "https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15" \ --aws-sigv4 "aws:amz:us-east-1:sts" \ --user "${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}" \ -H "x-amz-security-token: ${AWS_SESSION_TOKEN}" \ -H "Chainguard-Identity: " \ -H "Chainguard-Audience: https://issuer.enforce.dev" \ -H "Accept: application/json" \ -v 2>&1 > /dev/null \ | grep '^> ' \ | sed 's/> //' \ | base64 -w0) ``` Finally, use the token to login with `chainctl`. Provide the UIDP of the identity as ``: ```shell chainctl auth login \ --identity \ --identity-token "${TOK}" ``` Now you will be able to issue `chainctl` commands under this assumed identity. For instance, you could list the container image repositories available to your organization: ```shell chainctl image repo list ``` ### Go SDK The [Chainguard SDK For Go](https://pkg.go.dev/chainguard.dev/sdk) provides an [`aws.GenerateToken`](https://pkg.go.dev/chainguard.dev/sdk@v0.1.44/auth/aws#GenerateToken) function that makes it straightforward to generate a token from AWS credentials that can be exchanged for a Chainguard API token. For an example of how to leverage this function, refer to the [`aws-auth` example](https://github.com/chainguard-dev/platform-examples/tree/main/aws-auth) in Chainguard's public `platform-examples` repository. ## Learn more By following this guide, you will have created a Chainguard identity that you can use to authenticate to Chainguard from AWS. For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, we encourage you to read through the rest of our documentation on [Administering Chainguard resources](/platform/administration/). --- ### Getting started with FIPS containers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/getting-started.md Last Modified: September 28, 2026 Tags: FIPS, Tutorial, Getting Started ## Prerequisites Before starting, you'll need: - **Chainguard account with FIPS access**: FIPS containers are not included in the free tier. [Contact Chainguard](https://www.chainguard.dev/contact) to request access. - **Docker or compatible container runtime**: Install [Docker Desktop](https://www.docker.com/products/docker-desktop) or another OCI-compatible runtime. - **Basic container knowledge**: Familiarity with pulling and running container images. FIPS containers work on any recent Linux kernel, including: - Linux workstations - macOS (Docker Desktop) - Windows (WSL2 with Docker Desktop) ## Choosing a FIPS image Chainguard offers 400+ FIPS image variants. Choose based on your use case: **Language runtime images** if you're building applications: - `go-fips` - For building Go applications - `python-fips` - For Python applications - `node-fips` - For Node.js applications - `jdk-fips` or `jre-fips` - For Java applications - `dotnet-runtime-fips` - For .NET applications **Application images** if you need a specific tool or service: - `nginx-fips` - Web server - `postgres-fips` - Database - `prometheus-fips` - Monitoring - And many more **Base images** for minimal runtime environments: - `glibc-openssl-fips` - Minimal glibc-based runtime - `busybox-fips` - Minimal BusyBox environment Browse the complete catalog at [images.chainguard.dev/?category=fips](https://images.chainguard.dev/?category=fips). ## Your first FIPS container Let's start with a Python example to verify FIPS is working. ### Pull the image Replace `$ORGANIZATION` with your organization name in the Chainguard Registry: ```bash docker pull cgr.dev/$ORGANIZATION/python-fips:latest ``` ### Run a test script Create a Python script that uses cryptography: ```bash cat > test_fips.py << 'EOF' import hashlib import ssl # Test that we can use cryptographic functions data = b"Hello, FIPS!" hash_result = hashlib.sha256(data).hexdigest() print(f"SHA-256 hash: {hash_result}") # Check SSL/TLS configuration print(f"OpenSSL version: {ssl.OPENSSL_VERSION}") print("FIPS cryptography is active") EOF ``` Run the script in the FIPS container: ```bash docker run --rm -v $(pwd):/work -w /work \ cgr.dev/$ORGANIZATION/python-fips:latest \ test_fips.py ``` You should see output like: ``` SHA-256 hash: 8832699c6bbcc63001c75b098cb4499712bfc5680a1f517a9d5676abec8307bc OpenSSL version: OpenSSL 4.0.3 29 Sep 2026 FIPS cryptography is active ``` This confirms the container is using OpenSSL for cryptographic operations. ## Verifying FIPS configuration ### Check the SBOM Every Chainguard image includes a Software Bill of Materials (SBOM). Verify FIPS-required packages: ```bash cosign download attestation cgr.dev/$ORGANIZATION/python-fips:latest | jq -r .payload | base64 -d | jq '.predicate.buildDefinition.externalParameters."image-configuration".contents.packages' | grep -E "libcrypto|openssl-config" ``` Look for: - `libcrypto3` version 3.4.0-r2 or higher - `openssl-config-fipshardened` version 3.4.0-r3 or higher These packages indicate kernel-independent FIPS configuration. ### Inspect OpenSSL configuration Run a container interactively to check OpenSSL configuration: ```bash docker run --rm -it --entrypoint /usr/bin/bash cgr.dev/$ORGANIZATION/python-fips:latest-dev ``` Inside the container, verify the FIPS module: ```bash cat /etc/ssl/fipsmodule.cnf ``` You should see configuration for the FIPS provider. This file's presence and validity are essential for FIPS operation. ## Building applications with FIPS ### Multi-stage build pattern The recommended pattern is to build with a FIPS SDK image and run with a minimal FIPS runtime image. Example Dockerfile for a Go application: ```dockerfile # Build stage FROM cgr.dev/$ORGANIZATION/go-fips:latest AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o myapp # Runtime stage FROM cgr.dev/$ORGANIZATION/glibc-openssl-fips:latest COPY --from=builder /app/myapp /usr/bin/myapp ENTRYPOINT ["/usr/bin/myapp"] ``` This pattern: - Builds your application with FIPS-enabled Go toolchain - Produces a minimal runtime image with only the binary and FIPS runtime - Maintains FIPS compliance throughout ### Build and run ```bash docker build -t myapp-fips . docker run --rm myapp-fips ``` The resulting container runs on any Linux kernel and maintains FIPS validation. ## Common patterns ### Python application with requirements ```dockerfile FROM cgr.dev/$ORGANIZATION/python-fips:latest WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["python", "app.py"] ``` ### Node.js application ```dockerfile FROM cgr.dev/$ORGANIZATION/node-fips:latest AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY . . # Use multi-stage if you want a smaller runtime FROM cgr.dev/$ORGANIZATION/node-fips:latest WORKDIR /app COPY --from=builder /app . CMD ["node", "server.js"] ``` ### Java application ```dockerfile FROM cgr.dev/$ORGANIZATION/jdk-fips:latest AS builder WORKDIR /app COPY . . RUN javac MyApp.java FROM cgr.dev/$ORGANIZATION/jre-fips:latest WORKDIR /app COPY --from=builder /app/*.class . CMD ["java", "MyApp"] ``` ## Testing FIPS enforcement To verify that FIPS mode is enforced, you can intentionally break the FIPS configuration and confirm the application fails. **Warning**: This test should only be done in development environments. Run a container with access to modify system files: ```bash docker run --rm -it --user root --entrypoint /usr/bin/bash \ cgr.dev/$ORGANIZATION/python-fips:latest-dev ``` Inside the container, break the FIPS configuration: ```bash # Backup the config cp /etc/ssl/fipsmodule.cnf /tmp/fipsmodule.cnf.bak # Invalidate it ln -sf /dev/null /etc/ssl/fipsmodule.cnf # Try to use crypto python3 -c "import hashlib; print(hashlib.sha256(b'test').hexdigest())" ``` You should see an error indicating FIPS is required but not available. This proves the container enforces FIPS mode and won't fall back to non-validated cryptography. Restore the config to fix: ```bash cp /tmp/fipsmodule.cnf.bak /etc/ssl/fipsmodule.cnf ``` ## Development workflow ### Local development FIPS containers work on any kernel, so you can develop and test locally: 1. Pull the FIPS image for your language/framework 2. Mount your source code into the container 3. Iterate on code with hot-reload if your framework supports it 4. Test cryptographic operations work correctly Example for Python development: ```bash docker run --rm -it \ -v $(pwd):/app \ -w /app \ -p 8000:8000 \ --entrypoint /usr/bin/bash cgr.dev/$ORGANIZATION/python-fips:latest-dev ``` Inside, install development dependencies and run your app. ### CI/CD integration FIPS containers work in standard CI/CD pipelines without special infrastructure. GitHub Actions example: ```yaml jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Pull FIPS image run: docker pull cgr.dev/$ORGANIZATION/python-fips:latest - name: Run tests run: | docker run --rm -v $(pwd):/app -w /app \ cgr.dev/$ORGANIZATION/python-fips:latest \ python -m pytest ``` No special runners or kernel configurations required. ## Troubleshooting ### Issue: "FIPS mode requested but not available" **Cause**: The FIPS provider configuration is missing or invalid. **Solution**: Verify you're using a FIPS-tagged image (e.g., `python-fips`, not `python`). Check the SBOM contains required packages. ### Issue: Application crashes on startup **Cause**: The application may be using a cryptographic library that doesn't support FIPS. **Solution**: Ensure your application uses OpenSSL-backed cryptography. ### Issue: Performance degradation **Cause**: Likely not FIPS-related. Profile your application first. **Solution**: Use standard profiling tools to identify bottlenecks. FIPS cryptography typically adds minimal overhead. ### Issue: Can't pull the image **Cause**: FIPS images require account access. **Solution**: Verify your organization has FIPS access. Contact support if needed. ## Next steps Now that you've deployed your first FIPS container: - **Kernel-independent architecture**: Understand [how it works](/platform/fips/kernel-independent-architecture/) under the hood - **FAQs**: Check [common questions](/platform/fips/faqs/) about FIPS implementation - **Chainguard support**: [Contact us](https://www.chainguard.dev/contact) for questions or custom requirements --- ### Chainguard Factory FAQs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/faq.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Chainguard VMs ## What is the Chainguard Factory? The Chainguard Factory refers to all the engineering and automation work that goes into building, publishing, and maintaining the software packaged in Chainguard's products. This includes continuously monitoring, testing, and updating thousands of open source projects that make up Chainguard containers, libraries, and VMs. ## How does Chainguard keep its software up to date? Chainguard uses automated systems to vigilantly monitor for new releases using the GitHub API, [the Release Monitoring project](https://release-monitoring.org), [PyPI](https://pypi.org/), [Maven Central](https://mvnrepository.com/repos/central) and the [npm registry](https://docs.npmjs.com/about-the-public-npm-registry). When a new release is detected, automation opens a pull request, patches the relevant software, and kicks off a suite of tests. If issues arise, AI analyzes logs and suggests fixes before human engineers review and approve changes. This process ensures prompt, high-quality updates. ## How often are Chainguard packages, containers, and VMs updated? Updates happen constantly and at high speed. There are over 1,500 containers, plus VMs, and libraries – each of which is updated daily as needed, depending on upstream changes and security events. ## What happens when a core dependency (like Go, OpenSSL, or glibc) is updated? Updates to foundational packages require cascading rebuilds of all dependent packages and images. For updates to core dependencies, this results in rebuilds of hundreds, or even thousands, of packages and containers. Major updates like these receive additional engineering focus to ensure a smooth transition. ## How does Chainguard handle software that goes end-of-life (EOL) upstream? After a package goes EOL, it is no longer supported upstream, and updates will cease in the Wolfi repository. However, customers benefit from an extended EOL Grace Period with Chainguard OS, during which Chainguard continues to build old versions for an additional timeframe. ## How are CVEs (security advisories) managed? Chainguard uses the [grype](https://github.com/anchore/grype) security scanner to detect when a CVE affects one of its packages. Automation flags these CVEs for investigation by engineers. If determined to be a false positive, the status is set to `Not Affected`; if real, the issue is resolved by patching or updating dependencies and the status changes to `Fixed`. ## How does Chainguard prevent malware or malicious updates? The factory analyzes code for known malware and checks for unexpected changes in software behavior, such as new network connections not mentioned in changelogs. This helps detect subverted upstream projects or supply chain attacks (like the recent XZ utils incident) before they affect customers. ## What infrastructure is used for building? GitHub is used for source code management, but our builds execute on Kubernetes clusters for scalability and observability. The build environment is hardened following SLSA (Supply-chain Levels for Software Artifacts) and OpenSSF security guidelines, reinforcing both integrity and provenance. ## What is the role of automation, AI, and human engineers? Automation and AI enable high-velocity operations, handling the bulk of routine updates and testing. However, due to the complexity and variability of open source, human engineers with diverse technological expertise play a critical role in resolving exceptions, complex updates, and security events. ## Why is the Chainguard Factory important for customers? The factory enables Chainguard to reliably convert open source into ready-to-use, highly secure containers, VMs, and libraries, giving customers timely access to updates while minimizing their security risk and operational overhead. --- ### Compare chainctl usage with the Chainguard Console URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/comparing-chainctl-to-console.md Last Modified: October 5, 2026 Tags: chainctl, Chainguard Console Chainguard provides two powerful interfaces for managing container security resources: the web-based Console for visual exploration and the `chainctl` CLI for automation and scripting. Understanding when to use each tool maximizes your efficiency in managing Chainguard's security-hardened containers and access controls. ## Prerequisites To access the [Chainguard Console](/platform/console/images-directory/) you need to [create an account and sign in](https://console.chainguard.dev/auth/login). The Console is accessible to everyone, including users who aren't Chainguard customers. To use `chainctl`, start by [installing chainctl](/platform/chainctl-usage/how-to-install-chainctl/). Refer to [Get started with chainctl](/get-started/getting-started-with-chainctl/) to help you begin using it; the examples on this page assume you have `chainctl` installed and are authenticated. ## High-level comparison The Console is especially useful for one-off information searches, such as when you don't know precisely what you want to know. The Console provides detailed information, but may require a few clicks to hone in on precisely what you are looking for. You can perform useful container-related queries in the Console, as the examples on this page show. If you know specifically what you are looking for or what you want to accomplish, `chainctl` is a powerful way to do so. It can perform some additional tasks that are not yet available in the Console, such as [comparing images with a diff](/platform/chainctl-usage/comparing-images/). The following sections walk through a few use cases. ## Find available images ### Find available images in the Console To find the images available to you in the Console, do this: 1. Open the [Console](https://console.chainguard.dev) ![Screenshot showing the Overview page in the Console.](console-overview.png) 1. On the Overview page that opens, click **Images** in the sidebar. ![Screenshot showing the Images page in the Console, which lists all of the images available along with data for each including Status, Latest tag, Pull URL, and when the image was last updated.](console-org-images.png) ### Find available images with chainctl To find the images available to you using `chainctl`, use this command. The list of available images is likely to be long and scrolls past quickly in the terminal, so consider piping the output into `grep` or redirecting it into a file. ```sh chainctl images list ``` ## Invite users ### Invite users in the Console To invite a user using the Console, follow these steps: 1. Open the Console. 1. On the Overview page that opens, click the **Manage pull tokens** link, just below the search box. 1. On the Settings page that opens, click **Users** in the sidebar. ![Screenshot showing the Settings page in the Console.](console-settings.png) 1. Click **Invite users**. ![Screenshot showing the list of users in the Console.](console-users.png) 1. Enter the **Email address** of the user you are inviting and use the dropdown menu to assign a **Role** for this user. Click **Invite**. ### Invite users using chainctl To invite a user using `chainctl`, use this command, substituting your organization name for ORGANIZATION along with setting the role, email address, length of time for the invite to be valid, and whether this invite may only be used once: ```sh chainctl iam invite create ORGANIZATION --role=viewer --email=sample@organization.dev --ttl=7d --single-use ``` ## Review container image history ### Review container image history in the Console To examine the history of an image using the Console: 1. Open the Console and find the image you want to examine more closely. Click it in the **Recent image changes** list on the **Overview** page, or click **View all organization images** to open the full list and find it there. 1. On the image page you start on the **Tags** tab and see a list of tags which correspond to the release version of the image, like this one for `kubectl`. ![Screenshot showing the list of image versions for kubectl in the Console.](console-image-version-list.png) This list contains columns with data about each image release, like the Pull URL, Digest, and when it was last changed. Click the other tabs to learn more about the *latest* release version of this image. ### Review container image history using chainctl To examine the history of an image using `chainctl`, enter this: ```sh chainctl image history kubectl:latest ``` This returns a reverse-chronological history of when a specific tag was updated to point to a new manifest digest. This list can be long. Here's an excerpt: ```yaml - time: 2025-05-29 03:08:31 UTC digest: sha256:34798f562dffc3746cb69bab49b93ff83aa57bea393a07997e87c37bc83a62db architectures: amd64: sha256:a71ccfdc86cd73d395d3528ce3f8df1f4dd132b73ff03016b0ec42da23d4ec99 (18.13 MB) arm64: sha256:2876b0c3de431f0d7df8f888a0d40bb0c8259c47109f978237d305e7818b704b (16.43 MB) - time: 2025-05-28 17:19:58 UTC digest: sha256:1f798940981573c34e1d11c8b6d266f18d06e95b81251d6880f511d55b833cfd - time: 2025-05-27 19:53:02 UTC digest: sha256:81095db3adc00495fceb064e86dfd81c7ffdf081c55daf6b12be6ef1605bd18c architectures: amd64: sha256:56db73a4b66ad326a7858ca4157ded2d3b6d11ff1030cfbdf3a3bd879a5a5725 (18.13 MB) arm64: sha256:280160c9c422d7526169812cf89401043096f5d4ff385d1b59f3610109486aed (16.43 MB) - time: 2025-05-23 01:09:22 UTC digest: sha256:c0934fc335d8b24923487cb7d0b673490bd393fd4b6cd20f6f0e156a7481ffc7 architectures: amd64: sha256:46e11b8beed94d93272e5a87753f9f43f02f5f1b9d83d8ba279eeb18c114c863 (18.13 MB) arm64: sha256:b288bc13da78aa7b2a82d50dbca45ed2fe286f0f1f248fa2e12604ef9a109f33 (16.40 MB) ... ``` The details that are returned here and the details found in the Console vary in focus, but where the same details are provided they should match. Refer to [Examine the history of container images](/platform/chainctl-usage/chainctl-images/#examine-the-history-of-container-images) for more information about this command. ## Learn more To learn more about `chainctl`, see: * [chainctl usage](/platform/chainctl-usage/) * [chainctl reference](/platform/chainctl/) To learn more about the Chainguard Console, see: * [Using the Chainguard Directory and Console](/platform/console/images-directory/). ### Compare a Chainguard Container to a non-Chainguard alternative in the Console This is a feature unique to the Console and is described in detail in [Using CVE visualizations](/chainguard/containers/security-and-compliance/vulnerability-management/cve-visualizations/). ### Compare two Chainguard Containers with chainctl This is a feature unique to `chainctl` and is described in detail in [How to compare Chainguard Containers with chainctl](/platform/chainctl-usage/comparing-images/). --- ### How to integrate Keycloak with Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/idp-providers/keycloak.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural By default, the Chainguard platform supports Single sign-on (SSO) authentication for users with GitHub, GitLab, and Google. This guide outlines how to create a [Keycloak](https://www.keycloak.org/) Client on your existing Keycloak instance and integrate it with Chainguard. After completing this guide, you'll be able to log in to Chainguard using Keycloak and will no longer be limited to the default SSO options. ## Prerequisites To complete this guide, you will need the following: * `chainctl` installed on your system. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't already have this installed. * An existing Keycloak instance with admin access to the realm you will be using to authenticate. ## Create a Keycloak client To integrate Keycloak with the Chainguard platform, log in to your Keycloak admin interface. In the left-hand navigation menu, select **Create client**.
Screenshot of Keycloak's Admin menu, with the 'Create Client' option highlighted in a red box.

Configure the new client through the wizard: 1. Set the **Client Type** to OIDC Connect and set your **Client ID**. Add a friendly **Name** and **Description** if desired. Note your **Client ID** value — you'll need it to configure the Chainguard platform to use this Keycloak Client. Click **Next**. 2. Toggle **Client Authentication** on and click **Next**. 3. Set the Chainguard platform redirect URI in the **Valid redirect URIs** field, then click **Save** to finalize the client. Navigate to the **Credentials** tab of your newly created client.
Screenshot of the newly created Keycloak Client, with the Credentials tab highlighted in a red box.

Copy the **Client Secret** value. You'll need this to configure the Chainguard platform to use this Keycloak Client. ## Configure Chainguard to use your Keycloak client Now that your Keycloak Client is ready, you can create the custom identity provider. First, log in to Chainguard with `chainctl`, using an OIDC provider like Google, GitHub, or GitLab to bootstrap your account. ```sh chainctl auth login ``` Note that this bootstrap account can be used as a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) (that is, a backup account you can use to log in if you ever lose access to your primary account). However, if you prefer to remove this role-binding after configuring the custom IdP, you may also do so. To configure Chainguard, make a note of the following details from your Keycloak Client: * **Client ID**: This can be found on the **Settings** tab of the Keycloak Client. * **Client Secret**: This can be found on the **Credentials** tab of the Keycloak Client. * **Issuer**: Your **Issuer** URL is defined by the following pattern `https:///realms/` If you belong to more than one Chainguard organization, you will also need the UIDP of the one under which you want to install the identity provider. Your selection won’t affect how your users authenticate but will have implications on who has permission to modify the SSO configuration. You can retrieve a list of all the Chainguard organizations you belong to — along with their UIDPs — with the following command. ```shell chainctl iam organizations ls -o table ``` ```output ID | NAME | STATUS | DESCRIPTION ------------------------------------------|------------|--------|---------------------------------- 591**********************************c43 | sample_org | ready | A sample Chainguard organization . . . | . . . | . . . | . . . ``` Note down the `ID` value for your chosen organization. With this information in hand, create a new identity provider with the following commands. Replace `` and `` with the values from your Keycloak client, and `` and `` with your Keycloak server address and realm. ```sh export NAME=keycloak-idp export CLIENT_ID= export CLIENT_SECRET= export ISSUER="https:///realms/" chainctl iam identity-provider create \ --configuration-type=OIDC \ --oidc-client-id=${CLIENT_ID} \ --oidc-client-secret=${CLIENT_SECRET} \ --oidc-issuer=${ISSUER} \ --oidc-additional-scopes=email \ --oidc-additional-scopes=profile \ --default-role=viewer \ --name=${NAME} ``` `chainctl` installs the provider in your organization automatically when you belong to only one. If you have access to more than one, add `--parent=` to choose where it is installed. Note the `--default-role` option. This defines the default role granted to users registering with this identity provider. This example specifies the `viewer` role, but depending on your needs you might choose `editor` or `owner`. If you don't include this option, you'll be prompted to specify the role interactively. For more information, refer to the [IAM and security section](/platform/administration/custom-idps/custom-idps/#iam-and-security) of our Introduction to Custom Identity Providers in Chainguard tutorial. You can refer to our [Generic integration guide](/platform/administration/custom-idps/custom-idps/#generic-integration-guide) in our Introduction to Custom Identity Providers article for more information about the `chainctl iam identity-provider create` command and its required options. To log in to the Chainguard Console with the new identity provider you just created, navigate to [console.chainguard.dev](https://console.chainguard.dev) and click **Use Your Identity Provider**. Next, click **Use Your Organization Name** and enter the name of the organization associated with the new identity provider. Finally, click the **Login with Provider** button. This will open up a new window with the Keycloak login flow, allowing you to complete the login process through there. You can also use the custom identity provider to log in through `chainctl`. To do this, run the `chainctl auth login` command and add the `--identity-provider` option followed by the identity provider's ID value: ```sh chainctl auth login --identity-provider ``` The ID value appears in the `ID` column of the table returned by the `chainctl iam identity-provider create` command you ran previously. You can also retrieve this table at any time by running `chainctl iam identity-provider ls -o table` when logged in. --- ### Mirror new Containers to Google Artifact Registry with Chainguard CloudEvents URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/cloudevents/image-copy-gcr.md Last Modified: October 8, 2026 Tags: Chainguard Console, CloudEvents, Procedural Certain interactions with Chainguard resources will emit [CloudEvents](/platform/administration/cloudevents/events-reference/) that you or an application can subscribe to. This allows you to do things like receive alerts when a user downloads one or more of your organization's private container images or when a new image gets added to your organization's registry. This tutorial is meant to serve as a companion to the [Image Copy GCP](https://github.com/chainguard-dev/platform-examples/tree/main/image-copy-gcp) example application. It will guide you through setting up infrastructure to listen for `push` events on an organization's private registry and mirror any new Chainguard Containers in the registry to a repository in a GCP Artifact Registry repository. ## Prerequisites To follow along with this guide, it is assumed that you have the following set up and ready to use. * A [verified Chainguard organization](/platform/administration/iam-organizations/verified-orgs/) with a private [Registry](/chainguard/containers/registry/overview/) and access to [Production containers](/chainguard/containers/overview/#production-and-free-containers). * `chainctl`, the Chainguard command-line interface. You can install this by following our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/). * [`terraform`](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) to configure a Google Cloud service account, IAM permissions, and deploy the Cloud Run service. * A Google Cloud account with a project running. The example application assumes that your project has the following APIs enabled: * [Artifact Registry API](https://cloud.google.com/artifact-registry/docs/reference/rest) * [Cloud Run Admin API](https://cloud.google.com/run/docs/reference/rest) * The [`gcloud` CLI](https://cloud.google.com/sdk/docs/install) installed on your local machine. You'll need this to authenticate to Google Cloud over the command line. ## Setting up the Terraform configuration You can find all the code associated with this sample application in our [Platform Examples](https://github.com/chainguard-dev/platform-examples/tree/main/image-copy-gcp) repository on GitHub. To set up the sample application, you can create a Terraform configuration file and apply it to set up the necessary resources. To begin, create a new directory to hold the configuration and navigate into it. ```sh mkdir ~/gcp-example && cd $_ ``` From within the `gcp-example` directory, you can begin creating a Terraform configuration named `main.tf`. This configuration will consist of a single module. For the purposes of this example, we will call it `image-copy`. This module's `source` value will be the `iac` folder from the application code in the examples repository. ```hcl module "image-copy" { source = "github.com/chainguard-dev/platform-examples/image-copy-gcp/iac" ``` The next five lines configure a few variables that you will need to update to reflect your own setup. * First, the configuration defines a `name` value. This will be used to prefix resources created by this sample application where possible. * Next, it specifies the GCP project ID where certain resources will reside, including the container image for this application (along with mirrored images), the Cloud Run service hosting the application, and the Service Account that authorizes pushes to the Google Artifact Registry. * Following that, the configuration specifies the Chainguard IAM organization from which we expect to receive events. This is used to authenticate that the Chainguard events are intended for you, and not another user. Container images pushed to repositories under this organization will be mirrored to Artifact Registry. * You can find the names of every organization you have access to by running `chainctl iam organizations list -o table`. * The next line specifies the location of the Artifact Registry repository and the Cloud Run subscriber. * The final line defines `dst_repo` value, which is used to create a name for the repository in the Artifact Registry where container images will be mirrored. As an example, if the `name` value you specify is `chainguard-dev` and the `dst_repo` value is `mirrored` (as shown in the following example) any pushes to `cgr.dev//foo` will be mirrored to `-docker.pkg.dev//chainguard-dev-mirrored` Be sure to include a closing curly bracket after the final line. ```hcl name = "chainguard-dev" project_id = "" group_name = "" location = "us-central1" dst_repo = "mirrored" } ``` You can create this file with a command like the following. ```sh cat > main.tf <-docker.pkg.dev//chainguard-dev-mirrored`. You can find the results of the application in your GCP Project's dashboard. Navigate to your GCP Project's **Artifact Registry**, then click on the mirror repository you set up with Terraform. There, you will find any container images that have been added to your organization's Registry since you deployed the application. This example shows a repository named `chainguard-dev-mirrored` with two images (`node` and `python`) mirrored into it. ![Screenshot of a repository in a GCP Artifact Registry named "chainguard-dev-mirrored." This repository shows two images stored within it. The first, `node`, was created and updated 36 minutes ago, while the second, `python`, was created and last updated 33 minutes ago.](gcp-events-1.png) Be aware that just because the application is listening for `registry.push` events doesn't mean any will occur automatically. Chainguard's container images are generally updated at least once every twenty four hours, so new images may not immediately appear in your mirror repository. ## Removing sample resources If you'd like to remove the resources you created with Terraform, you can run the `terraform destroy` command. ```sh terraform destroy ``` This will destroy everything created in your Terraform configuration, including the Artifact Registry repository, the Service Account, and the Cloud Run service. You can then remove the working directory to clean up your system. ```sh rm -r ~/gcp-example/ ``` Following that, all of the example resources created in this guide will be removed from your system. ## Learn more Chainguard emits more CloudEvents than just the `registry:push` events highlighted in this guide. We encourage you to check out our overview of [Chainguard Events](/platform/administration/cloudevents/events-reference/) to learn the full breadth of event types that Chainguard generates. In addition, you may find it useful to explore the rest of our [Administration](/platform/administration/) resources to better understand how you can work with Chainguard's products. --- ### Verified organizations URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/iam-organizations/verified-orgs.md Last Modified: September 28, 2026 Tags: Chainguard Console, Conceptual The Chainguard platform organizes resources in a hierarchical structure called [IAM organizations](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/). A customer typically uses one root-level _organization_ to manage its Chainguard resources. ## About verified organizations A verified organization is a root-level organization whose name Chainguard has confirmed and reserved for you. Only one organization on the Chainguard platform can hold a given verified name. Because of that, you can use the name anywhere you would otherwise use the organization's unique ID. Verifying your organization lets you: - Pull container images from a readable path, such as `cgr.dev/example.com/python`, instead of `cgr.dev//python`. - Log in through your [custom identity provider](/platform/administration/custom-idps/custom-idps/) by entering your organization name. This works in `chainctl`, the Chainguard Console, and the [Terraform provider](/platform/administration/terraform-provider/), so users don't need your identity provider's ID. - [Request new container images](/chainguard/containers/reference/request-resources/) in the Chainguard Console. Currently, Chainguard verifies customer organizations manually, typically while setting up your organization during onboarding. If you want to try Chainguard Containers before becoming a customer, refer to [Chainguard Catalog Starter](/chainguard/containers/reference/catalog-starter/). ## Check whether your organization is verified You can check whether your organization is verified using [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/). The following command uses [`jq`](https://jqlang.org/) to filter the JSON output down to each organization's name and verification status. ```sh chainctl iam organization ls -o json | jq '.items[] | {name, verified}' ``` A verified organization has the field `"verified": true`. The output for an unverified organization doesn't include the `verified` field, so `jq` prints `null`. ```json { "name": "example.com", "verified": true } { "name": "example-unverified-org", "verified": null } ``` If your organization isn't verified, ask your Chainguard account team or support to verify it. ## Log in with your organization name If you've configured a [custom identity provider](/platform/administration/custom-idps/custom-idps/) and your organization is verified, you can select your identity provider by entering your organization name when you log in. When authenticating with `chainctl`, pass the `--org-name` flag. This example uses the organization name `example.com`. ```sh chainctl auth login --org-name example.com ``` As an alternative, you can set the organization name by editing the `chainctl` configuration file with the following command. ```sh chainctl config edit ``` This command opens your system's default text editor, where you can edit the local `chainctl` configuration. Add the following lines to this file. ```yaml default: org-name: example.com ``` You can also set this with a single command using the `chainctl config set` subcommand, as in this example. ```sh chainctl config set default.org-name example.com ``` After you set the organization name, `chainctl auth login` uses the configured identity provider automatically. When you log in to the Chainguard Console, the Console detects your organization name from your email address in most cases. If your organization name doesn't match your email domain, enter it manually to select your custom identity provider. ## Pull images by organization name If your organization has access to Chainguard Containers, its container images are in a private repository within the Chainguard registry. You can pull them from `cgr.dev//`, where `` is your organization's unique ID. After Chainguard verifies your organization, you can use its name in place of the ID. For example, if your verified organization is named `example.com`, you can pull private images with a command like `docker pull cgr.dev/example.com/`. ## Restrictions for verified organizations A verified organization's name works interchangeably with its unique ID. Changing the name can break image pulls from your organization's repository within the Chainguard registry, and it can break authentication for users who log in to your custom identity provider by organization name. For that reason, you can't rename a verified organization yourself. To rename it, contact support. --- ### How to integrate Microsoft Entra ID SSO with Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/idp-providers/ms-entra-id.md Last Modified: October 2, 2026 Tags: Chainguard Containers, Procedural The Chainguard platform supports single sign-on (SSO) authentication for users. By default, users can log in with GitHub, GitLab, and Google, but SSO support lets users bring their own identity provider for authentication. This guide outlines how to register a Microsoft Entra ID (formerly Azure Active Directory) application and integrate it with Chainguard. After completing this guide, you'll be able to log in to Chainguard using Entra ID and you'll no longer be limited to the default SSO options.

Note: If you plan to use SCIM provisioning, check out our guide on how to provision users into Chainguard from Microsoft Entra ID with SCIM instead. That guide covers SSO as well, and it creates the identity provider with the correlation rule SCIM linking depends on. The correlation rule is immutable, so adding it later means deleting and recreating your identity provider.

## Prerequisites To complete this guide, you need the following: * `chainctl` installed on your system. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) if you don't already have this installed. * Owner permissions on the Chainguard organization where you want to install the identity provider. * An Entra ID account with [Global Administrator](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#global-administrator) or [Application Administrator](https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/permissions-reference#application-administrator) permissions. Without one of these roles, you can't register applications or assign users to them. * A workforce Entra ID tenant. External ID (CIAM) tenants use a different application model, and this guide doesn't cover them. ## Create a Microsoft Entra ID application 1. Log in to the [Microsoft Entra admin center](https://entra.microsoft.com) and navigate to **Entra ID** > **App registrations**. 2. Click **New registration** and configure the application: * **Name**: Set the name to "Chainguard" (or similar) so users recognize this application is for authentication to Chainguard. * **Supported account types**: Select **Single tenant only** so that only your organization can use this application to authenticate to Chainguard. * **Redirect URI**: Set the platform to **Web** and the URI to `https://issuer.enforce.dev/oauth/callback`. 3. Click **Register**. 4. On the application's **Overview** tab, note the **Application (client) ID** and the **Directory (tenant) ID**. 5. Open **Manage** > **Certificates & secrets** and click **New client secret**. 6. Add a description, set an expiration, and click **Add**. Note the secret's **Value**. > **Warning**: Entra ID shows the secret's **Value** only once, and SSO through this application stops working when the secret expires. Record the expiration date so you can rotate the secret before then. You'll need all three of the values you noted — the client ID, the tenant ID, and the client secret — in the next section. ### Restrict who can log in By default, any user in your tenant can authenticate through this application. To limit access to specific users and groups: 1. Open **Entra ID** > **Enterprise applications** and select the application you just registered. 2. On its **Properties** page, set **Assignment required?** to **Yes**. 3. Under **Users and groups**, assign the users or groups you want to have access. Users can't log in to Chainguard unless they have access to the application, so grant access before directing them to log in. If you plan to [grant Chainguard roles from Entra ID groups](/platform/administration/custom-idps/grant-roles-from-groups/) and limit the groups claim to groups assigned to the application, the groups you assign here also control which groups appear in users' tokens. For details, refer to [Microsoft Entra ID](/platform/administration/custom-idps/grant-roles-from-groups/#microsoft-entra-id) in that guide. ## Configure Chainguard to use Microsoft Entra ID Now that your Microsoft Entra ID application is ready, you can create the custom identity provider. First, log in to Chainguard with `chainctl`, using an OIDC provider like Google, GitHub, or GitLab to bootstrap your account. ```sh chainctl auth login ``` Note that you can use this bootstrap account as a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) — that is, an account you can use to log in if you ever lose access to your primary account. However, if you prefer to remove this role-binding after configuring the custom IdP, you can do so. If you belong to more than one Chainguard organization, you also need the ID of the one where you want to install the identity provider. Your choice doesn't affect how your users authenticate, but it does determine who has permission to modify the SSO configuration. To retrieve a list of the Chainguard organizations you belong to, along with their IDs, run the following command. ```sh chainctl iam organizations ls -o table ``` ```output ID | NAME | STATUS | DESCRIPTION ------------------------------------------|------------|--------|---------------------------------- 591**********************************c43 | sample_org | ready | A sample Chainguard organization . . . | . . . | . . . | . . . ``` Note the `ID` value for your chosen organization. With this information in hand, create a new identity provider with the following commands. Replace ``, ``, and `` with the values from your Entra ID application. ```sh export NAME=entra-id export CLIENT_ID= export CLIENT_SECRET= export TENANT_ID= export ISSUER="https://login.microsoftonline.com/${TENANT_ID}/v2.0" chainctl iam identity-providers create \ --configuration-type=OIDC \ --name=${NAME} \ --oidc-issuer=${ISSUER} \ --oidc-client-id=${CLIENT_ID} \ --oidc-client-secret=${CLIENT_SECRET} \ --oidc-additional-scopes=email \ --oidc-additional-scopes=profile \ --default-role=viewer ``` `chainctl` installs the provider in your organization automatically when you belong to only one. If you have access to more than one, add `--parent=` to choose where it is installed.

Note: Customers using Azure Government Cloud should set ISSUER="https://login.microsoftonline.us/${TENANT_ID}/v2.0" instead.

Pass `--oidc-additional-scopes` once per scope; comma-separating the scopes doesn't work. The `--default-role` option defines the default role granted to users registering with this identity provider. This example specifies the `viewer` role, but depending on your needs you might choose `editor` or `owner`. If you don't include this option, `chainctl` prompts you to specify the role interactively. For more information, refer to the [IAM and security section](/platform/administration/custom-idps/custom-idps/#iam-and-security) of our Introduction to Custom Identity Providers in Chainguard tutorial. You can refer to our [Generic integration guide](/platform/administration/custom-idps/custom-idps/#generic-integration-guide) in our Introduction to Custom Identity Providers doc for more information about the `chainctl iam identity-providers create` command and its required options. ### Send a usable email claim Chainguard reads the standard `email` claim from the token Entra ID issues. Entra populates `email` from the user's `mail` attribute and omits the claim when that attribute is unset — common in tenants where users have no mailbox. Because `email` is one of Microsoft's [restricted claims](https://learn.microsoft.com/en-us/entra/identity-platform/reference-claims-customization#json-web-token-jwt-restricted-claim-set), you can't re-point it to another attribute such as `userPrincipalName` with a claims-mapping policy; the value has to come from `mail`. Set a `mail` attribute on the affected users so Entra includes the claim. Note that `mail` is a shared directory attribute other systems read, so prefer a real mailbox where one exists; a placeholder set only for this is stored by Chainguard as unverified, which is harmless here because the address is used only for display and the support-portal lookup. See [Required token claims](/platform/administration/custom-idps/custom-idps/#required-token-claims) for the full requirement. ## Log in to Chainguard with the Entra ID identity provider To log in to the Chainguard Console with the new identity provider you just created, navigate to [console.chainguard.dev/auth/login](https://console.chainguard.dev/auth/login), enter your Chainguard organization's name into the **Email or organization** box, and click **Continue**. This opens a new window with the Microsoft Entra ID login flow, where you can complete the login process. You can also use the custom identity provider to log in through `chainctl`. To do this, run the `chainctl auth login` command and add the `--identity-provider` option followed by the identity provider's ID value: ```sh chainctl auth login --identity-provider ``` The ID value appears in the `ID` column of the table returned by the `chainctl iam identity-providers create` command you ran previously. You can also retrieve this table at any time by running `chainctl iam identity-providers ls -o table` when logged in. To have Entra ID create and deactivate Chainguard user accounts automatically as you assign and unassign them, check out our guide on [how to provision users into Chainguard from Microsoft Entra ID with SCIM](/platform/administration/custom-idps/scim-provisioning/ms-entra-id-scim/). --- ### cg-oci: the Chainguard container registry MCP server URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/mcp-servers/cg-oci.md Last Modified: September 28, 2026 Tags: MCP, Containers `cg-oci` gives an AI tool read-only access to the Chainguard container registry at `cgr.dev`. Through it, a client can list the repositories and tags your account can reach, fetch a manifest or image config by tag or digest, and read the signed attestations attached to an image — its SPDX SBOM, its apko build configuration, and its SLSA build provenance. Every response comes from the registry as it exists at the moment of the call, so an AI tool can answer what a specific image actually contains rather than what its documentation says it contains. The server uses the Streamable HTTP transport, at this endpoint: ``` https://cgr.dev/mcp ``` ## Prerequisites - An MCP-compatible client such as Claude Code, Claude Desktop, or Cursor - A [Chainguard account](https://console.chainguard.dev/) ## Connect to the server ### Claude Code Add the server with `claude mcp add`, using the HTTP transport: ```shell claude mcp add --transport http cg-oci https://cgr.dev/mcp ``` Pick the scope that fits how you want to use it: `local` (the default — only you, in the current directory), `project` (writes a shared `.mcp.json` at the repository root, checked in for teammates), or `user` (only you, across every project). A registry lookup is useful almost everywhere, so `--scope user` is usually the right choice: ```shell claude mcp add --transport http --scope user cg-oci https://cgr.dev/mcp ``` The server is added unauthenticated. To complete OAuth, start a session and run the `/mcp` command: ```Prompt /mcp ``` Select **cg-oci**, choose **Authenticate**, and approve the connection in the browser window that opens. Check the status any time with: ```shell claude mcp list ``` ```output cg-oci: https://cgr.dev/mcp (HTTP) - ✓ Connected ``` ### Cursor Cursor supports HTTP transport natively. Add the server to your MCP configuration: ```json { "mcpServers": { "cg-oci": { "url": "https://cgr.dev/mcp" } } } ``` Restart Cursor, then connect the server from **Tools & MCPs** in settings and complete the browser sign-in. ### Claude Desktop Claude Desktop reads MCP servers from a JSON file but does not yet support HTTP transport directly. Use [`mcp-remote`](https://github.com/geelen/mcp-remote) to bridge to the hosted server: ```json { "mcpServers": { "cg-oci": { "command": "npx", "args": [ "mcp-remote", "https://cgr.dev/mcp" ] } } } ``` The configuration file lives at: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` `npx` downloads and runs `mcp-remote` on demand, so you need Node.js installed on the host. Restart Claude Desktop after saving the file. ### Other MCP clients Any client that supports a remote Streamable HTTP MCP server with OAuth can connect to the same endpoint. Point it at `https://cgr.dev/mcp` and complete the browser sign-in when prompted. Consult your client's documentation for where its MCP configuration lives. ## Authentication Authentication is OAuth 2.0 against the Chainguard issuer. You can read an image through `cg-oci` only if your account can pull it from the registry. On a remote or headless workstation, you can supply a token from `chainctl` instead of completing the browser flow. Refer to [Authenticate with chainctl instead of a browser](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser) for the full recipe; `cg-oci`'s audience is `https://cgr.dev/mcp`. ### Access scope Results are scoped to your account, which has two effects: - **`list_repos` does not list the public Chainguard catalog.** It lists every repository your token can reach, across every organization you belong to, in `_catalog` order. If your organization mirrors images into its own namespace, those repositories appear alongside the public ones. - **Public `chainguard/*` repositories expose only `latest` and `latest-dev`.** Version tags such as `3.12` live in your organization's own namespace and require an entitlement. `chainguard/python:3.12` is not a valid reference for an unentitled caller, even though 3.12 builds of the Python image exist. To browse the public catalog and its documented tags, use the [Chainguard Containers directory](https://images.chainguard.dev/) instead. ## Tool reference The tools fall into two groups. The `list_*` tools return one bounded page at a time — 50 items by default, 200 at most — and you continue by passing a cursor back. The `get_*` tools return a single object exactly as the registry stores it. Every tool identifies an image with two separate parameters rather than one fully qualified reference: - `repo` — the repository name without the registry host, for example `chainguard/python` - `reference` — a tag such as `latest`, or a digest such as `sha256:5a673f...` Passing `cgr.dev/chainguard/python:3.12` as a single string does not work. | Tool | Parameters | Returns | Example prompt | | ----- | ----- | ----- | ----- | | `list_repos` | `page_size`, `cursor` | `{repos, count, has_more}` | "What Chainguard repositories can I access?" | | `list_tags` | `repo`, `page_size`, `cursor`, `include_digest_tags` | `{tags, count, has_more, next_cursor}` | "What tags exist for chainguard/python?" | | `get_manifest` | `repo`, `reference` | `{digest, manifest, media_type, size}` | "Get the manifest for chainguard/python:latest" | | `get_config` | `repo`, `reference`, `architecture` | `{digest, config}` | "What's the entrypoint of the chainguard/python image?" | | `get_sbom` | `repo`, `reference`, `architecture` | `{subject_digest, size_bytes, attestations}` | "Show me the SBOM for chainguard/python:latest" | | `list_sbom_packages` | `repo`, `reference`, `architecture`, `page_size`, `cursor` | `{subject_digest, documents, packages, count, total_packages, has_more, next_cursor}` | "What packages are in the amd64 chainguard/python image?" | | `get_apko_config` | `repo`, `reference`, `architecture` | `{subject_digest, attestations}` | "What apko config built chainguard/python:latest?" | | `get_provenance` | `repo`, `reference`, `architecture` | `{subject_digest, attestations}` | "Show me the build provenance for chainguard/python:latest" | Depending on your organization's entitlements, the server may advertise additional tools — a vulnerability listing, for instance, where that feature is enabled. Run `/mcp` in Claude Code to see the tool list your own token receives. ### list_repos Lists the repositories your token can reach, through the registry's `/v2/_catalog` endpoint. The registry offers no server-side name filtering, so to find a specific repository, an MCP client must page through the whole list and filter the results itself. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `page_size` | integer | no | Repositories per page (default 50, max 200) | | `cursor` | string | no | The last repository name from the previous page; omit to start at the first page | The cursor here is a repository name rather than an opaque token, unlike the other `list_*` tools on this server. ### list_tags Lists the tags in one repository. Each call requests a single page from the registry, so a repository with thousands of tags responds as quickly as a small one. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host, for example `chainguard/python` | | `page_size` | integer | no | Tags per page (default 50, max 200) | | `cursor` | string | no | The `next_cursor` from the previous page | | `include_digest_tags` | boolean | no | Include the `sha256-` prefixed tags that Cosign uses for signatures, attestations, and SBOMs (default `false`) | Digest tags are omitted by default because they outnumber an image's real tags by a wide margin. Set `include_digest_tags=true` only when you are looking for the attachment tags themselves. ### get_manifest Fetches an OCI manifest by tag or digest. The server returns the manifest body as a raw JSON string, which preserves the exact bytes the registry computed the digest over. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host | | `reference` | string | yes | A tag such as `latest`, or a digest such as `sha256:...`. Prefer digests for content-addressable lookups. | For a multi-architecture tag this returns the image *index*, whose `manifests` array names the per-architecture children. The index's `annotations` carry useful metadata, including `org.opencontainers.image.created` — which is how you find out when an image was built. ### get_config Fetches the OCI image config: the build-time description of the image's entrypoint, environment, working directory, and user. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host | | `reference` | string | yes | Tag or manifest digest | | `architecture` | string | no | Which child manifest to descend into when the reference resolves to a multi-arch index (default `amd64`) | ### get_sbom Fetches the signed SPDX SBOM attestation (`predicateType` `https://spdx.dev/Document`). Each `predicate` in the response is the raw SPDX document exactly as it was signed. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host | | `reference` | string | yes | Tag or manifest digest | | `architecture` | string | no | Descend into this architecture's child manifest. Omit to read the attestation attached to the reference itself — the index, for a multi-arch tag. | The server refuses a document larger than 512 KB outright rather than truncating it; the error names the size and the `cosign` command that fetches the file directly. Use `list_sbom_packages` when you only need the package list. An image with no SBOM attestation returns an empty `attestations` list rather than an error. ### list_sbom_packages Pages through the packages in an image's SPDX SBOM, returning package names, versions, PURLs, licenses, and SPDX purposes without pulling the whole document into context. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host | | `reference` | string | yes | Tag or manifest digest | | `architecture` | string | no | Descend into this architecture's child manifest | | `page_size` | integer | no | Packages per page (default 50, max 200) | | `cursor` | string | no | The `next_cursor` from the previous page | The package list mixes several kinds of entry, distinguishable by their `purpose` and `purl`: - `CONTAINER` — the image and its layer descriptors, with `pkg:oci/...` PURLs - `OPERATING_SYSTEM` and `APPLICATION` — the Wolfi base and its APK packages, with `pkg:apk/wolfi/...` PURLs - `INSTALL` — the melange build definitions that produced those packages - `SOURCE` — the upstream source a package was built from, such as a CPython commit - `FILE` — individual files the SBOM records The `OPERATING_SYSTEM` and `APPLICATION` entries are the OS packages most readers are looking for. The OCI descriptors sort first, so the APK packages start after about a dozen `CONTAINER` rows on the first page. ### get_apko_config Fetches the apko image-configuration attestation (`predicateType` `https://apko.dev/image-configuration`) — the declarative build-time description of the image's contents, accounts, entrypoint, and environment. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host | | `reference` | string | yes | Tag or manifest digest | | `architecture` | string | no | Descend into this architecture's child manifest | ### get_provenance Fetches the SLSA build provenance attestation (`predicateType` `https://slsa.dev/provenance/v1`), describing the build that produced the image. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `repo` | string | yes | Repository name without the registry host | | `reference` | string | yes | Tag or manifest digest | | `architecture` | string | no | Descend into this architecture's child manifest | The predicate's `buildDefinition.internalParameters` pins every package version that went into the image, and `runDetails.builder` names the builder and its version. This makes `get_provenance` a fast way to read exact package versions without paging through an SBOM. ## Example usage Sample exchanges from a Claude Code session with `cg-oci` connected. You don't call the tools yourself — you describe what you want, and the AI tool decides which calls to make. Images are rebuilt often, so your answers will differ. ```Prompt What tags are available for chainguard/python? ``` ```output Called cg-oci (ctrl+o to expand) ● The chainguard/python repository exposes two tags: latest and latest-dev. Version-specific tags such as 3.12 live in an entitled namespace rather than the public chainguard/ one. ``` ```Prompt When was chainguard/python:latest built, and what Python version is in it? ``` ```output Called cg-oci (ctrl+o to expand) ● chainguard/python:latest was built on 2026-09-20. Its build provenance pins the python-3.14 package, so latest is currently Python 3.14. ``` ```Prompt What OS packages are in the amd64 build of chainguard/python:latest? ``` ```output Called cg-oci (ctrl+o to expand) ● The amd64 image's SBOM lists 134 entries. The APK packages include wolfi-baselayout, glibc, openssl's libcrypto3 and libssl3, sqlite-libs, and python-3.14 itself. ``` ## Troubleshooting | Symptom | What it means | What to do | | ----- | ----- | ----- | | `MANIFEST_UNKNOWN: Unknown manifest` | The repository is readable but that tag or digest is not. Most often the tag genuinely doesn't exist in the namespace you're reading — for example `chainguard/python:3.12`, where public `chainguard/*` repositories carry only `latest` and `latest-dev`. | Call `list_tags` on the repository to see the tags your token can actually read, then use one of those. | | `FORBIDDEN: Forbidden` | Your token cannot read the repository at all. The registry returns this for a repository that doesn't exist and for one you aren't entitled to, without distinguishing them. | Confirm the repository name with `list_repos`, and check your organization's entitlement for it. | | Server shows as not connected in `claude mcp list` | OAuth was never completed, or the token expired. Claude Code's tokens against the Chainguard issuer last about an hour and carry no refresh token. | Run `/mcp`, select **cg-oci**, and authenticate again. To stop re-authenticating, switch to the [`chainctl` helper](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser). | | `401 invalid token` when using the `chainctl` helper | The audience was registered as a bare hostname. MCP audiences must include the `/mcp` path. | Run `chainctl auth login --audience=https://cgr.dev/mcp` and try again. | | `no chainctl token for audience ...` | The helper script ran but that audience was never logged in. | Run the `chainctl auth login` command the error prints. | | An SBOM request is refused for size | The SPDX document exceeds the server's 512 KB ceiling. Documents are never truncated. | Use `list_sbom_packages` instead, or fetch the file with the `cosign` command named in the error. | | `list_sbom_packages` returns only two or three entries | The call read the index-level SBOM rather than a per-architecture one. | Pass `architecture=amd64` (or `arm64`). | | The AI tool can't find an image you know exists | It may be searching `list_repos`, which reflects your token rather than the public catalog, and offers no server-side name filter. | Name the repository explicitly, as `chainguard/`, or look it up in the [Containers directory](https://images.chainguard.dev/). | ## Next steps - [`cg-apk`](/platform/mcp-servers/cg-apk/) — search the Wolfi package index and read package SBOMs and build recipes - [`cg-versions`](/platform/mcp-servers/cg-versions/) — check upstream releases and end-of-life dates - [`cg-api`](/platform/mcp-servers/cg-api/) — query organizations, IAM, and registry metadata through the platform API - [Chainguard MCP servers overview](/platform/mcp-servers/overview/) — the full set, and the `chainctl` authentication recipe --- ### Disabling default social logins URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/disabling-social-logins.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural By default, users can authenticate to the Chainguard platform with a built-in social login provider: GitHub, GitLab, or Google. After you [configure a custom identity provider](/platform/administration/custom-idps/custom-idps/#setup-and-administration) for single sign-on (SSO), you may want to require that everyone in your organization authenticate through that provider instead. A common problem for SSO customers is that users click **Login with Google** (or another social provider) out of habit. Because a personal or non-federated Google account isn't tied to your organization, this creates an account *outside* it that an owner then has to clean up and re-provision. Preventing social logins keeps account lifecycle, group membership, and security policies (such as multi-factor authentication) enforced centrally through your identity provider. Chainguard doesn't currently offer a native setting to disable social logins. However, if your identity provider is Google Workspace, you can achieve a similar result by blocking the Chainguard application in the Google Workspace Admin console. This guide outlines how to block the application and how to redirect users afterward. > **Note**: If a native setting would be valuable to your organization, let your customer success manager know. ## Prerequisites Before blocking social logins, make sure you have a working custom identity provider and a recovery path in place. Some organizations intentionally keep a social login (or an email and password account) as a [backup, break-glass account](/platform/administration/custom-idps/custom-idps/#backup-accounts) in case they are ever locked out of their identity provider. If you block Google login without another recovery mechanism, an identity provider outage or misconfiguration could lock every user out of your organization. Chainguard manages MFA for email and password accounts, so replacing a lost authenticator device on a break-glass account of that kind takes a support ticket rather than a self-service change. Factor that turnaround into your recovery plan, and see [Change or reset your MFA device](/get-started/mfa-devices/) for the process. We recommend confirming both of the following before proceeding: * Your custom identity provider is configured and working. You can verify this by listing your identity providers and authenticating with one: ```sh chainctl iam identity-providers list ``` ```sh chainctl auth login --identity-provider ``` * You have a [backup account](/platform/administration/custom-idps/custom-idps/#backup-accounts) that does not rely on Google login (for example, an [assumable identity](/platform/administration/assumable-ids/assumable-ids/)), so you retain a recovery path. You also need administrator access to your organization's [Google Workspace Admin console](https://admin.google.com). ## How this works Google Workspace administrators control which third-party applications can access Google Workspace data and log in with a Google account. When you set the Chainguard application's access to **Blocked**, the **Login with Google** button on the Chainguard login screen fails for users in your Google Workspace organization. Those users must then authenticate through your custom identity provider (or another allowed method) instead, which prevents stray accounts from being created outside your organization. You do all of this configuration on the Google Workspace side — Chainguard requires no configuration changes. ## Blocking the Chainguard app in Google Workspace The following steps block the Chainguard application for your entire Google Workspace organization. Note that navigation paths and setting names in these steps may change over time. For full details and the latest instructions, refer to Google's documentation on [controlling which third-party and internal apps access Google Workspace data](https://knowledge.workspace.google.com/admin/apps/control-which-apps-access-google-workspace-data). 1. Log in to the [Google Workspace Admin console](https://admin.google.com) with an administrator account. 2. Navigate to **Security** > **Access and data control** > **API controls**. 3. Find the **App access control** section. The list of accessed apps may be hidden behind a ribbon-style scroll pane. Scroll through the tiles until you reach the one labeled ***N* Accessed Apps**, then click **View list**. This displays the apps that have accessed your organization's Google Workspace data. 4. Click **Add filter**, type `Chainguard`, and apply the filter to narrow the list to apps with "Chainguard" in the name. 5. Hover over the **Chainguard** app row. A **Change access** button appears on the right; click it. 6. Select which users the policy applies to — either **all** organizational units or **specific** ones, and then select the policy to **Block** access. 7. Save your changes to activate the policy. After you activate the policy, attempts to log in to the Chainguard Console using a Google account linked to your organization show a Google **Access blocked: Authorization error** pop-up with the message `Error 400: admin_policy_enforced`. Affected users must then authenticate through your custom identity provider instead. > **Tip**: Because you can scope the block to specific organizational units, you can retain break-glass access via Google social login for a chosen organizational unit (for example, a small unit containing only your recovery accounts) while blocking it for everyone else. ## After blocking social logins Direct your users to authenticate with your custom identity provider. If your organization is [verified](/platform/administration/iam-organizations/verified-orgs/), users can log in with your organization name: ```sh chainctl auth login --org-name example.com ``` Otherwise, users authenticate by passing the identity provider's ID: ```sh chainctl auth login --identity-provider ``` To avoid specifying this on every login, users can set a default identity provider or organization name in their `chainctl` configuration, as described in the [custom identity providers guide](/platform/administration/custom-idps/custom-idps/#set-a-default-identity-provider). In the Chainguard Console, users in a verified organization can enter their organization name or email address to be routed to your identity provider. If you need to restore Google login (for example, during a recovery scenario), follow the same steps to locate the Chainguard app, but instead of blocking it, change the access policy for the relevant organizational units to grant access. Select at least **Limited** access so that Google permits the login scopes. Google manages the exact console layout and terminology, which may change over time. If you require additional assistance configuring app access control or organizational units, refer to [Google Workspace support](https://support.google.com/a). --- ### Chainguard FIPS TLS connectivity requirements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/tls-requirements.md Last Modified: September 28, 2026 Tags: FIPS, TLS, Product This document provides an overview of FIPS TLS connectivity requirements for using Chainguard FIPS products. These FIPS products have **higher** minimum TLS requirements, which complicates connecting them to insecure EOL non-FIPS systems, as well as FIPS systems with lapsed (historical) certification. Chainguard strives to ensure the broadest connectivity possible for its FIPS products. However, many obsolete systems are still widely used and may not be able to connect with Chainguard FIPS products. ## FIPS TLS requirements ### TLSv1.3 [NIST Special Publication 800-52 Rev. 2](https://csrc.nist.gov/pubs/sp/800/52/r2/final) required all clients and servers to support TLSv1.3 by January 1, 2024. While the majority of FIPS modules do have support for TLSv1.3, there are many FIPS 140-2 certified products and operating systems that do not have TLSv1.3 support. As of November 2025 there are [768 Active FIPS 140-2](https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules/search?SearchMode=Advanced&Standard=140-2&CertificateStatus=Active&ValidationYear=0) validated modules, many of which do not have TLSv1.3 capability. As a rule of thumb, products launched prior to 2019 do not have TLSv1.3 support, and still require TLSv1.2. If at all possible, upgrade clients and servers to gain TLSv1.3 capability, as newer FIPS modules have started to drop support for validated TLSv1.2. This is primarily driven by adding Post-Quantum Cryptography (PQC) which is only supported with TLSv1.3. ### TLSv1.2 RFC 7627 A "triple handshake" man-in-the-middle attack was discovered in the original TLSv1.2 [RFC 5246](https://datatracker.ietf.org/doc/html/rfc5246) protocol specification. The TLSv1.2 [RFC 7627](https://datatracker.ietf.org/doc/html/rfc7627) (published in 2015) addresses this vulnerability with an Extended Master Secret extension. If you connect to Chainguard's FIPS products without support for TLSv1.2 RFC 7627, your privacy and data integrity are not guaranteed. To address this vulnerability in FIPS modules, NIST initiated a [programmatic transition](https://csrc.nist.gov/Projects/cryptographic-module-validation-program/programmatic-transitions) to require TLSv1.2 [RFC 7627](https://datatracker.ietf.org/doc/html/rfc7627) in approved mode for all new module validations from May 16, 2023. This means that all FIPS modules submitted after that date require TLSv1.2 with RFC 7627 support in approved mode. In practice, products that support TLS 1.2 with RFC 7627 almost always also support TLS 1.3; only a handful of cases—primarily specific Windows and Android versions—do not. ## Known compatible clients and servers The following Operating Systems, and newer versions of thereof, are known to support TLSv1.2 RFC 7627 and/or TLSv1.3 and should be able to establish TLS connectivity with Chainguard FIPS products: * Windows 10 * Windows Server 2016 * RHEL 8 * Fedora 28 * Ubuntu 18.04 * Amazon Linux 2023 * SLES 15 * OpenSUSE Leap 15 * Debian 10 * Android 6.0 * Apple macOS Siera 10.12 * iOS 10 * AWS RDS Postgres 16.11-R1 * AWS MQ Apache ActiveMQ * AWS ELB Application Load Balancer This list is not exhaustive. To find FIPS products that support TLSv1.2 RFC 7627 or TLS v1.3 one can use [CAVP search](https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/validation-search), select **Implementation**, and in the list of algorithms select "**TLS v1.2 RFC7627**" or "**TLS v1.3 KDF**". ### AWS RDS Postgres AWS RDS Postgres 16.11-R1 and later support TLSv1.3. If you require access to Postgres 14 or 15, please consider using Chainguard [postgres-fips](https://images.chainguard.dev/directory/image/postgres-fips/overview) container image or VM instead. ### AWS MQ RabbitMQ no connectivity At this time AWS MQ RabbitMQ [does not support](https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/data-protection.html) TLSv1.2 RFC 7627 or TLSv1.3. This product is currently out of compliance with [NIST Special Publication 800-52 Rev. 2](https://csrc.nist.gov/pubs/sp/800/52/r2/final). Please consider using AWS MQ Apache ActiveMQ, Chainguard's [rabbitmq-fips](https://images.chainguard.dev/directory/image/rabbitmq-fips/overview) container image, or a VM instead. ### AWS Elastic Load Balancing - Classic Load Balancers - no connectivity At this time the AWS Elastic Load Balancing - Classic Load Balancer [does not support](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-ssl-security-policy.html#elb-backend-cipher-suite) any secure or approved cipher suites for back-end connections. The usage of the backend connectivity in Classic Load balancer is currently out of compliance with [NIST Special Publication 800-52 Rev. 2](https://csrc.nist.gov/pubs/sp/800/52/r2/final). This is also documented at [Elastic Load Balancing in AWS GovCloud (US)](https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-elb.html) which states that: > Some features of Elastic Load Balancing (ELB) TLS do not support FIPS 140-3 requirements by default. When using the Classic or Network Load Balancer, you can pass TCP traffic and terminate TLS on your target (for example, web server), that is configured to support FIPS 140-3 requirements. Application Load Balancer (ALB) supports selecting FIPS algorithms. Please consider upgrading to AWS Elastic Load Balancing - Application Load Balancer product, or configure Classic Load Balancer to pass TCP traffic and terminate TLS on the target with any Chainguard FIPS images. ### Amazon Linux 2 connectivity support There is no approved mode for connecting Chainguard products with Amazon Linux 2 in its default FIPS and non-FIPS configurations. Amazon Linux 2 ships without support for TLSv1.2 RFC 7627 or TLS v1.3. Additionally, Amazon Linux 2 FIPS certification has lapsed. Amazon Linux 2 non-FIPS has an optional package: `openssl11`. With this installed, it is possible to gain approved connectivity with Chainguard FIPS products. As of this writing, Amazon Linux 2 is currently scheduled to sunset on June 30, 2026. We recommend upgrading to [Chainguard VMs](/chainguard/vms/overview/), [Amazon Linux 2023](https://aws.amazon.com/linux/amazon-linux-2023/), or [Bottlerocket](https://bottlerocket.dev/). ## Testing connectivity To guarantee connectivity, clients and servers communicating with Chainguard FIPS products must, at minimum, support the following TLS requirements: * TLSv1.3 with the TLS_AES_256_GCM_SHA384 cipher suite * TLSv1.2 with * ECDHE-ECDSA-AES256-GCM-SHA384 cipher string * [RFC 7627](https://datatracker.ietf.org/doc/html/rfc7627) Extended Master Secret Extension support * Signatures using P-256 with SHA-256 * Signatures using RSA with 2048 bits and SHA-256 These requirements can be approximately tested with the following OpenSSL client command: ```shell openssl s_client -cipher @SECLEVEL=2:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384 -ciphersuites TLS_AES_256_GCM_SHA384 -groups P-256 -connect HOST:PORT ``` > Note that in the case of TLSv1.2 connectivity you must check the output for `Extended master secret: yes`. --- ### Create an assumable identity for a Kubernetes pod URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/kubernetes-identity.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This procedural tutorial outlines how to create an identity that can be assumed by a Kubernetes pod and then used to interact with the Chainguard API. ## Prerequisites To complete this guide, you will need the following. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * `kubectl` - the command line interface tool for Kubernetes. * Access to a Kubernetes cluster. ## Find the issuer URL of the Kubernetes cluster A Kubernetes cluster operates as an OIDC issuer. How you find the URL for that issuer depends on where the cluster is running. ### GKE (Google Kubernetes Engine) The issuer URL for a GKE cluster will follow this format. ``` https://container.googleapis.com/v1/projects//locations//clusters/ ``` ### EKS (Amazon Elastic Kubernetes Service) ```shell aws eks describe-cluster \ --name \ --query "cluster.identity.oidc.issuer" \ --output text ``` ### AKS (Azure Kubernetes Service) ```shell az aks show \ --name \ --resource-group \ --query "oidcIssuerProfile.issuerUrl" \ --output tsv ``` ### Generic You can issue a token with `kubectl` and find the issuer URL in the `iss` claim with [`jwt`](https://github.com/mike-engel/jwt-cli). ```shell kubectl create token default | jwt decode - ``` ## Create an assumable identity Run this `chainctl` command to create an identity for the `default` service account in the `default` namespace and assign it the `registry.pull` role. Provide the issuer URL you identified in the previous step. ```shell chainctl iam id create example-identity \ --identity-issuer="" \ --subject-pattern="system:serviceaccount:default:default" \ --role=registry.pull ``` This will return the ID of the identity which we will use in the next section. If your cluster's issuer URL is not available over the internet then you can create an identity by exporting the JWKS (JSON Web Key Set) from the cluster with `kubectl` and provide it with the `--issuer-keys` flag. ```shell chainctl iam identities create example-identity \ --issuer-keys="$(kubectl get --raw /openid/v1/jwks)" \ --identity-issuer= \ --subject=system:serviceaccount:default:default \ --role=registry.pull ``` Many Kubernetes clusters will rotate the keys in the JWKS, so this command creates a static identity that expires after a period of time. The default duration is 30 days. In this scenario you should regularly recreate the identity with the latest JWKS. ## Assume the identity in a pod Create `pod.yaml` with this content. Replace `` in the list of environment variables with the ID of the identity you created in the previous step. ```yaml apiVersion: v1 kind: Pod metadata: name: assumable-id-demo namespace: default spec: serviceAccountName: default restartPolicy: Never containers: - name: curl image: cgr.dev/chainguard/curl:latest-dev command: - sh - -c - | # Exchange the service account token for a Chainguard # API token IDENTITY_TOKEN=$(cat /var/run/chainguard/oidc/oidc-token) API_TOKEN=$(curl \ -sSf \ -H "Authorization: Bearer $IDENTITY_TOKEN" \ "https://issuer.enforce.dev/sts/exchange?aud=https://console-api.enforce.dev&identity=$IDENTITY" \ | jq -r .token ) # List repos in the Chainguard registry curl -sSf -H "Authorization: Bearer $API_TOKEN" https://console-api.enforce.dev/registry/v1/repos | jq -r .items[].name env: - name: IDENTITY value: "" volumeMounts: - name: oidc-token mountPath: /var/run/chainguard/oidc readOnly: true volumes: - name: oidc-token projected: sources: - serviceAccountToken: path: oidc-token expirationSeconds: 600 audience: issuer.enforce.dev ``` Then create the pod. ```shell kubectl create -n default -f pod.yaml ``` Wait for the pod to complete and then view the logs. It should return a list of the repositories in your organization. ```shell kubectl -n default logs assumable-id-demo ``` ## Clean up Delete the pod. ```shell kubectl -n default delete pod assumable-id-demo ``` Delete the identity. ```shell chainctl iam id delete ``` ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). --- ### What does the Chainguard Factory build? URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/what-factory-builds.md Last Modified: September 28, 2026 Tags: Chainguard Factory, Chainguard Containers, Video, Overview [Watch the video on YouTube](https://www.youtube.com/watch?v=V4xIcHDhBhw) ## Transcript **Interviewer**: So Dustin, what does the Factory actually build every day? **Dustin Kirkland**: Yeah, so the input is open source software that's coming in from thousands of upstream maintainers who are very much the experts in what they do. That goes into our Factory. We apply our build automation system and, importantly, our quality control and testing system. Out of that comes a series of artifacts. We produce APKs, which is our package format. We produce container images, which is the output that our customers typically run in their Docker and Kubernetes environments. We also produce virtual machines that get loaded automatically into Google, Amazon, and Azure public clouds, as well as images that can run in QEMU and VMware on-prem. And it also produces libraries. So we're rebuilding from source not just open source projects that yield packages, but we're also rebuilding open source software that yields Java artifacts—JAR files—and Python modules—Python wheels. All of those are packaged up and built and tested from a Chainguard perspective, and we publish those into a variety of different registries and artifact repositories that our customers can then consume securely inside of their environment. --- ### Authenticating with the Chainguard SDK URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/api/authentication.md Last Modified: September 28, 2026 Tags: Chainguard Console, Procedural There are several ways for users to interact with the Chainguard platform, with [`chainctl`](/platform/chainctl/) (Chainguard's command-line tool) and the [Chainguard Console](https://console.chainguard.dev/overview) (Chainguard's web interface) being the two most commonly-used methods. However, both of these require a human user to authenticate, and aren't useful for working with Chainguard resources programmatically. The [Chainguard SDK](https://github.com/chainguard-dev/sdk) serves to ease programmatic integration with the Chainguard platform. This guide highlights two examples from the SDK repository that show how to authenticate to the [Chainguard registry](/chainguard/containers/registry/overview/) using the `chainguard.dev/sdk/auth` and `chainguard.dev/sdk/auth/ggcr` packages. The first has you authenticate as a local user, while the second has you authenticate as an [assumed identity](/platform/administration/assumable-ids/assumable-ids/). This page gives examples in Golang as well as using `curl`. For more information about the Golang examples, refer to the [`examples` folder](https://github.com/chainguard-dev/sdk/tree/main/examples/registry) in the SDK repository. ## Prerequisites To follow along with this guide you must have [Go installed](https://go.dev/doc/install) to run the examples provided by the SDK repository, which are written in Golang. Additional examples in `curl` are found just after the discussion of each Golang example. Additionally, it may help to run these examples from a temporary directory, like `/tmp` so the code is automatically removed from your system the next time it boots: ```shell cd /tmp ``` ## Example 1 — Authenticate using local credentials The first example from the SDK repository we will go over is the [`chainctl` example](https://github.com/chainguard-dev/sdk/tree/main/examples/registry/chainctl). This example shows how to use a token source backed by `chainctl` — Chainguard's command-line tool — to access the Chainguard registry using local credentials. ### Example 1 in Golang The `chainctl` example consists of the following Go code in a `main.go` file (with comments removed): ```go package main import ( "context" "encoding/json" "log" "os" "chainguard.dev/sdk/auth" "chainguard.dev/sdk/auth/ggcr" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" ) func main() { ctx := context.Background() ts := auth.NewChainctlTokenSource(ctx, auth.WithAudience("cgr.dev")) desc, err := remote.Get(name.MustParseReference("cgr.dev/chainguard/static"), remote.WithAuthFromKeychain(ggcr.TokenSourceKeychain(ts))) if err != nil { log.Fatalf("error getting reference: %v", err) } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") _ = enc.Encode(desc) } ``` This code works by executing the `chainctl` binary to retrieve a token and making a call directly to the registry. Specifically, it does the following things: * It imports some packages, including the Chainguard SDK's [auth](https://github.com/chainguard-dev/sdk/tree/main/auth) and [auth/ggcr](https://github.com/chainguard-dev/sdk/tree/main/auth/ggcr) packages. These are what allow the program to interact with the Chainguard registry. * The example uses the `auth` package to execute the `chainctl` CLI binary to retrieve a token based on the local user's credentials. * Using this token, the example makes a call to the Chainguard registry to retrieve some information about the supplied image. The example uses the `cgr.dev/chainguard/static` Free image. To run this example, save the `main.go` file to your local machine. Then run the following commands: ```shell go mod init github.com/chainguard-dev/sdk && go mod tidy ``` The `go mod init` command will initialize a new `go.mod` file in the current directory. Including the `github.com/chainguard-dev/sdk` URL tells Go to use that as the module path. The `go mod tidy` command ensures that the new `go.mod` file matches the source code in the module. Following that, execute the program with `go run`: ```shell go run main.go ``` This will return output like the following: ``` { "mediaType": "application/vnd.oci.image.index.v1+json", "size": 925, "digest": "sha256:633aabd19a2d1b9d4ccc1f4b704eb5e9d34ce6ad231a4f5b7f7a3af1307fdba8", "Manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLmluZGV4LnYxK2pzb24iLCJtYW5pZmVzdHMiOlt7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsInNpemUiOjgzOCwiZGlnZXN0Ijoic2hhMjU2OmJiMmRlMjU3MjFlMTE3Zjg4MDYwYjgyZGQ2YWQ1M2Y4ODdlZTgwOWU1MzE1Y2Y0MWEwMGNkOGExM2ZhMjMwNzciLCJwbGF0Zm9ybSI6eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgifX0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJzaXplIjo4MzgsImRpZ2VzdCI6InNoYTI1NjoxMmVjMWU3NDg0NmVhNDM1OTNkNmExMzMwZGEyOGVkNGQzNmRmNThhMTg2NGQwOTE1MjRlM2IwYTk5MDY4Y2QzIiwicGxhdGZvcm0iOnsiYXJjaGl0ZWN0dXJlIjoiYXJtNjQiLCJvcyI6ImxpbnV4In19XSwiYW5ub3RhdGlvbnMiOnsiZGV2LmNoYWluZ3VhcmQucGFja2FnZS5tYWluIjoiIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnMiOiJDaGFpbmd1YXJkIFRlYW0gaHR0cHM6Ly93d3cuY2hhaW5ndWFyZC5kZXYvIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmNyZWF0ZWQiOiIyMDI1LTA1LTI4VDEzOjM1OjQ4WiIsIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5zb3VyY2UiOiJodHRwczovL2dpdGh1Yi5jb20vY2hhaW5ndWFyZC1pbWFnZXMvaW1hZ2VzL3RyZWUvbWFpbi9pbWFnZXMvc3RhdGljIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybCI6Imh0dHBzOi8vaW1hZ2VzLmNoYWluZ3VhcmQuZGV2L2RpcmVjdG9yeS9pbWFnZS9zdGF0aWMvb3ZlcnZpZXciLCJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yIjoiQ2hhaW5ndWFyZCJ9fQ==" } ``` The example returns the image's digest and the OCI manifest data. This proves that you've gotten a response back from the registry and that authentication worked as expected. This example doesn't really reflect a real-world use case, as users will generally access `cgr.dev/chainguard` container repositories without authenticating. However, by presenting a token it will trigger the authentication checks, making this example useful for illustrating how this can be done with the Chainguard SDK. You can experiment with updating this example to retrieve information about a different Chainguard container image by changing the `cgr.dev/chainguard/static` value in the `main()` function. You could replace this with any Chainguard repository you have access to, and the example will return information about that image. ### Example 1 using curl To authenticate using `curl` and the API, first authenticate directly with `chainctl`: ```shell chainctl auth login ``` This will return output like the following: ```response Opening browser to https://issuer.enforce.dev/oauth?audience=https%3A%2F%2Fconsole-api.enforce.dev&client_id=auth0&connection=google-oauth2&create_refresh_token=true&exit=redirect&redirect=http%3A%2F%2Flocalhost%3A44723%2Fcallback%3Ftoken%3Dtrue%26error%3Dtrue&skip_registration=true Opening in existing browser session. ``` Then, retrieve an API token and use it to call the API: ```shell curl -H "Authorization: Bearer $(chainctl auth token)" https://console-api.enforce.dev/iam/v1/groups | jq '.' ``` In our example, [we requested a list of groups the account belongs to](/platform/api/spec/#tag/groups/GET/iam/v1/groups). Then we piped the API response, which is in JSON, into `jq` to make it more easily readable by humans for our documentation sample. This will return output like the following, which has been edited for length: ```response { "items": [ { "id": "", "name": "chainguard.edu", "description": "Developer Enablement images catalog", "resourceLimits": { "clusters": 0, "idps": 0, "repositories": 0 }, "verified": true }, { "id": "", "name": "chainguard", "description": " This group holds the public Chainguard Images hosted under\n cgr.dev/chainguard\n", "resourceLimits": {}, "verified": true } ] } ``` ## Example 2 — Authenticate with an assumed identity The [`exchange` example](https://github.com/chainguard-dev/sdk/tree/main/examples/registry/exchange) demonstrates how to exchange a token for an assumed identity to access the registry. ### Example 2 in Golang This example consists of the following Go code in a `main.go` file (with comments removed): ```go package main import ( "context" "encoding/json" "log" "os" "chainguard.dev/sdk/auth" "chainguard.dev/sdk/auth/ggcr" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/remote" ) const ( sub = "720909c9f5279097d847ad02a2f24ba8f59de36a/a033a6fabe0bfa0d" ) func main() { ctx := context.Background() ts := auth.NewChainctlTokenSource(ctx) desc, err := remote.Get(name.MustParseReference("cgr.dev/chainguard/static"), remote.WithAuthFromKeychain(ggcr.Keychain(sub, ts))) if err != nil { log.Fatalf("error getting reference: %v", err) } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") _ = enc.Encode(desc) } ``` This example is similar to the previous one, but has the following differences: * It creates a constant named `sub`. In this example, the `sub` constant's value is set to the UIDP of a Chainguard identity named `all-users` which can be assumed by any Chainguard user. * It takes the token retrieved with the `chainctl` binary and exchanges it for the assumable identity to make a call to the Chainguard registry in order to retrieve some information about the supplied image. The example uses the `cgr.dev/chainguard/static` Free image. To run this example, first, delete the previous example's `main.go` file if you haven't already. Then save the `exchange` example's `main.go` file to your local machine. Then run the following `go mod init` and `go mod tidy` commands: ```shell go mod init github.com/chainguard-dev/sdk && go mod tidy ``` Following that, execute the program: ```shell go run main.go ``` This will return output like the following: ``` { "mediaType": "application/vnd.oci.image.index.v1+json", "size": 925, "digest": "sha256:633aabd19a2d1b9d4ccc1f4b704eb5e9d34ce6ad231a4f5b7f7a3af1307fdba8", "Manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLmluZGV4LnYxK2pzb24iLCJtYW5pZmVzdHMiOlt7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5vY2kuaW1hZ2UubWFuaWZlc3QudjEranNvbiIsInNpemUiOjgzOCwiZGlnZXN0Ijoic2hhMjU2OmJiMmRlMjU3MjFlMTE3Zjg4MDYwYjgyZGQ2YWQ1M2Y4ODdlZTgwOWU1MzE1Y2Y0MWEwMGNkOGExM2ZhMjMwNzciLCJwbGF0Zm9ybSI6eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgifX0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQub2NpLmltYWdlLm1hbmlmZXN0LnYxK2pzb24iLCJzaXplIjo4MzgsImRpZ2VzdCI6InNoYTI1NjoxMmVjMWU3NDg0NmVhNDM1OTNkNmExMzMwZGEyOGVkNGQzNmRmNThhMTg2NGQwOTE1MjRlM2IwYTk5MDY4Y2QzIiwicGxhdGZvcm0iOnsiYXJjaGl0ZWN0dXJlIjoiYXJtNjQiLCJvcyI6ImxpbnV4In19XSwiYW5ub3RhdGlvbnMiOnsiZGV2LmNoYWluZ3VhcmQucGFja2FnZS5tYWluIjoiIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnMiOiJDaGFpbmd1YXJkIFRlYW0gaHR0cHM6Ly93d3cuY2hhaW5ndWFyZC5kZXYvIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmNyZWF0ZWQiOiIyMDI1LTA1LTI4VDEzOjM1OjQ4WiIsIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5zb3VyY2UiOiJodHRwczovL2dpdGh1Yi5jb20vY2hhaW5ndWFyZC1pbWFnZXMvaW1hZ2VzL3RyZWUvbWFpbi9pbWFnZXMvc3RhdGljIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybCI6Imh0dHBzOi8vaW1hZ2VzLmNoYWluZ3VhcmQuZGV2L2RpcmVjdG9yeS9pbWFnZS9zdGF0aWMvb3ZlcnZpZXciLCJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yIjoiQ2hhaW5ndWFyZCJ9fQ==" } ``` As with the `chainctl` example, the `exchange` example returns the image's digest and the OCI manifest data. This proves that you've gotten a response back from the registry and that authentication worked as expected. Again, this example doesn't really reflect a real-world use case. Users will generally access Free container repositories without authenticating, but this example is still useful for understanding how this can be done with the Chainguard SDK. You can experiment with updating this example to authenticate by assuming an identity you created and retrieve the digest of a container image from your organization's private repository within the Chainguard registry. To do so, you will need an appropriately-configured assumable identity. You can create an assumable identity with the [`chainctl iam identities create` command](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/). ### Example 2 using curl First, login with chainctl using an OIDC token. Where the token comes from will differ by platform. Refer to our [Identity provider samples](/platform/administration/custom-idps/idp-providers/) for some examples. ```shell chainctl auth login --identity "" --identity-token /var/run/chainguard/oidc/oidc-token ``` Then, retrieve an API token and use it to call the API: ```shell curl -H "Authorization: Bearer $(chainctl auth token)" https://console-api.enforce.dev/iam/v1/groups | jq '.' ``` In our example, [we requested a list of groups the account belongs to](/platform/api/spec/#tag/groups/GET/iam/v1/groups). Then we piped the API response, which is in JSON, into `jq` to make it more easily readable by humans for our documentation sample. This will return output like the following, which has been edited for length: ```response { "items": [ { "id": "", "name": "chainguard.edu", "description": "Developer Enablement images catalog", "resourceLimits": { "clusters": 0, "idps": 0, "repositories": 0 }, "verified": true }, { "id": "", "name": "chainguard", "description": " This group holds the public Chainguard Images hosted under\n cgr.dev/chainguard\n", "resourceLimits": {}, "verified": true } ] } ``` Using environment variables is helpful when working with assumable identities through the API and API tokens. Here's a high-level example of what you might do. Refer to your identity provider's documentation and [Assumable identities](/platform/administration/assumable-ids/assumable-ids/) to learn more. Here are some sample environment variables: ```shell # This is the UIDP of the assumable identity (refer to documentation) IDENTITY="" # Retrieve the OIDC token for your workload. How this is done will differ by platform. IDENTITY_TOKEN=$(cat /var/run/chainguard/oidc/oidc-token) # Exchange the OIDC token for a Chainguard API token. The token will be valid for an hour. API_TOKEN=$(curl -sSf \ -H "Authorization: Bearer $IDENTITY_TOKEN" \ "https://issuer.enforce.dev/sts/exchange?aud=https://console-api.enforce.dev&identity=$IDENTITY" \ | jq -r .token ) # Use the API token curl -H "Authorization: Bearer ${API_TOKEN}" https://console-api.enforce.dev/registry/v1/repos ``` Refer to the [Assumable identities documentation](/platform/administration/assumable-ids/assumable-ids/#using-the-chainguard-api) for another example of authentication using an assumed identity. ## Learn more The Chainguard SDK is a powerful tool for interacting with the Chainguard platform. As mentioned previously, the examples covered in this guide don't represent a practical real-world application, but they are useful for understanding how the Chainguard SDK works and can be used to authenticate to the Chainguard platform. To learn more, you may be interested in the following resources: * [Overview of assumable identities in Chainguard](/platform/administration/assumable-ids/assumable-ids/) * [Authenticate to Chainguard's registry](/chainguard/containers/registry/authenticating/) * [Chainguard OpenAPI specification](/platform/api/spec/) --- ### Manage your chainctl configuration URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/manage-chainctl-config.md Last Modified: October 1, 2026 Tags: chainctl Chainguard's `chainctl` keeps a local configuration file that controls how the CLI behaves: which Chainguard environment it talks to, how it logs you in, the defaults it applies when you omit an argument, and how it formats output. This page explains the commands that manage that file, where it lives, and every setting you can configure. ## chainctl config commands `chainctl config` groups the commands that read and write the local configuration file. To list them, run: ```shell chainctl config -h ``` You'll receive output like the following: ``` Local config file commands for chainctl. Usage: chainctl config [command] Available Commands: edit Edit the current chainctl config file. reset Remove local chainctl config files and restore defaults. save Save the current chainctl config to a config file. set Set an individual configuration value property. unset Unset a configuration property and return it to default. validate Run diagnostics on local config. view View the current chainctl config. Flags: -h, --help help for config Global Flags: --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") -o, --output string Output format. One of: ["", "json", "id", "table", "terse", "tree", "wide"] -v, --v int Set the log verbosity level. Use "chainctl config [command] --help" for more information about a command. ``` Each command is covered below. The full command reference is available on the [`chainctl config` reference page](/platform/chainctl/chainctl-docs/chainctl_config/). ## View your current configuration To print the configuration chainctl is currently using, run: ```shell chainctl config view ``` You'll receive output similar to this: ```output # Base Config file: /home/erika/.config/chainctl/config.yaml auth: mode: browser default: active-within: 24h0m0s autoclose: true autoclose-timeout: "10" group: "" log-level: "" identity-provider: "" org-name: "" skip-auto-login: false skip-version-check: false social-login: google-oauth2 use-refresh-token: true output: color: fail: '#ff0000' pass: '#00ff00' warn: '#ffa500' info: "" highlight: "" secondary: "" special: "" silent: false platform: api: https://console-api.enforce.dev audience: https://console-api.enforce.dev console: https://console.chainguard.dev issuer: https://issuer.enforce.dev registry: https://cgr.dev ``` `view` prints the effective values after defaults, the file, and any environment variables are combined. If you have `CHAINGUARD*` environment variables set, they are listed below the file contents. Add `--diff` to compare the effective configuration against what is written on disk. ## Where your configuration lives chainctl looks for a configuration file in three locations and uses the first one it finds: 1. `./chainctl/config.yaml` (relative to your current directory) 2. `/chainctl/config.yaml` 3. `~/.chainguard/config.yaml` `` is your operating system's user configuration directory: `~/Library/Application Support` on macOS, and `$XDG_CONFIG_HOME` (usually `~/.config`) on Linux. Passing `--config FILE` (or setting the `CHAINCTL_CONFIG` environment variable) points chainctl at a specific file and bypasses this search. When chainctl writes a file itself (for example with `save` or `edit`), it uses `/chainctl/config.yaml`. ## Edit the chainctl configuration Settings are resolved in order of precedence, where a higher source overrides a lower one: ``` ┌──────────────────────┐ │ command-line flags │ highest └──────────┬───────────┘ │ ┌──────────▼───────────┐ │ CHAINGUARD_ env vars │ └──────────┬───────────┘ │ ┌──────────▼───────────┐ │ config file │ └──────────┬───────────┘ │ ┌──────────▼───────────┐ │ built-in defaults │ lowest └──────────────────────┘ ``` ## Override settings with environment variables Any setting can be overridden with an environment variable without editing the file. Prefix the property name with `CHAINGUARD_`, uppercase it, and replace the dots with underscores: ```shell export CHAINGUARD_PLATFORM_API=https://console-api.enforce.dev export CHAINGUARD_DEFAULT_GROUP= export CHAINGUARD_OUTPUT_SILENT=true ``` Environment variables take precedence over the file, so they are useful for CI jobs or for temporarily targeting a different environment. ## Configurable properties Properties are addressed with dot-delimited, lowercase names (for example `output.color.pass`) and fall into four groups. The tables below list every property, its default, and what it does. ### Platform endpoints The `platform` keys tell chainctl which Chainguard environment to use. The defaults target Chainguard's production environment, so you normally only change these when working against a different environment. Each value must be a valid URL; an invalid value is discarded and the default is restored. | Property | Default | Description | |---|---|---| | `platform.api` | `https://console-api.enforce.dev` | Chainguard platform API endpoint. | | `platform.console` | `https://console.chainguard.dev` | Chainguard Console URL. | | `platform.issuer` | `https://issuer.enforce.dev` | Token issuer (STS) endpoint used at login. | | `platform.registry` | `https://cgr.dev` | Registry endpoint. | | `platform.audience` | same as `platform.api` | Token audience requested at login. | ### Authentication The `auth` key controls the login flow. | Property | Default | Description | |---|---|---| | `auth.mode` | `browser` | Login flow. One of `browser` (opens a browser) or `headless` (for environments without one). `chainctl auth login --headless` sets this to `headless`; see [Headless mode persists after you use it](/platform/chainctl-usage/authentication-options/#headless-mode-persists-after-you-use-it). | ### Defaults The `default` keys supply values chainctl uses when a command needs one and you do not pass it explicitly. | Property | Default | Description | |---|---|---| | `default.group` | (empty) | Group or folder ID used as the parent when a command needs one and you do not specify it. | | `default.org-name` | (empty) | Organization name to log in to. | | `default.social-login` | (empty) | Default social login provider. One of `email`, `github`, `gitlab`, or `google`. | | `default.identity-provider` | (empty) | ID of a custom identity provider to authenticate through. Using it to select a social provider is deprecated; use `default.social-login` instead. | | `default.active-within` | `24h` | Default time window for commands that filter by recent activity (the `--active-within` flag). Must be a positive duration. | | `default.autoclose` | `true` | Whether the browser "login successful" page closes itself. | | `default.autoclose-timeout` | `10` | Seconds before the login-success page closes. | | `default.use-refresh-token` | `true` | Whether login stores a refresh token so you are prompted to re-authenticate less often. | | `default.skip-auto-login` | `false` | When `true`, chainctl does not start a login flow automatically. | | `default.skip-version-check` | `false` | Skip the check for a newer chainctl release. | | `default.log-level` | (empty) | Log verbosity for chainctl. | ### Output The `output` keys control console output and coloring. | Property | Default | Description | |---|---|---| | `output.silent` | `false` | Suppress informational messages (printed to standard error). | | `output.color.pass` | (empty) | Color for success text. | | `output.color.fail` | (empty) | Color for failure text. | | `output.color.warn` | (empty) | Color for warnings. | | `output.color.info` | (empty) | Color for informational text. | | `output.color.highlight` | (empty) | Highlight color. | | `output.color.secondary` | (empty) | Secondary text color. | | `output.color.special` | (empty) | Special-case color. | Color values accept a hex code such as `#00ff00` or a CSS color name such as `green`. Regardless of these settings, chainctl disables color when the `NO_COLOR` environment variable is set, when output is piped or redirected (not a terminal), or when `TERM=dumb`. ## Change a setting You can edit the configuration file directly in an editor. The following command opens it in your default command-line text editor (typically `nano`; set the `EDITOR` environment variable to choose another), then prompts you to save your changes: ```shell chainctl config edit ``` To change a single property instead, use `set` with the property name and value as two separate arguments: ```shell chainctl config set platform.api https://console-api.enforce.dev ``` To return a property to its default, use `unset`: ```shell chainctl config unset output.color.pass ``` Both `set` and `unset` write to the configuration file immediately. Refer to the [`chainctl config set` reference page](/platform/chainctl/chainctl-docs/chainctl_config_set/) for more detail. ## Save a configuration file If you do not have a configuration file yet, `save` writes the current effective configuration (defaults plus any overrides) to disk so you have a file to edit: ```shell chainctl config save ``` Without `--config`, it writes to the default location, `/chainctl/config.yaml`. ## Validate your configuration `validate` runs connectivity diagnostics rather than checking the file's contents. It confirms that the platform and required supporting domains resolve, and tests that the API and issuer endpoints are reachable: ```shell chainctl config validate ``` Use it to diagnose networking or endpoint problems, for example when login fails behind a proxy or firewall. ## Reset your configuration If you run into issues with your configuration, reset it to the default state: ```shell chainctl config reset ``` This deletes the configuration files chainctl manages in the default locations and restores default values. Files you passed explicitly with `--config` are not deleted. You can review all available commands in the [`chainctl` reference documentation](/platform/chainctl/chainctl-docs/chainctl/). --- ### cg-versions: the upstream version history MCP server URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/mcp-servers/cg-versions.md Last Modified: September 28, 2026 Tags: MCP, Containers `cg-versions` gives an AI tool a read-only catalog of the upstream projects Chainguard tracks version information for. Through it, a client can find a tracked project, list its version streams with their end-of-life dates, and read the release history of one stream, whole or a page at a time, with attribution back to the upstream tag and commit each release came from. It answers questions about what upstream projects have published and what is still supported: whether Python 3.9 is past end of life, which streams of a project are still receiving releases, and which upstream commit a given version corresponds to. The server uses the Streamable HTTP transport, at this endpoint: ``` https://versions.cgr.dev/mcp ``` ## What this server does not tell you `cg-versions` tracks upstream releases, not what Chainguard builds. A version listed here means the upstream project published it. It doesn't mean Chainguard has built a package or image for that version. A stream that has reached end of life upstream may also still be one Chainguard supports for customers. To find out what Chainguard actually builds, use a different server: - [`cg-apk`](/platform/mcp-servers/cg-apk/) reads the APK index, so it shows whether a package exists at a given version. - [`cg-oci`](/platform/mcp-servers/cg-oci/) reads the registry, so it shows whether an image tag exists at a given version. If you ask an AI tool "Can I get Python 3.9 from Chainguard?", it may answer from this server's upstream end-of-life data, which can't answer that question. The [Chainguard Containers product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/) covers Chainguard's support for versions that upstream has dropped. ### Not to be confused with Chainguard Libraries versions "Versions" also names an unrelated concept in [Chainguard Libraries](/chainguard/libraries/introduction/overview/): the `+cgr.N` and `-0.cgr.N` suffixes that mark a remediated build of a library package. Those suffixes are Chainguard's own rebuild counters for Java, JavaScript, and Python artifacts, and they have nothing to do with this server. `cg-versions` serves upstream version history for the projects behind Chainguard's images and packages. It does not serve library remediation versions, and no tool here returns a `cgr.N` suffix. ## Prerequisites - An MCP-compatible client such as Claude Code, Claude Desktop, or Cursor - A [Chainguard account](https://console.chainguard.dev/) ## Connect to the server ### Claude Code Add the server with `claude mcp add`, using the HTTP transport: ```shell claude mcp add --transport http cg-versions https://versions.cgr.dev/mcp ``` Pick the scope that fits how you want to use it: `local` (the default — only you, in the current directory), `project` (writes a shared `.mcp.json` at the repository root, checked in for teammates), or `user` (only you, across every project): ```shell claude mcp add --transport http --scope user cg-versions https://versions.cgr.dev/mcp ``` The server is added unauthenticated. To complete OAuth, start a session and run the `/mcp` command: ```Prompt /mcp ``` Select **cg-versions**, choose **Authenticate**, and approve the connection in the browser window that opens. Check the status any time with: ```shell claude mcp list ``` ```output cg-versions: https://versions.cgr.dev/mcp (HTTP) - ✓ Connected ``` ### Cursor Cursor supports HTTP transport natively. Add the server to your MCP configuration: ```json { "mcpServers": { "cg-versions": { "url": "https://versions.cgr.dev/mcp" } } } ``` Restart Cursor, then connect the server from **Tools & MCPs** in settings and complete the browser sign-in. ### Claude Desktop Claude Desktop reads MCP servers from a JSON file but does not yet support HTTP transport directly. Use [`mcp-remote`](https://github.com/geelen/mcp-remote) to bridge to the hosted server: ```json { "mcpServers": { "cg-versions": { "command": "npx", "args": [ "mcp-remote", "https://versions.cgr.dev/mcp" ] } } } ``` The configuration file lives at: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` `npx` downloads and runs `mcp-remote` on demand, so you need Node.js installed on the host. Restart Claude Desktop after saving the file. ### Other MCP clients Any client that supports a remote Streamable HTTP MCP server with OAuth can connect to the same endpoint. Point it at `https://versions.cgr.dev/mcp` and complete the browser sign-in when prompted. ## Authentication Authentication is OAuth 2.0 against the Chainguard issuer. On a remote or headless workstation, you can supply a token from `chainctl` instead of completing the browser flow. Refer to [Authenticate with chainctl instead of a browser](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser) for the full recipe; `cg-versions`'s audience is `https://versions.cgr.dev/mcp`. ## Concepts The catalog has three levels, and each tool works at one of them: - A **project** is a tracked upstream piece of software, named as the catalog names it. Names follow upstream convention rather than Chainguard image names, so Node.js is `nodejs`, not `node`. - A **stream** is a release line within a project, such as Python's `3.12`. Each stream carries its own end-of-life date and support status. - A **version** is a single release within a stream, such as `3.9.25`, with attribution to the upstream tag and commit it was cut from. ## Tool reference | Tool | Parameters | Returns | Example prompt | | ----- | ----- | ----- | ----- | | `search_projects` | `name_pattern`, `page_size`, `page_token` | `{projects, next_page_token}` — names only | "Is there a tracked project for Node?" | | `get_project` | `project` | `{project: {name, streams}, size_bytes}` with per-stream EOL status | "Which Python versions are still supported upstream?" | | `get_stream` | `project`, `stream` | `{stream: {project, stream, eol_date, is_eol, versions}, size_bytes}` | "List every Python 3.9 release with its upstream commit" | | `list_stream_versions` | `project`, `stream`, `page_size`, `cursor` | `{page: {project, stream, eol_date, is_eol, versions, count, total_versions, has_more, next_cursor}}` | "What are the three most recent Python 3.12 releases?" | ### search_projects Finds tracked projects by name. Results are paginated alphabetically. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `name_pattern` | string | yes | A Go regular expression applied to project names, such as `kube.*`, `^go$`, or `.*` to match everything | | `page_size` | integer | no | Entries per page (default 25, max 200) | | `page_token` | string | no | The `next_page_token` from a previous call. Reuse the same `name_pattern` when continuing. | This takes a regular expression rather than a literal string. `^node` returns `node-feature-discovery`, `node-problem-detector`, and `nodejs`, so a partial name usually finds the catalog's name for the project. Anchor the pattern with `^` and `$` when you want one project and nothing else. Matches come back as names only, with no version data attached. Chain to `get_project` for anything more. ### get_project Returns a project's identity together with a support-status summary for each of its version streams. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `project` | string | yes | Project name as `search_projects` returns it, such as `python` | Each stream in the response carries: | Field | Description | | ----- | ----- | | `stream` | The release line, such as `3.12` | | `eol_date` | The upstream end-of-life date, where known | | `is_eol` | Whether the stream is past that date | | `version_count` | How many releases the catalog holds for the stream | Use this tool to find which streams are still supported. For `python` it returns streams `3.8` through `3.14`, with `3.9` and `3.8` marked `is_eol: true` and `3.14` carrying an `eol_date` of 2030-10-31. A `version_count` of zero means the catalog tracks the stream's support dates but holds no individual releases for it, which is common for streams that reached end of life some time ago. ### get_stream Returns the release history for one stream of a project, with upstream source attribution for each version. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `project` | string | yes | Project name, such as `python` | | `stream` | string | yes | Stream name within the project, such as `3.9` | Both parameters are required, so the call is always scoped to a single release line; there is no way to pull a project's entire history in one request. The response repeats the stream's `eol_date` and `is_eol`, then lists its versions newest first. Each version carries a `sources` array, where each source has a `type` and whatever attribution that source provides: | Source type | What it contributes | | ----- | ----- | | `git` | The upstream tag, a URL for it, the commit hash, and the tag's publication timestamp | | `endoflife.date` | When the release was recorded in endoflife.date | | `release_monitor` | The tag as release-monitoring.org saw it | A single version usually carries all three, which is what makes this tool useful for provenance questions: Python 3.9.25 resolves to tag `v3.9.25` at commit `0bbaf5de` in `python/cpython`, published 2025-10-31. This tool has no `page_size` or cursor. Streams usually hold only a few releases (Python 3.9 has five). The server refuses a response over 512 KB rather than truncating it, and `get_project` has the same limit. To read a long history a page at a time, use `list_stream_versions`. ### list_stream_versions Returns one stream's release history a page at a time, newest first, with the same source attribution as `get_stream`. | Parameter | Type | Required | Description | | ----- | ----- | ----- | ----- | | `project` | string | yes | Project name, such as `python` | | `stream` | string | yes | Stream name within the project, such as `3.12` | | `page_size` | integer | no | Versions per page (default 50, max 200) | | `cursor` | string | no | The `next_cursor` from a previous call. Omit it on the first request. | The response repeats the stream's `eol_date` and `is_eol`, and adds `count` for the versions on this page and `total_versions` for the whole stream. When `has_more` is true, pass `next_cursor` as the next call's `cursor`. For `python` stream `3.12`, a `page_size` of 2 returns 3.12.14 and 3.12.13 out of 11 versions. Use this tool instead of `get_stream` when you only need the most recent releases, or when `get_stream` refuses a stream for size. ## Example usage Sample exchanges from a Claude Code session with `cg-versions` connected. ```Prompt Which Python versions are still supported upstream? ``` ```output Called cg-versions (ctrl+o to expand) ● Upstream Python currently supports 3.10 through 3.14. End-of-life dates run from 2026-10-31 for 3.10 out to 2030-10-31 for 3.14. Python 3.9 went end of life on 2025-10-31 and 3.8 on 2024-10-07. This is upstream support only. It doesn't tell you what Chainguard builds; cg-apk or cg-oci can answer that. ``` ```Prompt Is there a tracked project for Node? ``` ```output Called cg-versions (ctrl+o to expand) ● Three projects match: nodejs, node-feature-discovery, and node-problem-detector. The Node.js runtime is tracked as "nodejs". ``` ```Prompt Show me every Python 3.9 release and where it came from. ``` ```output Called cg-versions (ctrl+o to expand) ● The catalog doesn't have every Python 3.9 release. Its history starts in December 2024 and holds five releases, newest first: 3.9.25 — tag v3.9.25, commit 0bbaf5de, published 2025-10-31 3.9.24 — tag v3.9.24, commit 91bdb00d, published 2025-10-09 3.9.23 — tag v3.9.23, commit 86527026, published 2025-06-03 3.9.22 — tag v3.9.22, commit 05c55492, published 2025-04-08 3.9.21 — tag v3.9.21, commit f3994ade, published 2024-12-03 All five are tagged in python/cpython. Python 3.9 reached end of life upstream on 2025-10-31. ``` ## Troubleshooting | Symptom | What it means | What to do | | ----- | ----- | ----- | | A project lookup finds nothing | The name doesn't match the catalog's. Names follow upstream convention, not Chainguard image names. | Search with an unanchored pattern first, such as `node`, and use the name the search returns. | | A search returns far more than you expected | `name_pattern` is a regular expression, so an unanchored pattern matches as a substring anywhere in the name. | Anchor it: `^go$` for the Go project alone, rather than `go`. | | A stream you expected is missing | The catalog tracks streams the upstream project publishes as distinct release lines. Some projects don't maintain parallel streams. | Call `get_project` to see the streams that exist before asking for one by name. | | `get_stream` or `get_project` is refused for size | The response exceeds the server's 512 KB limit. Responses are never truncated. | Use `list_stream_versions` to read the stream a page at a time. | | A stream shows `version_count: 0` | The catalog knows the stream's support dates but holds no individual releases for it. | Use `get_project` for the support status; there is no release history to fetch. | | The AI tool says a version is unavailable from Chainguard | It may be reasoning from upstream end-of-life data, which says nothing about Chainguard's builds. | Ask it to check [`cg-apk`](/platform/mcp-servers/cg-apk/) or [`cg-oci`](/platform/mcp-servers/cg-oci/) instead, and refer to the [product release lifecycle](/chainguard/containers/concepts/lifecycle-and-eol/versions/). | | Server shows as not connected in `claude mcp list` | OAuth was never completed, or the token expired. Claude Code's tokens against the Chainguard issuer last about an hour and carry no refresh token. | Run `/mcp`, select **cg-versions**, and authenticate again, or switch to the [`chainctl` helper](/platform/mcp-servers/overview/#authenticate-with-chainctl-instead-of-a-browser). | | `401 invalid token` when using the `chainctl` helper | The audience was registered as a bare hostname. MCP audiences must include the `/mcp` path. | Run `chainctl auth login --audience=https://versions.cgr.dev/mcp` and try again. | ## Next steps - [`cg-apk`](/platform/mcp-servers/cg-apk/) — check whether a package is actually built at a given version - [`cg-oci`](/platform/mcp-servers/cg-oci/) — check whether an image tag exists at a given version - [`cg-api`](/platform/mcp-servers/cg-api/) — query organizations, IAM, and registry metadata through the platform API - [Chainguard MCP servers overview](/platform/mcp-servers/overview/) — the full set, and the `chainctl` authentication recipe --- ### Enable PKCE for OAuth token exchange URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/custom-idps/enabling-pkce.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural You can add PKCE (Proof Key for Code Exchange, commonly referred to as "pixy") to the OAuth token exchange between Chainguard and your custom identity provider (IdP). PKCE is a security extension to OAuth that adds an extra layer of protection against authorization code interception during login, and it is required by the OAuth 2.1 standard. Chainguard supports two configurations, depending on your needs: - **Client ID + client secret + PKCE** — In this configuration, PKCE is layered on top of your existing confidential client setup. This is additive: you keep your client ID and client secret. - **Client ID + PKCE only, no client secret** — This is the newer "public client" model where PKCE replaces the client secret entirely, and is the approach OAuth 2.1 requires going forward. Enabling PKCE is optional. If you leave PKCE disabled or optional on your IdP, your login behavior remains unchanged and you don't need to take any action. ## Prerequisites To follow this guide, you need: - A custom identity provider (such as [Okta](/platform/administration/custom-idps/idp-providers/okta/), [Microsoft Entra ID](/platform/administration/custom-idps/idp-providers/ms-entra-id/), or [Ping Identity](/platform/administration/custom-idps/idp-providers/ping-id/)) already configured for login to Chainguard. If you haven't set one up yet, refer to our guide [Using custom identity providers to authenticate to Chainguard](/platform/administration/custom-idps/custom-idps/). - An IAM role that can manage identity providers in your Chainguard organization, such as the `owner` role. - [`chainctl` installed](/platform/chainctl-usage/how-to-install-chainctl/) on your local machine. You must also authenticate with `chainctl auth login`. The rest of this guide refers to your identity provider by its UIDP, stored in the `IDENTITY_PROVIDER` environment variable. Retrieve and set it with the following command: ```sh export IDENTITY_PROVIDER=$(chainctl iam identity-providers list -o json | jq -r '.items[0].id') ``` This command selects the first identity provider the list returns. If your account can access more than one identity provider, replace `.items[0]` with a filter that matches the one you want, or set the variable to the UIDP manually. ## Option 1: Client ID + client secret + PKCE This option adds PKCE on top of an existing confidential client setup. You keep using your client ID and client secret, and PKCE is layered on as an additional protection. To enable it, complete the following steps: 1. In your identity provider, locate the application registration used for your Chainguard integration. 2. Enable PKCE for that application. This is typically a toggle or checkbox labeled "Require PKCE" or similar. 3. Update your identity provider configuration on the Chainguard platform to turn on PKCE: ```sh chainctl iam identity-providers update $IDENTITY_PROVIDER --oidc-pkce-enabled ``` You can also set this at creation time by including the `--oidc-pkce-enabled` flag with [`chainctl iam identity-providers create`](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_create/). > **Note**: Complete steps 1 and 2 before you run the command in step 3. When you enable PKCE on the Chainguard side, we check your IdP's configuration and reject it if your IdP explicitly advertises that it doesn't support PKCE. Not all IdPs advertise this either way, so a successful command doesn't guarantee that your IdP is configured correctly. ## Option 2: Client ID + PKCE only, no client secret Unlike Option 1, this usually isn't a setting you can toggle on or off. Most IdPs tie "public client" (no secret) behavior to the *type* of application you've registered, not just a PKCE setting. To go secret-free, the application must be a type your IdP treats as a public client. Whether you can change an existing application's type depends on your identity provider. Some providers, including Okta, don't allow it: a "Web" app in Okta is confidential and always expects a secret, even with PKCE enabled. If your provider won't let you convert an existing application, you can instead register a new application as a public client type from the start, then create a matching identity provider configuration in Chainguard that includes the `--oidc-pkce-enabled` flag and omits the `--oidc-client-secret` flag. To switch an existing application, complete the following steps: 1. In your identity provider, locate the application registration used for your Chainguard integration and change its type to a public client type, such as **Native** or **SPA** in Okta, or the equivalent for your IdP. 2. Confirm that PKCE is required for that application. 3. Update your identity provider configuration on the Chainguard platform to remove the client secret and turn on PKCE: ```sh chainctl iam identity-providers update $IDENTITY_PROVIDER --oidc-client-secret="" --oidc-pkce-enabled ``` You must explicitly set the `--oidc-client-secret` field to `""` to clear it. The configured IdP must always have either a client secret or PKCE enabled. > **Note**: Complete steps 1 and 2 before you run the command in step 3. When you enable PKCE on the Chainguard side, we check your IdP's configuration and reject it if your IdP explicitly advertises that it doesn't support PKCE. Not all IdPs advertise this either way, so a successful command doesn't guarantee that your IdP is configured correctly. ## Verify that PKCE is enabled After completing either option, confirm that Chainguard has PKCE enabled for your identity provider: ```sh chainctl iam identity-providers list -o json | jq '.items[] | {name, pkce: .oidc.pkce_enabled}' ``` If PKCE is enabled, your provider reports `"pkce": true`. You can also run `chainctl iam identity-providers list -o table` and look for `PKCE Enabled` in the `CONFIGURATION` column. This checks the Chainguard side of the configuration only. Chainguard never returns client secrets in command output, so these commands can't confirm that a secret has been removed. Next, test a login from end to end: ```sh chainctl auth login --identity-provider=$IDENTITY_PROVIDER ``` You can also log in with your organization name: ```sh chainctl auth login --org-name=example.com ``` A successful login completes the browser redirect and returns an authenticated session. If you have any questions or issues, contact your Customer Success Team or Support, and we'll help confirm your configuration is working correctly. --- ### Overview of Chainguard EKS add-ons URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/eks-add-ons.md Last Modified: September 28, 2026 Tags: FIPS, Chainguard Containers Chainguard EKS add-ons are hardened, minimal container images for the foundational software components that power Amazon Elastic Kubernetes Service (EKS) clusters. Available through [AWS Marketplace](https://aws.amazon.com/marketplace), they serve as FIPS-validated drop-in replacements for AWS default add-ons, providing zero known CVEs and FIPS 140-3 validated cryptography without requiring custom image builds or manifest overrides. ## What are EKS add-ons? Amazon EKS add-ons are software components that provide supporting operational capabilities to Kubernetes applications — things like networking drivers, storage integrations, and observability agents that allow the cluster to interact with underlying AWS resources, but aren't specific to any application running on it. They handle cluster functions like internal DNS resolution, pod networking, persistent storage, and traffic routing, and are managed independently from your application workloads. AWS installs several add-ons in every EKS cluster by default. For a full explanation of how EKS add-ons work (including how to install, configure, and update them), refer to the [Amazon EKS add-ons documentation](https://docs.aws.amazon.com/eks/latest/userguide/eks-add-ons.html). ## Available Chainguard EKS add-ons Chainguard provides hardened images for five widely used EKS add-ons. All are FIPS-validated. | Add-on | Description | | -------- | ------------- | | **kube-proxy** | Routes network traffic between pods within the cluster | | **CoreDNS** | Handles internal DNS — how services discover and communicate with each other | | **Amazon VPC CNI** | Connects pods to the AWS VPC network backbone | | **Amazon EBS CSI Driver** | Enables persistent block storage volumes backed by Amazon EBS | | **Amazon EFS CSI Driver** | Enables shared, persistent file storage backed by Amazon EFS | ## Why use Chainguard EKS add-ons? ### Zero known CVEs Default EKS add-on images ship with known CVEs. Because customers do not own the upstream source code, they aren't able to patch these vulnerabilities. Chainguard rebuilds each add-on using its minimal, hardened container image approach, removing unnecessary packages and dependencies that are the most common source of CVEs. Like Chainguard Containers, Chainguard EKS add-ons are rebuilt continuously to incorporate available security patches, keeping the known CVE count at or near zero. ### FIPS 140-3 validated cryptography For organizations operating under FedRAMP, NIST, or other compliance frameworks that require FIPS-validated cryptography, existing options create additional developer toil to maintain the image. Chainguard EKS add-ons are the only available option that provides FIPS 140-3 validated cryptography for core EKS cluster infrastructure. No other provider offers FIPS-validated replacements for these components. To learn more about how Chainguard approaches FIPS, refer to the [Chainguard FIPS documentation](/platform/fips/). ### Drop-in compatibility Chainguard EKS add-ons are designed as direct replacements for the corresponding AWS-managed components. They use the same Helm-based deployment workflow as any other EKS add-on and require no changes to your existing cluster configuration. Chainguard EKS add-ons are available through AWS Marketplace and can be found through the AWS Console. There's no manual setup, you don't need a Chainguard account to subscribe, and billing is consolidated with your existing AWS bill. ## Who should use Chainguard EKS add-ons? Chainguard EKS add-ons are well-suited for: - **Regulated industries**: Healthcare, financial services, defense, and government organizations that require FIPS 140-3 validated cryptography or operate under FedRAMP, NIST 800-53, or similar compliance frameworks. - **Security-conscious engineering teams**: Platform and DevOps teams that want a clean, auditable baseline for cluster infrastructure without maintaining custom image builds or accepting audit exceptions. - **Organizations requiring infrastructure control**: Enterprises that need more control over their EKS deployments than EKS Auto Mode provides, and for whom FIPS compliance through Auto Mode is not a viable option. ## Using Chainguard EKS add-ons ### Prerequisites In order to use Chainguard EKS add-ons, you will need the following: - An active AWS account - An existing Amazon EKS cluster, or permission to create one - AWS IAM permissions sufficient to subscribe to AWS Marketplace offerings ### Subscribe through AWS Marketplace Chainguard EKS add-ons are listed in AWS Marketplace and can be found within the EKS Console. To subscribe: 1. In the AWS Console, navigate to your EKS cluster and open the **Add-ons** tab. 2. Search for the Chainguard add-on you want to install. 3. Subscribe. ### Deploy with Helm Once subscribed, deployment follows the standard EKS Helm workflow, and you can deploy the add-ons with AWS CLI or in your GitOps workflows. Unlike a standalone Helm chart, which is self-managed, EKS will automatically handle lifecycle management of your add-ons. No new tooling, no Chainguard account, and no changes to your existing Kubernetes manifests are required. For step-by-step installation guidance, refer to the deployment instructions included with each add-on listing in AWS Marketplace. ### Known limitations AWS Marketplace container products, including EKS add-ons, are not available in AWS GovCloud. As noted in the [AWS GovCloud User Guide](https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-marketplace.html), "Currently, container products and Amazon Machine Learning products are not available." Consequently, you cannot deploy Chainguard EKS add-ons through AWS Marketplace in GovCloud. Instead, deploy the add-ons using self-managed Helm charts or rendered manifests. Do not modify an add-on’s image references after deployment. These fields are effectively immutable, and modifying them prevents the add-on from receiving updates. --- ### Touring the Chainguard Factory URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/touring-the-factory.md Last Modified: October 8, 2026 Tags: Chainguard Factory, Video, Overview [Watch the video on YouTube](https://www.youtube.com/watch?v=gg_EdCrhzL4) ## Transcript **Interviewer**: So Dustin, can you give us a quick tour of the Chainguard Factory? **Dustin Kirkland**: Yeah, so the Chainguard Factory is the automation that we have inside of Chainguard itself that is able to reproducibly build thousands of open source projects. We start from a fully bootstrapped-from-source version of the source code. We pull the source code down, we apply our build rules, build that code, test that code, sign that code, and publish that code—first as packages. And then we take those packages and put them into various different form factors. In the nominal case, we put them into container images that typically run inside of a Docker or Kubernetes. But we also can take those same packages and build virtual machine appliances, fully booting with a Linux kernel. And then we also twist some of these into libraries, also built entirely from source. --- ### Find and update your chainctl release version URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/chainctl-version-update.md Last Modified: September 28, 2026 Tags: chainctl Chainguard regularly releases updates to `chainctl` with new security features, performance improvements, and expanded capabilities for container management. This guide explains how to check your current version and update to the latest release. For a full reference of all commands with details and switches, refer to [chainctl reference](/platform/chainctl/). ## View your chainctl version To see which `chainctl` version you have installed, use: ```shell chainctl version ``` This command tells you more than just a release number. ```output ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ____ _ _ _ ___ _ _ ____ _____ _ / ___| | | | | / \ |_ _| | \ | | / ___| |_ _| | | | | | |_| | / _ \ | | | \| | | | | | | | | |___ | _ | / ___ \ | | | |\ | | |___ | | | |___ \____| |_| |_| /_/ \_\ |___| |_| \_| \____| |_| |_____| chainctl: Chainguard Control GitVersion: v0.2.66 GitCommit: cb0ff7f1806341b20fd72be4769668a1985ed845 GitTreeState: clean BuildDate: 2025-04-09T07:49:51Z GoVersion: go1.24.1 Compiler: gc Platform: linux/amd64 ``` ## Update your chainctl install To update your `chainctl` installation to the latest release, use: ```shell chainctl update ``` This will present the following: ```output Current version v0.2.66 Latest version 0.2.73 Download URL https://dl.enforce.dev/chainctl/latest/chainctl_linux_x86_64 chainctl filepath /usr/local/bin/chainctl Cache /root/.cache/chainctl Operating System linux Architecture x86_64 Do you want to continue? [Y,n]: ``` Enter `Y` to proceed and the upgrade will continue and confirm like this: ```output ✔ Download complete! Backing up current chainctl (/usr/local/bin/chainctl) Updating chainctl... Update complete! ⛓ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ____ _ _ _ ___ _ _ ____ _____ _ / ___| | | | | / \ |_ _| | \ | | / ___| |_ _| | | | | | |_| | / _ \ | | | \| | | | | | | | | |___ | _ | / ___ \ | | | |\ | | |___ | | | |___ \____| |_| |_| /_/ \_\ |___| |_| \_| \____| |_| |_____| chainctl: Chainguard Control GitVersion: v0.2.73 GitCommit: bd6a3e9901c85801697599d72e2646a862570cde GitTreeState: clean BuildDate: 2025-04-22T17:27:55Z GoVersion: go1.24.2 Compiler: gc Platform: linux/amd64 Temporary files may need to be removed manually: rm -f /root/.cache/chainctl/chainctl.bak ``` `chainctl update` verifies the signature of the downloaded binary in-process before installing it, so you don't need a separate `cosign` binary on your `PATH`. Verification needs network access to `dl.enforce.dev` and, at least on first use, to the Sigstore trust root at `tuf-repo-cdn.sigstore.dev`. If verification fails or those hosts are unreachable, the update stops and your current installation is left in place. --- ### Introduction to the Chainguard Terraform provider URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/terraform-provider.md Last Modified: October 9, 2026 Tags: Platform, Procedural [Terraform](https://www.terraform.io/) is an infrastructure as code tool that lets you declaratively configure resources in cloud providers like AWS and GCP, SaaS platforms, and many other API-driven environments. Third-party developers write [Terraform providers](https://developer.hashicorp.com/terraform/language/providers) so that Terraform can manage resources in their environments. [The Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest) lets you manage resources on the Chainguard Platform, such as identities, role-bindings, custom roles, and more. This guide shows how to configure the provider and use it to manage your Chainguard resources. ## Prerequisites To use the Chainguard Terraform provider, [install Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) on your local machine. This guide also uses [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to retrieve some information about your Chainguard resources. The provider itself doesn't require it. ## Configure the Chainguard Terraform provider Terraform uses [a native configuration language](https://developer.hashicorp.com/terraform/language) to define resources. You store each configuration in one or more `.tf` files in a directory called the *root module*. With the Terraform CLI, the root module is the working directory where you run `terraform` commands to create or destroy your resources. To use the Chainguard Terraform provider, add it to the block of required providers in your configuration: ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } } } ``` If you don't have an active Chainguard token when you apply the configuration, the provider launches a browser to complete the OAuth 2.0 flow with one of the default identity providers: GitHub, GitLab, or Google. The provider doesn't need `chainctl` to authenticate. You can customize the behavior of the authentication flow in several ways. For example, you can specify a [verified organization](/platform/administration/iam-organizations/verified-orgs/) name to use a previously configured custom identity provider: ```hcl provider "chainguard" { login_options { organization_name = "my-org.com" } } ``` You can also configure the provider to use an OIDC token, either by supplying it directly or pulling it from a file, with the automatic browser flow disabled. This is useful when setting up CI workflows: ```hcl provider "chainguard" { login_options { # Disable the automatic browser authentication flow. disabled = true # identity_id is the exact ID of an assumable identity. # Get this ID with chainctl iam identities list identity_id = "1f127a7c0609329f04b43d845cf80eea4247a07c/d6305475446bbef6" identity_token = "/path/to/oidc/token" } } ``` You can find more information about authenticating with the Chainguard Terraform provider in the [provider documentation](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs) and [the included examples](https://github.com/chainguard-dev/terraform-provider-chainguard/tree/main/examples). ## Define organization references Terraform manages resources declaratively: you define the state you want your resources in, and Terraform and the provider handle the details of reaching that state. Resources on the Chainguard platform are organized in a hierarchical structure consisting of [organizations and folders](/platform/administration/iam-organizations/overview-of-chainguard-iam-model/#organizations-and-folders). An organization is a customer or group of customers working with the same Chainguard resources, while a folder is a collection of resources within a Chainguard organization. Most users only need to work at the organization level. ![Diagram outlining hierarchical structure of Chainguard resources. The diagram has two halves: one labeled "Organization" and another labeled "Chainguard". Under Organization is a box labeled "Tags" with an arrow pointing toward another box labeled "Repos." Under both halves is a box labeled "Role Bindings" which has four arrows pointing from it. Two arrows point to boxes (labeled "Identities" and "Custom Roles") under Organization and the other two point to boxes (labeled "User Identities" and "Roles") under Chainguard.](tf-diagram.png) All user-managed resources are defined in relation to the organization to which they belong. This means developers need to be able to reference their organization throughout their configuration. This section describes two ways to define this kind of reference: using local values and data sources. For more on referencing an organization, refer to the [`chainguard_group` resource documentation](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/resources/group). > **Note:** Chainguard organizations were previously called "groups," with a "root group" representing what is now referred to as an organization and "subgroups" referring to folders. The Chainguard Terraform provider still refers to "groups" instead of organizations. To align with our other [IAM resources](/platform/administration/iam-organizations/), this document uses the new nomenclature wherever possible. ### Define local values The Terraform configuration language allows you to define *local values* that assign a name to a given expression. This allows you to reference the name of a local value multiple times throughout a Terraform configuration rather than repeating the expression each time. This section shows how to define a local value that holds the ID of a Chainguard organization. If you are familiar with `chainctl`, you can find your organization's ID with the following command: ```shell chainctl iam organizations list -o table ``` Once you've copied the UIDP of your organization (the 40-character hex string listed in the `ID` column of the previous command's output), use it to create a local value in Terraform: ```hcl locals { org_id = "[organization UIDP]" } ``` Throughout your Terraform code, you can refer to this value as `local.org_id`. If you are setting up a reusable Terraform module, consider an input variable instead. For more information, refer to the [Terraform documentation](https://developer.hashicorp.com/terraform/language/values/variables). ### Use data sources *Data sources* allow Terraform to access and use information defined outside of Terraform. The available data sources for Chainguard resources are [groups](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/data-sources/group), [identities](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/data-sources/identity), and [roles](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs/data-sources/role). If you know the exact name of your organization, you can use a data source to query the API for it: ```hcl data "chainguard_group" "org" { # This indicates the group is an organization. parent_id = "/" name = "[organization name]" } ``` To refer to the organization's ID in other parts of your Terraform configuration, use the reference `data.chainguard_group.org.id`. The remaining examples in this guide use this reference. ## Manage users The Chainguard Terraform provider is useful for configuring your organization's users and roles. When authenticating with the Chainguard platform, you have the option of using the default OIDC providers (GitHub, GitLab, Google). You can also bring your own identity provider, as long as it is OIDC compliant. To configure a new identity provider for your organization, use the `chainguard_identity_provider` resource. You must provide a `parent_id` (your organization's ID), a `name` (the identity provider service is a good choice), a `default_role` that new users are bound to when they first log in, and an `oidc` configuration: ```hcl # The default role can be either a built-in role, or a custom role. # To see the list of available built-in roles # use chainctl iam roles list --managed data "chainguard_role" "default_role" { name = "registry.pull_token_creator" } resource "chainguard_identity_provider" "idp" { parent_id = data.chainguard_group.org.id name = "[identity provider service]" description = "My org's identity provider" # Role data sources return a list of matched roles. # Don't use data.chainguard_role.default_role.id here, as that # is not the ID of the returned role. default_role = data.chainguard_role.default_role.items.0.id oidc { issuer = "[URL of identity provider issuer]" client_id = "[identity provider client ID]" client_secret = "[identity provider client secret]" # The openid scope is always requested. Add any other scopes # your identity provider supports, such as email or profile. additional_scopes = ["email"] } } ``` As this example shows, you also have the option of including a `description` of the identity provider. If you're not bringing your own identity provider, but rather relying on one of the default OIDC providers, you can still pre-bind users to roles within your organization so they can log in and access your organization's resources right away. As an example, say you want your users to log in with their GitHub account. To pre-bind users to a role within your organization, you need their GitHub IDs. Add them to a Terraform configuration like the following: ```hcl # Create a custom role in this example for first-time users. resource "chainguard_role" "default_role" { parent_id = data.chainguard_group.org.id name = "org-default-role" description = "The role new users are bound to on first login." # A full list of all capabilities you can assign to a role # is available with chainctl iam roles capabilities list capabilities = [ "groups.list", "repo.list", "tag.list", ... ] } # Gather a list of user identities data "chainguard_identity" "users" { # Assumes a github_ids set variable that holds # your users' GitHub IDs for_each = var.github_ids # The issuer is always the same when using the default # OIDC providers. issuer = "https://auth.chainguard.dev/" # Subjects are prepended with the name of the OIDC provider # when using the default provider: github, gitlab, or google-oauth2 subject = "github|${each.key}" } # Bind your users to a default role resource "chainguard_rolebinding" "default_bindings" { for_each = var.github_ids group = data.chainguard_group.org.id identity = data.chainguard_identity.users[each.key].id role = chainguard_role.default_role.id } ``` After applying this Terraform configuration, any user whose GitHub ID you included can log in and is already bound to the default role. ## Learn more The [Chainguard Terraform provider documentation](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest/docs) includes examples of how you can use it to manage your Chainguard resources. For more information on setting up custom identity providers, refer to [Custom IdPs](/platform/administration/custom-idps/custom-idps/), as well as our examples for [Okta](/platform/administration/custom-idps/idp-providers/okta/), [Ping Identity](/platform/administration/custom-idps/idp-providers/ping-id/), and [Microsoft Entra ID](/platform/administration/custom-idps/idp-providers/ms-entra-id/). For a fuller example of this guide's approach, refer to the tutorial on [using the Terraform provider to grant a GitHub team access to a Chainguard organization](/platform/administration/iam-organizations/roles-role-bindings/rolebinding-terraform-gh/). --- ### Create an assumable identity for a Buildkite pipeline URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/buildkite-identity.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This tutorial outlines how to create an identity using Terraform, and then how to update a Buildkite pipeline so that it can assume the identity and interact with Chainguard resources. ## Prerequisites To complete this guide, you must have the following in place: * `terraform` installed on your local machine. Terraform is an open source Infrastructure as Code tool which this guide uses to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * A Buildkite agent and pipeline you can use to test out the identity you'll create. We recommend following Buildkite's [Getting Started guide](https://buildkite.com/docs/tutorials/getting-started) to set these up. ## Creating Terraform files This guide outlines using Terraform to create an identity for a Buildkite pipeline to assume. This step outlines how to create three Terraform configuration files that, together, produce such an identity. To help explain each configuration file's purpose, we will go over what they do and how to create each file one by one. First, create a directory to hold the Terraform configuration and navigate into it. ```sh mkdir ~/buildkite-id && cd $_ ``` This will help make it easier to clean up your system at the end of this guide. ### `main.tf` The first file, named `main.tf`, serves as the scaffolding for our Terraform infrastructure. The file consists of the following content: ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } } } ``` This is a fairly barebones Terraform configuration file, but we will define the rest of the resources in the other two files. In `main.tf`, we declare and initialize the Chainguard Terraform provider. Next, you can create the `sample.tf` file. ### `sample.tf` `sample.tf` creates a couple of structures that help us test out the identity in a workflow. This Terraform configuration consists of the following lines: ```hcl data "chainguard_group" "group" { name = "example.com" } ``` This section looks up a Chainguard IAM organization named `example.com`. You must change this to the name of your Chainguard organization where you want to create the Buildkite identity. Now you can move on to creating the last of our Terraform configuration files, `buildkite.tf`. ### `buildkite.tf` The `buildkite.tf` file is what actually creates the identity for your Buildkite workflow to assume. The file consists of four sections, which we'll go over one by one. The first section creates the identity itself: ```hcl resource "chainguard_identity" "buildkite" { parent_id = data.chainguard_group.group.id name = "buildkite" description = <` and `` with the name of your Buildkite organization and the name of your Buildkite pipeline. The next section outputs the new identity's `id` value. This is a unique value that represents the identity itself: ```hcl output "buildkite-identity" { value = chainguard_identity.buildkite.id } ``` The section after that looks up the `viewer` role: ```hcl data "chainguard_role" "viewer" { name = "viewer" } ``` The final section grants this role to the identity: ```hcl resource "chainguard_rolebinding" "view-stuff" { identity = chainguard_identity.buildkite.id group = data.chainguard_group.group.id role = data.chainguard_role.viewer.items[0].id } ``` After creating the `buildkite.tf` file with each of these sections, your Terraform configuration will be ready. Now you can run a few `terraform` commands to create the resources defined in your `.tf` files. ## Creating your resources First, run `terraform init` to initialize Terraform's working directory: ```sh terraform init ``` Then run `terraform plan`. This produces a speculative execution plan that outlines what steps Terraform will take to create the resources defined in the files you set up in the last section: ```sh terraform plan ``` If the plan worked successfully and you're satisfied that it will produce the resources you expect, you can apply it. First, though, you'll need to log in to `chainctl` to ensure that Terraform can create the Chainguard resources: ```sh chainctl auth login ``` Then apply the configuration: ```sh terraform apply ``` Before going through with applying the Terraform configuration, this command prompts you to confirm that you want it to do so. Enter `yes` to apply the configuration: ```output ... Plan: 4 to add, 0 to change, 0 to destroy. Changes to Outputs: + buildkite-identity = (known after apply) Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: ``` After pressing `ENTER`, the command will complete and output a `buildkite-identity` value: ``` ... Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: buildkite-identity = "" ``` This is the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers), which you configured the `buildkite.tf` file to emit in the previous section. Note this value down, as you'll need it when you test this identity using a Buildkite workflow. If you need to retrieve this UIDP later on, though, you can always run the following `chainctl` command to obtain a list of the UIDPs of all your existing identities: ```sh chainctl iam identities ls ``` You're now ready to edit a Buildkite pipeline in order to test out this identity. ## Testing the identity with a Buildkite pipeline To test the identity you created with Terraform in the previous section, navigate to your Buildkite pipeline. From the Buildkite Dashboard, click **Pipelines** in the top navigation bar and then click on the pipeline you specified in the `buildkite.tf` file. From there, click the **Edit Steps** button to add the following commands to a step in your pipeline. Be sure to replace `` with the identity UIDP you noted down in the previous section. If you followed the [**Getting Started** guide linked in the prerequisites](https://buildkite.com/docs/tutorials/getting-started), your pipeline will have a structure like this. ```Pipeline steps: - label: "Pipeline upload" command: buildkite-agent pipeline upload ``` You could add the commands for testing the identity like this: ```Pipeline - command: | curl -o chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m)" chmod +x chainctl mv chainctl /usr/bin buildkite-agent oidc request-token --audience issuer.enforce.dev > .token.txt chainctl auth login --identity-token $(cat .token.txt) --identity chainctl auth configure-docker --identity-token $(cat .token.txt) --identity rm .token.txt chainctl images repos list docker pull cgr.dev//: ``` These commands instruct your Buildkite pipeline to download `chainctl` and make it executable. The pipeline then runs a `buildkite-agent oidc request-token` command, which requests and prints an OIDC token that claims the Job ID and the specified audience. It pipes this token into a file named `.token.txt`, and then signs in to Chainguard using this token along with the Buildkite identity you generated previously. If this workflow can successfully assume the identity, it then executes the `chainctl images repos list` command and retrieves the list of repos available to the organization. Click the **Save and Build** button. Ensure that your Buildkite agent is running, and then wait a few moments for the pipeline to finish building. ### Testing the identity with a Buildkite plugin You can also create a [Buildkite plugin](https://buildkite.com/docs/pipelines/integrations/plugins/writing) that uses the assumable identity to access your Chainguard resources. Note that to use a plugin like the following example with the pipeline you created previously, the plugin code must reside in the same repository where the pipeline is running. To get started, create some directories within your repository: ```sh mkdir -p .buildkite/plugins/cgrauth/hooks ``` Create a `plugin.yml` file within the `cgrauth` directory. This is a special file that describes what information the plugin requires and what configuration options it accepts: ```sh cat > .buildkite/plugins/cgrauth/plugin.yml < requirements: [bash] configuration: properties: identity: { type: string } required: [identity] additionalProperties: false EOF ``` Be sure to replace `` with your GitHub username. Plugins can implement a number of [plugin hooks](https://buildkite.com/docs/agent/v3/hooks). Run the following command to create a pre-command hook within the `hooks/` subdirectory: ```sh cat > .buildkite/plugins/cgrauth/hooks/pre-command < chainctl auth configure-docker --identity-token $token --identity EOF ``` Be sure to replace `` with the UIDP of the assumable identity you created earlier. After merging these new files into your code repository, update your pipeline to use the plugin. The following example is useful for testing that the pipeline can read the identity from the plugin and use it to retrieve a list of your organization's image repositories: ```Pipeline steps: - label: "Buildkite test" plugins: - "./.buildkite/plugins/cgrauth": identity: "" command: - 'chainctl images repos list' ``` Assuming everything works as expected, your pipeline will be able to assume the identity and run the `chainctl images repos list` command, returning a list of images available to your Chainguard organization: ```Pipeline . . . $ .buildkite/plugins/cgrauth/hooks/pre-command % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 98.6M 100 98.6M 0 0 177M 0 --:--:-- --:--:-- --:--:-- 177M Successfully exchanged token. Valid! Id: 45a0c61ea6fd9EXAMPLE5fb9ac06a69eed764595/9753EXAMPLE6d0c8 Updated auth config for cgr.dev Running commands $ chainctl images repos list [cgr.dev/example.com] ├ [adoptium-jdk-fips] ├ [aws-efs-csi-driver] . . . ``` If you'd like to experiment further with this identity and what the pipeline can do with it, there are a few parts of this setup that you can tweak. For instance, if you'd like to give this identity different permissions you can change the role data source to the role you would like to grant: ```hcl data "chainguard_roles" "editor" { name = "editor" } ``` You can also edit the pipeline itself to change its behavior. For example, instead of listing repos, you could have the workflow inspect the organization with the `chainctl iam organizations ls` command. Of course, the Buildkite pipeline is only able to perform certain actions on certain resources depending on what kind of access you grant it. ## Removing sample resources To remove the resources Terraform created, run the `terraform destroy` command: ```sh terraform destroy ``` This destroys the role-binding and the identity created in this guide. It will not delete the organization. You can then remove the working directory to clean up your system. ```sh rm -r ~/buildkite-id/ ``` This removes the Terraform configuration files you used to create the example Buildkite resources. ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, the Terraform documentation includes a section on [recommended best practices](https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices) which you can refer to if you'd like to build on this Terraform configuration for a production environment. Likewise, for more information on using Buildkite, we encourage you to check out the [official project documentation](https://buildkite.com/docs), particularly their [documentation on Buildkite OIDC](https://buildkite.com/docs/agent/v3/cli-oidc). --- ### AI Docs: Chainguard documentation for AI tools URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/mcp-servers/ai-docs.md Last Modified: September 30, 2026 AI Docs gives AI tools access to Chainguard's documentation: the guides, security references, and tool references published on this site, plus the package and image mappings that the Dockerfile Converter (dfc) uses. You can connect to it as an MCP server, which returns only the sections that match each query, or download the whole documentation bundle and give it to an AI tool directly. AI Docs has no live container image data. It can tell you which Chainguard image replaces an upstream image, but for an image's tags, SBOM, or build date, use the `cg-oci` MCP server. Refer to [Container image data](#container-image-data). For background on MCP and the other Chainguard MCP servers, refer to the [MCP servers overview](/platform/mcp-servers/overview/). ## Why use the MCP server? - **Lower context cost.** Clients fetch only the sections they need instead of loading the entire multi-megabyte bundle into every prompt. - **Structured queries.** Search for a guide, read the security documentation, or find a package or image equivalent without writing custom scrapers. - **IDE integration.** Works with Claude Code, Claude Desktop, Cursor, and other MCP-compatible clients, so engineers can reference Chainguard docs while they write code. ## Connect to the server ### Prerequisites - An MCP-compatible client such as Claude Code, Claude Desktop, or Cursor ### Hosted server (recommended) Chainguard hosts a public MCP server at `https://mcp.edu.chainguard.dev/mcp`. This is the fastest way to get started — no Docker or local setup required, and no sign-in. How you register the server depends on your MCP client. Clients that support HTTP transport natively can connect to the URL directly. Clients that only spawn local processes (including Claude Desktop) need a small bridge such as [`mcp-remote`](https://github.com/geelen/mcp-remote). #### Claude Code Run this command: ```bash claude mcp add --transport http chainguard-docs https://mcp.edu.chainguard.dev/mcp ``` The server is available immediately. Verify it with `claude mcp list`. By default, the command registers the server for the current directory only. To make it available in every directory, add `--scope user`. #### Claude Desktop Claude Desktop reads MCP servers from a JSON file but does not yet support HTTP transport directly. Use `mcp-remote` to bridge to the hosted server: ```json { "mcpServers": { "chainguard-docs": { "command": "npx", "args": [ "mcp-remote", "https://mcp.edu.chainguard.dev/mcp" ] } } } ``` The configuration file lives at: - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` `npx` downloads and runs `mcp-remote` on demand, so you need Node.js installed on the host. Restart Claude Desktop after saving the file. #### Cursor and other clients with native HTTP transport Add the server URL to your client's MCP configuration: ```json { "mcpServers": { "chainguard-docs": { "url": "https://mcp.edu.chainguard.dev/mcp" } } } ``` Consult your client's documentation for the configuration file location, then restart the client. The Chainguard documentation tools appear in the next conversation. ### Local Docker setup To run the MCP server locally, pull the container image: ```bash docker pull ghcr.io/chainguard-dev/ai-docs:latest ``` The image's `serve-mcp` entrypoint uses the stdio transport, which works with any MCP client that launches local processes. For Claude Desktop, add this block to `claude_desktop_config.json`: ```json { "mcpServers": { "chainguard-docs": { "command": "docker", "args": [ "run", "--rm", "-i", "ghcr.io/chainguard-dev/ai-docs:latest", "serve-mcp" ] } } } ``` Restart the client after saving the file. ## Available tools The server exposes five tools for searching documentation and mapping packages and images. ### `search_docs` Search across all Chainguard documentation for relevant content. *Parameters:* - `query` (string, required): Search query - `max_results` (integer, optional): Maximum results to return (default: 5) *Example prompts:* - "Search Chainguard docs for python CVE management" - "Find information about FIPS compliance" - "Search for nginx configuration examples" ### `get_security_docs` Get security-related documentation including CVE management, SBOMs, and signing. *Example prompts:* - "How does Chainguard handle CVEs?" - "Show me security documentation" - "Explain SBOM generation" ### `get_tool_docs` Get documentation for Chainguard tools and ecosystem components. *Parameters:* - `tool_name` (string, required): Tool name: `wolfi`, `apko`, `melange`, or `chainctl` *Example prompts:* - "Show me wolfi documentation" - "How do I use apko?" - "Explain melange" ### `find_package_equivalent` Find the Wolfi package that replaces a Debian, Fedora, or Alpine package. Use this when migrating a Dockerfile to a Chainguard image and translating package names for `apk add`. *Parameters:* - `package` (string, required): Upstream OS package name (for example, "build-essential", "libssl-dev", "python3-pip") - `distro` (string, optional): Source distribution to search: `debian`, `fedora`, or `alpine`. Searches all distributions if omitted. The catalog has no Alpine mappings yet, so an Alpine lookup returns no match. *Example prompts:* - "What's the Wolfi equivalent of Debian's build-essential?" - "Find the Chainguard package for libssl-dev" - "I need to replace python3-pip in my Dockerfile" ### `find_image_equivalent` Find the Chainguard image that replaces an upstream container image, using the Dockerfile Converter mappings. Use this when migrating a Dockerfile's `FROM` line. The tool matches names the way the Dockerfile Converter does: it ignores tags and digests, accepts Docker Hub names with or without a `docker.io/` host, and falls back to the last part of the name. It returns the Chainguard image name and a pull reference. *Parameters:* - `image` (string, required): Upstream image reference as it appears in a `FROM` line (for example, "bitnami/pgpool", "docker.io/library/node:20", "gcr.io/kaniko-project/executor") *Example prompts:* - "What Chainguard image replaces bitnami/pgpool?" - "I'm migrating a Dockerfile that starts with FROM quay.io/prometheus/snmp-exporter. What's the Chainguard equivalent?" ## Container image data AI Docs doesn't carry live container image data. Chainguard's product MCP servers read it from the registry and package index at the moment you ask, so their answers describe the images as they are now. These servers require a Chainguard account. For connection and authentication steps, refer to the [MCP servers overview](/platform/mcp-servers/overview/). This table shows where to go for each image task, and which AI Docs tool used to cover it: | Task | Former AI Docs tool | Use now | | --- | --- | --- | | Read an image's documentation | `get_image_docs` | The image's page in the [Chainguard Containers Directory](https://images.chainguard.dev/). For how the image was built, the `get_config` and `get_apko_config` tools on [`cg-oci`](/platform/mcp-servers/cg-oci/). | | List images | `list_images` | The `list_repos` tool on [`cg-oci`](/platform/mcp-servers/cg-oci/), which lists the repositories your account can pull. To browse the public catalog, use the Chainguard Containers Directory. | | Check an image's tags and build date | `check_image_freshness` | The `list_tags` tool on [`cg-oci`](/platform/mcp-servers/cg-oci/) for tags, and `get_manifest` for the build date. The `registry_tags_list` tool on [`cg-api`](/platform/mcp-servers/cg-api/) also filters tags by update time. | | Map a Debian or Fedora package to Wolfi | `find_package_equivalent` | AI Docs, unchanged. For the Wolfi package's versions, SBOM, and build configuration, use the `search_packages` tool on [`cg-apk`](/platform/mcp-servers/cg-apk/). | | Find the Chainguard image that replaces an upstream image | None | The `find_image_equivalent` tool in AI Docs. |

Note: AI Docs removed its three image tools at the end of September 2026. Their data came from a snapshot of image documentation that had stopped updating.

The AI Docs server also sends these pointers to each client when it connects, so an AI tool asked about an image can direct you to the right server. ### Image references Write a Chainguard image reference as `cgr.dev//`, replacing `` with your Chainguard organization's name. Most Chainguard images are available only to organizations entitled to them, in the organization's own namespace. The public `cgr.dev/chainguard` namespace serves a free subset with `latest`-style tags only, so an image reference that works for one organization may not work in `cgr.dev/chainguard`. To check which images your organization can pull, use the `list_repos` tool on [`cg-oci`](/platform/mcp-servers/cg-oci/). ## Package catalog The `find_package_equivalent` and `find_image_equivalent` tools draw from a package catalog that ships with the server. The catalog holds the Dockerfile Converter's mappings: Debian and Fedora packages to their Wolfi equivalents, with no Alpine mappings, and upstream images to the Chainguard images that replace them. Each documentation build regenerates it. The catalog file is `package-mappings.json`. The container image and the extracted files also include an identical copy named `image-catalog.json`, the file's earlier name, so configurations that point at that name continue to work. ## Example usage Sample exchanges from a Claude Code session with `chainguard-docs` connected. The documentation changes often, so your answers will differ. ```Prompt How do I use a multi-stage build with the Chainguard Python image? ``` ```output Called chainguard-docs (ctrl+o to expand) ● Use the -dev variant as the build stage and the minimal image as the runtime stage. The -dev image includes pip and a shell, so install your dependencies there, then copy them into the minimal image, which has only Python and its standard library. The Getting started with the Python Chainguard Container guide walks through a full example. ``` ```Prompt What's the Wolfi equivalent of Debian's build-essential? ``` ```output Called chainguard-docs (ctrl+o to expand) ● Debian's build-essential maps to build-base in Wolfi. Install it with: apk add build-base ``` ```Prompt Which Chainguard image replaces bitnami/pgpool? ``` ```output Called chainguard-docs (ctrl+o to expand) ● bitnami/pgpool maps to Chainguard's pgpool2 image. Pull it as cgr.dev//pgpool2, replacing with your Chainguard organization's name. ``` ## Standalone installation (without Docker) The server script and its dependencies live in the [edu repository](https://github.com/chainguard-dev/edu). The documentation files ship inside the container image, which you can extract once and reuse. ```bash # Download the MCP server script and requirements curl -LO https://raw.githubusercontent.com/chainguard-dev/edu/main/scripts/mcp-server.py curl -LO https://raw.githubusercontent.com/chainguard-dev/edu/main/scripts/mcp-requirements.txt # Extract the documentation bundle from the container image docker run --rm --user "$(id -u):$(id -g)" \ -v $(pwd):/output ghcr.io/chainguard-dev/ai-docs:latest extract /output # Writes chainguard-ai-docs.md, package-mappings.json, image-catalog.json, # checksums.txt, and verification.sh into a chainguard-ai-docs/ subdirectory # Install dependencies into a virtual environment python3 -m venv .venv .venv/bin/pip install -r mcp-requirements.txt # Run the server DOCS_PATH=chainguard-ai-docs/chainguard-ai-docs.md \ CATALOG_PATH=chainguard-ai-docs/package-mappings.json \ .venv/bin/python mcp-server.py ``` The container runs as a non-root user, so pass `--user` to let it write to the mounted directory and to leave the extracted files owned by you. To run this script under Claude Desktop, point the configuration at the local files: ```json { "mcpServers": { "chainguard-docs": { "command": "/path/to/.venv/bin/python", "args": ["/path/to/mcp-server.py"], "env": { "DOCS_PATH": "/path/to/chainguard-ai-docs.md", "CATALOG_PATH": "/path/to/package-mappings.json" } } } } ``` ## Self-host with HTTP transport Run your own HTTP instance when you need to expose the server inside a firewall or with custom configuration. ### From the standalone script ```bash .venv/bin/python mcp-server.py --transport http --port 8080 ``` The server binds to `http://0.0.0.0:8080` with the MCP endpoint at `/mcp`. Environment variables work too: ```bash MCP_TRANSPORT=http MCP_PORT=8080 .venv/bin/python mcp-server.py ``` ### From Docker ```bash docker run --rm -p 8080:8080 ghcr.io/chainguard-dev/ai-docs:latest serve-mcp-http ``` Point your MCP client at `http://localhost:8080/mcp`. ### CLI flags | Flag | Env var | Default | Description | | --- | --- | --- | --- | | `--transport` | `MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` | | `--host` | `MCP_HOST` | `0.0.0.0` | HTTP server bind address | | `--port` | `MCP_PORT` | `8080` | HTTP server port | ## Use the documentation without a server The documentation bundle is one Markdown file holding every page on this site, plus the Dockerfile Converter mappings. It carries its compilation date at the top of the file. ### Download the bundle Download the bundle directly. It refreshes nightly. ```bash curl -LO https://edu.chainguard.dev/downloads/chainguard-complete-docs.md ``` ### Extract the bundle from the container image The container image carries the same bundle, with checksums and a verification script, and rebuilds whenever the documentation changes. Run the image with no command to print its available commands. To verify the bundle and extract it: ```bash docker pull ghcr.io/chainguard-dev/ai-docs:latest docker run --rm ghcr.io/chainguard-dev/ai-docs:latest verify docker run --rm --user "$(id -u):$(id -g)" \ -v $(pwd):/output ghcr.io/chainguard-dev/ai-docs:latest extract /output ``` The extracted bundle is `chainguard-ai-docs/chainguard-ai-docs.md`. To verify the container image's signature before you use it: ```bash cosign verify ghcr.io/chainguard-dev/ai-docs:latest \ --certificate-identity-regexp ".*github.com/chainguard-dev/edu.*" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com ``` ### Give the bundle to an AI tool The bundle is larger than most AI tools can hold in a single conversation. Tools that index attached files, such as a project knowledge base, handle it better than a chat that reads the whole file at once. For most questions, the MCP server is the better fit, because it returns only the sections that match. To use the bundle: 1. Download or extract the bundle. 2. Add the file to your AI tool's project knowledge, or attach it to a conversation. 3. Ask your question. The AI tool answers from Chainguard's documentation rather than from its training data alone. ## Security features The container image follows the standard Chainguard pattern: - Built on `cgr.dev/chainguard/wolfi-base` - Runs as a non-root user - Signed with Cosign - Rebuilt whenever the documentation changes, so known CVEs don't accumulate Each build also scans the bundle for credential patterns before publishing it. For the compilation process and verification steps, refer to [AI documentation security](/ai-docs-security/). The [build logs](https://github.com/chainguard-dev/edu/actions/workflows/compile-ai-docs-from-gcs.yaml) and the [compilation scripts](https://github.com/chainguard-dev/edu/tree/main/scripts) are public. ## Troubleshooting ### Server does not appear in Claude Desktop 1. Confirm that the configuration file path is correct for your platform. 2. Restart Claude Desktop after editing the file. 3. Check Claude Desktop's logs for parse or connection errors. 4. For the local Docker block, confirm Docker is running. ### Connection issues Test the hosted server with `curl`: ```bash curl -X POST https://mcp.edu.chainguard.dev/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' ``` A JSON response listing the server's capabilities confirms the connection. To test a local Docker server: ```bash docker run --rm -i ghcr.io/chainguard-dev/ai-docs:latest serve-mcp ``` The container prints startup messages and then waits for stdio input. ### An AI tool can't find image details AI Docs has no live container image data. Connect [`cg-oci`](/platform/mcp-servers/cg-oci/) for live registry data, or look up the image in the [Chainguard Containers Directory](https://images.chainguard.dev/). Refer to [Container image data](#container-image-data). ### Documentation out of date The hosted server updates automatically. For the local Docker setup, pull a fresh image: ```bash docker pull ghcr.io/chainguard-dev/ai-docs:latest ``` ## Resources - [Chainguard MCP servers overview](/platform/mcp-servers/overview/) - [`cg-oci`: the Chainguard container registry MCP server](/platform/mcp-servers/cg-oci/) - [Model Context Protocol documentation](https://modelcontextprotocol.io/) - [Chainguard MCP blog post](https://www.chainguard.dev/unchained/meet-chainguard-mcps-bringing-supply-chain-security-to-the-ai-era) - [AI documentation security](/ai-docs-security/) - [Chainguard Containers Directory](https://images.chainguard.dev/) ## Need help? - [Get support](/get-started/get-support/) - [Community Slack](https://join.slack.com/t/chainguardcommunity/shared_invite/zt-3nttdr807-V9BJHayWvsB0KbHsfZO5Rw) - [GitHub Issues](https://github.com/chainguard-dev/edu/issues) --- ### FIPS and non-approved algorithms URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/non-approved-algorithms.md Last Modified: September 28, 2026 Tags: FIPS, MD5 ## Overview FIPS cryptographic modules implement cryptographically strong protection of data at rest and in transit. NIST's position on this is very clear ([source](https://csrc.nist.gov/projects/cryptographic-module-validation-program)): > Non-validated cryptography is viewed as providing no protection to the information or data — in effect the data would be considered unprotected plaintext. If the agency specifies that the information or data be cryptographically protected, then FIPS 140-2 or FIPS 140-3 is applicable. In essence, if cryptography is required, then it must be validated. Should the cryptographic module be revoked, use of that module is no longer permitted. ## NIST guidance on non-approved algorithm usage As part of the FIPS collection of publications, NIST publishes [FIPS 140-3 Implementation Guidance](https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements) (FIPS 140 I.G.). The Cryptographic Module Validation Program (CMVP) requirements for the FIPS 140-3 include ISO standards, SP 800 series documents as well as the FIPS 140-3 I.G. The full set of documents and diagram are available from the [NIST Information Technology Laboratory Computer Security Resource Center](https://csrc.nist.gov/Projects/cryptographic-module-validation-program/fips-140-3-standards). The FIPS 140-3 I.G. 2.4.A "Definition and Use of a non-Approved Security Function" is three pages long, and must be read in conjungtion with all other relevant NIST & ISO publications. It provides many examples, exceptions, and carve outs that at times let you use non-approved algorithms as part of higher level approved services. For example, some algorithms might not be safe to use directly, but with appropriate safeguards can be cryptographically secure. This is often the case with complex protocols such as TLS, which combines cryptographic primitives in a safe way. Skipping to additional comments, let's focus on these statements (current edition, refer to the current [FIPS 140-3 I.G.](https://csrc.nist.gov/projects/cryptographic-module-validation-program/fips-140-3-ig-announcements) for any changes). ### FIPS 140-3 I.G. 2.4.A additional comment The vendor must provide clear documentation and reasoning as to why the non-approved cryptographic algorithms can be used in an approved mode, that is, not being used to meet the requirements of FIPS 140-3 sections 6 and 7. It is at the discretion of the CMVP to determine if such usage of an algorithm fits within the guidance laid out in this implementation guidance (IG). In addition, attempts to make use of this IG to include algorithms in the approved mode will not be accepted unless all of the following are met: 1) the algorithm is not used whatsoever to meet any FIPS 140-3 requirements; 1) the algorithm does not access or share CSPs in a way that counters the requirements of this IG; 1) the algorithm is either: 1) not intended to be used as a security function (for example, interoperability or for memory wear leveling); 1) redundant to an approved algorithm (such as double encryption); 1) a cryptographic or mathematical operation applied for “good measure” but not for providing sound security (that is XORing a CSP with a secret value, using a proprietary algorithm, or using non-approved algorithms to obfuscate stored CSPs which are considered plaintext); 1) the algorithm’s non-approved use and purpose (from 3, above) is unambiguous to the operator and can’t be easily confused for a security function. ### Chainguard FIPS commitment As documented in the [Chainguard FIPS commitment](https://www.chainguard.dev/legal/fips-commitment), our FIPS images enable only approved services and algorithms by default. This simplifies reasoning, audit and testing about what is or isn't a security function, since we are using only approved services. For example, Chainguard [gradle-fips](https://images.chainguard.dev/directory/image/gradle-fips/versions) has been modified to use an approved keystore to store build settings. While not a security function, this ensured that no unapproved keystore could leak into the build process and testing. All cases of usage that might be related to a security function are also made to only use approved services. This includes but is not limited to: * encryption / decryption * digital signature creation and verification * random number generation * message authentication code * key derivation functions * key encapsulation methods * key exchange The one functionality that errs on the side of non-security function is calculating a digest alone, not part of MAC, HMAC, Merkle tree, integrity scheme, or digital signatures. Specifically, MD4, MD5, and SHA1 are universally deprecated and disallowed as part of security schemes, and yet they remain widely used for non-security functionality. Examples of such non-security usage are: * Webpack 4 uses MD4 to precompute perfect hashtables from trusted input at build time, refer to [this issue](https://github.com/webpack/webpack/issues/14560). * Yarn, .ZIP, JAR, PDF require MD5 as part of the frozen fileformats they use * Amazon S3 supports many algorithms for object integrity checking over trusted channel, including MD5 and SHA1, refer to [the official docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity-upload.html). Many client implementations default to MD5. * Google Cloud Storage can use CRC32C or MD5, and clients typically default to MD5 for object integrity during uploads [docs](https://docs.cloud.google.com/storage/docs/data-validation) * AES-ECB insecure when used directly, but is used by QUIC and DTLSv1.3 protocols for non-security obfuscation of public information * SHA-1 is still widely used for hash addressable content and lookup tables, for example `apk-tools` and git. In all of the above use cases digest calculation does not provide any security functionality, it is meant to detect accidental corruption or improve speed. Overall, data is typically protected by SHA2-256 and is transmitted over a secure and authenticated TLS channel. One alternative to migrating away from MD5 is to choose a specialist function explicitly designed for non-security purposes with significantly higher performance, such as [XXHASH](https://xxhash.com/). In most cases, non-security functionality should upgrade from CRC32C, MD5, SHA1 to XXHASH3. However, if you need interoperability with existing formats and services *and* it is established that digest usage is for non-security purposes, you must use the insecure digests. Chainguard is integrating support for such use cases for MD5 and SHA1 across our FIPS images. Each language and application implementation is very different and specific, documented below. Although SHA1 is currently approved, it is already deprecated by RFCs. NIST is deprecating SHA1 by 2030. The implementations below are forward-looking and attempt to address access to MD5 today and SHA1 in the future. SHA1 is available as approved in Chainguard FIPS Provider for OpenSSL versions 3.0.9, 3.1.2 and 3.4.0. It is non-approved starting in version 3.6.0. ## Access to unapproved algorithms for non-security purposes ### Chainguard Legacy Approved provider for OpenSSL Chainguard FIPS and non-FIPS images are configured to use a legacy provider that exposes the above mentioned algorithms for non-security purposes. At runtime one can choose to disable them with an environment variable `CHAINGUARD_LEGACY_ALLOWED=0`. This provider enables all OpenSSL applications to uniformly access legacy algorithms for non-security purposes. They do remain blocked from being used inside the FIPS module boundary for security purposes such as protecting data at rest or in transit. This guidance applies to all images and software that uses OpenSSL: * python-fips * node-fips * jdk-openssl-fips * postgresql-fips * dotnet-fips And many others. ### Python grpc, pyca, cryptography FIPS and MD5 If you are compiling grpc or pycryptography from source; or installing it from PyPI; it may come with a vendored and statically linked copy of a cryptographic library and will not operate in FIPS mode. In such cases, all usage of it may be non-approved. If you install grpc and pycryptography through [Custom Assembly](https://edu.chainguard.dev/chainguard/containers/custom-assembly/overview/), it will be dynamically linked with Chainguard OpenSSL and will use the above guidance. ### Go-fips / Go-openssl-fips Access to MD5 digest is provided using native Go implementation, whilst any form of authentication, authorisation, digital signatures and TLS with MD5 is blocked. ### All other projects If you have queries about this guidance, or any other packages, projects, languages or ecosystems, in the feedback section on this page select "No" and please fill in feedback, or please [open a support ticket](https://support.chainguard.dev/). --- ### What is the future of the Chainguard Factory? URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/future-of-factory.md Last Modified: September 28, 2026 Tags: Chainguard Factory, Video, Conceptual [Watch the video on YouTube](https://www.youtube.com/watch?v=eF9EYK6AKPA) ## Transcript **Interviewer**: So what do you see as the future for the Factory? **Dustin Kirkland**: Well, I'm not going to get into the super secret plans we have for next generation products, but I will tell you that we are constantly expanding the scope of the open source projects that we're building. So more and more packages—each of those show up as new packages that we're now able to add to our existing images or build new images around. More and more images—there's a long list, a backlog of images that our customers have requested from us at Chainguard. We continue to expand that catalog from 1,300 to 1,400 to 1,500 images. We're growing by 50 to 100 images per month in that catalog. So even more packages, even more images, and more libraries too, for that matter. We are adding Java packages that one would typically find and add using the Maven tool. More and more Python libraries that one would typically install using pip. We're building more and more of those from source and getting greater coverage. And it's not just the projects themselves, but more versions of those projects. The fact that we can reproduce those builds means that we can apply patches. So we're fixing vulnerabilities and backporting fixes—CVEs that are fixed in latest versions—and now starting to backport some of those to older versions of those libraries. And then in terms of the virtual machines form factor, super interesting that we can take any container image that we have and render that as a VM appliance, fully tested, fully qualified, running in various different environments. And we found some customers that just prefer to run some very specific workloads directly on the VM without a containerization layer. So there's plenty of room to expand. The Factory gives us all of the power that we need to scale out the coverage across the open source ecosystem. **Interviewer**: Excellent. And what about supply chain security in general? What do you see as the future there? **Dustin Kirkland**: Yeah, so I mean, I think we've taken—very importantly, I think we've taken a key step in supply chain security by bootstrapping everything, building it from source, doing it well, doing it the right way, doing it all day every day, 24/7, 365, constantly. We don't snapshot the world and then attempt to backport patches to a version that just constantly drifts from where upstream is. And that goes for compilers and toolchains, script interpreters, as well as the end applications, databases, and application frameworks. We're constantly rebuilding with exactly the same work that the upstream maintainers are using. And that fact, I think, is the important part. And what makes the rolling distro work for us is that we're matching the pace of upstream development here. --- ### How to compare Chainguard Containers with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/comparing-images.md Last Modified: October 2, 2026 Tags: Chainguard Containers Chainguard's `chainctl images diff` command provides detailed comparisons between container image versions, enabling you to track security improvements, package updates, and vulnerability changes across builds. This powerful feature helps you understand exactly what changes between image versions, whether comparing daily builds, analyzing CVE remediation, or evaluating custom image updates. The [`chainctl`](/platform/chainctl/) diff functionality supports informed deployment decisions by revealing package-level differences, security posture changes, and build variations between any two Chainguard container images. ## Prerequisites In order to use the `chainctl images diff` subcommand, you'll need to have a few tools installed. * You'll need `chainctl` installed on your local machine. Follow our guide on [How to install chainctl](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. If you already have `chainctl` installed, be sure to update it to the latest version with `chainctl update`. * Next, ensure you have Cosign installed. Our guide on [How to install Cosign](/open-source/sigstore/cosign/how-to-install-cosign/) outlines several methods for installing Cosign. * You'll also need Grype installed on your local machine, as `chainctl` uses this to scan the images when performing the diff. Follow the installation instructions for your operating system on the [Grype project GitHub repository](https://github.com/anchore/grype#installation). * Lastly, an example command in this guide uses `jq` — a command-line JSON processor — to make the command's output more readable. You don't strictly need to have `jq` installed in order to use the `diff` subcommand, but if you'd like you can install it by following [the official documentation](https://jqlang.github.io/jq/download/). ## Using `chainctl images diff` The `chainctl images diff` subcommand accepts the names of two Chainguard Containers as arguments and uses Grype to perform a vulnerability scan on each of them. It then retrieves both container images' SBOM information and outputs the difference between the two along with the previously obtained Grype data. The `diff` subcommand follows this general syntax. ```sh chainctl images diff $FROM_IMAGE $TO_IMAGE ``` As an example, try comparing the `latest` public `go` Chainguard Container with its `latest-dev` version. ```sh chainctl images diff cgr.dev/chainguard/go:latest cgr.dev/chainguard/go:latest-dev | jq ``` This will return output like the following. ```output Fetching vulnerabilities for cgr.dev/chainguard/go@sha256:6fee3fff87854aa6e4762c7998c127436a68b09877f9c1010deca35e0f1e27bc Fetching vulnerabilities for cgr.dev/chainguard/go@sha256:e62ce9fe5e62296186066e647d22cd8d16565d8eee9c2d18541094cec9ddd7a3 { "packages": { "added": [ { "name": "sha256:e62ce9fe5e62296186066e647d22cd8d16565d8eee9c2d18541094cec9ddd7a3", "reference": "pkg:oci/index@sha256:e62ce9fe5e62296186066e647d22cd8d16565d8eee9c2d18541094cec9ddd7a3?mediaType=application%2Fvnd.oci.image.index.v1%2Bjson" }, { "name": "sha256:a5910c192d3bd6e473cd98a0553d55dba1e9ddee240732a91bf4985116f893d0", "reference": "pkg:oci/image@sha256:a5910c192d3bd6e473cd98a0553d55dba1e9ddee240732a91bf4985116f893d0?arch=amd64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" }, { "name": "sha256:35b2716760a4ec6652830a453d692cc7c55893eb8a6b4cc2afabc2bdfad2a10f", "reference": "pkg:oci/image@sha256:35b2716760a4ec6652830a453d692cc7c55893eb8a6b4cc2afabc2bdfad2a10f?arch=arm64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" } ], "removed": [ { "name": "sha256:6fee3fff87854aa6e4762c7998c127436a68b09877f9c1010deca35e0f1e27bc", "reference": "pkg:oci/index@sha256:6fee3fff87854aa6e4762c7998c127436a68b09877f9c1010deca35e0f1e27bc?mediaType=application%2Fvnd.oci.image.index.v1%2Bjson" }, { "name": "sha256:eaeb73fe40e46eabd28837f3b981791984fc40cac4833f872169f09c7c3cb4df", "reference": "pkg:oci/image@sha256:eaeb73fe40e46eabd28837f3b981791984fc40cac4833f872169f09c7c3cb4df?arch=arm64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" }, { "name": "sha256:87d4c21ede568d79d4ca51271dda3bf46a4164be2bcd7405b6b85b49801d3504", "reference": "pkg:oci/image@sha256:87d4c21ede568d79d4ca51271dda3bf46a4164be2bcd7405b6b85b49801d3504?arch=amd64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" } ] }, "vulnerabilities": {} } ``` This command first uses Grype to scan each container image's vulnerability data and then retrieves both images' [SBOMs](https://www.chainguard.dev/supply-chain-security-101/what-is-an-sbom). It then outputs the differences that it finds between the two. This sample output indicates that compared to the `go:latest` container image, the `go:latest-dev` image has three packages added, three removed, and no unique vulnerabilities. `chainctl`compares the images like this because of the order they appear in the command. If you reversed the order of the images in the example command, the packages shown as `added` and `removed` would also be flipped: ```output Fetching vulnerabilities for cgr.dev/chainguard/go@sha256:e62ce9fe5e62296186066e647d22cd8d16565d8eee9c2d18541094cec9ddd7a3 Fetching vulnerabilities for cgr.dev/chainguard/go@sha256:6fee3fff87854aa6e4762c7998c127436a68b09877f9c1010deca35e0f1e27bc { "packages": { "added": [ { "name": "sha256:6fee3fff87854aa6e4762c7998c127436a68b09877f9c1010deca35e0f1e27bc", "reference": "pkg:oci/index@sha256:6fee3fff87854aa6e4762c7998c127436a68b09877f9c1010deca35e0f1e27bc?mediaType=application%2Fvnd.oci.image.index.v1%2Bjson" }, { "name": "sha256:eaeb73fe40e46eabd28837f3b981791984fc40cac4833f872169f09c7c3cb4df", "reference": "pkg:oci/image@sha256:eaeb73fe40e46eabd28837f3b981791984fc40cac4833f872169f09c7c3cb4df?arch=arm64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" }, { "name": "sha256:87d4c21ede568d79d4ca51271dda3bf46a4164be2bcd7405b6b85b49801d3504", "reference": "pkg:oci/image@sha256:87d4c21ede568d79d4ca51271dda3bf46a4164be2bcd7405b6b85b49801d3504?arch=amd64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" } ], "removed": [ { "name": "sha256:e62ce9fe5e62296186066e647d22cd8d16565d8eee9c2d18541094cec9ddd7a3", "reference": "pkg:oci/index@sha256:e62ce9fe5e62296186066e647d22cd8d16565d8eee9c2d18541094cec9ddd7a3?mediaType=application%2Fvnd.oci.image.index.v1%2Bjson" }, { "name": "sha256:a5910c192d3bd6e473cd98a0553d55dba1e9ddee240732a91bf4985116f893d0", "reference": "pkg:oci/image@sha256:a5910c192d3bd6e473cd98a0553d55dba1e9ddee240732a91bf4985116f893d0?arch=amd64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" }, { "name": "sha256:35b2716760a4ec6652830a453d692cc7c55893eb8a6b4cc2afabc2bdfad2a10f", "reference": "pkg:oci/image@sha256:35b2716760a4ec6652830a453d692cc7c55893eb8a6b4cc2afabc2bdfad2a10f?arch=arm64&mediaType=application%2Fvnd.oci.image.manifest.v1%2Bjson&os=linux" } ] }, "vulnerabilities": {} } ``` Be aware that because this is a relatively new feature, the format of the `diff` subcommand's output is subject to change. ## Potential use cases Being able to find the exact difference between two Chainguard Containers with a single command allows users to make more informed decisions about what container images they use in their applications. This section goes over a couple scenarios where you may want to use the `chainctl images diff` command. One potential use case for why you would want to find the differences between two Chainguard Containers is that you're curious about the differences between available release versions. Say you're using Custom Chainguard Containers and your application is pinned to a specific version of `go`. By diffing the two container images, you could check what vulnerabilities you could remove by updating to the next patch or minor version. Another potential use could be in cases where you're interested in knowing the difference between a Chainguard Container's daily builds. For example, say you'd like to keep your images updated but only when there are significant changes between daily builds. You could diff between the running versions and the latest builds, only updating if there’s a meaningful difference. ## Learn more To learn more about the `chainctl image` subcommands, we encourage you to check out our [`chainctl` command resources](/platform/chainctl/chainctl-docs/chainctl_images/). You can also explore the rest of our [Chainguard Containers resources](/chainguard/containers/) to learn more about how images can help you keep your software secure by default. --- ### Create an assumable identity for a Bitbucket pipeline URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/bitbucket-identity.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This procedural tutorial outlines how to create an identity using Terraform, and then how to update a Bitbucket pipeline so that it can assume the identity and interact with Chainguard resources. ## Prerequisites To complete this guide, you will need the following. * `terraform` installed on your local machine. Terraform is an open source Infrastructure as Code tool which this guide will use to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * A Bitbucket pipeline you can use to test out the identity you'll create. We recommend following Bitbucket's [Getting Started guide](https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/) to set this up. If you need to enable pipelines for your repository, visit Bitbucket's [Configure your first pipeline](https://support.atlassian.com/bitbucket-cloud/docs/configure-your-first-pipeline/) page to get started. ## Creating Terraform files We will be using Terraform to create an identity for a Bitbucket pipeline to assume. This step outlines how to create three Terraform configuration files that, together, will produce such an identity. To help explain each configuration file's purpose, we will go over what they do and how to create each file one by one. First, though, create a directory to hold the Terraform configuration and navigate into it. ```sh mkdir ~/bitbucket-id && cd $_ ``` This will help make it easier to clean up your system at the end of this guide. ### `main.tf` The first file, which we will call `main.tf`, will serve as the scaffolding for our Terraform infrastructure. The file will consist of the following content. ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } } } ``` This is a fairly barebones Terraform configuration file, but we will define the rest of the resources in the other two files. In `main.tf`, we declare and initialize the Chainguard Terraform provider. Next, you can create the `sample.tf` file. ### `sample.tf` `sample.tf` will create a couple of structures that will help us test out the identity with a Bitbucket a workflow. This Terraform configuration consists of two main parts. The first part of the file will contain the following lines. ```hcl data "chainguard_group" "group" { name = "my-customer.biz" } ``` This section looks up a Chainguard IAM organization named `my-customer.biz`. This will contain the identity — which will be created by the `bitbucket.tf` file — to access when we test it out later on. Now you can move on to creating the rest of our Terraform configuration files, `bitbucket.tf`. ### `bitbucket.tf` The `bitbucket.tf` file is what will actually create the identity for your Bitbucket workflow to assume. The file will consist of four sections, which we'll go over one by one. The first section creates the identity itself. ```hcl resource "chainguard_identity" "bitbucket" { parent_id = data.chainguard_group.group.id name = "bitbucket" description = </: ``` Once you commit the `bitbucket-pipelines.yml` file the pipeline will run. Assuming everything works as expected, your pipeline will be able to assume the identity and run the `chainctl images repos list` command, listing repos available to the organization. ``` . . . chainctl 100%[===================>] 54.34M 6.78MB/s in 13s 2023-05-17 13:19:45 (4.28 MB/s) - ‘chainctl’ saved [56983552/56983552] Successfully exchanged token. Valid! Id: 3f4ad8a9d5e63be71d631a359ba0a91dcade94ab/d3ed9c70b538a796 ``` If you'd like to experiment further with this identity and what the pipeline can do with it, there are a few parts of this setup that you can tweak. For instance, if you'd like to give this identity different permissions you can change the role data source to the role you would like to grant. ``` data "chainguard_roles" "editor" { name = "editor" } ``` You can also edit the pipeline itself to change its behavior. For example, instead of listing the repos the identity has access to, you could have the workflow inspect the organizations. ```bitbucket-pipelines.yml - ./chainctl images repos list ``` Of course, the Bitbucket pipeline will only be able to perform certain actions on certain resources, depending on what kind of access you grant it. ## Removing sample resources To remove the resources Terraform created, you can run the `terraform destroy` command. ```sh terraform destroy ``` This will destroy the role-binding, and the identity created in this guide. It will not delete the organization. You can then remove the working directory to clean up your system. ```sh rm -r ~/bitbucket-id/ ``` Following that, all of the example resources created in this guide will be removed from your system. ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, the Terraform documentation includes a section on [recommended best practices](https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices) which you can refer to if you'd like to build on this Terraform configuration for a production environment. Likewise, for more information on using Bitbucket pipelines, we encourage you to check out the [official project documentation](https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/), particularly their [documentation on OIDC](https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/). --- ### Verify that Chainguard FIPS containers are configured to use FIPS modules URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/verify-fips.md Last Modified: September 28, 2026 Tags: FIPS, Chainguard Containers, Reference Chainguard offers hundreds of FIPS container image variants covering language runtimes (Go, Java, Python, Node.js, .NET, PHP, C/C++), databases, web servers, and Kubernetes components. These images use NIST-validated cryptographic modules including the OpenSSL FIPS provider, Bouncy Castle FIPS, and BoringCrypto. Refer to Chainguard's [FIPS Commitment](https://www.chainguard.dev/legal/fips-commitment) for a full list of the modules used in Chainguard FIPS Images, as well as their respective CMVP certificates and SBOM indicators. This guide outlines how to verify that Chainguard's FIPS images are properly configured to use these FIPS modules. ## Why verify FIPS configuration? FIPS (Federal Information Processing Standards) 140 is a U.S. government standard that specifies security requirements for cryptographic modules. Organizations in regulated industries—including federal agencies, defense contractors, healthcare organizations, and financial services—must verify that their containers are correctly using FIPS-validated modules to maintain compliance. These verification steps help you: - Confirm FIPS modules are active and operational - Validate compliance for audit purposes - Troubleshoot configuration issues - Generate evidence for compliance documentation ## SBOM indicators Container images include packages with `NIST-` prefix indicating applicable certification. These also provide URLs to the certificates in the APK database and the SPDX SBOM. The following prefixes are in use: - `NIST-CMVP-5132` indicates a validated cryptographic module with the certificate [#5132](https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/5132) - `NIST-ESV-191` indicates a validated entropy source with the certificate [#E191](https://csrc.nist.gov/projects/cryptographic-module-validation-program/entropy-validations/certificate/191) - `NIST-CMVP-5523-optin` indicates a validated optional cryptographic module with the certificate [#5523](https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/5523) that can be opted into at runtime. - `NIST-CMVP-4743-UPDATE` indicates a non-validated module that is expected to be submitted to NIST for an update. See [FedRAMP](https://www.fedramp.gov/2026/reference/cryptographic-module-use/) documentation for more information. - `NIST-MIP-module-name` indicates a non-validated submitted module only available in fips-mip images showcasing [Modules in process](https://csrc.nist.gov/projects/cryptographic-module-validation-program/modules-in-process/modules-in-process-list) containing future FIPS module development ## OpenSSL Because most software relies on OpenSSL for cryptographic operations, all of Chainguard's FIPS container images ship OpenSSL in FIPS mode. You can verify whether OpenSSL is properly configured to use its FIPS module with [the `openssl-fips-test` tool](https://github.com/chainguard-dev/openssl-fips-test). This tool, developed by Chainguard and included in all of our FIPS container images, prints the public API version, the FIPS module version, and the executed self tests. When run on a system using the OpenSSL Project FIPS provider, `openssl-fips-test` returns a summary of available algorithms, which is useful to compare different CMVP modules and the algorithms they offer. It also retrieves FIPS module information and returns a CMVP search URL where one can find applicable certificates. You can use the `openssl-fips-test` tool to check whether any Chainguard Container is properly configured to use its FIPS module with a command like the following. Be sure to replace `$ORGANIZATION` with the name of your own organization and [`python-fips`](https://images.chainguard.dev/directory/image/python-fips/overview) with any FIPS image your organization has access to: ```shell docker run -e OPENSSL_CONF_INCLUDE=/etc/ssl-3.6.0 -it --entrypoint openssl-fips-test cgr.dev/$ORGANIZATION/python-fips ``` ```output Checking OpenSSL lifecycle assurance. ✓ Self-test KAT_Integrity HMAC ... passed. ✓ Self-test Module_Integrity HMAC ... passed. ✓ Self-test KAT_Digest SHA2 ... passed. ✓ Self-test KAT_Digest SHA3 ... passed. ✓ Self-test KAT_Cipher AES_GCM ... passed. ✓ Self-test KAT_Cipher AES_ECB_Decrypt ... passed. ✓ Self-test KAT_Signature RSA ... passed. ✓ Self-test KAT_Signature ECDSA ... passed. ✓ Self-test KAT_Signature DetECDSA ... passed. ✓ Self-test KAT_Signature EDDSA ... passed. ✓ Self-test KAT_Signature EDDSA ... passed. ✓ Self-test KAT_Signature ML-DSA ... passed. ✓ Self-test KAT_Signature SLH-DSA ... passed. ✓ Self-test KAT_Signature SLH-DSA ... passed. ✓ Self-test KAT_KDF TLS13_KDF_EXTRACT ... passed. ✓ Self-test KAT_KDF TLS13_KDF_EXPAND ... passed. ✓ Self-test KAT_KDF TLS12_PRF ... passed. ✓ Self-test KAT_KDF PBKDF2 ... passed. ✓ Self-test KAT_KDF KBKDF ... passed. ✓ Self-test KAT_KDF KBKDF_KMAC ... passed. ✓ Self-test KAT_KDF HKDF ... passed. ✓ Self-test KAT_KDF SSKDF ... passed. ✓ Self-test KAT_KDF X963KDF ... passed. ✓ Self-test KAT_KDF X942KDF ... passed. ✓ Self-test DRBG HASH ... passed. ✓ Self-test DRBG CTR ... passed. ✓ Self-test DRBG HMAC ... passed. ✓ Self-test KAT_KA DH ... passed. ✓ Self-test KAT_KA ECDH ... passed. ✓ Self-test KAT_AsymmetricKeyGeneration ML-KEM ... passed. ✓ Self-test KAT_AsymmetricKeyGeneration ML-DSA ... passed. ✓ Self-test KAT_AsymmetricKeyGeneration SLH-DSA ... passed. ✓ Self-test KAT_KEM KEM_Encap ... passed. ✓ Self-test KAT_KEM KEM_Decap ... passed. ✓ Self-test KAT_KEM KEM_Decap_Reject ... passed. ✓ 35 out of 35 self-tests passed. ✓ Check FIPS cryptographic module is available... passed. ✓ Check FIPS approved only mode (EVP_default_properties_is_fips_enabled)... passed. ✓ Check non-approved algorithm blocked (HMAC-MD5)... passed. Digests available for non-security use as per FIPS 140-3 I.G. 2.4.A (fips=no): ✓ MD5 ✓ SHA1 Available approved algorithms for security purposes (provider=fips,fips=yes): ✗ MD5 ✗ SHA-1 ✓ SHA-2 ✓ SHA-3 ✗ DSA ✓ RSA ✓ ECDSA ✓ Ed25519 ✓ DetECDSA ✓ ML-DSA ✓ SLH-DSA ✓ ML-KEM ✓ X25519MLKEM768 ✓ SecP256r1MLKEM768 ✓ SecP384r1MLKEM1024 Public OpenSSL API (libssl.so & libcrypto.so): name: OpenSSL 3.6.4 25 Aug 2026 version: 3.6.4 FIPS cryptographic module provider details (fips.so): name: Chainguard FIPS Provider for OpenSSL version: 3.6.0 build: 3.6.0-r4 Locate applicable certificate(s) at: CMVP #5523 (with entropy #E191) Lifecycle assurance satisfied. ``` This output confirms that OpenSSL in the `python-fips` image is properly configured to use its FIPS module. ### OpenSSL FIPS 140-3 enforcement quick checks [NIST SP 800-131A](https://csrc.nist.gov/pubs/sp/800/131/a/r2/final) requires HMAC to use approved digest and keys of at least 112 bits long (14 characters) for security purposes. Success scenario is HMAC with a 14-character key and a SHA256 digest: ```sh openssl mac -macopt key:14charslongkey -macopt digest:sha256 -in /dev/null HMAC ``` ```output 0DB994567D50545AC5A44823F82AAE06B1A21B99F8DD0A42B3D572B1AF62F182 ``` Negative test is HMAC with a short key, and a SHA256 digest: ```sh # openssl mac -macopt key:shortkey -macopt digest:sha256 -in /dev/null HMAC MAC parameter error 801B86DD147F0000:error:1C800069:Provider routines:hmac_setkey:invalid key length:providers/implementations/macs/hmac_prov.c:173: ``` Cryptographically insecure digests are also rejected: ```sh # openssl mac -macopt key:14charslongkey -macopt digest:md5 -in /dev/null HMAC MAC parameter error 808B8049E17F0000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:FIPS internal library context, Algorithm (md5 : 0), Properties () ``` You can also perform these checks in other programming languages, such as Python, Node.js, PHP, and Perl, in images and systems that use OpenSSL to power FIPS cryptography. In non-FIPS images, all of these commands succeed. ### OpenSSL FIPS 140-3 tamper test Cryptographic modules are required to perform startup self-tests, and must enter error state and stop all cryptographic services upon failure. One of the startup self-tests is an integrity check of the cryptographic module itself. To observe this one can tamper with the fips.so module itself, or tamper with the expected module HMAC value. ```sh echo 'module-mac = 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00' >> /etc/ssl/fipsmodule.cnf ``` After tampering with the module integrity mac: ```output # openssl-fips-test Checking OpenSSL lifecycle assurance. ✓ Self-test KAT_Integrity HMAC ... passed. ✗ Self-test Module_Integrity HMAC ... FAILED. ✗ Check FIPS cryptographic module is available... FAILED. ✓ Check FIPS approved only mode (EVP_default_properties_is_fips_enabled)... passed. ✓ Check non-approved algorithm blocked (HMAC-MD5)... passed. Failed to retrieve cryptographic module version information ``` And previously approved and successful operations now fail, for example HMAC with a long key and SHA2 fails, when the fips module integrity check has been tampered with: ``` # openssl mac -macopt key:14charslongkey -macopt digest:sha256 -in /dev/null HMAC Invalid MAC name HMAC mac: Use -help for summary. 80DB6818FB7F0000:error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:376:Global default library context, Algorithm (HMAC : 0), Properties () ``` If an application continues to operate, even when the FIPS module has been tampered with or removed, this indicates that the given application and algorithms have stopped using the FIPS module, are not using OpenSSL, or have fallbacks. For example, applications might prefer OpenSSL when it is operational, but have fallbacks to other libraries or have statically compiled alternative implementations of algorithms. ### Opt in to different FIPS provider versions Images that contain `NIST-CMVP-5523-optin` or `NIST-CMVP-5132-optin` SBOM indicator packages offer ability to switch between different versions of validated CMVP modules at runtime. - `NIST-CMVP-5523-optin` enables opt-into Chainguard v3.6 module with `OPENSSL_CONF_INCLUDE=/etc/ssl-3.6.0` environment variable - `NIST-CMVP-5132-optin` enables opt-into Chainguard v3.4 module with `OPENSSL_CONF_INCLUDE=/etc/ssl-3.4.0` environment variable For example: ```sh export OPENSSL_CONF_INCLUDE=/etc/ssl-3.6.0 ``` ```output export OPENSSL_CONF_INCLUDE=/etc/ssl-3.6.0 openssl-fips-test ... FIPS cryptographic module provider details (fips.so): name: Chainguard FIPS Provider for OpenSSL version: 3.6.0 build: 3.6.0-r4 ``` ```sh export OPENSSL_CONF_INCLUDE=/etc/ssl-3.4.0 ``` ```output export OPENSSL_CONF_INCLUDE=/etc/ssl-3.4.0 openssl-fips-test ... FIPS cryptographic module provider details (fips.so): name: Chainguard FIPS Provider for OpenSSL version: 3.4.0 build: 3.4.0-r5 ``` ## Bouncy Castle FIPS Java API All of Chainguard's Java FIPS images support FIPS 140-3 compliance using Bouncy Castle FIPS cryptographic modules. To verify whether Bouncy Castle FIPS is operating correctly, you can execute its `DumpInfo` command. The following example shows how you can do this with the [`jre-fips`](https://images.chainguard.dev/directory/image/jre-fips/overview) container image: ```shell docker run -it --rm cgr.dev/$ORGANIZATION/jre-fips org.bouncycastle.util.DumpInfo ``` ```output Version Info: BouncyCastle Security Provider (FIPS edition) v2.1.1 FIPS Ready Status: READY Native Ready Status: READY Native Variant: vaesf Native Build Date: 2024-11-15T15:56:42 Native Support: AES/CBC AES/CFB AES/CTR AES/ECB AES/GCM DRBG NRBG SHA2 Module SHA-256 HMAC: … ``` Note that this example worked because the `jre-fips` image's entrypoint is `/usr/bin/java`, so it executes `java org.bouncycastle.util.DumpInfo` within the container. > **Note:** You can find a Chainguard container image's entrypoint in its [**Specifications** tab](https://images.chainguard.dev/directory/image/jre-fips/specifications) within the Containers Directory. ## BoringCrypto Some of Chainguard's FIPS container images — including [`envoy-fips`](https://images.chainguard.dev/directory/image/envoy-fips/overview) and others that ship `envoy-fips` forks — use [BoringCrypto](https://boringssl.googlesource.com/boringssl/+/master/crypto/fipsmodule/FIPS.md), Google's fork of OpenSSL. Chainguard FIPS images that use BoringCrypto list the version of the module in use in their `version` output: ```shell docker run -it --rm cgr.dev/$ORGANIZATION/envoy-fips --version ``` ```output envoy version: dc2d3098ae5641555f15c71d5bb5ce0060a8015c/1.36.2/Modified/RELEASE/BoringSSL-FIPS-2023042800 ``` You can contrast this with the `version` output of the standard, non-FIPS `envoy` image: ```shell docker run -it --rm cgr.dev/$ORGANIZATION/envoy --version ``` ```output envoy version: dc2d3098ae5641555f15c71d5bb5ce0060a8015c/1.36.2/Modified/RELEASE/BoringSSL ``` This output shows that although it still uses BoringSSL, the non-FIPS `envoy` container image doesn't use the FIPS-enabled BoringSSL module. Chainguard also ships the `envoy-fips` container image with `bssl-test_fips`. This tool prints useful information, including whether FIPS mode is on and which version of the module is in use: ```shell docker run -it --rm --entrypoint /usr/bin/bssl-test_fips cgr.dev/$ORGANIZATION/envoy-fips ``` ```output Module version: 2023042800 About to AES-CBC encrypt . . . got . . . About to AES-CBC decrypt . . . got . . . About to AES-GCM seal . . . got . . . About to AES-GCM open . . . got . . . About to 3DES-CBC encrypt . . . got . . . About to 3DES-CBC decrypt . . . got . . . About to SHA-1 hash . . . got . . . About to SHA-256 hash . . . got . . . About to SHA-512 hash . . . got . . . About to generate RSA key About to RSA sign . . . got . . . About to RSA verify . . . About to generate P-256 key About to compute key-agreement Z with P-256: got . . . About to ECDSA sign . . . About to seed CTR-DRBG with . . . generated . . . About to run HKDF got . . . About to run TLS v1.0 KDF got . . . About to run TLS v1.2 KDF got . . . About to run TLS v1.3 KDF got . . . About to compute FFDH key-agreement: got . . . PASS ``` ## Troubleshooting If verification checks fail, consider these common issues: - **OpenSSL not in FIPS mode**: Ensure you're using a `-fips` tagged image variant - **Missing FIPS configuration**: Check that the `OPENSSL_CONF` environment variable is set correctly - **Module integrity failures**: Re-pull the container image to ensure it hasn't been modified - **Incorrect image variant**: Verify you're using the FIPS variant of the image (e.g., `python-fips` not `python`) For additional support, contact Chainguard support or consult the FIPS documentation. ## Related resources - [Chainguard FIPS Images](https://images.chainguard.dev/directory?fips=true) - Browse all available FIPS-validated container images - [NIST CMVP Certificate Search](https://csrc.nist.gov/projects/cryptographic-module-validation-program/validated-modules/search) - Verify cryptographic module certifications - [OpenSSL FIPS Test Tool](https://github.com/chainguard-dev/openssl-fips-test) - Source code and documentation for the verification tool --- ### Use chainctl to create an assumable identity for a Jenkins pipeline URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/jenkins-chainctl.md Last Modified: September 28, 2026 Tags: Chainguard Containers, Procedural [Jenkins](https://www.jenkins.io/) is an open source automation server that supports building, deploying, and automating projects. This guide explains how to use `chainctl` to create an assumable identity and configure Jenkins to use that identity to authenticate to Chainguard. To accomplish this, create an OIDC token credential in Jenkins and a matching Chainguard identity that uses the Jenkins OIDC URL, then put the process into an example Jenkins build pipeline. To do this using Terraform, follow the instructions in [Use Terraform to create an assumable identity for a Jenkins pipeline](/platform/administration/assumable-ids/identity-examples/jenkins-terraform/). ## Prerequisites - A running [Jenkins](https://www.jenkins.io/doc/pipeline/tour/getting-started/) instance. - This Jenkins instance should have the [**Open ID Connect Provider** plugin](https://plugins.jenkins.io/oidc-provider/) installed, allowing you to create an OIDC token with Jenkins. - [`chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) installed locally. - Administrative privileges within your Chainguard organization to create IAM identities (`identity.create`); this capability is available to users with [the owner role](/platform/administration/iam-organizations/roles-role-bindings/capabilities-reference/#chainguard-role-capabilities). ## Configure Jenkins credentials Jenkins needs a way to supply `chainctl` with an API token so it can exchange it for short-lived credentials when a pipeline runs. In this example, Jenkins mints an OIDC ID token during each build and `chainctl` uses that token to allow the build pipeline to assume your Chainguard identity — no long-lived secrets are required. > **Note**: Chainguard does not issue general-purpose, long-lived API tokens. This ensures your automation relies only on short-lived, scoped credentials. ### Create an OIDC token credential To get started, create an OIDC token with Jenkins: 1. Log in to the Jenkins UI 2. Navigate to the **Manage Jenkins** page by clicking the settings gear (**⚙**) in the upper-right corner 3. Navigate to the **Credentials** page by clicking **Credentials** 4. In the Domain column, click **(global)** 5. On the **Global credentials (unrestricted)** page, click **Add Credentials** 6. Use the **Kind** dropdown to select **OpenID Connect id token** 7. Enter an **ID**, our example uses `jenkins-oidc`. 8. Click **Create** ## Create a matching Chainguard identity Create a Chainguard identity that uses your Jenkins OIDC URL. This is typically `https://YOUR_JENKINS/oidc`: ```shell chainctl iam identities create jenkins-ci \ --identity-issuer https://YOUR_JENKINS/oidc \ --subject jenkins \ --description "Identity for Jenkins builds" \ --output json ``` Bind the identity to a role. We chose `registry.pull` for this example, but you should adjust according to your needs. Refer to [Overview of roles and role-bindings in Chainguard](/platform/administration/iam-organizations/roles-role-bindings/roles-role-bindings/) to learn more: ```shell chainctl iam role-bindings create \ --identity=jenkins-ci \ --role=registry.pull ``` You can now use this identity in your build pipeline to authenticate to Chainguard. ## Use the identity and create a token in your Jenkins pipeline Here is an example Jenkinsfile that uses what we just created. In this pipeline: - `jenkins-oidc` is the credential ID you created in Jenkins for an OpenID Connect (OIDC) token. - The `withCredentials` step injects the value of that credential into the environment variable `IDTOKEN` for the duration of the block. - Inside the `sh` section of the `withCredentials` step, `$IDTOKEN` refers to that environment variable containing the actual OIDC token issued by Jenkins at build time while `chainctl auth login --identity-token "$IDTOKEN"` uses that token to authenticate to Chainguard and assume the Jenkins identity. Be sure to replace the `ORGANIZATION` placeholder with the name used for your organization's private repository within the Chainguard Registry. ```groovy pipeline { agent any environment { CHAINCTL_IDENTITY = "iam-1234567890" // replace with your identity ID } stages { stage('Install chainctl') { steps { sh ''' mkdir -p .bin curl -fsSL -o .bin/chainctl \ "https://dl.enforce.dev/chainctl/latest/chainctl_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/aarch64/arm64/; s/x86_64/amd64/')" chmod +x .bin/chainctl ''' } } stage('Auth with Chainguard') { steps { withCredentials([string(credentialsId: 'jenkins-oidc', variable: 'IDTOKEN')]) { sh ''' echo "Logging in to Chainguard using OIDC..." chainctl auth login \ --identity "$CHAINCTL_IDENTITY" chainctl auth configure-docker ''' } } } stage('Pull image') { steps { sh 'docker pull cgr.dev/ORGANIZATION/python:latest' //replace with your org } } } } ``` > **Note**: `$IDTOKEN` is not something you create manually; it comes from the Jenkins credentials plugin at runtime. Ensure that you created the Jenkins credentials with ID `jenkins-oidc` and that the credential type is OpenID Connect ID token. After you run this pipeline, check to see that the requested Chainguard image was pulled to confirm everything is set up properly. ## Learn more In this guide you used `chainctl` to create an assumable identity and configure Jenkins to use that identity to authenticate to Chainguard. Refer to the following to learn more about how Chainguard has designed assumable IDs, `chainctl`, and authentication. - [Assumable IDs](/platform/administration/assumable-ids/) - [How to install chainctl](/platform/chainctl-usage/how-to-install-chainctl/) - [Authenticating with Chainguard registry](/chainguard/containers/registry/authenticating/) --- ### Inside the Chainguard Factory - Assemble 2025 URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/factory/assemble.md Last Modified: September 28, 2026 Tags: Chainguard Factory, Video, Overview [Watch the video on YouTube](https://www.youtube.com/watch?v=iU9hmW6hrGs) ## Transcript **Sam (Introduction)**: We're very fortunate our Vice President of Engineering, Dustin Kirkland, is going to be walking us through the Chainguard Factory, which you would have heard a little bit about during the keynote today. So I'll go ahead and turn things over to Dustin. We will have time at the end for questions, so keep that in mind. Thank you. **Dustin Kirkland**: All right, thank you Sam. Cool, so thank you for joining us. This is the technical session we can call it a factory tour. I once went on a factory tour when I was, I don't know, a teenager or so to the Corvette factory in Bowling Green, Kentucky, and I still think back to that—what a cool experience that was. Once upon a time, we've got a lot of analogies in here that, you know, sort of point to what we build is a lot like a modern automotive factory. ### Overview So just an overview of what we're going to talk about and hopefully what you can take away from this. We've built a new operating system distro designed from the ground up, completely modern, overwhelmingly using cloud technologies and automation in a way that has provided tremendous advantages and opportunities to scale this and secure the entire software supply chain. That's a bold statement, but I'm going to walk you through some of how that comes together. At the heart of that is a factory, and we really call it that because flowing through the factory constantly is a ton of inputs: source code, open source code, third-party libraries, compilers, tool chains, scripting languages, runtimes—all of that assimilates together into a software system that our customers can then build and depend on, build their own software on top of and depend on in production. And then we're going to pull all of that together with what we call the Chainguard Operating System, and it truly is an OS from end to end. I would say now that we have added this capability to run full operating systems inside of virtual machine environments and then stripe that across other OSs with our libraries product. Just a little bit about my background: I have spent two halves of my career—one as an engineer and a second as a product manager. I've also spent two halves of my career—one with some of the world's biggest companies (IBM, Google, and Goldman Sachs) and then another in startups and growth mode. And so with that comes a lot of experience and a real personal interest in solving the operating system Linux distro security problem and fitting that into the overall mission of Chainguard to secure the software supply chain. ### Our SLA: The foundation All right, we're going to kick this off and start with our SLA—our service level agreement. This is what it says, this is on our website. Not everyone publishes their SLA on the website—we do publish our SLA on the website, and this is the fine print. This says that Chainguard will remediate security vulnerabilities that are rated critical within seven calendar days and highs, mediums, and lows within 14 calendar days. Seven and 14—we remediate criticals within seven, highs, mediums, and lows within 14. Where did those numbers come from? That actually came from FedRAMP, believe it or not. Is anyone here subject to FedRAMP requirements directly or indirectly? Have you read the FedRAMP spec? Yes, exactly. I'll zoom into the most important piece, which is 30, 90, and 180. This is what FedRAMP says: if you are a vendor selling software or services to the government, you've got 30, 90, or 180 days to remediate those vulnerabilities. This is the piece on page 97 1.5 where this comes into focus. What this actually looks like from a Chainguard perspective is this right here: an nginx image that is completely free of CVEs as of 15 hours ago when these screenshots were taken. And there's a bit of data in here in terms of when the image was updated—less than a week before that—and it was scanned on a daily or an hourly basis constantly. So if you care about, say, nginx, you're not worried about meeting those 30, 90, or 180-day obligations. As long as you're using the latest Chainguard image, you will have those CVEs remediated for you. Now I said seven and 14. In actuality, it takes us less than two days typically to remediate a CVE that we even know exists. And I want to add that last asterisk in there: there are hundreds of CVEs that are remediated that you never even see, that we never even see, that are getting fixed upstream before the scanner is able to find that inside of a Chainguard image—it's gone. I'm not even waiting, using those effectively zeros that would stack that histogram even more. Our average time to remediate any CVE that we know about is actually under two. Here's a pie chart of all of the CVEs that we've known about since January 1st of this year. So this is year to date with 65% within two days, 28% within seven, the pink within 14—6% within 14. What's that last sliver? Less than a half a percent. These are CVEs that are either still awaiting an upstream fix typically, at which point we work as quickly as possible to patch and remediate that, rebuild that as soon as upstream has that fix available. ### How we do it: The Factory How do we do that? It's the factory. We are continuously rebuilding the entirety of the Chainguard OS. So let's dig into some of those pieces. First of all, this is the hard part: we build from source. Yes, everything, constantly. I want to make that part abundantly clear, especially for anyone who's maybe new to the Chainguard story. We are not a derivative of any other operating system. We don't start with Debian or Fedora or Alpine or any other Linux and harden that. And that's a little bit different than some of the other alternatives to Chainguard's technology. We literally start at the source code—the tarballs or the git checkouts—and build everything from scratch, including the compilers. We've bootstrapped the actual tool chain itself all the way down to GCC. And so a change in GCC, the C compiler that underpins almost everything including Python, including Java and C bindings—we rebuild everything up from that. That takes a lot of, first of all, expertise, but a whole lot of automation in order to do that. ### The Wolfi OS repository So this is just one piece of our OS. This is the Wolfi Dev OS. This is a snapshot from GitHub. You can see that this gigantic mono repo had 2,600 entries, and it shows a thousand but there's another 2,600. These are our source packages. And it's just for the piece that we call Wolfi, which is this open source core, with 63,000 commits, and that's going up by a couple hundred commits per day. Many of those are driven by our automation. Our automation is a bot that we call Octos, and it's what enables us to securely do this in GitHub and GitHub Actions itself. ### Package building example: sed Securely, this is what a single package looks like to build, and this is a lot simpler. I've long been a packager of Debian packages and Fedora packages. The fact that we can define the entirety of the sed package—my favorite editor, I don't know, anyone else a sed fan? If I need to edit a file, this is the way I'm going to do it. This is sed 4.9, which is the latest version. You can see the version, the name, the epoch, the build, and then the URI—the URL where to get that from. And this is a GNU package, the tarball and the expected SHA. If that changes—and we have monitors that are constantly watching this—as soon as that changes upstream, our automation will kick in and download the new tarball. We'll apply these same build rules, the build configure rules, the test against it. We'll then, if it passes, we'll republish that, and then we'll restart building all the images that might include sed inside of it. ### Vim example For those who are not sed fans for editing their files, we've also got a Vim example. All of 77 lines. And again, to compare this to another distro's package manager, that's going to be dozens of files in a directory. It's going to be probably hundreds of lines of code and a stack of carried patches. There are no carried patches against either of these, so this is farm-to-market pure, straight from the upstream source with easy to view build rules, configure build rules. And you can see some of these have, you know, security implications. Without X, this is going to be a command line-only Vim that cuts out a whole bunch of code and vulnerabilities that might come along with code that's not necessarily needed. There's also a handful of tests, so every almost every one of our packages itself includes tests that have to run and execute and succeed at the package build time, or else the package itself doesn't get published, which means that, you know, we'll hold that back and that will need to get attention from one of our engineers who's going to dig into what we need to fix in order to satisfy that build or those tests. ### Git-based packages So this one is just slightly different in that we're not fetching a tarball. We actually prefer, much prefer to fetch directly from a git repo at a particular tagged version. And again, we will check that expected commit and that hash. Again, we've got these watchers—event-driven automation that's constantly watching for a new commit to, or sorry, a new tagged release from any of the 6,000 source packages that we're monitoring. As soon as that happens, within minutes we trigger a whole new build, which then creates this entire directed acyclic graph, a DAG tree of all the other things that we're going to need to do based on the success criteria of each of those. Vim is a little bit interesting in that the way that this open source project is set up, with every single commit, Vim auto-tags a new release, which is kind of crazy to think about. But typically within 30 minutes of the upstream Vim developer making a commit to Vim, we've already built and tested and published a whole new version of that package. Now this, of course, is a, you know, human interactive thing. This isn't making its way typically into your production containers, but to me it's just a great example of how fast we've taken that farm to market from that upstream open source developer straight to, you know, this distro itself and tested it. ### Automated updates And this is what an update looks like. This was merged by Octos—that's our automated bot. This is all it took to bump the Vim package from version 9.1.189 to 9.1.194: a change in the version which affected the variable of the tag itself and then the difference in commit. With that, this package automatically rebuilt, retested, and made its way through the entirety of the process. So this is a very, very important key point: we typically don't, except for exceptional circumstances, we typically don't backport patches from some cherry-pick from head of the open source project and try to munge that and make that work with a codebase that's months or potentially years and years old. Instead, we bring everything forward, fast forward to the latest and greatest release. And in doing so, that's how we solve many security vulnerabilities that will go unpatched in other distros because it's just not practical, possible, or feasible to backport that really complicated patch to an older codebase. That's largely how we solve the lows and mediums, right? The highs and criticals are, you know, maybe possible with excessive effort to backport, but the lows and mediums you can't even fix typically in open source code without moving the entire codebase forward. So that's how we solve that. Now we can and do carry patches when we have to, but it's typically for a very small period of time. It's usually just until that upstream maintainer tags a new release, then we can discard that patch and get right back onto the vanilla interpretation of that upstream maintainer's code. For the most part, you know, we have really good signals from maintainers who really prefer this approach. We're not creating some Frankenstein of the code that they've put their heart and soul into developing and building and maintaining. What we've done is really just taken a secure snapshot of that, and we keep it moving forward. ### Real-world impact So this is like a random two-hour window from a couple of weeks ago of our backend build system catching new releases. And you can see that Grafana had a fix, had a new release going from 10.4 to 10.4.16 that addressed a CVE. You can see GitLab, you can see Helm and Argo and GCP and Azure. This was just like one little piece of one page during one hour of the automation doing its work, and you can see it fixing CVEs along the way. In fact, you can zoom in a little bit and you can see the majority of these are happening by the Octos. So the actual bot that's constantly pushing the factory and pushing the factory forward. I'll actually zoom into the status of a couple of these, and you can see that for this snapshot, these three were in progress, not yet done—that's with the amber light, five of six tests complete. You can see the ones that completed successfully, checks all the way across the board. And then you can see some ones that needed a little bit more attention. This meant that five out of the six passed, but there was something else failing in the process. That's when we get a member of our sustaining team, an engineer who has expertise in this type of problem, to dig in and figure out, you know, what's gone wrong. But we estimate almost 90% of the time this is flowing through without any human interaction whatsoever. ### Security Advisories So we start minimal, we fast forward, the third piece of the puzzle is publishing advisories—and a lot of them. We've got a couple of different advisory repositories. This is the one that maps to Wolfi, which was the open source package repository that I showed you before. Here we have some 1,200 files which are the advisory files. These are constantly growing YAML files with something like 17,000 commits and growing every single day. This is what an advisory looks like, and these files can go on for hundreds and hundreds of lines. But I grabbed one for containerd. Containerd will have this stanza that tags a particular unique vulnerability. You can see this is 2023 CVE-2023-45283. There's a timestamp for when it was first found. Our research—and this one did involve human research—was that it was a false positive determination, and we documented why. This particular vulnerability only affects code in Windows. This is a container that is not running—this is not containerd running on Windows. Therefore, we provide this, we publish this, we attest this, and then we feed that information to the scanners. The scanners read these advisory files, and the scanner itself is able to provide you, the customer, the user of containerd scanning containerd with better information on is this actually a vulnerability. Because it's no good if you just get this fire hose of information that is, you know, unreadable. You know, if you're overwhelmed by false positives, you really stop trusting the value of your scanner itself. ### Scanner partnerships This is something that you can't just DIY. You know, we're often asked, "Look, I've got a team," you know, we may be talking to a director of platform engineering, "Hey, I've got a team, they handle the vulnerability fixes." Well, what do they do? "Well, they're constantly apt-get update, apt-get upgrading our distro, and you know, we've got all the patches that the upstream distro has provided." Yeah, but what about the false positives? "Well, I mean, those we just kind of have to ignore." Becoming a trusted source of advisory data to the actual scanner itself—that's a very privileged position. And we're delighted to partner with Snyk and Wiz and Grype and Trivy and X-Ray and Prisma amongst others as well and provide those advisories to them in a machine-readable format that's able to make the quality of that scanner information even better. If your favorite scanner isn't up here, please do come talk to the product team. We've definitely got people busy working on the right partnerships and ensuring that our trusted advisory data is accessible to them as well. ### The build and test pipeline Okay, so with all of that, we're able to build, test, sign, publish packages as soon as any upstream project tags a release. And so this is what happens. I showed you the first piece—this is what's actually happening inside of the testing. You can see these are 19 checks and growing that we run. And you can see the Wiz scanners are running, we've got Malcontent, which is running looking for malicious data that might have been injected in the build. We've got static analysis, lint analysis, as well as the actual build process. I showed you the testing that we actually execute those binaries. We check the libraries, we check the SO files, we ensure that Python modules are loadable, Ruby modules are loadable and listable. And then, you know, we've got logs that we can drill into. ### Testing at scale Now I really want to double down on the testing—the testing part. If I really have to talk about, you know, the place that we've invested most heavily, once we completed the initial build—build is great, but man, how do you trust a rolling distro? A distro that every time an upstream maintainer presses a release button that worked for them on their laptop, how do you trust that that's going to make its way through the Chainguard automation into production in your environment? And that is the testing. So hopefully some people perked up at the quality part of the discussion. So I showed you some YAML files. We were going to pull GCC, all right, so a compiler. This one's now, this is a bigger build, this is 400 lines, but arguably one of the most important core critical pieces of an entire distro. We're going to zoom into this entire test section, which goes on for pages and pages. And I just pulled out one little piece for easily readable bit of C code, right? Hash includes stdio, int main, print, return zero. Doesn't do a whole lot, but this has to compile in order, you know, for the entire build to succeed. And that's not much, and there's a whole bunch of other things, but if this breaks, boy, I really don't want to get into, you know, what else is going to break. So let's fail early and fail often. This is just one example. We've got, you know, somewhere around 3,500 open source package specifications in Wolfi itself, the vast majority of which have, you know, various degrees of testing. But that's just the functional verification—that's the FVT on the package itself. ### Image testing If we go into the actual image testing, this is what an image test file is going to look like. And I'll zoom into, you know, once we've actually put that image together, we go and docker run that. And this is RabbitMQ, so we're actually running the RabbitMQ image and running it through its own tests—one, two, three, four shown right here. And we do that for every single image, and we ensure that that goes all the way through before we publish that image. In some other cases, we've actually got to start up an entire Kubernetes, and we'll use K3s or we'll use EKS in Amazon. We've got to spin up a whole Kubernetes to test some of these images. And some of these image tests take orders of magnitude longer than the actual build. We may be able to build it within the first five or 10 or 15 minutes, but it may take a couple of hours to really put a core and critical image through full testing. ### The results: Our image catalog All right, so how does that work in practice? And for those of you who might be prospects and not yet customers, maybe that sounds great, but you know, you don't believe us yet. Dan mentioned in the keynote we've got over 1,200 images. Again, these images were taken a couple of weeks ago—this was at 1,254 images, 1,200 and counting. There's a handful of different types of images that we've sort of grouped things into. We've got some customers who are more interested in the AI front, others are more interested in the base or the starter front. We've got, you know, some customers who use a single image and they start there. We've got others that leverage a handful, five or 10 images and get started. And then we've got some other customers who've gone all in on this, and Chainguard is the only way that their developers are allowed to build on, or they're encouraged to use. ### Public image view: PostgreSQL FIPS example All right, so it's the factory that enables all of this at scale. Now let me show you what this looks like publicly. You could see this on images.chainguard.dev. This is PostgreSQL FIPS. This is the PostgreSQL database, this is with FIPS encryption enabled. So this is compiled against our OpenSSL FIPS libraries and ensures that if you're storing data and doing any cryptographic transactions on that data in PostgreSQL, you're doing so in accordance with the FIPS cryptography standard. Here, starting on this page, you can see the versions. We've of course got multiple versions of that PostgreSQL FIPS image available. The latest and latest-dev are floating tags that move. You can see the dotted versions, you can see 17, 17.4, 16.8, all the way back to 15.12. You can see the last time those changed. And you know, the 24/7 rebuilding means that something inside of that PostgreSQL image changed, which meant that we had to rebuild that image because there was probably a security vulnerability somewhere in that image that needed to be addressed, and we addressed that by sucking in a newer version of that. ### Beyond containers: Virtual machines Now what we heard from a number of customers over the last year was, "You know, that thing that you've done for containers, can you do that for our virtual machines and our libraries?" In particular on the virtual machine front, we had some customers who said, "Look, this is great. We've deployed Chainguard everywhere we can deploy it inside of the containers, but those containers are running on Amazon Linux hosts in AWS or EKS. They're a couple of years old, there's hundreds of unpatched vulnerabilities. I feel great about my container estate, but what about the thing running under my containers? And I've got hundreds of thousands of those." So the first one to mention is that same factory—everything that I've talked about that powers the packages and the images front—we've extended to also produce virtual machine images. That meant that we had to add a couple of packages we didn't have before: a package called linux.yaml, which tracks Greg Kroah-Hartman (Greg KH)—he's the maintainer of the security stable tree, the LTS and the stable tree in the Linux kernel. Within minutes of Greg tagging a new release of Linux the kernel that fixes a CVE, our automation catches that and rebuilds that kernel on two different architectures across three clouds, as well as VMware and QEMU—so five different environments—and is constantly rebuilding that and applying, you know, multiple different kernel configs, hardened kernel configs on it. We also had to add systemd, which is how a Linux system typically boots and manages the processes on it. And then for our initial target use case for this virtual machine image, we've targeted the container runner. So back to that customer feedback: "You've helped us with all of our container images, what about the thing running under the containers?" So as of today, we've announced we have a Chainguard virtual machine running in Amazon EKS. You can bring your own node running Chainguard VM with a Chainguard hardened kernel, systemd, containerd, the cloud agents, the Amazon agents as well. And the other clouds are works in progress right now, as well as other formats. ### Libraries: Starting with Java The other piece are our libraries. And we started very much with Java. The signal that we got was Java was the biggest pain point in some of the enterprise, especially financial services, healthcare, government organizations. And this idea that everything that these technologists are getting from a trusted source is great, but there's this other realm of dependencies that aren't as well understood yet, perhaps by the scanners number one, and by their developers number two. But I can guarantee you that the malicious actors out there in the world absolutely know about this. And you know, there's a long list of bodies, a trail of bodies where pulling in code from an untrusted third party has created real vulnerabilities. So we've started rebuilding Java itself—Java archives, JARs—from source. To date, we've got 20,000 JARs, which include from our analysis the most heavily depended upon JARs across the last five years. And this covers about 98, 99% of the most used JARs over the last five years in dependencies. If it's not on the list, we can—we have a process by which we can add others as well. Java is the start. We're working on Python next. PyPI is the next target. That's a work in progress, and definitely come talk to us if you're interested in that. ### The Chainguard operating system So that's containers, VMs, libraries, all built from source the hard way. It's that same factory that I've spent the last 30, 40 minutes talking about cranking through that. And then ultimately it's the Chainguard operating system that's powering all of this. It's at the core of it. It's not the value that we project as, you know, the thing that we've built, but it's the thing behind what we've built that enables all of this to go and go really fast. ### A personal story So I thought I would tell you one little story, and this is pretty much the end of the presentation. I grew up in South Louisiana, and I was about 10 or 11 years old, and my uncle invited me to come help him dig a well—a water well. And I remember this like it was yesterday. Has anyone ever dug a well before? Wow, okay. I'm going to tell you how digging a well in South Louisiana works. First of all, it's a lot of people, and this was very manual labor. It starts with a drill bit that's about this big and probably cost $20,000, okay? And then you attach to that drill bit a galvanized pipe, 10 foot long galvanized pipe, and you attach two pipe wrenches to it at 180 degrees, and you have two people that walk around in a circle and make that go down 10 feet. And when it goes down 10 feet, guess what you do? You take a galvanized coupler, you put another 10-foot pipe on the top of that, and you attach the pipe wrenches and walk around in a circle. And you do that 20 times because you need to go 200 feet deep to get to water, the water table in South Louisiana. Now, thankfully we're drilling through swamp and not mud, and so that's why two humans can walk around in a circle. When you're done, you're not done, because remember that $20,000 bit I told you about? You got to get that back out of the ground. How do you get it back out of the ground? You walk all 20 of those pipes up, and then you replace the bit with—I forgot the name of it—but the filter you put at the bottom, and then you go right back down. It's a little easier going the second time around. What I learned from this is that there are some things in life I love doing myself, and there are some things in life I do not love doing myself. And accessing drinkable water is one of those. The other one is security. Security is definitely something that I think if you can trust someone who's really good at doing what they do, you'll end up in a much better place. I hope that we, Chainguard, can be that for you. That's the end of my talk. [Applause] ### Q&A session **Sam**: Thank you, Dustin. We got about 10 minutes for questions. I believe we have somebody with a microphone, so I'll go ahead and open up the floor if anybody has any questions on what you just heard. We got some in the front here. **Question 1**: Short and sweet—Twistlock. I didn't see it on the slide. I think there's a ton of presence in APAC. We'd love to see support for that. **Dustin**: Twistlock, yes, on the scanners for sure. Very familiar with it. I know that we have some customers—we've got a list of things that we typically require doing in order to ensure that we can fully certify that. But Twistlock is definitely a common one. I think the driving reason was that I think Go series were not getting captured by active scan, and then a lot of time Twistlock was picking them up. Okay, one of the reasons. Noted. **Question 2**: How do you skip some of those vulnerabilities from the scanners? Like you have mentioned that some of the vulnerabilities you are making some modifications which will—the scanners will not be able to find those, right? So one of that file which you are showing... **Dustin**: So we ingest the CVE and the GHSA from GitHub and the National Vulnerability Database, so those are feeds that we consume. We run that through—I guess I didn't show that part of the factory—but yes, there's an ingestion piece. As soon as a vulnerability, especially a Go vulnerability gets published, we take that. That does typically hit a person, you know, hits a human who analyzes that. We'll rescore it. You know, there are places where things are certainly more severe than they're thought to be upstream. There are places where it might be super severe on Windows but is not applicable to us, so you know, we can raise and lower that. That's what drives the CVE automation side of things is consuming information from those feeds. The scanners also consume that information as well, and then what they have to do is map that against a whole bunch of different operating systems and versions and so forth. So part of working with Twistlock is ensuring that Twistlock understands Chainguard, our packaging system, our versioning system, and then ultimately our advisory feed format. **Question 3**: Nice job, Dustin. The testing—so the unit tests and all the automated tests you showed, is that something you guys are maintaining yourselves or you're deriving from open source or some combination? Maybe you can talk through how you're building up the tests over time? **Dustin**: Great question. Bit of both. Where there is an upstream test harness, we should be running that. And sometimes it's configure, make, make install, make test. We can do that, and that would fail at the build step. The runtime tests are largely things that we've generated or written ourselves. Some of it's automatically generated. If you had eagle eyes and you could see many of those were running whatever the binary is --help and --version, which doesn't do a whole lot functionally, but you know what has to happen in order for that thing to run? The linker, the loader, all of that had to work. And there are certainly cases where if you don't compile a thing right, running foo --version is just going to fail out. So we call those sniff tests. A couple of scripts that we created generated thousands of those, and that gave us at least a starting point to know that you can run vim --help. Now that's actually not editing a file, but in order for vim --help to work, it has to link against all of those binaries. There's a whole bunch of others that are much more sophisticated that configure, make file—sorry, that GCC hello world file. There are a bunch of others that, you know, with a little bit of sophistication you can test it out, you can run a thing and grep for some output, and we've created a bunch of those manually as well. **Question 4**: So you've mentioned a lot of manual efforts around all these packages and writing the tests and making sure the CVEs rescore—that's like a lot of humans in the loop. How do you actually scale that, or do you even plan to trust AI/LLMs? How much do you trust them to do all of this work? **Dustin**: Yeah, it may seem like a lot of humans. It's not. I mean, the entire engineering team at Chainguard is just about a hundred people. I think we're doing the work of 10,000 people using the automation. I would compare that to, you know, Debian, another distro that I've been involved in for a very long time. There are one or more human maintainers tied to every single Debian package. There's 25, 30,000 Debian packages with 25 or 30,000 or more emails associated with every one of those packages. And only a maintainer whose key has been signed as a Debian maintainer in the, you know, GPG root of trust in Debian can upload. That's not how we've designed Chainguard the OS. We've designed it without an individual being responsible for any package. Do we have people who are experts at what they do? Yes, absolutely. If there's a GCC FIPS bug, it's going to be Dmitri or Luca Cage who fixes that. For sure, we love them for it, but their name is not exclusively tied to that. So yeah, I appreciate the question, but I mean, I think the automation that we have is overwhelmingly driving what we do. The second part of the question though, I think is smart though, and we are leaning into AI in certain places, especially for the debugging. So when something fails, Octos is able to query a couple of the AI engines, Gemini especially, and it looks at it and often makes suggestions on how to fix it. So the first thing a sustaining engineer does when they're looking at a build failure is look at what our trained models tell us could be the problem. And when it's right, we give it a thumbs up, and that retrains and improves the model. When it's wrong, we can give it a thumbs down, and you know, we can bury that suggestion in the future. **Question 5**: Hi, my question is related to testing again. When you're building a Docker image, maybe depends on a bunch of packages, libraries. So how do you detect the incompatibility between the software versions, and then when you detect it, how do you fix—what, how do you find the right combination? **Dustin**: Yeah, so I actually didn't go into—if we go back to the factory analogy—I didn't go into the order intake side of things, which is when a customer comes with a list of images, Docker images that they need that we don't have. So I showed you the 1,254 and counting that we do have, but that's not all of them that we need. And so we'll end up with a customer or prospect who says, "Yeah, you've got these, but we have all of these that we don't have yet." So what happens is we take those requests, and those go to a different engineering team we call it the delivery team. And that team looks at the customer's request for images that we don't have and we've maybe never seen before—maybe we've seen it, it's on our backlog, but we might have never seen. And then that delivery engineer—it is a very human process to build an image the first time. Sometimes that means packaging software that we've never packaged before and creating an image for something that we've never created an image for. In some cases, there's a reference Docker image that we can go and look at at Docker Hub or somewhere else. In other cases, it's just a git repo. A customer says, "Look, we start with an Alpine base or Debian base, and our developers are git cloning this thing that we don't know anything about, but we would love to have a hardened image and resilient against CVEs." So the human creates it for the first time, and after that it's constantly rebuilding, and the automation takes care of that. Only when it fails does a human have to get involved the second time. And the testing—we have continued to raise the standards on what it takes to publish that image the first time and ensure that the testing is in place. It's—that's a hard one. It's complicated, and we're not able to automate all of that yet. **Question 6**: Yeah, so you guys are building like a lot of images and packages like all the time. What's your underlying build system? Is it all GitHub Actions or Surface CI? **Dustin**: Yeah, good question. It's a lot of GitHub Actions. The builds—or excuse me, are you self-hosting those or using just GitHub's built-in? We use GitHub. The builds actually take place in GCP largely. The tests happen in various clouds—AWS, EKS—where we need it. The libraries product is using a lot of Argo CI, you know, to build those at scale. We test with Cloudsmith there. Cloudflare is typically the CDN side of things for images. Yeah, I didn't get into some of the underlying tech, but yeah, happy to tell you more if you need it. **Sam**: Cool, thank you. Will you be at the expert table? **Dustin**: Yeah, sure. **Sam**: Thank you. If we just want to give Dustin one more round of applause. [Applause] --- ### Create, view, and delete chainctl events URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/chainctl-events.md Last Modified: September 28, 2026 Tags: chainctl Chainguard's `chainctl events` commands provide programmatic access to security event streams using the [CloudEvents](https://cloudevents.io/) specification. These commands enable you to monitor container activities, security alerts, and supply chain events across your organization for enhanced observability and compliance. Chainguard Academy has several deeper guides on [Chainguard CloudEvents](/platform/administration/cloudevents/). You may find our guide on [Subscribing to Chainguard CloudEvents](/platform/administration/cloudevents/events-example/) to be particularly useful for understanding how to work with events from Chainguard while [Chainguard Events](/platform/administration/cloudevents/events-reference/) provides a deeper dive into the content and make up of events. There are three `chainctl events` commands available: `create`, `list`, and `delete`. ## Create event subscriptions To create a new event and subscribe to events in that organization or folder, use: ```shell chainctl events subscriptions create $SINK_URL ``` A sink is an addressable or callable resource that can receive incoming events delivered over HTTPS and will translate the delivered event into a returned response that includes promised information. The style and type of response is set by the sink. Depending on the sink, you may be prompted to respond to some questions before this action is complete. You can add a `-y` to the command to automatically assume `yes` and run without interaction. ## View your event subscriptions To retrieve a list of all your Chainguard account's subscriptions, use: ```shell chainctl events subscriptions list ``` This will return a list of IDs and sinks for all of your subscriptions. ## Delete event subscriptions To delete an existing event, use: ```shell chainctl events subscriptions delete $SUBSCRIPTION_ID ``` Depending on the sink, you may be prompted to respond to some questions before this action is complete. You can add a `-y` to the command to automatically assume `yes` and run without interaction. --- ### Kernel-independent FIPS architecture URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/kernel-independent-architecture.md Last Modified: September 28, 2026 Tags: FIPS, Architecture, Entropy ## Overview Chainguard FIPS Containers use a userspace entropy source instead of relying on the host kernel to provide validated randomness. This kernel-independent design allows FIPS containers to run on any recent Linux kernel, eliminating the traditional requirement for kernels configured in FIPS mode. This architectural approach addresses a longstanding limitation in deploying FIPS-compliant workloads (FIPS being the U.S. federal cryptographic standard) by removing kernel dependencies that previously restricted deployment options, prevented local development, and limited cloud platform choices. ## Chainguard's architecture ### Core components **Jitterentropy library**: A userspace entropy generator that produces randomness from CPU execution timing variations. This library runs entirely within the container and has its own NIST SP 800-90B Entropy Source Validation (ESV). **OpenSSL FIPS provider**: The CMVP-validated cryptographic module that uses the userspace entropy source instead of kernel entropy. **Self-contained packaging**: Both the entropy source and cryptographic module are bundled in the container image, making each container self-sufficient for FIPS compliance. This page uses the OpenSSL FIPS provider as its example because all Chainguard FIPS images ship OpenSSL in FIPS mode. Chainguard FIPS images also use other validated cryptographic modules and entropy sources. The [Chainguard FIPS commitment](https://www.chainguard.dev/legal/fips-commitment) lists the current modules, entropy sources, and their certificate numbers. ### How it works When a Chainguard FIPS container starts: 1. The container includes the Jitterentropy library with SP 800-90B validation 2. OpenSSL is configured at build time to use the userspace entropy source 3. Cryptographic operations obtain entropy from Jitterentropy running in userspace 4. No kernel entropy source is required or accessed The FIPS cryptographic boundary is entirely within the container. The host kernel is not part of the compliance story. ### Validation architecture The design relies on two separate NIST certifications: **CMVP certificate**: Validates the OpenSSL FIPS provider's cryptographic algorithms. This certificate includes a caveat noting that an approved entropy source is required. **ESV certificate**: Validates the Jitterentropy library as meeting SP 800-90B entropy requirements, satisfying the CMVP certificate's entropy caveat. For the current CMVP and ESV certificate numbers, refer to the [Chainguard FIPS commitment](https://www.chainguard.dev/legal/fips-commitment). The entropy source sits outside the cryptographic boundary but is independently validated. This architecture is sound and meets NIST requirements. ## Why this matters ### The problem with traditional FIPS containers Before kernel-independent FIPS, containers relied on the host kernel to provide SP 800-90B validated entropy through `/dev/random`. This meant: **Kernel must be in FIPS mode**: Organizations had to provision and maintain kernels configured for FIPS, typically older LTS versions. **Limited deployment options**: - Cloud platforms without FIPS-enabled kernels (GKE with COS, AWS Bottlerocket, Azure Linux) were unavailable - Latest instance types and hardware often unsupported - Specific Linux distributions and versions required **Development environment gaps**: Developers couldn't run FIPS containers on workstations, creating a gap between local development and production. **CI/CD complexity**: Testing FIPS applications required dedicated infrastructure with FIPS-enabled kernels. ### Chainguard's solution benefits **Any Linux kernel**: Deploy on any recent kernel without FIPS mode configuration. Use latest kernels with security patches and performance improvements. **All cloud platforms**: Run on GKE, EKS, AKS, and other managed Kubernetes services regardless of their kernel configuration. **Local development**: Test FIPS containers on developer workstations (macOS, Windows WSL2, Linux) without special setup. **Standard CI/CD**: Use existing pipeline infrastructure without FIPS-specific runners. **Latest hardware**: Access newest instance types and hardware optimizations immediately. **Simplified operations**: Container updates handle FIPS compliance. No separate kernel maintenance track. ## Supported runtimes and languages Kernel-independent FIPS is available for: **Go** (versions 1.21+): Uses OpenSSL via golang-fips/go or microsoft/go implementations. **Python** (versions 3.10-3.13): Native OpenSSL integration through `_ssl` and `hashlib` modules. **Node.js** (versions 18-23): Built-in crypto module uses OpenSSL. **.NET** (versions 6-8): System.Security.Cryptography uses OpenSSL on Linux. **PHP** (versions 8.2-8.3): OpenSSL extension provides cryptographic functions. **C/C++**: Direct OpenSSL API usage with FIPS provider. **Java** (versions 11, 17, 21): As of August 2025, supports kernel-independent FIPS using Bouncy Castle Entropy Provider with Jitterentropy. ### Java's transition Java deserves special mention as it recently gained kernel-independent capability. **Before August 2025**: Java-based FIPS images required the host kernel in FIPS mode because the Bouncy Castle FIPS provider (BC-FJA) lacked its own entropy source. **August 2025 update**: Chainguard integrated Jitterentropy with a Bouncy Castle Entropy Provider, enabling Java FIPS containers to run on any kernel. Java images now ship with this configuration by default. Read more: [Kernel-Independent FIPS for Java](https://www.chainguard.dev/unchained/announcing-kernel-independent-fips-for-java) ## What still requires kernel FIPS mode A few use cases remain kernel-dependent: **Certain Kubernetes CNI plugins**: Some Container Network Interface plugins require kernel cryptographic modules for network encryption. **LUKS2 full-disk encryption**: Disk encryption operates at the block device level, requiring kernel cryptographic support. **StrongSwan VPN**: IPsec VPN implementations typically use kernel cryptographic modules. These involve kernel-level operations where userspace entropy cannot be substituted. For these workloads, configure the host kernel in FIPS mode. ## Verifying kernel independence Check an image's SBOM (Software Bill of Materials) to verify kernel independence. For images built from November 7, 2024 onward, the SBOM must contain: ``` libcrypto3>=3.4.0-r2 openssl-config-fipshardened>=3.4.0-r3 ``` These package versions indicate userspace entropy source configuration. Retrieve an image's SBOM: ```bash cosign download sbom cgr.dev/ORGANIZATION/IMAGE:TAG ``` Then search for the required packages and versions. For other ways to check an image's FIPS configuration, refer to [Verify that Chainguard FIPS containers are configured to use FIPS modules](/platform/fips/verify-fips/). ## Architecture comparison ### Traditional FIPS stack ``` ┌─────────────────────────┐ │ Application Code │ ├─────────────────────────┤ │ FIPS Crypto Module │ ← CMVP Validated ├─────────────────────────┤ │ Container Runtime │ └─────────────────────────┘ ↓ (entropy request) ┌─────────────────────────┐ │ Host Kernel │ ← Must be in FIPS mode │ /dev/random │ ← SP 800-90B source └─────────────────────────┘ ↓ ┌─────────────────────────┐ │ Hardware RNG │ └─────────────────────────┘ ``` ### Chainguard kernel-independent stack ``` ┌─────────────────────────┐ │ Application Code │ ├─────────────────────────┤ │ FIPS Crypto Module │ ← CMVP Validated ├─────────────────────────┤ │ Jitterentropy Library │ ← SP 800-90B Validated ├─────────────────────────┤ │ Container Runtime │ └─────────────────────────┘ ↓ (CPU timing) ┌─────────────────────────┐ │ Any Linux Kernel │ ← No FIPS mode required └─────────────────────────┘ ``` ### Feature comparison | Aspect | Traditional FIPS | Chainguard Kernel-Independent | | -------- | ------------------ | ------------------------------- | | **Kernel requirement** | Must be in FIPS mode | Any recent Linux kernel | | **Entropy source** | Kernel (`/dev/random`) | Userspace (Jitterentropy) | | **Developer testing** | Requires FIPS VM/hardware | Works on local workstation | | **Cloud platforms** | Limited to FIPS-enabled kernels | All platforms supported | | **Instance types** | Restricted to older hardware | Latest hardware available | | **CI/CD** | Special FIPS infrastructure | Standard pipelines | | **Validation** | CMVP cert + kernel validation | CMVP cert + ESV cert | | **Container portability** | Limited | Full portability | | **Maintenance** | Kernel + container updates | Container updates only | ## Performance considerations Userspace entropy generation raises performance questions, but testing shows minimal impact. **Jitterentropy overhead**: Minimal on modern processors. The library generates entropy from CPU timing jitter, which is fast. **Comparison to kernel entropy**: Userspace entropy can be **faster** in some cases by avoiding context switches and system calls to kernel space. **Entropy caching**: OpenSSL caches entropy appropriately. Frequent cryptographic operations don't repeatedly call the entropy source. **Production validation**: Chainguard FIPS images have been tested in production workloads with no significant performance degradation. If you encounter performance issues, profile your application to identify the actual bottleneck before assuming entropy generation is the cause. ## Technical deep dive ### Cryptographic boundaries Understanding what's inside versus outside the FIPS boundary is essential. **Inside the boundary**: OpenSSL FIPS provider containing: - Approved cryptographic algorithms (AES, SHA-2, SHA-3, RSA, EdDSA, etc.) - Key management functions - Self-tests and integrity checks **Outside the boundary**: - Jitterentropy library (separately validated under SP 800-90B) - Application code calling OpenSSL APIs - Operating system services The entropy source validation satisfies the cryptographic module's requirements without being inside the CMVP boundary. This separation is architecturally appropriate and compliant. ### Entropy generation Jitterentropy works by: 1. **Measuring CPU timing variations**: Execute code loops and measure execution time with high-resolution timers 2. **Collecting jitter**: Timing variations arise from CPU pipeline effects, cache behavior, interrupts, and other micro-architectural events 3. **Extracting entropy**: Statistical analysis determines how much entropy is present in the timing measurements 4. **SP 800-90B validation**: NIST testing confirms the source produces sufficient entropy This approach is entirely in userspace and doesn't require hardware random number generators or kernel support. ### OpenSSL configuration Chainguard configures OpenSSL at build time to: - Load the FIPS provider automatically - Configure the provider to use Jitterentropy for entropy - Operate in approved-only mode (no non-approved algorithms) - Enforce FIPS configuration at runtime (cannot be disabled) The `openssl-config-fipshardened` package implements these configurations. Applications using OpenSSL automatically get FIPS-validated cryptography without code changes. ## Next steps - [Getting started with FIPS](/platform/fips/getting-started/) - Deploy your first FIPS container - [Frequently asked questions](/platform/fips/faqs/) - Common questions about FIPS implementation - [Chainguard FIPS commitment](https://www.chainguard.dev/legal/fips-commitment) - Current validated modules, entropy sources, and certificates - [Blog: Kernel-Independent FIPS Images](https://www.chainguard.dev/unchained/kernel-independent-fips-images) - Original announcement with additional details - [Blog: Kernel-Independent FIPS for Java](https://www.chainguard.dev/unchained/announcing-kernel-independent-fips-for-java) - Java-specific implementation --- ### Use Terraform to create an assumable identity for a Jenkins pipeline URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/jenkins-terraform.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This procedural tutorial outlines how to create an identity using Terraform, and then how to update a Jenkins pipeline so that it can assume the identity and interact with Chainguard resources. If you would like to follow this guide using `chainctl`, Chainguard's command line tool, you can review [Use chainctl to create an assumable identity for a Jenkins pipeline](/platform/administration/assumable-ids/identity-examples/jenkins-chainctl/). ## Prerequisites To complete this guide, you will need the following. * `terraform` installed on your local machine. Terraform is an open source Infrastructure as Code tool which this guide will use to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * A [Jenkins](https://www.jenkins.io/) server with the [OpenID Connect Provider plugin](https://plugins.jenkins.io/oidc-provider/) installed and configured, as well as a pipeline you can use to test out the identity you'll create. ## Creating Terraform files We will be using Terraform to create an identity for a Jenkins pipeline to assume. This step outlines how to create three Terraform configuration files that, together, will produce such an identity. To help explain each configuration file's purpose, we will go over what they do and how to create each file one by one. First, though, create a directory to hold the Terraform configuration and navigate into it. ```sh mkdir ~/jenkins-id && cd $_ ``` This will help make it easier to clean up your system at the end of this guide. ### `main.tf` The first file, which we will call `main.tf`, will serve as the scaffolding for our Terraform infrastructure. The file will consist of the following content. ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } } } ``` This is a fairly bare-bones Terraform configuration file, but we will define the rest of the resources in the other two files. In `main.tf`, we declare and initialize the Chainguard Terraform provider. To create the `main.tf` file, run the following command. ```sh cat > main.tf < ./chainctl auth configure-docker --identity-token $token --identity ''' } } } } } ``` The important line is `withCredentials` option, which maps the generated OIDC token from the `oidc-token` credential parameter to `token` variable in the pipeline step. Now you can add the commands for testing the identity using `chainctl images repos list` in the following example: ``` sh ''' wget -O chainctl "https://dl.enforce.dev/chainctl/latest/chainctl_linux_\$(uname -m)" chmod +x chainctl ./chainctl auth login --identity-token $token --identity ./chainctl auth configure-docker --identity-token $token --identity ./chainctl images repos list docker pull cgr.dev//: ''' ``` Save the job, and then build it using the `Build with Parameters` option. Assuming everything works as expected, your pipeline will be able to assume the identity and run the `chainctl images repos list` command, listing repositories available to the organization. ``` . . . chainctl 100%[===================>] 54.34M 6.78MB/s in 13s 2023-05-17 13:19:45 (4.28 MB/s) - ‘chainctl’ saved [56983552/56983552] Successfully exchanged token. Valid! Id: 3f4ad8a9d5e63be71d631a359ba0a91dcade94ab/d3ed9c70b538a796 ``` If you'd like to experiment further with this identity and what the pipeline can do with it, there are a few parts of this setup that you can tweak. For instance, if you'd like to give this identity different permissions you can change the role data source to the role you would like to grant. ```hcl data "chainguard_role" "editor" { name = "editor" } ``` You can also edit the pipeline itself to change its behavior. For example, instead of inspecting the policies the identity has access to, you could have the workflow inspect the organizations. ``` . . . - './chainctl iam organizations ls' ``` Of course, the Jenkins pipeline will only be able to perform certain actions on certain resources, depending on what kind of access you grant it. ## Removing sample resources To remove the resources Terraform created, you can run the `terraform destroy` command. ```sh terraform destroy ``` This will destroy the role-binding, and the identity created in this guide. It will not delete the organization. You can then remove the working directory to clean up your system. ```sh rm -r ~/jenkins-id/ ``` Following that, all of the example resources created in this guide will be removed from your system. ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, the Terraform documentation includes a section on [recommended best practices](https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices) which you can refer to if you'd like to build on this Terraform configuration for a production environment. Likewise, for more information on using OIDC with Jenkins pipelines, we encourage you to check out the [OpenID Connect Provider documentation](https://plugins.jenkins.io/oidc-provider/). --- ### Manage identity and access with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/chainctl-iam.md Last Modified: September 28, 2026 Tags: chainctl, iam Chainguard's identity and access management (IAM) system provides fine-grained control over container registries, security resources, and organizational permissions. The `chainctl iam` commands enable you to manage users, roles, identities, and access policies programmatically for enhanced security and compliance. For the following, assume that returned information only includes that which your account has permissions to view. Also, actions such as `create` and `delete` are similarly limited. This page is intended as an introductory overview of IAM with chainctl. For a full reference of all commands with details and switches, refer to [chainctl reference](/platform/chainctl/). ## List folders Folders contain the catalogs of things your organization has access to. Find out what folders are available to your organization with: ```shell chainctl iam folders list $ORGANIZATION_NAME ``` For example, for our Developer Enablement team, which uses the `chainguard.edu` organization, the interaction looks like this: ```shell chainctl iam folders list chainguard.edu ``` ```output [chainguard.edu] Developer Enablement images catalog ``` This command can also delete, describe, and update folders by replacing `list` with `delete`, `describe`, or `update`. Refer to the reference guide for more details. ## List and describe identities To list all of the existing identities along with roles, types, and more, use: ```shell chainctl iam identities list ``` Because this command requests a large amount of information, you may find it useful to direct the output into a file or pipe it into a filter. If you know the specific `IDENTITY_NAME` or `IDENTITY_ID` that you want to know more about, use: ```shell chainctl iam identities describe {IDENTITY_NAME | IDENTITY_ID} ``` This command can also create, delete, describe, and update identities by replacing `list` with `create`, `delete`, `describe`, or `update`. Refer to the reference guide for more details. ## List and create identity providers This command enables you to manage your own identity management provider, such as a custom OIDC provider. To list all currently configured identity management providers, use: ```shell chainctl iam identity-providers list ``` This command can also create, delete, and update your organization's identity providers by replacing `list` with `create`, `delete`, or `update`. Refer to the reference guide for more details. To tell chainctl about your OIDC provider and enable users to start using it, use create: ```shell chainctl iam identity-provider create --name=google \ --oidc-issuer=https://accounts.google.com \ --oidc-client-id=foo \ --oidc-client-secret=bar \ --default-role=viewer ``` ## List and create invites This command lets you manage invite codes that register identities with Chainguard. To list current invites, use: ```shell chainctl iam invites list ``` This will return a list of invites by ID with information about the invite's expiration date, associated roles, and keyID. This command can also create and delete invites by replacing `list` with `create` or `delete`. Refer to the reference guide for more details. To create a new invite, use create, like in this example that defines a role, an email address to tie the invite to, the valid length of the invitation, and that it can only be used once: ```shell chainctl iam invite create ORGANIZATION_NAME --role=viewer --email=sandra@organization.dev --ttl=7d --single-use ``` ## List organizations To list all of the organizations your account is associated with, use: ```shell chainctl iam organizations list ``` Most users will only be associated with one organization, but admin and support users may find using this command especially useful to determine whether they have needed permissions to interact with specific organizations when help is needed. This command can also delete and describe organizations by replacing `list` with `delete` or `describe`. Refer to the reference guide for more details. ## List roles To list all of the roles your account is associated with, use: ```shell chainctl iam roles list ``` This command can also create, delete, and update identities by replacing `list` with `create`, `delete` or `update`. It is possible to define role details during creation or create a role interactively. To create a role interactively, use: ```shell chainctl iam roles create ROLE_NAME ``` To find out what actions can be done by each role, use: ```shell chainctl iam roles capabilities list ``` This returns a list like this sample: ```shell $ chainctl iam roles capabilities list RESOURCE | ACTION -------------------------+----------------------------------------- account_associations | create, delete, list, update attestations | list build_report | list clusters | create, delete, discover, list, update group_invites | create, delete, list groups | create, delete, list, update identity | create, delete, list, update identity_providers | create, delete, list, update libraries.entitlements | create, delete, list libraries.java | list libraries.python | list manifest | create, delete, list, update manifest.metadata | list namespaces | list nodes | list policy | create, delete, list, update record_contexts | list record_policy_results | list record_signatures | list records | list registry.entitlements | list repo | create, delete, list, update risks | list role_bindings | create, delete, list, update roles | create, delete, list, update sboms | list sigstore | create, delete, list, update sigstore.certificate | create subscriptions | create, delete, list, update tag | create, delete, list, update version | list vuln | create vuln_report | create, list vuln_reports | list workloads | list ``` To find out about role bindings, use: ```shell chainctl iam role-bindings list ``` The `chainctl iam role-bindings` command can also create, delete, and update identities by replacing `list` with `create`, `delete` or `update`. --- ### Manage Chainguard container images with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/chainctl-images.md Last Modified: October 8, 2026 Tags: chainctl Chainguard's `chainctl images` commands provide comprehensive tools for managing security-hardened container images in your organization's registry. These commands enable you to discover available images, analyze version histories, examine security metadata, and compare different image versions to make informed deployment decisions. For a full reference of all commands with details and switches, refer to [chainctl reference](/platform/chainctl/). ## List available Chainguard container images When you want to know which container images are available to your account, use the following command: ```shell chainctl images list ``` If your account has access to more than one organization, the command asks which one to list images from. Most users belong to a single organization, and `chainctl` selects it automatically. This example shows an account with access to several organizations within the fictional MyCorp. ```output Which organization would you like to list images from? > [MyCorp-prod] This group holds the production Chainguard Containers hosted under cgr.dev/MyCorp-prod [MyCorp-starter] This group holds the Free Chainguard Containers hosted under cgr.dev/MyCorp-starter [MyCorp-eval] This group holds the evaluation Chainguard Containers hosted under cgr.dev/MyCorp-eval ``` Move the `>` up and down with the arrow keys and hit `Enter` to select the appropriate org. Then the command will return the list. Be warned, that list may take a while to generate and is likely to scroll past quickly in your command line terminal. You may prefer to direct the output into a file. Here's an abbreviated example of what will be returned: ```output ... ├ [python] │ ├ sha256:038449621d30e512645107e6b141fbfb5320d8f0caacd3d788e5a3be8da16def │ │ ├ [3.11] │ │ └ [3.11.12] │ ├ sha256:07756f3cf511a6227ae70d816c57b01a6fd9e805a587db5ebb4e17a5954b38c4 │ │ └ [3.11.9] │ ├ sha256:088b946519e5c766685e335366abe8a6160e3bd108a6525309f7186e943a4666 │ │ └ [3.11.9-dev] │ ├ sha256:09cb17be345eac82e3fdb017590d5907c582b4bd5ac86ed5e054bae739a9babd │ │ └ [3.13.0] │ ├ sha256:16b52893f316d9d7074b9c24c30f82eab1e94356461439d4be1a62fe229e6933 │ │ └ [3.12.7] │ ├ sha256:1b53a821fe44d699687d1ca2318e3f90fed4af97833ab40384ea77c746b54ed9 │ │ └ [3.10.16] │ ├ sha256:1c3e412aff5bddf54718d1bc0b4598ea30d18005e7dbfeb7e68bdae0f874682b │ │ ├ [3] │ │ ├ [3.13] │ │ ├ [3.13.3] │ │ └ [latest] ... ``` This will continue until all images (like `python` above) are listed with all their variants (releases like `3.13.3`). Notice that the list is not necessarily in order of release. ## List available container repos For a list of image repositories available to your account, use: ```shell chainctl images repos list ``` ## Examine the history of container images To examine the history of an image tag in chainctl, like when it was updated and the associated digests for each update, use `chainctl images history`. This will also return information such as how many times a variant has been built and for which platforms, along with the time and digests for each. Pass the image and tag directly to skip the menu shown earlier: ```shell chainctl images history $IMAGE:$TAG ``` For example, let's find the history of one of the `python` image variants from our previous list, `3.12.7`. So we enter: ```shell chainctl images history python:3.12.7 ``` The returned list is longer than is shown here, but here's a useful excerpt: ```output - time: 2024-11-29 08:11:03 UTC digest: sha256:16b52893f316d9d7074b9c24c30f82eab1e94356461439d4be1a62fe229e6933 architectures: amd64: sha256:755b79b43c1e76472cc467c4636bdb75bf4c2fcc551103087df0a4b0dc039164 (23.14 MB) arm64: sha256:d7dfaf24f292f490279611afcec49289aa528dba531590bebae059c7d3139ed6 (22.13 MB) - time: 2024-11-25 23:45:42 UTC digest: sha256:025bbe734f9bb3550ea845f028f28c76c6dff742527e2764f4ba40b3773ee4f8 architectures: amd64: sha256:c7b70882c6fe9b563aa5d5bdfb1a960c65b8c73e0830fc809ceb977e7f778e98 (23.14 MB) arm64: sha256:864f735794264120aabdc9eda9e1126140762e1c11eed96a89b0ab2056cc3662 (22.13 MB) - time: 2024-11-22 01:32:55 UTC digest: sha256:2a936c40669150e1a92e4c80b27410786925b8619c0ecc1679ec0a0f6b707235 architectures: amd64: sha256:8db3676319588dca04664a4a57c9fa464398fa2fc6874d5a1500273cabdbde04 (23.16 MB) arm64: sha256:ec8b8ef474ad6be50649cd9403882569d2cd1e71ac65e3bc263fc78aaef7608e (22.13 MB) ... - time: 2024-10-02 23:56:00 UTC digest: sha256:c7cf9f46124502b9e8aadf26b4c58e0cdbd5a08b0b97b6d3a451b89563a308e8 architectures: amd64: sha256:d4606173598e7103015b37dec4894771bf9cc221db6f7b102006eb81962c0696 (23.43 MB) arm64: sha256:f76a0a2f49418b030f3a31bcd2c8bddb8bbef7b006006aa59b74282955ab671d (22.41 MB) - time: 2024-10-01 23:40:56 UTC digest: sha256:0b0daf09eeb92741efe0eae51dbdeea5a66bed870e0e00895630de729b233b7f architectures: amd64: sha256:637af1b20e5f8cee7e538b07a1ae3934297769216a65acc454e34fac3dcd3828 (23.46 MB) arm64: sha256:8dce068942fa4dd155c87b6b8a3e4b8e2482a5fdb5232cb9dd73c39e63003038 (22.41 MB) ``` The command returns a reverse-chronological history of when a specific tag was updated to point to a new manifest digest. If images are not multi-arch, only a single digest without architecture will be displayed. When the release version tag is not provided, the command will present you with a menu that lets you select which tag you'd like to obtain the history for. For example, if you enter: ```shell chainctl images history python ``` This will present you with a menu like this: ```output Which tag of python would you like to view history for? > 3 3-dev 3.10 3.10-dev 3.10.14 3.10.14-dev 3.10.14-r2 3.10.14-r2-dev 3.10.14-r3 3.10.14-r3-dev 3.10.14-r4 3.10.14-r4-dev 3.10.14-r5 ``` Once you make a selection, the details will be returned for that variant. ## Compare Chainguard container images When you want to compare two Chainguard images, enter: ```shell chainctl images diff $FROM_IMAGE $TO_IMAGE ``` See [How to compare Chainguard Containers with chainctl](/platform/chainctl-usage/comparing-images/) to learn more. --- ### Chainguard FIPS container FAQs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/fips/faqs.md Last Modified: October 8, 2026 Tags: Chainguard Containers, FIPS Answers to your questions about Chainguard FIPS container images. ## Is there a way to enable or disable the FIPS mode in a FIPS image? All Chainguard FIPS Containers are configured in approved-only mode as noted in our [FIPS commitment](https://www.chainguard.dev/legal/fips-commitment). For non-approved mode, our recommendation is to use a non-FIPS Chainguard Container. Because it is error prone, difficult to support, and fragile, Chainguard does not provide the ability to switch to non-FIPS from a FIPS container image. If you require that, please contact a [NIST-approved security lab](https://csrc.nist.gov/projects/testing-laboratories) to help you achieve your certification needs when using our FIPS modules. ## Does a given Chainguard FIPS container require me to “bring my own license”? From the Containers Directory or Containers Console, search for the container image you would like to know more about, and check if it has the "Bring Your Own License" badge. If it does, one can or must (depending on the container image) bring their own license keys for the product. Review the Overview documentation for the given image, and review the application’s documentation for further guidance. ## Which Chainguard Containers tags have kernel-independent FIPS? For Chainguard Containers built from November 7, 2024 onward, the minimum requirements for kernel-independent FIPS are based on the package listings in their relevant SBOMs. The SBOMs must contain: * `libcrypto3>=3.4.0-r2` * `openssl-config-fipshardened>=3.4.0-r3` This enables the use of a user-space entropy source with the ESV (Entropy Source Validation) certificate as listed on our [FIPS commitment](https://www.chainguard.dev/legal/fips-commitment) page. Note there is no change to the CMVP (Cryptographic Module Validation Program) certificate, as the entropy source is outside of the FIPS boundary. The ESV certificate satisfies the entropy requirements caveat of the CMVP certificate. The following packages are excluded: * `bouncycastle-fips` * `bouncycastle-fips-1.0` * Any with `-cni-` in the name You can read more about [kernel-independent FIPS Container in our blog announcement](https://www.chainguard.dev/unchained/kernel-independent-fips-images). ## How can I block SHA-1 in OpenSSL or FIPS? Right now, there are limitations on blocking specific algorithms. OpenSSL currently only offers a limited set of policies that can be applied to block generic algorithms. It has better runtime configuration options for TLS (with regard to CipherSuite selection and ECDH Curves), but nothing that can be more specific (like blocking RSA signatures with SHA2-224). Chainguard engineering is actively working with the relevant upstream groups to offer more solutions in the future. ## How long does it take to develop and submit a new module for FIPS certification? FIPS submission requirements continuously evolve. No project is always compliant with all the latest requirements, and typically requires testing by a certification lab followed by patching and fixing. Once the testing and patching are done, a project moves onto getting entropy certifications. Once the certifications and relevant code changes are in order, the project can submit to FIPS. Once submitted, it must move through several states: pending review, coordination, finalization, and if everything is in order a certificate will be issued. The current average wait time from submission to certificate received is 590 days. ## Many popular applications use Mozilla NSS, and have no alternative (that is, they cannot switch to use OpenSSL or Bouncy Castle). Can these applications get Chainguard FIPS containers? Given [Mozilla NSS](https://firefox-source-docs.mozilla.org/security/nss/index.html)’s [rapid release cycles](https://wiki.mozilla.org/NSS:Release_Versions) that quickly reach EOL (end-of-life), coupled with the length of time necessary for FIPS certification, the likely scenarios where NSS could become FIPS certified would not be compatible with Chainguard’s product commitments. Because Chainguard provides up-to-date software with zero-to-limited CVEs, it is not currently feasible for us to offer FIPS container images of software that use NSS. NSS does not have a stable API/ABI as it is a collection of low-level libraries, which typically are tightly coupled with the application that uses them. This is in contrast to OpenSSL, which presents one application facing API-ABI, and has an internal provider (plugin) architecture allowing the use of a separately built FIPS provider. For example, OpenSSL v3.4 released in 2024 is using the FIPS provider v3.0 released in 2021 due to strong API-ABI stability commitments. ## Are Go binaries compiled with the upstream Golang provided `GOEXPERIMENT=boringcrypto` covered by the Chainguard FIPS commitment? For older OpenSSL-based Go FIPS workflows, the short answer is no. If you want a Chainguard-supported Go-native FIPS path, use the `go-geomys-fips` Chainguard Container. That image is based on the upstream Geomys Go Cryptographic Module, defaults to the validated module, removes the non-validated alternatives, blocks many disallowed algorithms by default, uses a validated entropy source for kernel-independent FIPS, and includes `go-fips-test` to verify that produced binaries are using the validated module. If you are using older workflows, we strongly recommend using the [go-fips](https://images.chainguard.dev/directory/image/go-fips/versions), [go-msft-fips](https://images.chainguard.dev/directory/image/go-msft-fips/versions), and [go-openssl](https://images.chainguard.dev/directory/image/go-openssl/versions) container images. These images use system OpenSSL as the cryptographic module in approved mode, are covered by the [Chainguard FIPS commitment](https://www.chainguard.dev/legal/fips-commitment), and are in scope for [Kernel-Independent FIPS Containers](https://www.chainguard.dev/unchained/kernel-independent-fips-images) Google and Golang upstream do not provide any support for `GOEXPERIMENT=boringcrypto` compiled binaries. The security policy for those modules contains many unapproved algorithms. The Golang upstream toolchain does not indicate missbuilt binaries that do not use boringcrypto (at all or partially) at runtime, and correctly compiled binaries allow using unapproved algorithms without restriction. Using the Golang upstream `GOEXPERIMENT=boringcrypto` requires strict adherence to the security policy, manual code audits of all source code, and all go.sum vendored copies of code. ## Can WireGuard with Tailscale or Shadowsocks be used for Chainguard FIPS containers? No, the WireGuard protocol, and popular ways to use it with Tailscale and Shadowsocks cannot use approved cryptography, because the [protocol](https://www.wireguard.com/protocol/) requires unapproved algorithms. As it stands today, the WireGuard protocol is not customizable (like TLS), and it is not possible to replace any of the unapproved cryptographic primitives with approved equivalents as it will not be a WireGuard protocol anymore. For approved cryptography and similar functionality, please consider using OpenVPN or Strongswan, both of which can be configured to use approved cryptography only, with a FIPS cryptographic module. ## How to add a custom certificate to the default cacerts Java truststore? Note these instructions work correctly for both FIPS and non-FIPS Java images. Note the "changeit" password is the default password for cacerts public certificate trust store. It is FIPS compliant to not change it, and it is best left as is. ```sh USER root RUN keytool -importcert -noprompt -cacerts -storepass changeit -file /tmp/servercert.pem -alias servercert ``` Explanation of every option: * `-importcert` import a PEM formatted certificate * `-noprompt` do not interactively ask `Yes/No` confirmation to trust the certificate, for non-interactive usage * `-cacerts` access the default trust store * `-storepass changeit` specify the cacerts default password, the default password like this is FIPS compliant for public certificates * `-file /tmp/servercert.pem` filepath to your PEM certificate * `-alias servercert` desired alias for your certificate, must be unique. Otherwise import will fail or will ask interactively to specify server alias After import, execute list command to check if the certificate was imported successfully: ```sh RUN keytool -list -cacerts -storepass changeit -alias servercert ``` If you have an existing Java truststore, you can import all certificates from it using the `-importkeystore` command. ```sh USER root RUN keytool -importkeystore -noprompt \ -srckeystore mytruststore.jks -destkeystore /etc/ssl/certs/java/cacerts \ -srcstoretype JKS -deststoretype JKS \ -srcstorepass changeit -deststorepass changeit ``` Review the list for an explanation of each option. * `-importkeystore` bulk import all aliases from one keystore to another * `-noprompt` do not interactively ask `Yes/No` confirmation to trust the certificates, best for non-interactive usage * `-srckeystore mytruststore.jks` specify source trust store * `-destkeystore /etc/ssl/certs/java/cacerts` specify destination trust store, this is the default cacerts trust store * `-srcstoretype JKS` specify source trust store type, note this can be autodetected from extension as well * `-deststoretype JKS` specify destination trust store type * `-srcstorepass changeit` specify source trust store password, the default password like this is FIPS compliant for public certificates * `-deststorepass changeit` specify destination trust store password, the default password like this is FIPS compliant for public certificates --- ### Create an assumable identity for a CLI session authenticated with Keycloak URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/administration/assumable-ids/identity-examples/keycloak-identity.md Last Modified: October 8, 2026 Tags: Chainguard Containers, Procedural Chainguard's [*assumable identities*](/platform/administration/assumable-ids/assumable-ids/) are identities that can be assumed by external applications or workflows in order to perform certain tasks that would otherwise have to be done by a human. This procedural tutorial outlines how to create an identity using Terraform, and then assume the identity with the CLI to interact with Chainguard resources. ## Prerequisites To complete this guide, you will need the following. * `terraform` installed on your local machine. Terraform is an open source Infrastructure as Code tool which this guide will use to create various cloud resources. Follow [the official Terraform documentation](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) for instructions on installing the tool. * `chainctl` — the Chainguard command line interface tool — installed on your local machine. Follow our guide on [How to install `chainctl`](/platform/chainctl-usage/how-to-install-chainctl/) to set this up. * A Keycloak deployment. [Keycloak](https://www.keycloak.org/) is an Open Source identity provider which Chainguard provides as an [image](https://images.chainguard.dev/directory/image/keycloak/versions?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement&utm_content=edu-content-chainguard-administration-iam-organizations-identity-examples-keycloak-identity) ## Creating Terraform files We will be using Terraform to create an identity for a Keycloak user to assume. This step outlines how to create three Terraform configuration files that, together, will produce such an identity. To help explain each configuration file's purpose, we will go over what they do and how to create each file one by one. First, though, create a directory to hold the Terraform configuration and navigate into it. ```sh mkdir ~/keycloak-id && cd $_ ``` This will help make it easier to clean up your system at the end of this guide. ### `main.tf` The first file, which we will call `main.tf`, will serve as the scaffolding for our Terraform infrastructure. The file will consist of the following content. ```hcl terraform { required_providers { chainguard = { source = "chainguard-dev/chainguard" } } } ``` This is a fairly barebones Terraform configuration file, but we will define the rest of the resources in the other two files. In `main.tf`, we declare and initialize the Chainguard Terraform provider. Next, you can create the `sample.tf` file. ### `sample.tf` `sample.tf` will create a couple of structures that will help us test out the identity in a workflow. This Terraform configuration consists of two main parts. The first part of the file will contain the following lines. ```hcl data "chainguard_group" "group" { name = "my-customer.biz" } ``` This section looks up a Chainguard IAM organization named `my-customer.biz`. This organization will contain the identity — which will be created by the `keycloak.tf` file — to access when we test it out later on. If you aren't sure of the name of your organization, you can retrieve a list of available organizations with the following command. ```sh chainctl iam organizations list -o table ``` Now you can move on to creating the last of our Terraform configuration files, `keycloak.tf`. ### `keycloak.tf` The `keycloak.tf` file is what will actually create the identity for your CLI to assume. The file will consist of four sections, which we'll go over one by one. The first section creates the identity itself. ```hcl resource "chainguard_identity" "keycloak" { parent_id = data.chainguard_group.group.id name = "keycloak" description = <&client_secret=&username=&password=" https:///realms//protocol/openid-connect/token | jq -r .access_token | cut -d\. -f2 | sed 's/$/==/' | base64 -d | jq | jq .iss,.aud,.sub ``` The next section of the `keycloak.tf` file will output the new identity's `id` value. This is a unique value that represents the identity itself. ```hcl output "keycloak-identity" { value = chainguard_identity.keycloak.id } ``` The section after that looks up the `viewer` role. ```hcl data "chainguard_role" "viewer" { name = "viewer" } ``` The final section grants this role to the identity. ```hcl resource "chainguard_rolebinding" "view-stuff" { identity = chainguard_identity.keycloak.id group = data.chainguard_group.group.id role = data.chainguard_role.viewer.items[0].id } ``` Following that, your Terraform configuration will be ready. Now you can run a few `terraform` commands to create the resources defined in your `.tf` files. ## Creating your resources First, run `terraform init` to initialize Terraform's working directory. ```sh terraform init ``` Then run `terraform plan`. This will produce a speculative execution plan that outlines what steps Terraform will take to create the resources defined in the files you set up in the last section. ```sh terraform plan ``` Then apply the configuration. ```sh terraform apply ``` Before going through with applying the Terraform configuration, this command will prompt you to confirm that you want it to do so. Enter `yes` to apply the configuration. ``` ... Plan: 4 to add, 0 to change, 0 to destroy. Changes to Outputs: + keycloak-identity = (known after apply) Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: ``` After typing `yes` and pressing `ENTER`, the command will complete and will output a `keycloak-ci` value. ``` ... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: keycloak-identity = "" ``` This is the identity's [UIDP (unique identity path)](/platform/administration/cloudevents/events-reference/#uidp-identifiers), which you configured the `keycloak.tf` file to emit in the previous section. Note this value down, as you'll need it to authenticate with `chainctl`. If you need to retrieve this UIDP later on, though, you can always run the following `chainctl` command to obtain a list of the UIDPs of all your existing identities. ```sh chainctl iam identities ls ``` Note that you may receive a `PermissionDenied` error part way through the apply step. If so, run `chainctl auth login` once more, and then `terraform apply` again to resume creating the identity and resources. ## Testing the identity From a CLI with the `chainctl` binary installed and access to the Keycloak server: First, create a pair of environment variables for when you log into Chainguard using the identity. This command generates a JSON Web Token (JWT) by logging in to Keycloak with a username and password and then assigning the token to a variable named `ID_TOKEN`. ```sh export ID_TOKEN=$(curl \ --data "grant_type=password&client_id=&client_secret=&username=&password=" \ https:///realms//protocol/openid-connect/token | jq -r .access_token) ``` Next set a variable named `ID` to your identity's UIDP value with the following command. Be sure to replace `` with the identity's UIDP value, which you noted down in the previous section. ```sh export ID= ``` After creating these variables, run the following commands to log in to Chainguard under the assumed identity. ```shell chainctl auth login \ --identity-token $ID_TOKEN \ --identity $ID chainctl auth configure-docker \ --identity-token $ID_TOKEN \ --identity $ID ``` After logging in, the pipeline will be able to run any `chainctl` command under the assumed identity. To test out this ability, this configuration runs the `chainctl images repos list` command to list all available image repositories associated associated with the organization. ```sh chainctl images repos list ``` After updating the Keycloak configuration, commit the changes and the pipeline will run automatically. A status box in the dashboard will let you know whether the pipeline runs successfully. If the token times out, `chainctl` will try to reauthenticate and fail. If you repeat the same login command with the old token you will see messages like this: ```sh chainctl auth login --identity-token $ID_TOKEN --identity $ID Error: [101] unable to exchange tokens: rpc error: code = PermissionDenied desc = verifying token: oidc: token is expired (Token Expiry: 2024-03-18 11:44:35 +0000 UTC) ``` To authenticate with `chainctl` you will need to generate a new token with Keycloak. If you'd like to experiment further with this identity and what the workflow can do with it, there are a few parts of this setup that you can tweak. For instance, if you'd like to give this identity different permissions you can change the role data source to the role you would like to grant. ```hcl data "chainguard_roles" "editor" { name = "editor" } ``` To retrieve a list of all the available roles, you can run the following command. ```sh chainctl iam roles list ``` This command's output will also include any custom roles you are able to grant. ## Removing sample resources To remove the resources Terraform created, you can run the `terraform destroy` command. ```sh terraform destroy ``` This will destroy identity and the role-binding created in this guide. It will not delete the organization. You can then remove the working directory to clean up your system. ```sh rm -r ~/keycloak-id/ ``` Following that, all of the example resources created in this guide will be removed from your system. ## Learn more For more information about how assumable identities work in Chainguard, check out our [conceptual overview of assumable identities](/platform/administration/assumable-ids/assumable-ids/). Additionally, the Terraform documentation includes a section on [recommended best practices](https://developer.hashicorp.com/terraform/cloud-docs/recommended-practices) which you can refer to if you'd like to build on this Terraform configuration for a production environment. For more information about OIDC you can find a lot of documentation on the [OpenID Foundation website](https://openid.net/). For Keycloak specific information, we encourage you to check out the [official Keycloak documentation](https://www.keycloak.org/documentation) --- ### Automating with chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl-usage/automating-chainctl.md Last Modified: September 25, 2026 Tags: chainctl `chainctl` works well at the command line for interactive use, but it is equally suited to automation. Most `chainctl` commands accept an `--output=json` flag, which lets you pipe their output to `jq` and build reliable scripting patterns around the results. This page collects practical snippets you can adapt for shell scripts, scheduled jobs, and CI/CD pipelines. Each snippet is a self-contained building block; combine them to fit your workflow.

Note: The jq field names used in these examples reflect the expected JSON output structure. Run chainctl <command> --output=json | jq '.' to inspect the exact structure for your version of chainctl.

## Prerequisites Before running any of these snippets, you need: - `chainctl` [installed](/platform/chainctl-usage/how-to-install-chainctl/) - An active [Chainguard account](/chainguard/containers/registry/authenticating/) - `jq` installed (`brew install jq` or `apt install jq`) - For CI/CD use, a [Chainguard assumable identity](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/) configured for your pipeline Many `chainctl` commands drop into an interactive selection menu when run without enough arguments. In scripts and pipelines, always supply flags like `--parent` explicitly so the command runs without waiting for input. ## Get a non-interactive auth token Every automated workflow that touches Chainguard's registry or API needs a credential. The process is two steps: authenticate with `chainctl auth login`, then capture the resulting token with `chainctl auth token`. To authenticate non-interactively, you need the UIDP of an assumable identity configured to trust your CI platform's OIDC issuer. Find it with: ```bash chainctl iam identities list --output=json \ | jq -r '.items[] | select(.name == "my-ci-identity") | .id' ``` Then authenticate using the identity and your platform's OIDC token, and export the result: ```bash chainctl auth login \ --identity="$IDENTITY_ID" \ --identity-token="$PATH_TO_OIDC_TOKEN" export CHAINGUARD_TOKEN=$(chainctl auth token) ``` `$PATH_TO_OIDC_TOKEN` is the path to a file containing the OIDC token provided by your CI platform. Refer to the OIDC-aware CI platforms section below for platform-specific examples. Pass the token to Docker or `crane` to authenticate to `cgr.dev`: ```bash echo "$CHAINGUARD_TOKEN" | docker login cgr.dev \ --username=_token \ --password-stdin ``` ### Use in OIDC-aware CI platforms Many CI platforms can provide a short-lived OIDC token that `chainctl` uses to authenticate without storing long-lived credentials. You configure a Chainguard assumable identity that trusts your platform's OIDC issuer, and your pipeline exchanges the token for registry access at run time. Tested examples are available for the following platforms: - [Authenticating with GitHub Actions](/chainguard/containers/registry/authenticating/#authenticating-with-github-actions) - [Authenticating with CircleCI OIDC token](/chainguard/containers/registry/authenticating/#authenticating-with-circleci-oidc-token) - [Authenticating with Microsoft Entra ID OIDC token](/chainguard/containers/registry/authenticating/#authenticating-with-microsoft-entra-id-oidc-token) ## Pin an image by its digest Mutable tags like `latest` can silently change. Pinning to a digest prevents unexpected changes from reaching production. This snippet retrieves the current digest for a given tag and rewrites a Kubernetes manifest in place. ```bash #!/usr/bin/env bash ORG="your-org.example.com" IMAGE="python" TAG="3.13" MANIFEST="deployment.yaml" DIGEST=$(chainctl images history "$IMAGE:$TAG" \ --parent="$ORG" --output=json \ | jq -r '.[0].digest') echo "Pinning $IMAGE:$TAG to $DIGEST" sed -i "s|cgr.dev/$ORG/$IMAGE:.*|cgr.dev/$ORG/$IMAGE@$DIGEST|g" "$MANIFEST" ``` Add this as a pipeline step that runs after each image update notification to keep your manifests pinned to the most recent verified build. ## Detect when an image has been updated This script compares the current digest for an image tag against the last known digest and runs a notification when the image changes. Schedule it on a cron or in a recurring GitHub Actions workflow. ```bash #!/usr/bin/env bash ORG="your-org.example.com" IMAGE="nginx" TAG="latest" STATE_FILE="/tmp/.${IMAGE}-${TAG}-digest" CURRENT=$(chainctl images history "$IMAGE:$TAG" \ --parent="$ORG" --output=json \ | jq -r '.[0].digest') if [[ -f "$STATE_FILE" && "$(cat "$STATE_FILE")" != "$CURRENT" ]]; then echo "Image updated: cgr.dev/$ORG/$IMAGE:$TAG" echo "New digest: $CURRENT" # Add your notification logic here: webhook, Slack, GitHub dispatch, etc. fi printf '%s' "$CURRENT" > "$STATE_FILE" ``` If you run this in a stateless CI environment, store `STATE_FILE` in a persistent cache between runs — for example, a GitHub Actions cache keyed on the image name. ## Compare images before promotion Before promoting a new image version to production, use `chainctl images diff` to surface changed packages and CVE-count deltas. This gives you an audit trail for the promotion decision. This command requires [grype](https://github.com/anchore/grype) to be installed and available on the system PATH. ```bash #!/usr/bin/env bash ORG="your-org.example.com" IMAGE="python" OLD_TAG="3.12" NEW_TAG="3.13" echo "Comparing $OLD_TAG -> $NEW_TAG for $IMAGE" chainctl images diff \ "cgr.dev/$ORG/$IMAGE:$OLD_TAG" \ "cgr.dev/$ORG/$IMAGE:$NEW_TAG" ``` Capture the output with `| tee diff-report.txt` to attach it to a pull request or deployment ticket. Refer to [Compare Chainguard Containers with chainctl diff](/platform/chainctl-usage/comparing-images/) for details on reading the diff output. ## Audit role bindings for compliance This snippet exports a snapshot of all current role bindings to a JSON file. Diff the output against a prior snapshot to detect unexpected access changes — useful for periodic security reviews or SOC 2 evidence collection. ```bash #!/usr/bin/env bash DATE=$(date +%Y-%m-%d) SNAPSHOT="role-bindings-${DATE}.json" chainctl iam role-bindings list --output=json > "$SNAPSHOT" echo "Snapshot written to $SNAPSHOT" # Summarize: binding count per role jq -r '.items[].role_name' "$SNAPSHOT" | sort | uniq -c | sort -rn ``` Archive snapshots to an S3 bucket or similar to build a durable audit trail. You can compare two snapshots with `diff <(jq -S . previous.json) <(jq -S . current.json)`. ## Batch-onboard new users When a new team joins or a project kicks off, generate all the invite codes at once from a CSV file rather than creating each one by hand. The CSV should have one `email,role` pair per line with a header row. ```bash #!/usr/bin/env bash # Usage: ./onboard-users.sh users.csv your-org.example.com CSV_FILE="${1:?provide a CSV file}" ORG="${2:?provide an org name}" while IFS=',' read -r EMAIL ROLE; do [[ "$EMAIL" == "email" ]] && continue # skip header row echo "Creating invite: $EMAIL with role $ROLE" chainctl iam invite create "$ORG" \ --role="$ROLE" \ --email="$EMAIL" \ --ttl=7d \ --single-use done < "$CSV_FILE" ``` The `--single-use` flag ensures each invite code can only be redeemed once. Adjust `--ttl` to match your onboarding window. ## Revoke a user's access When someone leaves your organization, remove their Chainguard identity to prevent further authentication. This script looks up the identity by email address using role bindings and prints the delete command for you to confirm before running. ```bash #!/usr/bin/env bash # Usage: ./offboard.sh jane@example.com EMAIL="${1:?provide an email address}" IDENTITY_ID=$(chainctl iam role-bindings list --output=json \ | jq -r --arg email "$EMAIL" \ '.items[] | select(.email == $email) | .identity_id' | head -1) if [[ -z "$IDENTITY_ID" ]]; then echo "No identity found for: $EMAIL" exit 1 fi echo "Found identity: $IDENTITY_ID" echo "Run the following to delete it:" echo " chainctl iam identities delete $IDENTITY_ID --yes" ``` The script prints the delete command rather than running it, so you can verify the identity ID before committing. Replace the final two lines with `chainctl iam identities delete "$IDENTITY_ID"` once you are confident in the match. ## Manage event subscriptions as code Event subscriptions route Chainguard platform events — such as new vulnerability detections — to an external endpoint. Rather than creating subscriptions by hand, treat them as managed configuration and verify their existence in your infrastructure provisioning pipeline. ```bash #!/usr/bin/env bash # Usage: ./ensure-subscription.sh your-org.example.com https://hooks.example.com/chainguard ORG="${1:?provide an org name}" ENDPOINT="${2:?provide a webhook URL}" EXISTING=$(chainctl events subscriptions list --output=json \ | jq -r --arg url "$ENDPOINT" \ '.items[] | select(.sink == $url) | .id') if [[ -n "$EXISTING" ]]; then echo "Subscription already present (id: $EXISTING)" else echo "Creating subscription: $ORG -> $ENDPOINT" chainctl events subscriptions create "$ENDPOINT" \ --parent="$ORG" fi ``` Run this script idempotently during infrastructure provisioning. If the subscription already exists, it exits cleanly without creating a duplicate. --- For a full reference of every `chainctl` command and flag, refer to the [chainctl reference](/platform/chainctl/). --- ### chainctl URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl Chainguard Control ``` chainctl [flags] ``` ### Options ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions](/platform/chainctl/chainctl-docs/chainctl_actions/) - Interact with the Chainguard Actions product. * [chainctl agent](/platform/chainctl/chainctl-docs/chainctl_agent/) - Agent-powered commands. * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. * [chainctl events](/platform/chainctl/chainctl-docs/chainctl_events/) - Events related commands for the Chainguard platform. * [chainctl guardener](/platform/chainctl/chainctl-docs/chainctl_guardener/) - Manage guardener integrations. * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl packages](/platform/chainctl/chainctl-docs/chainctl_packages/) - Interact with Chainguard packages * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. * [chainctl starter](/platform/chainctl/chainctl-docs/chainctl_starter/) - Manage catalog starter organizations * [chainctl update](/platform/chainctl/chainctl-docs/chainctl_update/) - Update chainctl. * [chainctl version](/platform/chainctl/chainctl-docs/chainctl_version/) - Prints the version --- ### chainctl actions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions Interact with the Chainguard Actions product. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl actions catalog](/platform/chainctl/chainctl-docs/chainctl_actions_catalog/) - Browse the public Chainguard Actions catalog. * [chainctl actions discover](/platform/chainctl/chainctl-docs/chainctl_actions_discover/) - Discover GitHub Actions dependencies used by your workflows. * [chainctl actions entitlements](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements/) - Manage Actions product entitlements. * [chainctl actions list](/platform/chainctl/chainctl-docs/chainctl_actions_list/) - List Chainguard Actions catalog entries for an organization. --- ### chainctl actions catalog URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_catalog.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions catalog Browse the public Chainguard Actions catalog. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions](/platform/chainctl/chainctl-docs/chainctl_actions/) - Interact with the Chainguard Actions product. * [chainctl actions catalog list](/platform/chainctl/chainctl-docs/chainctl_actions_catalog_list/) - List Chainguard Actions in the public catalog. --- ### chainctl actions catalog list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_catalog_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions catalog list List Chainguard Actions in the public catalog. ``` chainctl actions catalog list [--upstream-owner=OWNER [--upstream-repo=REPO]] [--output=json|table] [flags] ``` ### Options ``` --page-size int32 Maximum number of actions to fetch per page (0 uses the server default). --upstream-owner string Filter to actions mirroring this upstream owner (e.g. "actions"). --upstream-repo string Filter to actions mirroring this upstream repo (requires --upstream-owner). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions catalog](/platform/chainctl/chainctl-docs/chainctl_actions_catalog/) - Browse the public Chainguard Actions catalog. --- ### chainctl actions discover URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_discover.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions discover Discover GitHub Actions dependencies used by your workflows. ### Synopsis Discover GitHub Actions dependencies. Walks the target's workflows and composite-action definitions and lists every action and container image they reference. TARGET may be: - a local directory (default: current directory) — scans .github/workflows/ and action.{yml,yaml} - a repo "owner/repo" — scans the repo's workflows and action.{yml,yaml} - a single action ref "owner/repo[/subpath]@version" By default only the directly-referenced actions are listed. Pass --recursive to follow each referenced action into its own definition on GitHub and resolve the full transitive dependency graph. Requires a GitHub token via $GITHUB_TOKEN or 'gh auth token'. ``` chainctl actions discover [TARGET] [flags] ``` ### Examples ``` chainctl actions discover chainctl actions discover . chainctl actions discover actions/checkout@v4 chainctl actions discover chainguard-dev/mono ``` ### Options ``` --cache-dir string Directory for caching GitHub API responses (default: $TMPDIR/chainctl-discover-cache) --clear-cache Clear the cache directory before running --recursive Recurse into referenced actions' definitions on GitHub to resolve transitive dependencies --skip-catalog Skip matching discovered actions against the Chainguard Actions catalog --timeout duration Maximum time to spend resolving dependencies (default 5m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions](/platform/chainctl/chainctl-docs/chainctl_actions/) - Interact with the Chainguard Actions product. --- ### chainctl actions entitlements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_entitlements.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions entitlements Manage Actions product entitlements. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions](/platform/chainctl/chainctl-docs/chainctl_actions/) - Interact with the Chainguard Actions product. * [chainctl actions entitlements create](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements_create/) - Enable the Actions product for an organization. * [chainctl actions entitlements delete](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements_delete/) - Disable the Actions product for an organization. * [chainctl actions entitlements list](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements_list/) - List Actions product entitlements for an organization. --- ### chainctl actions entitlements create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_entitlements_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions entitlements create Enable the Actions product for an organization. ``` chainctl actions entitlements create --parent=PARENT [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the org to enable the Actions product for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions entitlements](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements/) - Manage Actions product entitlements. --- ### chainctl actions entitlements delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_entitlements_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions entitlements delete Disable the Actions product for an organization. ``` chainctl actions entitlements delete --parent=PARENT [flags] ``` ### Options ``` --parent string The name or id of the org to disable the Actions product for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions entitlements](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements/) - Manage Actions product entitlements. --- ### chainctl actions entitlements list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_entitlements_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions entitlements list List Actions product entitlements for an organization. ``` chainctl actions entitlements list --parent=PARENT [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the org to list Actions entitlements for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions entitlements](/platform/chainctl/chainctl-docs/chainctl_actions_entitlements/) - Manage Actions product entitlements. --- ### chainctl actions list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_actions_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl actions list List Chainguard Actions catalog entries for an organization. ``` chainctl actions list --parent=PARENT [--upstream-owner=OWNER [--upstream-repo=REPO]] [--output=json|table] [flags] ``` ### Options ``` --page-size int32 Maximum number of actions to fetch per page (0 uses the server default). --parent string The name or id of the org to list actions for. --upstream-owner string Filter to actions mirroring this upstream owner (e.g. "actions"). --upstream-repo string Filter to actions mirroring this upstream repo (requires --upstream-owner). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl actions](/platform/chainctl/chainctl-docs/chainctl_actions/) - Interact with the Chainguard Actions product. --- ### chainctl agent URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_agent.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl agent Agent-powered commands. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl agent accept-terms](/platform/chainctl/chainctl-docs/chainctl_agent_accept-terms/) - Accept required legal terms. --- ### chainctl agent accept-terms URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_agent_accept-terms.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl agent accept-terms Accept required legal terms. ### Synopsis Presents the legal agreement prompt for the given group. ``` chainctl agent accept-terms [flags] ``` ### Options ``` --parent string Name or UIDP of the group to accept terms for. Defaults to your configured default group; prompts if unset. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl agent](/platform/chainctl/chainctl-docs/chainctl_agent/) - Agent-powered commands. --- ### chainctl auth URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth Auth related commands for the Chainguard platform. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl auth configure](/platform/chainctl/chainctl-docs/chainctl_auth_configure/) - Configure a local tool to authenticate to Chainguard. * [chainctl auth configure-docker](/platform/chainctl/chainctl-docs/chainctl_auth_configure-docker/) - Configure a Docker credential helper * [chainctl auth configure-npm](/platform/chainctl/chainctl-docs/chainctl_auth_configure-npm/) - Configure npm credentials for Chainguard Libraries for JavaScript * [chainctl auth configure-refresh-service](/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service/) - Manage a background service that keeps Chainguard tokens refreshed * [chainctl auth delete-account](/platform/chainctl/chainctl-docs/chainctl_auth_delete-account/) - Permanently delete your user account. * [chainctl auth login](/platform/chainctl/chainctl-docs/chainctl_auth_login/) - Login to the Chainguard platform. * [chainctl auth logout](/platform/chainctl/chainctl-docs/chainctl_auth_logout/) - Logout from the Chainguard platform. * [chainctl auth pull-token](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/) - Create a pull token. * [chainctl auth status](/platform/chainctl/chainctl-docs/chainctl_auth_status/) - Inspect the local Chainguard Token. * [chainctl auth token](/platform/chainctl/chainctl-docs/chainctl_auth_token/) - Print the local Chainguard Token. --- ### chainctl auth configure URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure Configure a local tool to authenticate to Chainguard. ### Synopsis Configure a local tool to authenticate to Chainguard. Each subcommand writes credentials where its tool looks for them, using your current Chainguard session where supported. With --pull-token it writes a longer-lived credential for environments that cannot run an interactive login (CI systems, build servers, etc.). Bundler always uses a pull token. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. * [chainctl auth configure docker](/platform/chainctl/chainctl-docs/chainctl_auth_configure_docker/) - Configure a Docker credential helper * [chainctl auth configure npm](/platform/chainctl/chainctl-docs/chainctl_auth_configure_npm/) - Configure npm credentials for Chainguard Libraries for JavaScript * [chainctl auth configure nuget](/platform/chainctl/chainctl-docs/chainctl_auth_configure_nuget/) - Configure NuGet credentials for Chainguard Libraries for .NET --- ### chainctl auth configure docker URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure_docker.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure docker Configure a Docker credential helper ``` chainctl auth configure docker [flags] ``` ### Options ``` --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --images Also configure the experimental customer images registry --name string Optional name for the pull token (default "pull-token") --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --parent string The IAM organization or folder with which the pull-token identity is associated. --pull-token Whether to register a pull token that can pull images --save If true with --pull-token, save the pull token to the Docker config --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth configure](/platform/chainctl/chainctl-docs/chainctl_auth_configure/) - Configure a local tool to authenticate to Chainguard. --- ### chainctl auth configure npm URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure_npm.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure npm Configure npm credentials for Chainguard Libraries for JavaScript ### Synopsis Configure npm to use Chainguard Libraries for JavaScript. By default, this command authenticates using your current Chainguard session and writes a project-level .npmrc file with a bearer token. With the --pull-token flag, it creates a longer-lived pull token that can be used in environments that don't support OIDC (CI systems, build servers, etc.) and writes a project-level .npmrc with basic auth credentials. ``` chainctl auth configure npm [flags] ``` ### Examples ``` # Configure npm using your current Chainguard session. chainctl auth configure npm # Configure npm with a long-lived pull token. chainctl auth configure npm --pull-token # Configure npm with a pull token for a specific organization. chainctl auth configure npm --pull-token --parent=my-org # Configure npm with a pull token that lasts for 24 hours. chainctl auth configure npm --pull-token --ttl=24h ``` ### Options ``` --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --name string Optional name for the pull token. (default "pull-token") --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --parent string The IAM organization or folder with which the pull-token identity is associated. --pull-token Whether to create a pull token for npm authentication. --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth configure](/platform/chainctl/chainctl-docs/chainctl_auth_configure/) - Configure a local tool to authenticate to Chainguard. --- ### chainctl auth configure nuget URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure_nuget.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure nuget Configure NuGet credentials for Chainguard Libraries for .NET ### Synopsis Configure NuGet to use Chainguard Libraries for .NET. By default, this command authenticates using your current Chainguard session and writes a project-level nuget.config with that session's token. With the --pull-token flag, it creates a longer-lived pull token that can be used in environments that don't support OIDC (CI systems, build servers, etc.). A config created from scratch names the Chainguard feed as its only source, so a restore cannot fall back to nuget.org — including sources inherited from a parent directory or the user-level config, which a new file also clears for everything beneath it. An existing config is amended instead: its own sources and settings are kept, which means the feed may not be the only one — and directives already in the file can stop NuGet consulting it at all. Either way the command reports whether a restore would actually reach the feed. The file configures restore only: the publish endpoint is not a v3 service index, so a source pointing at it would break restore, and publishing needs its own config. Credentials are written in cleartext, because NuGet's encrypted credential store is Windows-only, and the file is readable by its owner alone on platforms where that is enforceable. ``` chainctl auth configure nuget [flags] ``` ### Examples ``` # Configure NuGet using your current Chainguard session. chainctl auth configure nuget # Configure NuGet with a long-lived pull token. chainctl auth configure nuget --pull-token # Configure NuGet with a pull token for a specific organization. chainctl auth configure nuget --pull-token --parent=my-org # Configure NuGet with a pull token that lasts for 24 hours. chainctl auth configure nuget --pull-token --ttl=24h ``` ### Options ``` --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --name string Optional name for the pull token. (default "pull-token") --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --parent string The IAM organization or folder with which the pull-token identity is associated. --pull-token Whether to create a pull token for NuGet authentication. --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth configure](/platform/chainctl/chainctl-docs/chainctl_auth_configure/) - Configure a local tool to authenticate to Chainguard. --- ### chainctl auth configure-docker URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure-docker.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure-docker Configure a Docker credential helper ``` chainctl auth configure-docker [flags] ``` ### Options ``` --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --images Also configure the experimental customer images registry --name string Optional name for the pull token (default "pull-token") --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --parent string The IAM organization or folder with which the pull-token identity is associated. --pull-token Whether to register a pull token that can pull images --save If true with --pull-token, save the pull token to the Docker config --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. --- ### chainctl auth configure-npm URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure-npm.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure-npm Configure npm credentials for Chainguard Libraries for JavaScript ### Synopsis Configure npm to use Chainguard Libraries for JavaScript. By default, this command authenticates using your current Chainguard session and writes a project-level .npmrc file with a bearer token. With the --pull-token flag, it creates a longer-lived pull token that can be used in environments that don't support OIDC (CI systems, build servers, etc.) and writes a project-level .npmrc with basic auth credentials. ``` chainctl auth configure-npm [flags] ``` ### Examples ``` # Configure npm using your current Chainguard session. chainctl auth configure-npm # Configure npm with a long-lived pull token. chainctl auth configure-npm --pull-token # Configure npm with a pull token for a specific organization. chainctl auth configure-npm --pull-token --parent=my-org # Configure npm with a pull token that lasts for 24 hours. chainctl auth configure-npm --pull-token --ttl=24h ``` ### Options ``` --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --name string Optional name for the pull token. (default "pull-token") --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --parent string The IAM organization or folder with which the pull-token identity is associated. --pull-token Whether to create a pull token for npm authentication. --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. --- ### chainctl auth configure-refresh-service URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure-refresh-service Manage a background service that keeps Chainguard tokens refreshed ### Synopsis Manage a user-level background service that keeps Chainguard tokens refreshed by periodically running 'chainctl auth login --refresh-only'. The service manager is selected automatically for your operating system: systemd (Linux) or launchd (macOS). By default every cached token is refreshed; specific audiences can be selected at install time instead. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. * [chainctl auth configure-refresh-service install](/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service_install/) - Install a background service for automatic token refresh * [chainctl auth configure-refresh-service uninstall](/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service_uninstall/) - Uninstall the background token-refresh service --- ### chainctl auth configure-refresh-service install URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service_install.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure-refresh-service install Install a background service for automatic token refresh ### Synopsis Install (or update) a user-level service that refreshes Chainguard tokens in the background. By default the service refreshes every cached token: the audiences to refresh are discovered by scanning the local token cache on each refresh cycle, so tokens created later are picked up without restarting the service. With --audience, the service refreshes only the given audiences instead. Rerunning install updates the existing service in place. ``` chainctl auth configure-refresh-service install [flags] ``` ### Examples ``` # Install (or update) the token refresh service. chainctl auth configure-refresh-service install # Refresh only specific audiences. chainctl auth configure-refresh-service install --audience=libraries.cgr.dev --audience=apk.cgr.dev ``` ### Options ``` --audience stringArray Audience to refresh (can be specified multiple times). Defaults to refreshing every cached token. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth configure-refresh-service](/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service/) - Manage a background service that keeps Chainguard tokens refreshed --- ### chainctl auth configure-refresh-service uninstall URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service_uninstall.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth configure-refresh-service uninstall Uninstall the background token-refresh service ### Synopsis Uninstall the background token-refresh service. This stops the service and removes its files. ``` chainctl auth configure-refresh-service uninstall [flags] ``` ### Examples ``` # Uninstall the token refresh service. chainctl auth configure-refresh-service uninstall ``` ### Options ``` -y, --yes Skip confirmation prompt ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth configure-refresh-service](/platform/chainctl/chainctl-docs/chainctl_auth_configure-refresh-service/) - Manage a background service that keeps Chainguard tokens refreshed --- ### chainctl auth delete-account URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_delete-account.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth delete-account Permanently delete your user account. ``` chainctl auth delete-account [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. --- ### chainctl auth login URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_login.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth login Login to the Chainguard platform. ``` chainctl auth login [--invite-code=INVITE_CODE] [--identity-token=PATH_TO_TOKEN] [--identity=IDENTITY_ID] [--identity-provider=IDP_ID] [--org-name=ORG_NAME] [--audience=AUDIENCE]... [--social-login={email|google|github|gitlab}] [--headless] [--prefer-ambient-credentials] [--refresh] [--output=id|json|none|table] ``` ### Examples ``` # Default auth login flow: chainctl auth login # Refreshing a token within a Kubernetes context: chainctl auth login --identity-token=PATH_TO_TOKEN --refresh # Headless login using --org-name chainctl auth login --headless --org-name my-org # Register by accepting an invite to an existing location chainctl auth login --invite-code eyJncnAiOiI5MzA... ``` ### Options ``` --audience stringArray The Chainguard token audience to request. Can be specified multiple times. An interactive login for a platform audience (API, registry, apk, images, libraries, skills, uploads, the hosted sandbox sessions and workflow runs, and Facts analytics) mints one refresh token covering all of them, so a single login serves every platform audience; any other audience is minted on its own. When the cached session lacks any platform audience, the login signs in again rather than renewing it. Defaults to the comma-separated list in $CHAINCTL_AUDIENCES when set. --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --invite-code string Registration invite code. --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --prefer-ambient-credentials Auth with ambient credentials, if present, before using a supplied identity token. --refresh Enable auto refresh of the Chainguard token (for workloads). --refresh-all Refresh every cached token, discovered from the local token cache instead of --audience (implies --refresh-only). Re-evaluated on each refresh cycle, so tokens created after startup are picked up without a restart. --refresh-only Only refresh existing tokens, skip initial token creation (implies --refresh). Must authenticate separately. --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab --sts-http1-downgrade Downgrade STS requests to HTTP/1.x ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. --- ### chainctl auth logout URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_logout.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth logout Logout from the Chainguard platform. ``` chainctl auth logout [--audience=AUDIENCE] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. --- ### chainctl auth pull-token URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_pull-token.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth pull-token Create a pull token. ``` chainctl auth pull-token [flags] ``` ### Options ``` --description string Optional description for the pull token. --name string Optional name for the pull token. (default "pull-token") --parent string The IAM organization or folder with which the pull token identity is associated. --repository string The repository type to create a pull token for. Must be one of: oci, apk, javascript_athena, dotnet, go, go_athena, javascript, java_athena, dotnet_athena, ruby, ruby_athena, java, python, python_athena. (default "oci") --save Save the OCI registry pull token to the Docker configuration. --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. * [chainctl auth pull-token create](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token_create/) - Create a pull token. * [chainctl auth pull-token list](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token_list/) - List all pull-tokens --- ### chainctl auth pull-token create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_pull-token_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth pull-token create Create a pull token. ``` chainctl auth pull-token create [--save=true|false] [--name=NAME] [--description=DESC] [--ttl=NUM_HOURS_ACTIVE] [--parent=PARENT] [--repository={oci|apk|javascript_athena|dotnet|go|go_athena|javascript|java_athena|dotnet_athena|ruby|ruby_athena|java|python|python_athena}] [flags] ``` ### Examples ``` # Create a pull token for container registry pull access. chainctl auth pull-token create # Create a pull token for pull access to a library ecosystem. chainctl auth pull-token create --repository=java # Create a pull token that lasts for 24 hours. chainctl auth pull-token create --ttl=24h # Create a pull token for a particular organization. chainctl auth pull-token create --parent=my-org # Create a pull token with a description. chainctl auth pull-token create --description="CI builds for service-foo" ``` ### Options ``` --description string Optional description for the pull token. --name string Optional name for the pull token. (default "pull-token") --parent string The IAM organization or folder with which the pull token identity is associated. --repository string The repository type to create a pull token for. Must be one of: oci, apk, javascript_athena, dotnet, go, go_athena, javascript, java_athena, dotnet_athena, ruby, ruby_athena, java, python, python_athena. (default "oci") --save Save the OCI registry pull token to the Docker configuration. --ttl ns Time To Live for the validity of the pull token. Valid unit strings range from nanoseconds to hours and are ns, `us`, `ms`, `s`, `m`, and `h`. Maximum value is 8760h or one year. (default 720h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth pull-token](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/) - Create a pull token. --- ### chainctl auth pull-token list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_pull-token_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth pull-token list List all pull-tokens ``` chainctl auth pull-token list [--parent=PARENT] [--expired=true|false] [--repository={oci|apk|javascript_athena|dotnet|go|go_athena|javascript|java_athena|dotnet_athena|ruby|ruby_athena|java|python|python_athena}] [flags] ``` ### Examples ``` # List all pull tokens. chainctl auth pull-token list # List all pull tokens associated with the Java library ecosystem. chainctl auth pull-token list --repository=java # List expired pull tokens. chainctl auth pull-token list --expired # List pull tokens associated to a particular organization. chainctl auth pull-token list --parent=my-org # List all expired APK pull tokens. chainctl auth pull-token list --repository=apk --expired ``` ### Options ``` --expired If true return only expired pull tokens. --parent string The IAM organization or folder with which the pull-token identity is associated. --repository string The repository type to list pull tokens for. Must be one of: oci, apk, javascript_athena, dotnet, go, go_athena, javascript, java_athena, dotnet_athena, ruby, ruby_athena, java, python, python_athena ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth pull-token](/platform/chainctl/chainctl-docs/chainctl_auth_pull-token/) - Create a pull token. --- ### chainctl auth status URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_status.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth status Inspect the local Chainguard Token. ``` chainctl auth status [--output=json|table|terse] [flags] ``` ### Options ``` --headless Skip browser authentication and use device flow. --identity string The unique ID of the identity to assume when logging in. --identity-provider string The unique ID of the customer managed identity provider to authenticate with. Mutually exclusive with --org-name. --identity-token string Use an explicit passed identity token or token path. --org-name string Organization to use for authentication. If configured the organization's custom identity provider will be used. Mutually exclusive with --identity-provider. --quick Whether to perform quick offline token checks (vs. calling the Validate API). --social-login string Which of the default identity providers to use for authentication. Must be one of: email, google, github, gitlab ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. --- ### chainctl auth token URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_token.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth token Print the local Chainguard Token. ### Synopsis Print the local Chainguard Token. With --capabilities and/or --scope, print a token narrowed to that access instead of the cached one. With --delegate, mint a grant for a delegate (see "chainctl iam identities create delegate"): a token whose only audience is the delegation audience the delegate pins, narrowed to --scope (required) and to the capabilities of --role plus any --capabilities. The delegate exchanges the grant for tokens carrying a subset of that access and expiring no later than the grant, which lives at most 60 minutes. No API accepts the grant itself. A grant cannot carry more than you hold; chainctl warns before minting one that asks for more. Grants are stateless, so there is no per-grant list or revoke: deleting the delegate stops every future exchange, and grants already minted expire within 60 minutes. The grant is minted from the cached refresh token when there is one, otherwise from ambient credentials or a fresh login, and it is never cached. The global --audience flag keeps its meaning here: it selects the API audience of the cached token, and is unrelated to the grant's audience. ``` chainctl auth token [flags] ``` ### Examples ``` # Mint a grant for a delegate carrying the viewer role's capabilities in an organization. chainctl auth token --delegate=my-delegate --role=viewer --scope=ORGANIZATION_ID # Inspect what a grant carries. chainctl auth token --delegate=my-delegate --capabilities=repo.list --scope=ORGANIZATION_ID | chainctl auth token capabilities --token - ``` ### Options ``` --capabilities strings Request a token narrowed to the given capabilities. With --delegate, capabilities the grant carries in addition to --role's. --delegate string Mint a grant for this delegate (name or ID). Requires --scope and --role or --capabilities. --interactive Allow browser or device login when needed, even when stderr is redirected. --role strings With a grant (--delegate), the roles whose capabilities the grant carries. --scope strings Request a token with scope reduced to the given groups (names or IDs). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth](/platform/chainctl/chainctl-docs/chainctl_auth/) - Auth related commands for the Chainguard platform. * [chainctl auth token capabilities](/platform/chainctl/chainctl-docs/chainctl_auth_token_capabilities/) - Print the capabilities of the local Chainguard Token. --- ### chainctl auth token capabilities URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_auth_token_capabilities.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl auth token capabilities Print the capabilities of the local Chainguard Token. ### Synopsis Print the capabilities of the local Chainguard Token. With --token -, decode the token read from standard input instead. This shows what a grant minted with "chainctl auth token --delegate" carries: chainctl auth token --delegate=my-delegate --role=viewer --scope=my-org | chainctl auth token capabilities --token - Prefer "--token -" to passing a token as the flag's value: a token on the command line is visible to other local users in the process list and is kept in shell history. The token is decoded, not verified. ``` chainctl auth token capabilities [--token=TOKEN|-] [flags] ``` ### Options ``` --token string Decode another token instead of the local one. Use "-" to read it from standard input; a token given as the value is visible in the process list and shell history. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl auth token](/platform/chainctl/chainctl-docs/chainctl_auth_token/) - Print the local Chainguard Token. --- ### chainctl config URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config Local config file commands for chainctl. ### Synopsis Local config file commands for chainctl. Every config property can also be set with an environment variable, which is useful for scripts and CI. Take the property name, uppercase it, replace dots and hyphens with underscores, and add a `CHAINGUARD_` prefix: - `default.group` becomes `CHAINGUARD_DEFAULT_GROUP` - `default.skip-version-check` becomes `CHAINGUARD_DEFAULT_SKIP_VERSION_CHECK` - `output.silent` becomes `CHAINGUARD_OUTPUT_SILENT` Environment variables take precedence over the config file, and command line flags take precedence over both. Setting `default.group` supplies the default for --parent, so exporting `CHAINGUARD_DEFAULT_GROUP` avoids passing --parent to every command. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl config edit](/platform/chainctl/chainctl-docs/chainctl_config_edit/) - Edit the current chainctl config file. * [chainctl config reset](/platform/chainctl/chainctl-docs/chainctl_config_reset/) - Remove local chainctl config files and restore defaults. * [chainctl config save](/platform/chainctl/chainctl-docs/chainctl_config_save/) - Save the current chainctl config to a config file. * [chainctl config set](/platform/chainctl/chainctl-docs/chainctl_config_set/) - Set an individual configuration value property. * [chainctl config unset](/platform/chainctl/chainctl-docs/chainctl_config_unset/) - Unset a configuration property and return it to default. * [chainctl config validate](/platform/chainctl/chainctl-docs/chainctl_config_validate/) - Run diagnostics on local config. * [chainctl config view](/platform/chainctl/chainctl-docs/chainctl_config_view/) - View the current chainctl config. --- ### chainctl config edit URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_edit.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config edit Edit the current chainctl config file. ### Synopsis Edit the current chainctl config file. Use the environment variable EDITOR to set the path to your preferred editor (default: nano). ``` chainctl config edit [--config FILE] [--yes] [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl config reset URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_reset.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config reset Remove local chainctl config files and restore defaults. ``` chainctl config reset [--yes] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl config save URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_save.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config save Save the current chainctl config to a config file. ``` chainctl config save [--config FILE] [--yes] [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl config set URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_set.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config set Set an individual configuration value property. ### Synopsis Set an individual configuration value property. Property names are dot delimited and lowercase (for example, output.color.pass). ``` chainctl config set PROPERTY_NAME PROPERTY_VALUE ``` ### Examples ``` # Set the api URL chainctl config set platform.api https://console-api.enforce.dev ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl config unset URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_unset.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config unset Unset a configuration property and return it to default. ### Synopsis Unset a configuration property and return it to default. Property names are dot delimited and lowercase (for example, output.color.pass). ``` chainctl config unset PROPERTY_NAME ``` ### Examples ``` # Return the pass color to its default chainctl config unset output.color.pass ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl config validate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_validate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config validate Run diagnostics on local config. ``` chainctl config validate [--output=json|table] [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl config view URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_config_view.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl config view View the current chainctl config. ``` chainctl config view [--diff] [flags] ``` ### Options ``` --diff Show the difference between the local config file and the active configuration. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl config](/platform/chainctl/chainctl-docs/chainctl_config/) - Local config file commands for chainctl. --- ### chainctl events URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_events.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl events Events related commands for the Chainguard platform. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl events subscriptions](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions/) - Subscription interactions. --- ### chainctl events subscriptions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_events_subscriptions.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl events subscriptions Subscription interactions. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl events](/platform/chainctl/chainctl-docs/chainctl_events/) - Events related commands for the Chainguard platform. * [chainctl events subscriptions create](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions_create/) - Subscribe to events under an organization or folder. * [chainctl events subscriptions delete](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions_delete/) - Delete a subscription. * [chainctl events subscriptions list](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions_list/) - List subscriptions. --- ### chainctl events subscriptions create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_events_subscriptions_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl events subscriptions create Subscribe to events under an organization or folder. ``` chainctl events subscriptions create SINK_URL [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--yes] [--output=id|json|table] ``` ### Options ``` --parent string The parent location name or id of the subscription. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl events subscriptions](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions/) - Subscription interactions. --- ### chainctl events subscriptions delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_events_subscriptions_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl events subscriptions delete Delete a subscription. ``` chainctl events subscriptions delete SUBSCRIPTION_ID [--yes] [--output=id] [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl events subscriptions](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions/) - Subscription interactions. --- ### chainctl events subscriptions list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_events_subscriptions_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl events subscriptions list List subscriptions. ``` chainctl events subscriptions list [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--output=id|json|table] ``` ### Options ``` --parent string The parent location name or id of the subscription. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl events subscriptions](/platform/chainctl/chainctl-docs/chainctl_events_subscriptions/) - Subscription interactions. --- ### chainctl guardener URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener Manage guardener integrations. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl guardener entitlement](/platform/chainctl/chainctl-docs/chainctl_guardener_entitlement/) - Manage a group's guardener entitlement. * [chainctl guardener github](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - Link and unlink a GitHub organization to a Chainguard group. * [chainctl guardener scans](/platform/chainctl/chainctl-docs/chainctl_guardener_scans/) - View guardener dependency scans. --- ### chainctl guardener entitlement URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_entitlement.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener entitlement Manage a group's guardener entitlement. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener](/platform/chainctl/chainctl-docs/chainctl_guardener/) - Manage guardener integrations. * [chainctl guardener entitlement get](/platform/chainctl/chainctl-docs/chainctl_guardener_entitlement_get/) - Show the guardener entitlement for a group. --- ### chainctl guardener entitlement get URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_entitlement_get.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener entitlement get Show the guardener entitlement for a group. ### Synopsis Show the guardener entitlement for a group. The entitlement records the configured repository visibility scope for the group. This administrative setting does not determine current repository coverage, which is controlled by the guardener GitHub App's repository selection. A group with no configured entitlement shows the default public scope. Requires the guardener.entitlement.list capability on the group. ``` chainctl guardener entitlement get [flags] ``` ### Options ``` --parent string Name or UIDP of the Chainguard group. Prompts interactively if omitted. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener entitlement](/platform/chainctl/chainctl-docs/chainctl_guardener_entitlement/) - Manage a group's guardener entitlement. --- ### chainctl guardener github URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github Link and unlink a GitHub organization to a Chainguard group. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener](/platform/chainctl/chainctl-docs/chainctl_guardener/) - Manage guardener integrations. * [chainctl guardener github link](/platform/chainctl/chainctl-docs/chainctl_guardener_github_link/) - Link a GitHub organization to a Chainguard group. * [chainctl guardener github migrate](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate/) - Migrate the Actions and images enabled by a repository's configuration. * [chainctl guardener github status](/platform/chainctl/chainctl-docs/chainctl_guardener_github_status/) - List the GitHub organizations linked to a Chainguard group. * [chainctl guardener github unlink](/platform/chainctl/chainctl-docs/chainctl_guardener_github_unlink/) - Unlink a GitHub organization from its Chainguard group. --- ### chainctl guardener github link URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_link.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github link Link a GitHub organization to a Chainguard group. ### Synopsis Link a GitHub organization to a Chainguard group. Requires that you are an owner of both the Chainguard group (the guardener.association.manage capability) and the GitHub organization. A browser window opens to authorize with GitHub; the guardener GitHub App must already be installed on the organization. ``` chainctl guardener github link [flags] ``` ### Options ``` --github-org string GitHub account login to link (an organization, or your own user). --parent string Name or UIDP of the Chainguard group to link to. Prompts interactively if omitted. --port int Local loopback port for the GitHub OAuth callback. (default 8989) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - Link and unlink a GitHub organization to a Chainguard group. --- ### chainctl guardener github migrate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github migrate Migrate the Actions and images enabled by a repository's configuration. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - Link and unlink a GitHub organization to a Chainguard group. * [chainctl guardener github migrate create](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_create/) - Enqueue the repository's configured Actions and image migrations. * [chainctl guardener github migrate get](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_get/) - Show the state of a migration operation. * [chainctl guardener github migrate list](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_list/) - List a group's migration operations. --- ### chainctl guardener github migrate create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github migrate create Enqueue the repository's configured Actions and image migrations. ### Synopsis Enqueue the repository's configured Actions and image migrations. Each feature requires migrate.enabled: true in its effective configuration: .chainguard/actions.yaml for Actions and .chainguard/images.yaml for images. The enabled migrations open or update separate pull requests. The command returns one operation tracking both results; by default it waits and prints each result, including any failures. Pass --wait=false to return immediately with the operation name, which you can pass to "migrate get" later. REPOSITORY may be a full URL (https://github.com/owner/repo) or the "owner/repo" shorthand; only github.com is supported today. Actions migration requires guardener.actions.migrate, and image migration requires guardener.images.migrate on the group, which must own the repository's GitHub App installation. Permissions are checked independently for each feature: an unauthorized feature reports a failure while authorized migrations continue. At least one migration permission is required to create or read an operation. A feature disabled in the deployment or repository configuration completes as a successful no-op without failing the operation. ``` chainctl guardener github migrate create REPOSITORY [flags] ``` ### Options ``` --parent string Name or UIDP of the Chainguard group that owns the installation. Prompts interactively if omitted. --timeout duration How long to wait for the migration when --wait is set. (default 10m0s) --wait Wait for the migration operation to complete before returning. (default true) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github migrate](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate/) - Migrate the Actions and images enabled by a repository's configuration. --- ### chainctl guardener github migrate get URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_get.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github migrate get Show the state of a migration operation. ### Synopsis Show the state of a migration operation returned by "migrate create". OPERATION is the operation name (operations/migrate/{group}/{id}). The owning group is derived from the name unless --parent is given. Pass --wait to poll until the operation completes. Each feature requires its own migration permission on the owning group; denied features are reported separately. Actions migration uses guardener.actions.migrate, and image migration uses guardener.images.migrate. ``` chainctl guardener github migrate get OPERATION [flags] ``` ### Options ``` --parent string Name or UIDP of the owning group. Derived from the operation name if omitted. --timeout duration How long to poll when --wait is set. (default 10m0s) --wait Poll until the migration operation completes. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github migrate](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate/) - Migrate the Actions and images enabled by a repository's configuration. --- ### chainctl guardener github migrate list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github migrate list List a group's migration operations. ### Synopsis List a group's migration operations, newest first. Each migration started with "migrate create", from the console, or by the scheduled resync is listed once. Pass its OPERATION to "migrate get" to see each feature's pull request. Use --repo to list one repository's migrations. Listing requires guardener.actions.migrate or guardener.images.migrate on the group. Operations for a feature you lack the permission for are left out. ``` chainctl guardener github migrate list [flags] ``` ### Options ``` --limit int The most migrations to list. (default 20) -o, --output string Output format: table or json. (default "table") --parent string Name or UIDP of the Chainguard group whose migrations to list. Prompts interactively if omitted. --repo string Only list migrations of this repository (a github.com URL or "owner/repo"). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github migrate](/platform/chainctl/chainctl-docs/chainctl_guardener_github_migrate/) - Migrate the Actions and images enabled by a repository's configuration. --- ### chainctl guardener github status URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_status.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github status List the GitHub organizations linked to a Chainguard group. ### Synopsis List the GitHub organizations linked to a Chainguard group. Which repositories guardener acts on is controlled by the guardener GitHub App's repository selection on GitHub — follow the SETTINGS link in the output to review or change it. Requires the guardener.association.list capability on the group (read-only; no GitHub authorization or browser flow is involved). When --parent is omitted, you are prompted to pick from your groups. ``` chainctl guardener github status [flags] ``` ### Options ``` --parent string Name or UIDP of the Chainguard group to list. Prompts interactively if omitted. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - Link and unlink a GitHub organization to a Chainguard group. --- ### chainctl guardener github unlink URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_github_unlink.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener github unlink Unlink a GitHub organization from its Chainguard group. ### Synopsis Unlink a GitHub organization from its Chainguard group. If --parent is set, this first tries your Chainguard credentials: if you hold guardener.association.manage on the group, the org is unlinked with no browser involved. Otherwise (or if those credentials are insufficient) it falls back to the GitHub authorization flow, which proves you own the organization. Either way you must be logged in to Chainguard; the GitHub flow just needs no access to the group. ``` chainctl guardener github unlink [flags] ``` ### Options ``` --github-org string GitHub account login to unlink (an organization, or your own user). --parent string Name or UIDP of the Chainguard group; enables unlinking with your Chainguard credentials (no browser). --port int Local loopback port for the GitHub OAuth callback. (default 8989) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener github](/platform/chainctl/chainctl-docs/chainctl_guardener_github/) - Link and unlink a GitHub organization to a Chainguard group. --- ### chainctl guardener scans URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_scans.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener scans View guardener dependency scans. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener](/platform/chainctl/chainctl-docs/chainctl_guardener/) - Manage guardener integrations. * [chainctl guardener scans get](/platform/chainctl/chainctl-docs/chainctl_guardener_scans_get/) - Show one dependency scan. * [chainctl guardener scans list](/platform/chainctl/chainctl-docs/chainctl_guardener_scans_list/) - List a group's dependency scans. --- ### chainctl guardener scans get URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_scans_get.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener scans get Show one dependency scan. ### Synopsis Show one dependency scan by the ID a "scans list" summary carried: every declared artifact (its package URL and the files that declared it) and the relationships between artifacts. Requires the guardener.scan.get capability on the group that owns the scan. ``` chainctl guardener scans get SCAN_ID [flags] ``` ### Options ``` -o, --output string Output format: table or json. (default "table") --parent string Name or UIDP of the Chainguard group that owns the scan. Prompts interactively if omitted. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener scans](/platform/chainctl/chainctl-docs/chainctl_guardener_scans/) - View guardener dependency scans. --- ### chainctl guardener scans list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_guardener_scans_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl guardener scans list List a group's dependency scans. ### Synopsis List summaries of a group's dependency scans. Guardener continuously scans the repositories of the group's linked GitHub installations. The listing shows one summary per repository — its most recent scan — newest first. Resolve a summary's ID to the full scan with "scans get". Requires the guardener.scan.list capability on the group. ``` chainctl guardener scans list [flags] ``` ### Options ``` -o, --output string Output format: table or json. (default "table") --parent string Name or UIDP of the Chainguard group whose scans to list. Prompts interactively if omitted. --repo string Only list scans of this repository (a github.com URL or "owner/repo"). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl guardener scans](/platform/chainctl/chainctl-docs/chainctl_guardener_scans/) - View guardener dependency scans. --- ### chainctl iam URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam IAM related commands for the Chainguard platform. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl iam account-associations](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations/) - Configure and manage cloud provider account associations. * [chainctl iam external-group-role-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings/) - Manage IdP group-to-role mappings (also available under 'iam identity-providers group-mappings'). * [chainctl iam folders](/platform/chainctl/chainctl-docs/chainctl_iam_folders/) - IAM folders interactions. * [chainctl iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) - Identity management. * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management * [chainctl iam invites](/platform/chainctl/chainctl-docs/chainctl_iam_invites/) - Manage invite codes that register identities with Chainguard. * [chainctl iam organizations](/platform/chainctl/chainctl-docs/chainctl_iam_organizations/) - IAM organization interactions. * [chainctl iam role-bindings](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings/) - IAM role-bindings resource interactions. * [chainctl iam roles](/platform/chainctl/chainctl-docs/chainctl_iam_roles/) - IAM role resource interactions. --- ### chainctl iam account-associations URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations Configure and manage cloud provider account associations. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam account-associations check](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check/) - Check the OIDC federation configurations for cloud providers. * [chainctl iam account-associations describe](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_describe/) - Describe cloud provider account associations for a location. * [chainctl iam account-associations set](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set/) - Set cloud provider account associations for a location. * [chainctl iam account-associations unset](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset/) - Remove cloud provider account associations from a location. --- ### chainctl iam account-associations check URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations check Check the OIDC federation configurations for cloud providers. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations/) - Configure and manage cloud provider account associations. * [chainctl iam account-associations check aws](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check_aws/) - Checks that the given location has been properly configured for OIDC federation with AWS * [chainctl iam account-associations check azure](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check_azure/) - Checks that the given location has been properly configured for OIDC federation with AZURE * [chainctl iam account-associations check gcp](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check_gcp/) - Checks that the given location has been properly configured for OIDC federation with GCP --- ### chainctl iam account-associations check aws URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check_aws.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations check aws Checks that the given location has been properly configured for OIDC federation with AWS ``` chainctl iam account-associations check aws ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations check](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check/) - Check the OIDC federation configurations for cloud providers. --- ### chainctl iam account-associations check azure URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check_azure.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations check azure Checks that the given location has been properly configured for OIDC federation with AZURE ``` chainctl iam account-associations check azure ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations check](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check/) - Check the OIDC federation configurations for cloud providers. --- ### chainctl iam account-associations check gcp URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check_gcp.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations check gcp Checks that the given location has been properly configured for OIDC federation with GCP ``` chainctl iam account-associations check gcp ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations check](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_check/) - Check the OIDC federation configurations for cloud providers. --- ### chainctl iam account-associations describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations describe Describe cloud provider account associations for a location. ``` chainctl iam account-associations describe ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [--aws] [--gcp] [--chainguard] [--output=id|json|table] [flags] ``` ### Options ``` --aws Include the AWS account association. --chainguard Include the Chainguard service principal account association. --gcp Include the GCP account association. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations/) - Configure and manage cloud provider account associations. --- ### chainctl iam account-associations set URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations set Set cloud provider account associations for a location. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations/) - Configure and manage cloud provider account associations. * [chainctl iam account-associations set aws](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set_aws/) - Set AWS account association for a location. * [chainctl iam account-associations set azure](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set_azure/) - Set AZURE account association for a location. * [chainctl iam account-associations set gcp](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set_gcp/) - Set GCP account association for a location. --- ### chainctl iam account-associations set aws URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set_aws.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations set aws Set AWS account association for a location. ``` chainctl iam account-associations set aws ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID --account=ACCOUNT [--name=NAME] [--description=DESCRIPTION] [--yes] [--output=id|json|table] [flags] ``` ### Options ``` --account string The AWS account ID. -d, --description string The description of the resource. -n, --name string Given name of the resource. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations set](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set/) - Set cloud provider account associations for a location. --- ### chainctl iam account-associations set azure URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set_azure.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations set azure Set AZURE account association for a location. ``` chainctl iam account-associations set azure ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID --tenant-id=TENANT_ID --client-ids=COMPONENT_NAME=CLIENT_ID [--name=NAME] [--description=DESCRIPTION] [--yes] [--output=id|json|table] [flags] ``` ### Options ``` --client-ids stringToString A chainguard component_name to azure client_id map (default []) -d, --description string The description of the resource. -n, --name string Given name of the resource. --tenant-id string The Azure Tenant ID. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations set](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set/) - Set cloud provider account associations for a location. --- ### chainctl iam account-associations set gcp URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set_gcp.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations set gcp Set GCP account association for a location. ``` chainctl iam account-associations set gcp ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID --project-id=PROJECT_ID --project-number=PROJECT_NUMBER [--name=NAME] [--description=DESCRIPTION] [--yes] [--output=id|json|table] [flags] ``` ### Options ``` -d, --description string The description of the resource. -n, --name string Given name of the resource. --project-id string The GCP project ID. --project-number string The GCP project number. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations set](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_set/) - Set cloud provider account associations for a location. --- ### chainctl iam account-associations unset URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations unset Remove cloud provider account associations from a location. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations/) - Configure and manage cloud provider account associations. * [chainctl iam account-associations unset aws](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset_aws/) - Remove AWS account configuration for a location. * [chainctl iam account-associations unset azure](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset_azure/) - Remove AZURE account configuration for a location. * [chainctl iam account-associations unset gcp](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset_gcp/) - Remove GCP account configuration for a location. --- ### chainctl iam account-associations unset aws URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset_aws.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations unset aws Remove AWS account configuration for a location. ``` chainctl iam account-associations unset aws ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [--yes] [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations unset](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset/) - Remove cloud provider account associations from a location. --- ### chainctl iam account-associations unset azure URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset_azure.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations unset azure Remove AZURE account configuration for a location. ``` chainctl iam account-associations unset azure ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [--yes] [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations unset](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset/) - Remove cloud provider account associations from a location. --- ### chainctl iam account-associations unset gcp URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset_gcp.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam account-associations unset gcp Remove GCP account configuration for a location. ``` chainctl iam account-associations unset gcp ORGANIZATION_NAME|ORGANIZATION_ID|FOLDER_NAME|FOLDER_ID [--yes] [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam account-associations unset](/platform/chainctl/chainctl-docs/chainctl_iam_account-associations_unset/) - Remove cloud provider account associations from a location. --- ### chainctl iam external-group-role-mappings URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam external-group-role-mappings Manage IdP group-to-role mappings (also available under 'iam identity-providers group-mappings'). ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam external-group-role-mappings create](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings_create/) - Create an IdP group-to-role mapping. * [chainctl iam external-group-role-mappings delete](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings_delete/) - Delete IdP group-to-role mappings. * [chainctl iam external-group-role-mappings list](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings_list/) - List IdP group-to-role mappings. --- ### chainctl iam external-group-role-mappings create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam external-group-role-mappings create Create an IdP group-to-role mapping. ``` chainctl iam external-group-role-mappings create --external-group-id GROUP --role ROLE --scope SCOPE --idp IDP [flags] ``` ### Examples ``` # Map IdP group "Platform-SRE" to the owner role at the org root chainctl iam external-group-role-mappings create --external-group-id "Platform-SRE" --role owner --scope my-org --idp my-idp ``` ### Options ``` --external-group-id string IdP group identifier (the claim value from the IdP token) --idp string Identity provider (name or UIDP) that owns this mapping --role string Role UIDP or name to grant --scope string Organization or group (name or UIDP) where the role applies ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam external-group-role-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings/) - Manage IdP group-to-role mappings (also available under 'iam identity-providers group-mappings'). --- ### chainctl iam external-group-role-mappings delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam external-group-role-mappings delete Delete IdP group-to-role mappings. ``` chainctl iam external-group-role-mappings delete {MAPPING_ID | --all --idp IDP} [--yes] [flags] ``` ### Examples ``` # Delete a single group mapping by ID chainctl iam external-group-role-mappings delete MAPPING_UIDP # Delete all group mappings for an identity provider chainctl iam external-group-role-mappings delete --all --idp IDP_UIDP ``` ### Options ``` --all Delete all mappings for the identity provider given by --idp --idp string Identity provider (name or UIDP) whose mappings to delete (with --all) -y, --yes Skip confirmation prompt ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam external-group-role-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings/) - Manage IdP group-to-role mappings (also available under 'iam identity-providers group-mappings'). --- ### chainctl iam external-group-role-mappings list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam external-group-role-mappings list List IdP group-to-role mappings. ``` chainctl iam external-group-role-mappings list --parent PARENT [--idp IDP] [flags] ``` ### Examples ``` # List all group mappings in an organization chainctl iam external-group-role-mappings list --parent my-org # List mappings for a specific identity provider chainctl iam external-group-role-mappings list --parent my-org --idp my-idp ``` ### Options ``` --idp string Narrow results to a specific identity provider (name or UIDP) --parent string Organization or folder (name or UIDP) to list mappings under ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam external-group-role-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_external-group-role-mappings/) - Manage IdP group-to-role mappings (also available under 'iam identity-providers group-mappings'). --- ### chainctl iam folders URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_folders.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam folders IAM folders interactions. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam folders delete](/platform/chainctl/chainctl-docs/chainctl_iam_folders_delete/) - Delete a folder. * [chainctl iam folders describe](/platform/chainctl/chainctl-docs/chainctl_iam_folders_describe/) - Describe a folder. * [chainctl iam folders list](/platform/chainctl/chainctl-docs/chainctl_iam_folders_list/) - List folders under an organization. * [chainctl iam folders update](/platform/chainctl/chainctl-docs/chainctl_iam_folders_update/) - Update a folder. --- ### chainctl iam folders delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_folders_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam folders delete Delete a folder. ``` chainctl iam folders delete [FOLDER_NAME | FOLDER_ID] [--skip-refresh] [--yes] ``` ### Examples ``` # Delete a folder by ID chainctl iam folders delete 19d3a64f20c64ba3ccf1bc86ce59d03e705959ad/efb53f2857d567f2 # Delete a folder by name chainctl iam folders delete my-folder # Delete a folder to be selected interactively chainctl iam folders delete ``` ### Options ``` --skip-refresh Skips attempting to reauthenticate and refresh the Chainguard auth token if it becomes out of date. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam folders](/platform/chainctl/chainctl-docs/chainctl_iam_folders/) - IAM folders interactions. --- ### chainctl iam folders describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_folders_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam folders describe Describe a folder. ``` chainctl iam folders describe [FOLDER_NAME | FOLDER_ID] [--output=json] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam folders](/platform/chainctl/chainctl-docs/chainctl_iam_folders/) - IAM folders interactions. --- ### chainctl iam folders list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_folders_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam folders list List folders under an organization. ``` chainctl iam folders list ORGANIZATION_NAME | ORGANIZATION_ID [--output=id|json|table|tree] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam folders](/platform/chainctl/chainctl-docs/chainctl_iam_folders/) - IAM folders interactions. --- ### chainctl iam folders update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_folders_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam folders update Update a folder. ``` chainctl iam folders update FOLDER_NAME | FOLDER_ID [--name FOLDER_NAME] [--description FOLDER_DESCRIPTION] ``` ### Examples ``` # Update a folder's name chainctl iam folders update my-folder --name new-folder-name # Update a folder's description chainctl iam folders update 19d3a64f20c64ba3ccf1bc86ce59d03e705959ad/efb53f2857d567f2 --description "A description of the folder." # Remove a folder's description chainctl iam folders update my-folder --description "" ``` ### Options ``` -d, --description string The updated description for the folder. -n, --name string The updated name for the folder. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam folders](/platform/chainctl/chainctl-docs/chainctl_iam_folders/) - IAM folders interactions. --- ### chainctl iam identities URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities Identity management. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam identities create](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/) - Create a new identity. * [chainctl iam identities delete](/platform/chainctl/chainctl-docs/chainctl_iam_identities_delete/) - Delete one or more identities. * [chainctl iam identities describe](/platform/chainctl/chainctl-docs/chainctl_iam_identities_describe/) - View the details of an identity. * [chainctl iam identities list](/platform/chainctl/chainctl-docs/chainctl_iam_identities_list/) - List identities. * [chainctl iam identities update](/platform/chainctl/chainctl-docs/chainctl_iam_identities_update/) - Update an identity --- ### chainctl iam identities create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create Create a new identity. ``` chainctl iam identities create NAME {--filename FILE | {--identity-issuer=ISS | --identity-issuer-pattern=PAT} {--subject=SUB | --subject-pattern=PAT} [--audience=AUD | --audience-pattern=PAT] [--claim-pattern=claim:pattern,claim:pattern...] | --identity-issuer=ISS --issuer-keys=KEYS --subject=SUB [--expiration=yyyy-mm-dd]} [--parent=PARENT] [--description=DESC] [--role=ROLE,ROLE,...] [--output=id|json|table] ``` ### Examples ``` # Create a static identity using the default expiration. chainctl iam identities create my-identity --identity-issuer=https://issuer.mycompany.com --issuer-keys=deadbeef --subject=1234 # Create an identity with literal values to match from claims. chainctl iam identities create my-identity --identity-issuer=https://issuer.mycompany.com --subject=1234 # Create an identity using patterns to match claims chainctl iam identities create my-identity --identity-issuer-pattern="https://*.mycompany\.com" --subject-pattern="^\d{4}$" # Create an identity from a JSON file definition and bind to a role chainctl iam identities create my-identity -f path/to/identity-definition.json --role=viewer ``` ### Options ``` --audience string The audience of the identity (optional). --audience-pattern string A pattern to match the audience of the identity (optional). --claim-pattern stringArray A comma-separated list of claim:pattern pairs of custom claims to match for this identity (optional). -d, --description string The description of the resource. --expiration string The time when the issuer_keys will expire. Defaults to / Maximum of 30 days after creation time (yyyy-mm-dd). -f, --filename string A file that contains the identity definition, in either YAML or JSON. --identity-issuer string The issuer of the identity. --identity-issuer-pattern string A pattern to match the issuer of the identity. --issuer-keys string JWKS-formatted public keys for the issuer. -n, --name string Given name of the resource. --parent string The name or id of the parent location to create this identity under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --role strings A comma separated list of names or IDs of roles to bind this identity to (optional). --service-principal string The service principal that is allowed to assume this identity. --subject string The subject of the identity. --subject-pattern string A pattern to match the subject of the identity. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) - Identity management. * [chainctl iam identities create aws](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws/) - Create a new identity for an AWS IAM resource. * [chainctl iam identities create delegate](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_delegate/) - Create a delegate: an identity that acts only on grants you mint for it. * [chainctl iam identities create github](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_github/) - * [chainctl iam identities create gitlab](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_gitlab/) - --- ### chainctl iam identities create aws URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create aws Create a new identity for an AWS IAM resource. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities create](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/) - Create a new identity. * [chainctl iam identities create aws role](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws_role/) - * [chainctl iam identities create aws user](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws_user/) - --- ### chainctl iam identities create aws role URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws_role.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create aws role ``` chainctl iam identities create aws role NAME --aws-account-id=ACCOUNT --aws-role-name=NAME [--aws-role-id=ID] [--aws-partition=PARTITION] [--parent=PARENT] [--description=DESC] [--role=ROLE,ROLE,...] [--output=id|json|table] ``` ### Examples ``` # Create an assumable identity for an AWS IAM role. chainctl iam identities create aws role my-aws-identity --aws-account-id=123456789012 --aws-role-name=my-role # Create an assumable identity for an AWS IAM role. Bind it to the registry.pull role. chainctl iam identities create aws role my-aws-identity --aws-account-id=123456789012 --aws-role-name=my-role --role=registry.pull # Provide the unique ID of an AWS IAM role. This prevents the identity from being assumed if the role is deleted and then recreated with the same name. chainctl iam identities create aws role my-aws-identity --aws-account-id=123456789012 --aws-role-name=my-role --aws-role-id=AROAEXAMPLEC2UL7LUB # Create an assumable identity for an AWS IAM role in the aws-us-gov partition. chainctl iam identities create aws role my-aws-identity --aws-partition=aws-us-gov --aws-account-id=123456789012 --aws-role-name=my-role ``` ### Options ``` --aws-account-id string The ID of the AWS account. --aws-partition string The partition in which the role is located. For instance: aws, aws-cn or aws-us-gov. (default "aws") --aws-role-id string The unique ID of the IAM role. --aws-role-name string The name of the IAM role. -d, --description string The description of the resource. -n, --name string Given name of the resource. --parent string The name or id of the parent location to create this identity under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --role strings A comma separated list of names or IDs of roles to bind this identity to (optional). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities create aws](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws/) - Create a new identity for an AWS IAM resource. --- ### chainctl iam identities create aws user URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws_user.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create aws user ``` chainctl iam identities create aws user NAME --aws-account-id=ACCOUNT --aws-user-name=NAME [--aws-user-id=ID] [--aws-partition=PARTITION] [--parent=PARENT] [--description=DESC] [--role=ROLE,ROLE,...] [--output=id|json|table] ``` ### Examples ``` # Create an assumable identity for an IAM user chainctl iam identities create aws user my-aws-identity --aws-account-id=123456789012 --aws-user-name=my-user # Create an assumable identity for an IAM user. Bind it to the registry.pull role. chainctl iam identities create aws user my-aws-identity --aws-account-id=123456789012 --aws-user-name=my-user --role=registry.pull # Provide the unique ID of the user. This prevents the identity from being assumed if the user is deleted and then recreated with the same name. chainctl iam identities create aws user my-aws-identity --aws-account-id=123456789012 --aws-user-name=my-user --aws-user-id=AIDAEXAMPLEC2UL7LUB # Create an assumable identity for a user in the aws-us-gov partition chainctl iam identities create aws user my-aws-identity --aws-partition=aws-us-gov --aws-account-id=123456789012 --aws-user-name=my-user ``` ### Options ``` --aws-account-id string The ID of the AWS account. --aws-partition string The partition in which the user is located. For instance: aws, aws-cn or aws-us-gov. (default "aws") --aws-user-id string The unique ID of the IAM user. --aws-user-name string The name of the IAM user. -d, --description string The description of the resource. -n, --name string Given name of the resource. --parent string The name or id of the parent location to create this identity under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --role strings A comma separated list of names or IDs of roles to bind this identity to (optional). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities create aws](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_aws/) - Create a new identity for an AWS IAM resource. --- ### chainctl iam identities create delegate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_delegate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create delegate Create a delegate: an identity that acts only on grants you mint for it. ### Synopsis Create a delegate: an identity that acts only on grants you mint for it. A delegate holds no role bindings and cannot be assumed directly. Its only use is the delegated exchange of a grant minted by its subject with "chainctl auth token --delegate=NAME". The exchanged token's subject is the delegate, it records you as the actor, and it carries at most the grant's capabilities within the delegate's organization. The delegate pins your identity as its subject (or --subject, which requires permission to create identities), this environment's issuer, and a randomly generated delegation audience that grants for it are minted for. Grants are stateless, so there is no per-grant list or revoke. Deleting the delegate stops every future exchange; grants already minted for it expire within 60 minutes. ``` chainctl iam identities create delegate NAME [--parent=PARENT] [--description=DESC] [--subject=IDENTITY_ID] [--yes] [--output=id|json|table] ``` ### Examples ``` # Create a delegate in an organization and mint a grant for it. chainctl iam identities create delegate my-delegate --parent=my-org chainctl auth token --delegate=my-delegate --role=viewer --scope=ORGANIZATION_ID # As an administrator, create a delegate for another user. chainctl iam identities create delegate their-delegate --parent=my-org --subject=IDENTITY_ID ``` ### Options ``` -d, --description string The description of the resource. -n, --name string Given name of the resource. --parent string The name or id of the parent location to create this identity under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --subject string The Chainguard identity ID allowed to mint grants for this delegate (default: your own). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities create](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/) - Create a new identity. --- ### chainctl iam identities create github URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_github.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create github ``` chainctl iam identities create github NAME --github-repo=REPO [--github-ref=REF] [--github-audience=AUD] [--parent=PARENT] [--description=DESC] [--role=ROLE,ROLE,...] [--output=id|json|table] ``` ### Examples ``` # Create a GitHub Actions identity for any branch in a repo chainctl iam identities create github my-gha-identity --github-repo=my-org/repo-name --parent=eng-org # Create a GitHub Actions identity for a given branch in a repo and bind to a role chainctl iam identities create github my-gha-identity --github-repo=my-org/repo-name --github-ref=refs/heads/test-branch --role=owner ``` ### Options ``` -d, --description string The description of the resource. --github-audience string The audience for the GitHub OIDC token --github-ref string The branch reference for the executing action (optional). --github-repo string The name of a GitHub repo where the action executes. -n, --name string Given name of the resource. --parent string The name or id of the parent location to create this identity under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --role strings A comma separated list of names or IDs of roles to bind this identity to (optional). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities create](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/) - Create a new identity. --- ### chainctl iam identities create gitlab URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_create_gitlab.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities create gitlab ``` chainctl iam identities create gitlab NAME --project-path=GITLAB-GROUP/GITLAB-PROJECT --ref-type={tag|branch} [--ref=REF] [--parent=PARENT] [--description=DESC] [--role=ROLE,ROLE,...] [--output=id|json|table] ``` ### Examples ``` # Create a Gitlab CI identity for any branch in a given Gitlab project chainctl iam identities create gitlab my-gitlab-identity --project-path=my-group/my-project --ref-type=branch --parent=eng-org # Create a Gitlab CI identity for a given branch in a Gitlab project and bind to a role chainctl iam identities create gitlab my-gitlab-identity --project-path=my-group/my-project --ref-type=branch --ref=main --role=owner ``` ### Options ``` -d, --description string The description of the resource. -n, --name string Given name of the resource. --parent string The name or id of the parent location to create this identity under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --project-path string The name of a Gitlab project where the action executes in the form "group-name/project-name[/foo/bar]". You can use a "*" for project-name (or sub-projects) to match any project in the group. --ref string The reference for the executing action. If left empty or "*", all references will match. --ref-type string The type of reference for the executing action, must be either "tag" or "branch". --role strings A comma separated list of names or IDs of roles to bind this identity to (optional). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities create](/platform/chainctl/chainctl-docs/chainctl_iam_identities_create/) - Create a new identity. --- ### chainctl iam identities delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities delete Delete one or more identities. ### Synopsis Delete one or more identities. Deleting a delegate stops every future exchange of its grants. Grants are stateless, so ones already minted for it are not revoked; they expire within 60 minutes. ``` chainctl iam identities delete {IDENTITY_NAME | IDENTITY_ID | --expired [--parent=PARENT]} [--yes] ``` ### Examples ``` # Delete an identity by name chainctl iam identities delete my-identity # Delete all expired static identities in an organization chainctl iam identities delete --expired --parent=my-org # Delete all expired static identities in every location, even with default.group set chainctl iam identities delete --expired --parent="" ``` ### Options ``` --expired Delete all expired identities. --parent string Name or ID of the parent location to delete expired identities from. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) - Identity management. --- ### chainctl iam identities describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities describe View the details of an identity. ``` chainctl iam identities describe {IDENTITY_NAME | IDENTITY_ID} [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) - Identity management. --- ### chainctl iam identities list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities list List identities. ``` chainctl iam identities list [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--name=NAME] [--relationship={aws|claim_match|delegate|pull_token|service_principal|static}] [--expired] [--recursive] [--output=id|json|table] ``` ### Examples ``` # List all identities. chainctl iam identities list # List all static identities. chainctl iam identities list --relationship=static # Filter identities by name. chainctl iam identities list --name=my-identity # List expired identities chainctl iam identities list --expired # List only identities directly in an organization, excluding nested locations chainctl iam identities list --parent=my-org --recursive=false ``` ### Options ``` --expired Return only expired static identities. --name string Filter identities by name. --parent string The name or id of the parent location to list identities from. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --recursive List identities from the parent location and all nested locations. Set to false to list only the parent location. (default true) --type string Filter identities by type (one of aws, claim_match, delegate, pull_token, service_principal, static). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) - Identity management. --- ### chainctl iam identities update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identities_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identities update Update an identity ``` chainctl iam identities update IDENTITY_NAME | IDENTITY_ID [--description=DESC] [--identity-issuer=ISS | --identity-issuer-pattern=PAT] [--subject=SUB | --subject-pattern=PAT] [--audience=AUD | --audience-pattern=PAT] [--claim-pattern=claim:pattern,claim:pattern...] [--issuer-keys=KEYS] [--expiration=yyyy-mm-dd] [--output=id|json|table] [flags] ``` ### Examples ``` # Update the issuer of an identity. chainctl iam identities update my-identity --identity-issuer=https://new-issuer.mycompany.com # Update the subject to a pattern and update the audience of an identity. chainctl iam identities update my-identity --subject-pattern="^\d{4}$" --audience=some-audience ``` ### Options ``` --audience string The audience of the identity (optional). --audience-pattern string A pattern to match the audience of the identity (optional). --claim-pattern stringArray A comma-separated list of claim:pattern pairs of custom claims to match for this identity. --description string A description of the identity (optional). --expiration string The time when the issuer_keys will expire. Defaults to / Maximum of 30 days after creation time (yyyy-mm-dd). --identity-issuer string The issuer of the identity. --identity-issuer-pattern string A pattern to match the issuer of the identity. --issuer-keys string JWKS-formatted public keys for the issuer. --subject string The subject of the identity. --subject-pattern string A pattern to match the subject of the identity. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identities](/platform/chainctl/chainctl-docs/chainctl_iam_identities/) - Identity management. --- ### chainctl iam identity-providers URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers customer managed identity provider management ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam identity-providers create](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_create/) - Create an identity provider * [chainctl iam identity-providers delete](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_delete/) - Delete an identity provider. * [chainctl iam identity-providers describe](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_describe/) - Describe an identity provider. * [chainctl iam identity-providers group-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings/) - Manage IdP group-to-role mappings for an identity provider. * [chainctl iam identity-providers list](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_list/) - List identity providers. * [chainctl iam identity-providers scim](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim/) - Manage SCIM provisioning for an identity provider. * [chainctl iam identity-providers update](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_update/) - Update an identity provider --- ### chainctl iam identity-providers create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers create Create an identity provider ``` chainctl iam identity-providers create --parent ORGANIZATION_NAME | ORGANIZATION_ID [--name=NAME] [--description=DESCRIPTION] --oidc-issuer=ISSUER --oidc-client-id=CLIENT_ID {--oidc-client-secret=CLIENT_SECRET | --oidc-pkce-enabled} [--oidc-additional-scopes=SCOPE,...] --default-role=ROLE [--output=id|json|table] ``` ### Examples ``` # Setup a confidential OIDC provider and bind new users to the viewer role chainctl iam identity-providers create --name=google --parent=example \ --oidc-issuer=https://accounts.google.com \ --oidc-client-id=foo \ --oidc-client-secret=bar \ --default-role=viewer # Setup a public OIDC provider chainctl iam identity-providers create --name=google --parent=example \ --oidc-issuer=https://accounts.google.com \ --oidc-client-id=foo \ --oidc-pkce-enabled \ --default-role=viewer ``` ### Options ``` --configuration-type string Type of identity provider. Only OIDC supported currently (default "OIDC") --default-role string Role to grant users on first login --description string Description of identity provider --name string Name of identity provider --oidc-additional-scopes stringArray additional scopes to request for OIDC type identity provider --oidc-client-id string client id for OIDC type identity provider --oidc-client-secret string client secret for OIDC type identity provider --oidc-correlation-rule string How OIDC logins are matched to SCIM-provisioned users: sub_equals_external_id (default; stock Okta) or oid_equals_external_id (Microsoft Entra ID; requires --oidc-additional-scopes=profile). Permanent: the rule cannot be changed after creation. --oidc-groups-claim string OIDC token claim carrying group memberships, used for group-to-role mappings (empty disables group mapping). Manage the mappings with 'chainctl iam identity-providers group-mappings'. --oidc-issuer string Issuer URL for OIDC type identity provider --oidc-pkce-enabled Include a PKCE challenge during authorization code exchange. Upstream IDP must be configured to respond. --oidc-token-endpoint-auth-method string How the client secret is presented at the token endpoint: basic or post. Empty selects the method the IDP advertises in discovery. Set it when the IDP endpoint advertises one method but the specific app registration expects the other. --parent string The name or ID of the location the identity provider belongs to. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management --- ### chainctl iam identity-providers delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers delete Delete an identity provider. ``` chainctl iam identity-providers delete IDENTITY_PROVIDER_ID|IDENTITY_PROVIDER_NAME [--yes] [--output=id] ``` ### Examples ``` # Delete an identity provider by ID chainctl iam identity-providers delete 9b6da6e64b45129eb4e9f9f3ce9b69ca2a550c6b/034e4afcda8c0b07 # Delete an identity provider by name chainctl iam identity-providers delete my-idp ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management --- ### chainctl iam identity-providers describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers describe Describe an identity provider. ### Synopsis Describe an identity provider, including its OIDC configuration and SCIM provisioning status. The SCIM row reports one of: enabled SCIM provisioning is on. disabled SCIM provisioning is off. not reported the API surface did not return SCIM status (for example, a v1-negotiated organization). This is not the same as disabled. When SCIM is reported, describe also shows the credential lifecycle state (live, not_issued, expired, revoked, or rotating), the SCIM endpoint URL, and the token expiry, so an enabled-but-expired or tokenless provider is not mistaken for a healthy one. In JSON output the same three states are {"scim":{"enabled":true}}, {"scim":{"enabled":false}}, and the scim key omitted entirely. A missing scim key means "not reported", not "disabled", so a scripted check must treat it as unknown rather than off (with jq, ".scim == null" is unknown and ".scim.enabled" is the boolean when present). To change SCIM state or manage tokens, see: chainctl iam identity-providers scim --help ``` chainctl iam identity-providers describe [IDENTITY_PROVIDER_NAME | IDENTITY_PROVIDER_ID] [--output=json|table] ``` ### Examples ``` # Describe an identity provider by name chainctl iam identity-providers describe my-idp # Describe an identity provider by ID chainctl iam identity-providers describe 9b6da6e64b45129eb4e9f9f3ce9b69ca2a550c6b/034e4afcda8c0b07 # Emit JSON for scripting (the scim key is omitted when SCIM status is not reported) chainctl iam identity-providers describe my-idp -o json ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management --- ### chainctl iam identity-providers group-mappings URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers group-mappings Manage IdP group-to-role mappings for an identity provider. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management * [chainctl iam identity-providers group-mappings create](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings_create/) - Create an IdP group-to-role mapping. * [chainctl iam identity-providers group-mappings delete](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings_delete/) - Delete an identity provider's group-to-role mappings. * [chainctl iam identity-providers group-mappings list](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings_list/) - List an identity provider's group-to-role mappings. --- ### chainctl iam identity-providers group-mappings create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers group-mappings create Create an IdP group-to-role mapping. ``` chainctl iam identity-providers group-mappings create IDENTITY_PROVIDER --external-group-id GROUP --role ROLE --scope SCOPE [flags] ``` ### Examples ``` # Map IdP group "Platform-SRE" to the owner role at the org root chainctl iam identity-providers group-mappings create my-idp --external-group-id "Platform-SRE" --role owner --scope my-org ``` ### Options ``` --external-group-id string IdP group identifier (the claim value from the IdP token) --role string Role UIDP or name to grant --scope string Organization or group (name or UIDP) where the role applies ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers group-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings/) - Manage IdP group-to-role mappings for an identity provider. --- ### chainctl iam identity-providers group-mappings delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers group-mappings delete Delete an identity provider's group-to-role mappings. ``` chainctl iam identity-providers group-mappings delete IDENTITY_PROVIDER {MAPPING_ID | --all} [--yes] [flags] ``` ### Examples ``` # Delete a single group mapping by ID chainctl iam identity-providers group-mappings delete my-idp MAPPING_UIDP # Delete all group mappings for an identity provider chainctl iam identity-providers group-mappings delete my-idp --all ``` ### Options ``` --all Delete all mappings for the identity provider -y, --yes Skip confirmation prompt ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers group-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings/) - Manage IdP group-to-role mappings for an identity provider. --- ### chainctl iam identity-providers group-mappings list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers group-mappings list List an identity provider's group-to-role mappings. ``` chainctl iam identity-providers group-mappings list IDENTITY_PROVIDER [flags] ``` ### Examples ``` # List all group mappings for an identity provider chainctl iam identity-providers group-mappings list my-idp ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers group-mappings](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_group-mappings/) - Manage IdP group-to-role mappings for an identity provider. --- ### chainctl iam identity-providers list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers list List identity providers. ``` chainctl iam identity-providers list [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--recursive] [--output=json|table|tree] ``` ### Examples ``` # List identity providers chainctl iam identity-providers list # Filter list by location chainctl iam identity-providers list --parent=my-org # List only identity providers directly in an organization, excluding nested locations chainctl iam identity-providers list --parent=my-org --recursive=false ``` ### Options ``` --parent string List identity providers from this location. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --recursive List identity providers from the parent location and all nested locations. Set to false to list only the parent location. (default true) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management --- ### chainctl iam identity-providers scim URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim Manage SCIM provisioning for an identity provider. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management * [chainctl iam identity-providers scim disable](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_disable/) - Disable SCIM provisioning for an identity provider. * [chainctl iam identity-providers scim enable](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_enable/) - Enable SCIM provisioning for an identity provider. * [chainctl iam identity-providers scim token](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token/) - Manage the SCIM provisioning bearer token for an identity provider. * [chainctl iam identity-providers scim users](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_users/) - Inspect the SCIM-provisioned users of an identity provider. --- ### chainctl iam identity-providers scim disable URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_disable.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim disable Disable SCIM provisioning for an identity provider. ### Synopsis Disable SCIM provisioning for an identity provider. The identity provider's SCIM configuration is created when its first SCIM token is generated, so run `chainctl iam identity-providers scim token generate` before switching provisioning on or off. ``` chainctl iam identity-providers scim disable IDENTITY_PROVIDER [flags] ``` ### Options ``` -y, --yes Skip confirmation prompt ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim/) - Manage SCIM provisioning for an identity provider. --- ### chainctl iam identity-providers scim enable URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_enable.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim enable Enable SCIM provisioning for an identity provider. ### Synopsis Enable SCIM provisioning for an identity provider. The identity provider's SCIM configuration is created when its first SCIM token is generated, so run `chainctl iam identity-providers scim token generate` before switching provisioning on or off. ``` chainctl iam identity-providers scim enable IDENTITY_PROVIDER [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim/) - Manage SCIM provisioning for an identity provider. --- ### chainctl iam identity-providers scim token URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim token Manage the SCIM provisioning bearer token for an identity provider. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim/) - Manage SCIM provisioning for an identity provider. * [chainctl iam identity-providers scim token generate](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token_generate/) - Generate the first SCIM bearer token for an identity provider. * [chainctl iam identity-providers scim token regenerate](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token_regenerate/) - Regenerate the SCIM bearer token for an identity provider. * [chainctl iam identity-providers scim token revoke](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token_revoke/) - Revoke the SCIM bearer token for an identity provider. --- ### chainctl iam identity-providers scim token generate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token_generate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim token generate Generate the first SCIM bearer token for an identity provider. ### Synopsis Generate the first SCIM bearer token for an identity provider. Provisioning stays off until SCIM is explicitly enabled for the identity provider. The plaintext token is shown exactly once; only its SHA-256 digest is stored. If a token already exists (even expired or revoked), regenerate it instead. ``` chainctl iam identity-providers scim token generate IDENTITY_PROVIDER [--output=json] [flags] ``` ### Examples ``` # Generate a SCIM token that expires in a year chainctl iam identity-providers scim token generate my-idp --expires-in 8760h ``` ### Options ``` --expires-in duration How long until the token expires (maximum two years). Unset defaults to one year. --never-expires Explicitly issue a token without a planned expiry. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim token](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token/) - Manage the SCIM provisioning bearer token for an identity provider. --- ### chainctl iam identity-providers scim token regenerate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token_regenerate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim token regenerate Regenerate the SCIM bearer token for an identity provider. ### Synopsis Regenerate the SCIM bearer token, whether the current one is live, expired, or revoked. A live token keeps authenticating for the overlap window (at most 24h) while the IdP connector is reconfigured with the new one. The new plaintext token is shown exactly once. If the current token may be exposed, do not rely on the overlap: revoke it, or regenerate with --overlap 0 for an immediate cutover. ``` chainctl iam identity-providers scim token regenerate IDENTITY_PROVIDER [--output=json] [flags] ``` ### Examples ``` # Regenerate with a one-hour overlap for reconfiguring the IdP chainctl iam identity-providers scim token regenerate my-idp --overlap 1h # Regenerate with a one-year expiry and an immediate cutover chainctl iam identity-providers scim token regenerate my-idp --overlap 0 --expires-in 8760h ``` ### Options ``` --expires-in duration How long until the new token expires (maximum two years). Unset defaults to one year. --never-expires Explicitly issue a token without a planned expiry. --overlap duration How long a live previous token keeps authenticating (at most 24h). 0 cuts over immediately; unset defaults to one hour. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim token](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token/) - Manage the SCIM provisioning bearer token for an identity provider. --- ### chainctl iam identity-providers scim token revoke URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token_revoke.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim token revoke Revoke the SCIM bearer token for an identity provider. ### Synopsis Immediately invalidate the identity provider's SCIM bearer tokens (current and regeneration-overlap). No SCIM request can authenticate until a new token is issued, so inbound provisioning stops at once. Regenerate to install a fresh credential. ``` chainctl iam identity-providers scim token revoke IDENTITY_PROVIDER [--yes] [flags] ``` ### Options ``` -y, --yes Skip confirmation prompt ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim token](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_token/) - Manage the SCIM provisioning bearer token for an identity provider. --- ### chainctl iam identity-providers scim users URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_users.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim users Inspect the SCIM-provisioned users of an identity provider. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim/) - Manage SCIM provisioning for an identity provider. * [chainctl iam identity-providers scim users list](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_users_list/) - List the SCIM-provisioned users of an identity provider. --- ### chainctl iam identity-providers scim users list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_users_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers scim users list List the SCIM-provisioned users of an identity provider. ### Synopsis List the users a SCIM connector has provisioned for an identity provider, including users provisioned but not yet logged in — their linked identity is empty until first login. If the identity provider is omitted, the single identity provider in your organization is used, or you are prompted to choose. ``` chainctl iam identity-providers scim users list [IDENTITY_PROVIDER] [--output=json|table] [flags] ``` ### Examples ``` # List SCIM users for your organization's identity provider chainctl iam identity-providers scim users list # List for a specific identity provider, showing only active users chainctl iam identity-providers scim users list my-idp --active # Find a provisioned user by their SCIM userName chainctl iam identity-providers scim users list --user-name=alice@example.com ``` ### Options ``` --active Show only active users. --external-id string Filter by exact IdP-assigned externalId. --inactive Show only inactive (deprovisioned) users. --user-name string Filter by exact SCIM userName (case-insensitive). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers scim users](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_scim_users/) - Inspect the SCIM-provisioned users of an identity provider. --- ### chainctl iam identity-providers update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam identity-providers update Update an identity provider ``` chainctl iam identity-providers update IDENTITY_PROVIDER_ID [--name=NAME] [--description=DESCRIPTION] [--oidc-issuer=ISSUER] [--oidc-client-id=CLIENTID] [--oidc-client-secret=CLIENTSECRET] [--oidc-additional-scopes=SCOPE,...] [--oidc-pkce-enabled] [--default-role=ROLE] [--output=id|json|table] ``` ### Examples ``` # Update name and description of an identity provider by ID chainctl iam identity-providers update fb694596eb1678321f94eec283e1e0be690f655c/a2973bac66ebfde3 --name=new-name --description=new-description # Update the default role for an identity provider by name chainctl iam identity-providers update my-idp --default-role=viewer ``` ### Options ``` --configuration-type string Type of identity provider. Only OIDC supported currently (default "OIDC") --default-role string Optional role to grant users on first login --description string Description of identity provider --name string Name of identity provider --oidc-additional-scopes stringArray additional scopes to request for OIDC type identity provider --oidc-client-id string client id for OIDC type identity provider --oidc-client-secret string client secret for OIDC type identity provider --oidc-groups-claim string OIDC token claim carrying group memberships, used for group-to-role mappings (empty disables group mapping). Manage the mappings with 'chainctl iam identity-providers group-mappings'. --oidc-issuer string Issuer URL for OIDC type identity provider --oidc-pkce-enabled Include a PKCE challenge during authorization code exchange. Upstream IDP must be configured to respond. --oidc-token-endpoint-auth-method string How the client secret is presented at the token endpoint: basic or post. Empty selects the method the IDP advertises in discovery. Set it when the IDP endpoint advertises one method but the specific app registration expects the other. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam identity-providers](/platform/chainctl/chainctl-docs/chainctl_iam_identity-providers/) - customer managed identity provider management --- ### chainctl iam invites URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_invites.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam invites Manage invite codes that register identities with Chainguard. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam invites create](/platform/chainctl/chainctl-docs/chainctl_iam_invites_create/) - Generate an invite code to identities with Chainguard. * [chainctl iam invites delete](/platform/chainctl/chainctl-docs/chainctl_iam_invites_delete/) - Delete invite codes * [chainctl iam invites list](/platform/chainctl/chainctl-docs/chainctl_iam_invites_list/) - List organization and folder invites. --- ### chainctl iam invites create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_invites_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam invites create Generate an invite code to identities with Chainguard. ``` chainctl iam invites create [ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--role=ROLE_ID|ROLE_NAME] [--ttl=TTL_DURATION] [--email=EMAIL] [--single-use] [--output=id|json|table] ``` ### Examples ``` # Create an invite that will be valid for 5 days: chainctl iam invite create my-org-name --role=viewer --ttl=5d # Create an invite that only Kim can accept: chainctl iam invite create my-org-name --email=kim@example.com # Create an invite code that can only be used once. chainctl iam invite create my-org-name --single-use ``` ### Options ``` --email string The email address that is allowed to accept this invite code. --role string Role is used to role-bind the invited to the associated location. --single-use The invite can only be used once before it is invalidated. --ttl duration Duration the invite code will be valid. (default 168h0m0s) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam invites](/platform/chainctl/chainctl-docs/chainctl_iam_invites/) - Manage invite codes that register identities with Chainguard. --- ### chainctl iam invites delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_invites_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam invites delete Delete invite codes ``` chainctl iam invites delete {INVITE_ID... | --expired} [--yes] [flags] ``` ### Options ``` --expired When true, delete all expired invite codes. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam invites](/platform/chainctl/chainctl-docs/chainctl_iam_invites/) - Manage invite codes that register identities with Chainguard. --- ### chainctl iam invites list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_invites_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam invites list List organization and folder invites. ``` chainctl iam invites list [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--output=id|json|table] ``` ### Examples ``` # List all accessible invites chainctl iam invites list # Filter invites by location chainctl iam invites list --parent=my-org ``` ### Options ``` --parent string List invites from this location. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam invites](/platform/chainctl/chainctl-docs/chainctl_iam_invites/) - Manage invite codes that register identities with Chainguard. --- ### chainctl iam organizations URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_organizations.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam organizations IAM organization interactions. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam organizations delete](/platform/chainctl/chainctl-docs/chainctl_iam_organizations_delete/) - Delete an organization. * [chainctl iam organizations describe](/platform/chainctl/chainctl-docs/chainctl_iam_organizations_describe/) - Describe an organization. * [chainctl iam organizations list](/platform/chainctl/chainctl-docs/chainctl_iam_organizations_list/) - List organizations. --- ### chainctl iam organizations delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_organizations_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam organizations delete Delete an organization. ``` chainctl iam organizations delete [ORGANIZATION_NAME | ORGANIZATION_ID] [--skip-refresh] [--yes] ``` ### Examples ``` # Delete an organization by ID chainctl iam organizations delete e533448ca9770c46f99f2d86d60fc7101494e4a3 # Delete an organization by name chainctl iam organizations delete my-org # Delete an organization to be selected interactively chainctl iam organizations delete ``` ### Options ``` --skip-refresh Skips attempting to reauthenticate and refresh the Chainguard auth token if it becomes out of date. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam organizations](/platform/chainctl/chainctl-docs/chainctl_iam_organizations/) - IAM organization interactions. --- ### chainctl iam organizations describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_organizations_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam organizations describe Describe an organization. ``` chainctl iam organizations describe [ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam organizations](/platform/chainctl/chainctl-docs/chainctl_iam_organizations/) - IAM organization interactions. --- ### chainctl iam organizations list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_organizations_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam organizations list List organizations. ``` chainctl iam organizations list [--output=id|json|table|tree] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam organizations](/platform/chainctl/chainctl-docs/chainctl_iam_organizations/) - IAM organization interactions. --- ### chainctl iam role-bindings URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam role-bindings IAM role-bindings resource interactions. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam role-bindings create](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_create/) - Create a role-binding * [chainctl iam role-bindings delete](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_delete/) - Delete a role-binding. * [chainctl iam role-bindings list](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_list/) - List role-bindings. * [chainctl iam role-bindings update](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_update/) - Update a role-binding. --- ### chainctl iam role-bindings create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam role-bindings create Create a role-binding ``` chainctl iam role-bindings create [--identity=IDENTITY] [--role=ROLE] [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--output=id|json|table] ``` ### Examples ``` # Bind a user-created identity as viewer to a location chainctl iam role-bindings create --identity=guest-identity --role=viewer --parent=engineering # Create a new role-binding using interactive selection for identity, role, and location chainctl iam role-bindings create ``` ### Options ``` --identity string The name or ID of the identity to bind. --parent string The name or ID of the location the role-binding belongs to. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --role string The name or ID of the role to bind to the identity. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam role-bindings](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings/) - IAM role-bindings resource interactions. --- ### chainctl iam role-bindings delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam role-bindings delete Delete a role-binding. ``` chainctl iam role-bindings delete ROLE_BINDING_ID [--yes] [--output=id] ``` ### Examples ``` # Delete a role-binding chainctl iam role-bindings delete 9b6da6e64b45129eb4e9f9f3ce9b69ca2a550c6b/034e4afcda8c0b07/55b470f08e38b4d2 ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam role-bindings](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings/) - IAM role-bindings resource interactions. --- ### chainctl iam role-bindings list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam role-bindings list List role-bindings. ``` chainctl iam role-bindings list [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [--recursive] [--output=json|table|tree] ``` ### Examples ``` # List role-bindings chainctl iam role-bindings list # Filter role-bindings by organization chainctl iam role-bindings list --parent=my-org # List only role-bindings directly in an organization, excluding nested locations chainctl iam role-bindings list --parent=my-org --recursive=false ``` ### Options ``` --parent string List role-bindings from this location. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --recursive List role-bindings from the parent location and all nested locations. Set to false to list only the parent location. (default true) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam role-bindings](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings/) - IAM role-bindings resource interactions. --- ### chainctl iam role-bindings update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam role-bindings update Update a role-binding. ``` chainctl iam role-bindings update BINDING_ID [--role=ROLE] [--identity=IDENTITY] [--output=id|json|table] ``` ### Examples ``` # Update the role an identity is bound to chainctl iam role-bindings update fb694596eb1678321f94eec283e1e0be690f655c/a2973bac66ebfde3 --role=editor # Update the identity bound to a role chainctl iam role-bindings update fb694596eb1678321f94eec283e1e0be690f655c/a2973bac66ebfde3 --identity=support-identity ``` ### Options ``` --identity string The name or ID of the identity to bind. --role string The name or ID of the role to bind to the identity. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam role-bindings](/platform/chainctl/chainctl-docs/chainctl_iam_role-bindings/) - IAM role-bindings resource interactions. --- ### chainctl iam roles URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles IAM role resource interactions. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam](/platform/chainctl/chainctl-docs/chainctl_iam/) - IAM related commands for the Chainguard platform. * [chainctl iam roles capabilities](/platform/chainctl/chainctl-docs/chainctl_iam_roles_capabilities/) - IAM role capabilities * [chainctl iam roles create](/platform/chainctl/chainctl-docs/chainctl_iam_roles_create/) - Create an IAM role. * [chainctl iam roles delete](/platform/chainctl/chainctl-docs/chainctl_iam_roles_delete/) - Delete a custom IAM role. * [chainctl iam roles list](/platform/chainctl/chainctl-docs/chainctl_iam_roles_list/) - List IAM roles. * [chainctl iam roles update](/platform/chainctl/chainctl-docs/chainctl_iam_roles_update/) - Update an IAM role. --- ### chainctl iam roles capabilities URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles_capabilities.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles capabilities IAM role capabilities ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam roles](/platform/chainctl/chainctl-docs/chainctl_iam_roles/) - IAM role resource interactions. * [chainctl iam roles capabilities list](/platform/chainctl/chainctl-docs/chainctl_iam_roles_capabilities_list/) - List IAM role capabilities. --- ### chainctl iam roles capabilities list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles_capabilities_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles capabilities list List IAM role capabilities. ``` chainctl iam roles capabilities list [--actions=ACTION,...] [--resources=RESOURCE,...] [--output=json|table|tree] ``` ### Examples ``` # List all capabilities chainctl iam roles capabilities list # List all capabilities for groups and repos chainctl iam roles capabilities list --resources=groups,repos # List all capabilities that include list chainctl iam roles capabilities list --actions=list ``` ### Options ``` --actions strings Capability actions to list. --resources strings Capability resources to list. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam roles capabilities](/platform/chainctl/chainctl-docs/chainctl_iam_roles_capabilities/) - IAM role capabilities --- ### chainctl iam roles create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles create Create an IAM role. ``` chainctl iam roles create ROLE_NAME --parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID --capabilities=CAPABILITY,... [--description=DESCRIPTION] [--yes] [--output=id|json|table] ``` ### Examples ``` # Create a role chainctl iam roles create my-role --parent=engineering --capabilities=policy.list,groups.list # Create a role and choose parameters interactively chainctl iam roles create my-role ``` ### Options ``` --capabilities strings A comma separated list of capabilities to grant this role. --description string A description of the role. --parent string Location to create this role under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam roles](/platform/chainctl/chainctl-docs/chainctl_iam_roles/) - IAM role resource interactions. --- ### chainctl iam roles delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles delete Delete a custom IAM role. ``` chainctl iam roles delete ROLE_NAME|ROLE_ID [--yes] [--output=id|json|table] ``` ### Examples ``` # Delete a role by ID chainctl iam roles delete 3ed98fc... ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam roles](/platform/chainctl/chainctl-docs/chainctl_iam_roles/) - IAM role resource interactions. --- ### chainctl iam roles list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles list List IAM roles. ``` chainctl iam roles list [--name=NAME] [--capabilities=CAPABILITY,...] [--parent=PARENT | --managed] [--recursive] [--output=id|json|table] ``` ### Examples ``` # List all accessible roles chainctl iam roles list # List all managed (built-in) roles chainctl iam roles list --managed # List all roles that can create groups chainctl iam roles list --capabilities=groups.create # List only roles directly in an organization, excluding nested locations chainctl iam roles list --parent=my-org --recursive=false ``` ### Options ``` --capabilities strings A comma separated list of capabilities to grant this role. --managed Only list managed (built-in) roles. --name string The exact name of roles to list. --parent string Location to list roles from. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --recursive List roles from the parent location and all nested locations. Set to false to list only the parent location. (default true) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam roles](/platform/chainctl/chainctl-docs/chainctl_iam_roles/) - IAM role resource interactions. --- ### chainctl iam roles update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_iam_roles_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl iam roles update Update an IAM role. ``` chainctl iam roles update ROLE_NAME|ROLE_ID [--capabilities=CAPABILITY,...] [--add-capabilities=CAPABILITY,...] [--remove-capabilities=CAPABILITY,...] [--description=DESCRIPTION] [--yes] [--output=id|json|table] ``` ### Examples ``` # Update a role with a complete set of capabilities chainctl iam roles update my-role --capabilities=policy.list,groups.list,identity.list # Add new capabilities to a role chainctl iam roles update my-role --add-capabilities=policy.create # Remove an existing capabilities from a role chainctl iam roles update my-role --remove-capabilities=identity.list # Interactively choose capabilities to add to a role chainctl iam roles update my-role --add-capabilities= ``` ### Options ``` --add-capabilities strings A comma separated list of capabilities to add to this role (can't be used with --capabilities). --capabilities strings A comma separated list of capabilities to grant this role. --description string A description of the role. --remove-capabilities strings A comma separated list of capabilities to remove from this role (can't be used with --capabilities). -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl iam roles](/platform/chainctl/chainctl-docs/chainctl_iam_roles/) - IAM role resource interactions. --- ### chainctl images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images Images related commands for the Chainguard platform. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl images advisories](/platform/chainctl/chainctl-docs/chainctl_images_advisories/) - Security advisory commands for images. * [chainctl images catalog](/platform/chainctl/chainctl-docs/chainctl_images_catalog/) - List the Chainguard Images catalog. * [chainctl images changelog](/platform/chainctl/chainctl-docs/chainctl_images_changelog/) - Show changelog for image history * [chainctl images diff](/platform/chainctl/chainctl-docs/chainctl_images_diff/) - Diff images. * [chainctl images discover](/platform/chainctl/chainctl-docs/chainctl_images_discover/) - Find Chainguard replacements for the images your project uses. * [chainctl images entitlements](/platform/chainctl/chainctl-docs/chainctl_images_entitlements/) - Manage entitlements to registry repositories. * [chainctl images helm](/platform/chainctl/chainctl-docs/chainctl_images_helm/) - Helm chart related commands * [chainctl images history](/platform/chainctl/chainctl-docs/chainctl_images_history/) - Show history for a specific image tag. * [chainctl images list](/platform/chainctl/chainctl-docs/chainctl_images_list/) - List tagged images from Chainguard registries. * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. * [chainctl images repos](/platform/chainctl/chainctl-docs/chainctl_images_repos/) - Image repo related commands for the Chainguard platform. * [chainctl images tags](/platform/chainctl/chainctl-docs/chainctl_images_tags/) - Tags related commands for images. --- ### chainctl images advisories URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_advisories.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images advisories Security advisory commands for images. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl images advisories list](/platform/chainctl/chainctl-docs/chainctl_images_advisories_list/) - List security advisories for packages in an image. --- ### chainctl images advisories list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_advisories_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images advisories list List security advisories for packages in an image. ### Synopsis List security advisories for APK packages in a container image. This command fetches the SBOM attestation from the image registry, extracts the list of APK packages, and queries the Chainguard advisory database for each package. The image reference can be any valid OCI image reference that has SBOM attestations attached. ``` chainctl images advisories list {IMAGE_REF} [--platform=PLATFORM] [--status=STATUS,...] ``` ### Examples ``` # List advisories for a Chainguard image chainctl images advisories list cgr.dev/chainguard/nginx:latest # List advisories for a specific platform chainctl images advisories list cgr.dev/chainguard/python:latest --platform=linux/arm64 # Filter by status (e.g., only show detected vulnerabilities) chainctl images advisories list cgr.dev/chainguard/go:latest --status=detected # Filter by multiple statuses (comma-separated or multiple flags) chainctl images advisories list cgr.dev/chainguard/go:latest --status=detected,pending-upstream chainctl images advisories list cgr.dev/chainguard/go:latest --status=detected --status=pending-upstream # Output as JSON chainctl images advisories list cgr.dev/chainguard/go:latest -o json ``` ### Options ``` --platform string Platform to fetch SBOM for (e.g., linux/amd64, linux/arm64) (default "linux/amd64") --status strings Filter advisories by status; can be specified multiple times or comma-separated (e.g., --status=detected,pending-upstream) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images advisories](/platform/chainctl/chainctl-docs/chainctl_images_advisories/) - Security advisory commands for images. --- ### chainctl images catalog URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_catalog.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images catalog List the Chainguard Images catalog. ### Synopsis List the Chainguard Images catalog. Shows every image Chainguard publishes, along with the upstream images each one replaces. This is the full catalog, not the images your organization is entitled to pull: use 'chainctl images repos list' for that. --name matches any image whose name contains the given text, so --name jdk finds both "jdk" and "adoptium-jdk". An image can carry hundreds of active tags, so a long page previews a few per image and reports how many are left. A page short enough to afford it lists every tag instead, which is what narrowing down gets you; --all-tags asks for the full list whatever the page length. JSON always carries every tag. In a terminal the catalog is shown a page at a time and --limit sets the page size, so the whole catalog stays reachable. When the output is piped or JSON, --limit is the maximum number of images returned. ``` chainctl images catalog ``` ### Examples ``` chainctl images catalog chainctl images catalog --name postgres chainctl images catalog --name go --all-tags chainctl images catalog --limit 200 -o json ``` ### Options ``` --all-tags List every active tag for each image, however long the page. A short page does this anyway. --limit int32 The number of images per page in an interactive terminal; otherwise the maximum number to return. (default 50) --name string Show only images whose name contains this substring. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. --- ### chainctl images changelog URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_changelog.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images changelog Show changelog for image history ### Synopsis Show changelog for image history. This command fetches the tag history for an image, analyzes changes between versions, and displays human-readable summaries of changes (similar to git log). ``` chainctl images changelog IMAGE_REFERENCE [flags] ``` ### Examples ``` # Show changelog for an image (default: last 10 versions) chainctl images changelog cgr.dev/chainguard/nginx:latest # Show only last 5 versions chainctl images changelog cgr.dev/chainguard/nginx:latest --depth 5 # Output as JSON chainctl images changelog cgr.dev/chainguard/nginx:latest --output json # Output as table chainctl images changelog cgr.dev/chainguard/nginx:latest --output table ``` ### Options ``` --depth int Number of historical versions to show (default 10) --platform string Platform to use for multi-arch images (e.g., linux/amd64, linux/arm64) (default "linux/amd64") ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. --- ### chainctl images diff URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_diff.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images diff Diff images. ### Synopsis Diffs 2 images together, based on their SBOM and Vulnerability scan. SBOM packages are diffed based on their PURL (https://github.com/package-url/purl-spec) and version. PURLs are grouped without their @version component. If identical PURLs are found, the first one found is used. If an SBOM package contains multiple PURLs, results will contain multiple entries for the same package name based on the purl type. Vulnerability scans are done via grype, which must be available on the system PATH. ``` chainctl images diff FROM_IMAGE TO_IMAGE [flags] ``` ### Options ``` -t, --artifact-types strings Specifies the purl artifact types to diff. If "-" is provided, all types are included. (default [apk]) -o, --output string Output format. One of: [go-template, json, markdown] (default "json") --platform string Specifies the platform in the form os/arch (e.g. linux/amd64, linux/arm64) (default "linux/amd64") --template string Go template to use when --output=go-template --template-file string Path to a file containing the Go template ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. --- ### chainctl images discover URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_discover.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images discover Find Chainguard replacements for the images your project uses. ### Synopsis Find Chainguard replacements for the images your project uses. Reads every image reference under DIR (default: the current directory). For each upstream image, it reports whether Chainguard publishes a hardened replacement and whether your organization can pull it today. Resolve references directly with repeatable --image flags. This skips the directory scan. Private-registry references are supported. An exact catalog alias is preferred. Otherwise, repository suffixes are tried from most to least specific. Thus registry.example/cache/dotnet/sdk retains dotnet/sdk, while a reference ending in nginx can still suggest Chainguard's nginx. Namesake matching identifies a catalog offering, not identical image contents. Both what you build and what you run are covered: Dockerfile FROM instructions, Kubernetes manifests, Compose files, Helm values, Terraform, shell scripts and Makefiles. Helm values are read as written, not rendered. An image assembled from registry, repository, tag and digest keys is resolved. This includes a chart-wide imageRegistry or imageNamespace and an empty tag standing for the chart's appVersion. A reference computed inside templates is beyond what reading values can see, so treat the result as what values declare. Your immutability choice stays as written: a digest-pinned reference comes back digest-pinned. A maintained tag is kept. A variant or older patch resolves to the same version without the source-image variant suffix when possible. An untagged reference is made explicit as latest, which is what Docker already uses. When a requested version is not maintained, the suggestion uses the newest concrete stable version on the same minor line, then the newest in the same major, never an older line than the one you asked for. Symbolic latest is the fallback when that major is not maintained. The resolved tag is part of the suggested image reference. An image not yet entitled is named with a tag because its digest cannot be resolved until it is in your catalog. Up to three candidates are shown for each reference. Pass one image reference to --all-candidates to show every matching variant and all of its maintained tags. FIPS variants rank first by default; pass --fips=false to rank standard variants first instead. Both variants remain visible when Chainguard publishes them. Every reference is listed with what you can do: pull the replacement today, ask for entitlement, nothing (already on Chainguard), or nothing to move to. What has no replacement is as much of the answer as what does. ``` chainctl images discover [DIR] ``` ### Examples ``` chainctl images discover chainctl images discover ./services chainctl images discover --parent my-org chainctl images discover --image nginx:1.29 chainctl images discover --all-candidates nginx:1.29 chainctl images discover --image nginx:1.29 --fips=false chainctl images discover -o json ``` ### Options ``` --all-candidates string Show every variant and maintained tag for one image reference. --fips Prefer FIPS variants when both FIPS and standard images are available. (default true) --image stringArray Resolve an image reference directly instead of scanning a directory; may be repeated. --parent string Name or UIDP of the organization whose entitlements to check. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. --- ### chainctl images entitlements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_entitlements.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images entitlements Manage entitlements to registry repositories. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl images entitlements list](/platform/chainctl/chainctl-docs/chainctl_images_entitlements_list/) - List registry entitlements of an organization. --- ### chainctl images entitlements list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_entitlements_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images entitlements list List registry entitlements of an organization. ``` chainctl images entitlements list --parent=PARENT [--all] [--output=json|table|wide] [flags] ``` ### Options ``` -a, --all Include expired entitlements in the output. --parent string The name or id of the org to list registry entitlements for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images entitlements](/platform/chainctl/chainctl-docs/chainctl_images_entitlements/) - Manage entitlements to registry repositories. --- ### chainctl images helm URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_helm.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images helm Helm chart related commands ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl images helm add-chart](/platform/chainctl/chainctl-docs/chainctl_images_helm_add-chart/) - Add a Chainguard Helm chart and its image dependencies to an organization. * [chainctl images helm history](/platform/chainctl/chainctl-docs/chainctl_images_helm_history/) - Show digest history for a Chainguard Helm chart tag. * [chainctl images helm refs](/platform/chainctl/chainctl-docs/chainctl_images_helm_refs/) - List image references pinned in a Chainguard Helm chart * [chainctl images helm values](/platform/chainctl/chainctl-docs/chainctl_images_helm_values/) - Generate relocation overrides for a Chainguard Helm chart --- ### chainctl images helm add-chart URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_helm_add-chart.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images helm add-chart Add a Chainguard Helm chart and its image dependencies to an organization. ### Synopsis Add a Chainguard Helm chart and its image dependencies to an organization. Creates synced image repositories in the destination organization for the given Helm chart and all image dependencies listed in the chart's metadata. Repositories that already exist in the destination organization are skipped. All tags are synced. Charts are looked up by name in the community catalog by default. To add a chart from the iamguarded catalog, prefix the name with "iamguarded-charts/". The chart's image dependencies are read from a single chart tag. By default the newest non-FIPS tag is used; pass --tag to inspect a specific chart version instead (including a FIPS variant). Examples: # Add the argo-cd community chart and all of its image dependencies to an organization chainctl images helm add-chart argo-cd --parent my-org # Add a specific version of the argo-cd chart chainctl images helm add-chart argo-cd --tag 9.5.20 --parent my-org # Add the redis iamguarded chart and all of its image dependencies to an organization chainctl images helm add-chart iamguarded-charts/redis --parent my-org ``` chainctl images helm add-chart CHART [flags] ``` ### Options ``` --dry-run Dry-run mode: don't create repos, only show what would change. --parent string The organization to add the chart and its images to. --tag string The chart tag to inspect for image dependencies. Defaults to the newest non-FIPS tag. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images helm](/platform/chainctl/chainctl-docs/chainctl_images_helm/) - Helm chart related commands --- ### chainctl images helm history URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_helm_history.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images helm history Show digest history for a Chainguard Helm chart tag. ### Synopsis Show the digest history for a Chainguard Helm chart tag. Chart tags are mutable. This command lists every digest a chart tag has pointed at over time, ordered newest first, so you can identify when a regression was introduced and pin to an earlier digest. CHART accepts either a bare chart name (with --parent) or a full OCI reference. If no tag is provided, you will be prompted to select one. ``` chainctl images helm history CHART[:TAG] [flags] ``` ### Examples ``` # Bare chart name with an explicit parent group path chainctl images helm history flux:v2.18.4 --parent my-org/charts # Bare chart name in the iamguarded-charts folder chainctl images helm history flux:v2.18.4 --parent my-org/iamguarded-charts # Interactively pick a tag chainctl images helm history flux --parent my-org/charts # Full OCI reference chainctl images helm history cgr.dev/my-org/charts/flux:v2.18.4 # JSON output chainctl images helm history flux:v2.18.4 --parent my-org/charts -o json ``` ### Options ``` --parent string Group path where the chart lives (e.g. my-org/charts or my-org/iamguarded-charts). Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images helm](/platform/chainctl/chainctl-docs/chainctl_images_helm/) - Helm chart related commands --- ### chainctl images helm refs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_helm_refs.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images helm refs List image references pinned in a Chainguard Helm chart ### Synopsis List every distinct image reference pinned in a Chainguard Helm chart's chart-lock attestation, including images from subcharts. Charts without a chart-lock attestation (e.g. iamguarded charts) fall back to the helm.sh/images annotation carried on the chart artifact's OCI config. All entries surfaced through that fallback are treated as required. By default each ref is printed on its own line as {registry}/{repoName}:{tag}@{digest}, with the {registry}/{org} prefix derived from the chart's own reference. Use --repository to override that prefix when emitting refs for a relocated copy of the chart's images. Use --requirement to narrow the output to refs the chart-lock template marks "required" or "optional"; the default empty value emits every ref. With -o json, each ref is emitted as a JSON object containing the repoName, tag and digest fields; the repository override does not affect the JSON output. If the chart reference has no tag or digest, the highest semver tag in the repository is selected, matching Helm's behaviour. ``` chainctl images helm refs CHART_REFERENCE [flags] ``` ### Examples ``` # Print pinned image refs for a chart chainctl images helm refs cgr.dev/my-org/charts/flux:v2.18.4 # Emit as JSON chainctl images helm refs cgr.dev/my-org/charts/flux:v2.18.4 -o json # Override the prefix to describe a relocated mirror of the images chainctl images helm refs cgr.dev/my-org/charts/flux:v2.18.4 \ --repository myregistry.internal/images/chainguard # Only refs the chart-lock template marks required (and any without a # template entry, which default to required) chainctl images helm refs cgr.dev/my-org/charts/flux:v2.18.4 \ --requirement required # Only refs the chart-lock template marks optional chainctl images helm refs cgr.dev/my-org/charts/flux:v2.18.4 \ --requirement optional ``` ### Options ``` --repository string Override the {registry}/{org} prefix for image references (e.g., myregistry.internal/images/chainguard) --requirement string Filter image references by chart-lock template requirement. One of: [required,optional] (default: include all) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images helm](/platform/chainctl/chainctl-docs/chainctl_images_helm/) - Helm chart related commands --- ### chainctl images helm values URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_helm_values.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images helm values Generate relocation overrides for a Chainguard Helm chart ### Synopsis Generate minimal Helm value overrides to relocate a chart's images to a different registry and/or organization. The output contains only the values that differ from the chart defaults and can be passed directly to helm install -f. Use --registry to point images at a different registry host, and --org to change the organization prefix. If neither flag is set the output is empty. If the chart reference has no tag or digest, the highest semver tag in the repository is selected, matching Helm's behaviour. ``` chainctl images helm values CHART_REFERENCE [flags] ``` ### Examples ``` # Point images at a registry mirror chainctl images helm values cgr.dev/my-org/charts/nginx:latest --registry myregistry.example.com # Change the organization prefix (my-org -> other-org) chainctl images helm values cgr.dev/my-org/charts/nginx:latest --org other-org # Both registry and organization chainctl images helm values cgr.dev/my-org/charts/nginx:latest \ --registry myregistry.example.com --org other-org # Install with relocated images helm install my-release oci://cgr.dev/my-org/charts/nginx \ -f <(chainctl images helm values cgr.dev/my-org/charts/nginx:latest --registry myregistry.example.com) # Output as JSON chainctl images helm values cgr.dev/my-org/charts/nginx:latest --registry myregistry.example.com -o json ``` ### Options ``` --org string Override the organization prefix for image repositories (e.g., other-org) --registry string Override the registry host for all image references ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images helm](/platform/chainctl/chainctl-docs/chainctl_images_helm/) - Helm chart related commands --- ### chainctl images history URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_history.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images history Show history for a specific image tag. ### Synopsis Show history for a specific image tag. If a digest does not represent a multi-arch image, only a single digest without architecture information will be displayed. Architecture information may not be available for all digests. Examples: # Show history for a specific tag (selected interactively) chainctl images history nginx # Show history for a specific tag (specified in the command) chainctl images history nginx:1.21.0 # Show history for a tag in a specific organization chainctl images history nginx:1.21.0 --parent=my-org ``` chainctl images history IMAGE[:TAG] [flags] ``` ### Options ``` --parent string Organization to view image history from. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --recursive Search repositories recursively through all descendants instead of just children ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. --- ### chainctl images list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images list List tagged images from Chainguard registries. ``` chainctl images list [--repo=REPO_NAME] [--public | --parent=PARENT_NAME|PARENT_ID] [--updated-within=DURATION] [--show-dates] [--show-epochs] [--show-referrers] [--show-vcs-snapshots] [--active-only] [--output=csv|id|json|table|terse|tree|wide] ``` ### Options ``` --active-only Whether to only show active tags. --parent string The name or id of the parent location to list image repos. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --public List repos from the public Chainguard registry. --recursive Search repositories recursively through all descendants instead of just children --repo string Search for a specific repo by name. --show-dates Whether to show date tags of the form latest-{date}. --show-epochs Whether to show epoch tags of the form 1.2.3-r4. --show-referrers Whether to show referrer tags of the form sha256-deadbeef.{sig,sbom,att}. --show-vcs-snapshots Whether to show VCS snapshot tags of the form 1.2.3_git20260914. --updated-within duration The duration within which an image must have been updated (0 disables the filter). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. --- ### chainctl images overlays URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays Manage Custom Assembly overlays and the repos they are attached to. ### Synopsis Manage tag-based Custom Assembly overlays. An overlay is a reusable image customization (packages, environment variables, annotations, accounts, and certificates) owned by an organization. Attaching an overlay to a repo — or, with --all-repos, to every repo under an organization or folder, including repos created later — creates a binding that selects which tags the overlay applies to: - --all: every tag. - --variant: a tag variant, such as dev. - --tag: specific tags. When bindings of different kinds match the same tag, they layer in the order ALL, then VARIANT, then EXACT, with later layers taking precedence. All-repos bindings layer by scope depth — the organization first, then each nested folder, deeper folders taking precedence — and a repo's own bindings layer above every all-repos binding. Bindings of the same kind may coexist in one scope only when their overlays do not conflict. The Guarded Entrypoint keys layer per key: fail_mode and command_override come from the most specific binding that sets them, and a binding that leaves one unset uses a broader binding's value (an empty command_override counts as set and cancels a broader one); guarded_entrypoint is on when any matching binding sets it, but an overlay that sets fail_mode, preflight, or command_override must still set guarded_entrypoint: true itself; preflight checks from every matching binding are combined, broader bindings first. A binding is rejected when it is created if its overlay sets a different fail_mode or command_override than a binding it co-matches in the same layer: two --all bindings, two --variant bindings for one variant, or two --tag bindings sharing a tag. Chainguard rebuilds the matching images after an overlay or binding changes. These rebuilds run only for organizations enrolled in tag-based Custom Assembly. Contact your Chainguard account team to enroll. ### Examples ``` # Create an overlay that adds packages chainctl images overlays create my-overlay --parent my-org --package curl --package jq # Attach it to every tag of a repo chainctl images overlays attach --overlay my-overlay --repo python --parent my-org --all # List overlays and their bindings chainctl images overlays list --parent my-org ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl images overlays attach](/platform/chainctl/chainctl-docs/chainctl_images_overlays_attach/) - Attach Custom Assembly overlays to a repo or to every repo in an organization. * [chainctl images overlays create](/platform/chainctl/chainctl-docs/chainctl_images_overlays_create/) - Create a Custom Assembly overlay. * [chainctl images overlays delete](/platform/chainctl/chainctl-docs/chainctl_images_overlays_delete/) - Delete a Custom Assembly overlay. * [chainctl images overlays detach](/platform/chainctl/chainctl-docs/chainctl_images_overlays_detach/) - Detach a Custom Assembly overlay from a repo. * [chainctl images overlays list](/platform/chainctl/chainctl-docs/chainctl_images_overlays_list/) - List Custom Assembly overlays and their bindings. * [chainctl images overlays update](/platform/chainctl/chainctl-docs/chainctl_images_overlays_update/) - Update a Custom Assembly overlay's name or configuration. * [chainctl images overlays update-binding](/platform/chainctl/chainctl-docs/chainctl_images_overlays_update-binding/) - Change which tags a Custom Assembly overlay binding applies to. --- ### chainctl images overlays attach URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_attach.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays attach Attach Custom Assembly overlays to a repo or to every repo in an organization. ### Synopsis Attach one or more Custom Assembly overlays by creating a binding per overlay. A binding attaches an overlay to a single repo (--repo) or to every repo under an organization or folder (--all-repos with --parent naming the organization or the folder), including repos created after the binding exists. Every binding selects which tags its overlay applies to. Pass exactly one of: - --all: every tag. - --variant: a tag variant, such as dev. - --tag: specific tags. A repo can hold several bindings of the same kind only when their overlays do not conflict; a conflicting binding is rejected, and the error names the conflicting binding and fields. An overlay can be attached to a repo only once. Multiple overlays attach in the order given, one binding each; on a failure, the bindings already created are kept and the error names the overlay that failed. An all-repos binding is owned by the organization or folder named by --parent and applies its overlay to the matching tags of every repo under that scope. An overlay can have at most one all-repos binding per organization or folder. When bindings at several scopes match the same tag, they layer by depth — the organization first, then each nested folder — with deeper scopes taking precedence where both overlays set the same field, the repo's own binding above all, and their package lists combining. Repos with a legacy customization keep the legacy behavior; all-repos bindings do not apply to them. The command prints each binding UID. Use them with "update-binding" and "detach", or look them up later with "chainctl images overlays list". ``` chainctl images overlays attach [flags] ``` ### Examples ``` # Apply an overlay to every tag of a repo chainctl images overlays attach --overlay my-overlay --repo python --parent my-org --all # Apply an overlay to every tag of every repo in the organization, # including repos created later chainctl images overlays attach --overlay my-overlay --all-repos --parent my-org --all # Apply an overlay to every repo under a folder, including repos # created later chainctl images overlays attach --overlay my-overlay --all-repos --parent my-org/golden-images --all # Apply two overlays to every tag of a repo chainctl images overlays attach --overlay my-certs,my-packages --repo python --parent my-org --all # Apply an overlay to -dev tags only chainctl images overlays attach --overlay my-overlay --repo python --parent my-org --variant dev # Apply an overlay to specific tags chainctl images overlays attach --overlay my-overlay --repo python --parent my-org --tag 3.12 --tag 3.13 ``` ### Options ``` --all Bind to every tag on the repo; multiple --all bindings may coexist when their overlays do not conflict. Mutually exclusive with --tag and --variant. --all-repos Attach to every repo under the organization or folder named by --parent, including repos created later; mutually exclusive with --repo. --overlay strings Overlay to attach: UIDP or name (resolved within the repo's org). Comma separated and repeatable; each overlay gets its own binding with the same tag selector. --parent string Org or folder name or UIDP: the scope the binding is created under with --all-repos, or the group for resolving --repo by name (unused when --repo is a UIDP). If unset, auto-selects when the caller belongs to a single org, otherwise prompts. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --repo string Target repo: UIDP, or name resolved within --parent. Mutually exclusive with --all-repos. --tag strings Exact tag names to bind to (repeatable). Mutually exclusive with --all and --variant. "all" and "dev" are not tags: use --all or --variant=dev. --variant string Bind to a tag variant: currently only "dev" (matches tags ending in -dev). Mutually exclusive with --tag and --all. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images overlays create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays create Create a Custom Assembly overlay. ### Synopsis Create a Custom Assembly overlay under an organization. Overlays are organization-level resources: one definition is referencable from every scope. Attach scopes a binding to a single repo, to a folder's repos, or to the whole organization. Pass the overlay configuration either as repeated --package flags, or as a YAML file with --file in the same shape that "chainctl images repos build apply" accepts. Certificates, runtime APK repositories, and runtime APK signing keys can also be read from files with --with-certificates, --with-runtime-repositories, and --with-runtime-keys; they merge into the --package or --file configuration. Creating an overlay does not change any image; attach it to a repo with "chainctl images overlays attach". ``` chainctl images overlays create [flags] ``` ### Examples ``` # Create an overlay from packages chainctl images overlays create my-overlay --parent my-org --package curl --package jq # Create an overlay from a configuration file chainctl images overlays create my-overlay --parent my-org -f overlay.yaml # Create an overlay carrying custom CA certificates from a PEM bundle chainctl images overlays create my-overlay --parent my-org --with-certificates ca-bundle.pem ``` ### Options ``` -f, --file chainctl images repos build The name of the YAML file containing the overlay configuration, the same shape chainctl images repos build accepts. Takes precedence over --package. --package strings Package to include (repeatable). --parent string Org name or UIDP under which to create the overlay; overlays are always organization-level. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --with-certificates strings Comma separated list of files to read the custom certificates from. --with-runtime-keys strings Comma separated list of files to read customer APK signing public keys from. Each file becomes a key in /etc/apk/keys named after the file's basename, which must match the filename referenced by the repository's APKINDEX signature (.SIGN.RSA256.). --with-runtime-repositories strings Comma separated list of runtime APK repository URLs to write to /etc/apk/repositories in the image. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images overlays delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays delete Delete a Custom Assembly overlay. ### Synopsis Delete a Custom Assembly overlay by UID or name. An overlay that is still attached to a repo cannot be deleted; detach every binding first with "chainctl images overlays detach". A name must match exactly one overlay you can access; pass the UID if it is ambiguous. ``` chainctl images overlays delete [flags] ``` ### Examples ``` chainctl images overlays delete my-overlay ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images overlays detach URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_detach.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays detach Detach a Custom Assembly overlay from a repo. ### Synopsis Detach a Custom Assembly overlay from a repo by deleting its binding. The binding UID is printed by "attach" and shown under each overlay by "chainctl images overlays list". The overlay itself is kept. ``` chainctl images overlays detach [flags] ``` ### Examples ``` # Find the binding UID, then detach it chainctl images overlays list --parent my-org chainctl images overlays detach ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images overlays list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays list List Custom Assembly overlays and their bindings. ### Synopsis List the Custom Assembly overlays under an organization or folder, with each overlay's configuration and the repos and tags it is attached to. Use --output json for the binding and overlay UIDs in machine-readable form. ``` chainctl images overlays list [flags] ``` ### Examples ``` chainctl images overlays list --parent my-org # Print every binding UID chainctl images overlays list --parent my-org -o json | jq -r '.overlays[].bindings[].id' ``` ### Options ``` -o, --output string Output format: text or json. (default "text") --parent string Parent group name or UIDP to list overlays and bindings under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images overlays update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays update Update a Custom Assembly overlay's name or configuration. ### Synopsis Update a Custom Assembly overlay's name, configuration, or both. --package and --file replace the stored configuration wholesale; they do not merge with it. The --with-* flags merge into that replacement when --package or --file is also given; passed alone, they merge into the stored configuration instead, as "chainctl images repos build edit" merges them into a repo's current build config. Repos the overlay is attached to are rebuilt with the new configuration. An update that would conflict with another overlay attached to the same tags is rejected. ``` chainctl images overlays update [flags] ``` ### Examples ``` # Rename an overlay chainctl images overlays update my-overlay --name my-renamed-overlay # Replace an overlay's configuration from a file chainctl images overlays update my-overlay -f overlay.yaml # Add custom CA certificates to an overlay's stored configuration chainctl images overlays update my-overlay --with-certificates ca-bundle.pem ``` ### Options ``` -f, --file chainctl images repos build The name of the YAML file containing the overlay configuration, the same shape chainctl images repos build accepts. Replaces the stored config wholesale and takes precedence over --package. --name string New overlay name. --package strings Package to include (repeatable). Declares the FULL replacement set; the stored config is replaced wholesale. --with-certificates strings Comma separated list of files to read the custom certificates from. --with-runtime-keys strings Comma separated list of files to read customer APK signing public keys from. Each file becomes a key in /etc/apk/keys named after the file's basename, which must match the filename referenced by the repository's APKINDEX signature (.SIGN.RSA256.). --with-runtime-repositories strings Comma separated list of runtime APK repository URLs to write to /etc/apk/repositories in the image. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images overlays update-binding URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_overlays_update-binding.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images overlays update-binding Change which tags a Custom Assembly overlay binding applies to. ### Synopsis Change the tag selector of a Custom Assembly overlay binding. Only the selector can change. To attach the overlay to a different repo, or a different overlay to the repo, detach the binding and attach again. Find the binding UID with "chainctl images overlays list". ``` chainctl images overlays update-binding [flags] ``` ### Examples ``` chainctl images overlays update-binding --tag 3.12 --tag 3.13 ``` ### Options ``` --all Bind to every tag on the repo; multiple --all bindings may coexist when their overlays do not conflict. Mutually exclusive with --tag and --variant. --tag strings Exact tag names to bind to (repeatable). Mutually exclusive with --all and --variant. "all" and "dev" are not tags: use --all or --variant=dev. --variant string Bind to a tag variant: currently only "dev" (matches tags ending in -dev). Mutually exclusive with --tag and --all. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images overlays](/platform/chainctl/chainctl-docs/chainctl_images_overlays/) - Manage Custom Assembly overlays and the repos they are attached to. --- ### chainctl images repos URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos Image repo related commands for the Chainguard platform. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl images repos build](/platform/chainctl/chainctl-docs/chainctl_images_repos_build/) - Manage custom image builds * [chainctl images repos create](/platform/chainctl/chainctl-docs/chainctl_images_repos_create/) - Create an image repository. * [chainctl images repos delete](/platform/chainctl/chainctl-docs/chainctl_images_repos_delete/) - Remove an image repository. * [chainctl images repos list](/platform/chainctl/chainctl-docs/chainctl_images_repos_list/) - List image repositories. * [chainctl images repos update](/platform/chainctl/chainctl-docs/chainctl_images_repos_update/) - Update image repositories. --- ### chainctl images repos build URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_build.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos build Manage custom image builds ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos](/platform/chainctl/chainctl-docs/chainctl_images_repos/) - Image repo related commands for the Chainguard platform. * [chainctl images repos build apply](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_apply/) - Apply a build config * [chainctl images repos build edit](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_edit/) - Edit a build config, optionally creating a new repo with --save-as * [chainctl images repos build list](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_list/) - List build reports * [chainctl images repos build logs](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_logs/) - Get build logs --- ### chainctl images repos build apply URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_build_apply.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos build apply Apply a build config ### Synopsis Apply a pre-written YAML configuration file, custom certificates, and/or runtime repositories to customize one or more Chainguard images. You can use Custom Assembly to customize any image you are entitled to by adding packages from Chainguard's repository, setting environment variables, adding OCI annotations, customizing user accounts and groups, or including custom certificates. The customized image is built automatically without requiring you to fork images or maintain custom build pipelines. This command applies Custom Assembly configurations without opening an interactive editor. Use this for automated workflows, CI/CD pipelines, or when you have configuration files managed in version control. You can create variants by choosing to save the customized configuration as a new repository instead of modifying the existing one (single-repo only). How it works: You can customize the image by providing configuration through a YAML file, certificates, runtime repositories, or any combination. At least one of --file, --with-certificates, --with-runtime-repositories, or --with-runtime-keys must be provided. When --file is used alone, the YAML configuration from the file is applied as-is. When only flags are used, only those values will be applied to the image. When multiple inputs are provided, they are merged together. The command validates the resulting configuration and displays a diff comparing it to the current repository configuration (or an empty baseline for new repositories). After reviewing the diff, you confirm the changes. The command then updates the repository configuration and starts a custom build automatically. Batch mode: Apply the same config to multiple repos by passing --repo multiple times, or by using a wildcard pattern. Supported syntax: * matches any string, ? matches a single character, [abc] matches a character class: - --repo=nginx --repo=redis Target explicit repos - --repo="nginx*" Match repos whose name starts with "nginx" - --repo="*" Match all repos under the group - --repo="*fips*" Match repos containing "fips" in the name In batch mode the command shows per-repo diffs, then asks for a single confirmation before applying all matched repos in parallel (up to 10 concurrent). Results are printed as a summary at the end. --save-as is not available in batch mode. Dry-run mode: Pass --dry-run to preview changes without applying them. No confirmation is requested and no changes are made. Exits with a non-zero code if changes would be applied, suitable for CI drift detection. Customizable sections: contents.packages Add additional packages to install in the image (e.g., development tools, utilities). Packages must be available in Chainguard's package repository. Package names may include the {{major}} and {{minor}} template tokens, which expand to the corresponding components of the base image's main package version at build time. For example, "py{{major}}.{{minor}}-cryptography" becomes "py3.13-cryptography" on an image whose main package is python-3.13, and tracks the base image as its version moves. contents.runtime_repositories Add APK repositories to /etc/apk/repositories in the image for runtime package installation. When set, these replace the default virtualapk.cgr.dev repositories. Must be HTTPS URLs. contents.runtime_keyring Trust additional APK signing public keys for the runtime repositories (e.g., a re-signing mirror). Each entry has a name and a PEM PUBLIC KEY content; the key is written to /etc/apk/keys under its name, which must match the filename referenced by the repository's APKINDEX signature (.SIGN.RSA256.). Keys can also be loaded from files using the --with-runtime-keys flag. environment Set environment variables that will be available in the image. Variables with the 'CHAINGUARD_' or 'GUARDED_' prefix are reserved and cannot be used. annotations Add custom OCI annotations to the image for tracking build information, compliance, or metadata. Keys with the 'dev.chainguard' prefix are reserved and cannot be used. accounts Customize image users and groups. You can define custom users with specific UIDs/GIDs, home directories, and group memberships. You can also specify which user the image should run as. certificates Provide custom certificates that will be merged with the default certificate bundle in the image. This is useful for adding internal CA certificates. Certificates can be defined in the YAML manifest or loaded from files using the --with-certificates flag (can be specified multiple times). Both methods can be combined and all certificates are merged together. NOTE: This is a Beta feature that requires enrollment. Contact your Customer Success Team to enable this feature. guarded_entrypoint Set to true to wrap the image entrypoint with /usr/bin/guarded-entrypoint when the image is rebuilt. At container start the wrapper replaces each environment value written as a secret reference (cg+BACKEND://REF, for example cg+gsm://projects/acme/secrets/db/versions/latest) with the secret, runs any preflight checks, and then starts the app. preflight, fail_mode, and command_override require guarded_entrypoint: true in the same manifest. NOTE: Guarded Entrypoint, including preflight, fail_mode, and command_override, is a Beta feature. Contact Chainguard customer support to get access. preflight Readiness checks the wrapper runs before starting the app, up to 32. Each entry sets exactly one of tcp (a host:port to dial) or path (a file that must exist), and optionally timeout (default 30s), interval (default 500ms), and on_failure: fail (the default; the container does not start) or continue (the failure is logged). tcp and path expand ${VAR} from the resolved environment and cannot contain ',' or '='. fail_mode What the wrapper does when a secret reference cannot be resolved: closed (the default) stops the container; open logs the failure and starts the app anyway. command_override What the wrapper starts after preparing the environment. In default mode it runs command only when it receives no arguments at all (the image has no ENTRYPOINT or CMD and none are passed at run time), and otherwise runs the arguments it received; prepend runs command followed by those arguments; override runs command and drops them. prepend and override require a command. Each command entry expands ${VAR} from the resolved environment; an unset ${VAR} stops the container instead of expanding to empty. Write $$ for a literal $, for example $${PORT:-8080} to leave ${PORT:-8080} for a shell. The command is stored in the image config, which anyone who can pull the image can read, so never write a secret into it; set it in environment as a cg+BACKEND://REF reference. An expanded ${VAR} is visible in the process's command line, so prefer an app that reads the secret from its environment. Notice: Customer shall not provide Chainguard any personal data (or similarly regulated data) as part of the Custom Assembly tool, other than the personal data that Chainguard collects in the ordinary course of business, as further detailed in its [Privacy Notice](https://www.chainguard.dev/legal/privacy-notice). ``` chainctl images repos build apply [flags] ``` ### Examples ``` # Apply configuration from a file (interactive repo selection) chainctl images repos build apply --file=config.yaml # Apply to a specific repository chainctl images repos build apply --repo=my-custom-python --file=config.yaml # Apply and save as a new repository chainctl images repos build apply --repo=my-custom-python --file=config.yaml --save-as=my-new-python # Apply with automatic confirmation (for CI/CD) chainctl images repos build apply --repo=my-custom-python --file=config.yaml --yes # Add only custom certificates (no config file needed) chainctl images repos build apply --repo=my-custom-python --with-certificates=ca1.pem --with-certificates=ca2.pem # Set custom runtime APK repositories (replaces default virtualapk.cgr.dev) chainctl images repos build apply --repo=my-custom-python --with-runtime-repositories=https://apk-mirror.example.com/chainguard # Set multiple runtime repositories chainctl images repos build apply --repo=my-custom-python --with-runtime-repositories=https://apk-mirror.example.com/chainguard,https://apk-mirror.example.com/extras # Add custom certificates alongside a config file chainctl images repos build apply --repo=my-custom-python --file=config.yaml --with-certificates=ca1.pem --with-certificates=ca2.pem # Apply to all repos under a group (batch mode) chainctl images repos build apply --parent=my-org --repo="*" --file=config.yaml # Apply to repos matching a wildcard pattern (batch mode) chainctl images repos build apply --parent=my-org --repo="nginx*" --file=config.yaml # Apply to multiple explicit repos (batch mode) chainctl images repos build apply --repo=nginx --repo=nginx-fips --file=config.yaml # Batch apply with automatic confirmation (for CI/CD) chainctl images repos build apply --parent=my-org --repo="*" --file=config.yaml --yes # Dry-run: preview changes without applying (exits 1 if diff detected) chainctl images repos build apply --repo=my-custom-python --file=config.yaml --dry-run # Dry-run across all repos (useful for CI drift detection) chainctl images repos build apply --parent=my-org --repo="*" --file=config.yaml --dry-run ``` ### Options ``` --dry-run Print the diff without applying changes. Exits with a non-zero code if changes would be made. -f, --file string The name of the file containing the build config. --parent string The name or id of the parent location to apply build config. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --repo stringArray The name or id of the repo to apply build config. Supports wildcards (*, ?, [abc]). Can be specified multiple times. --save-as string Create a new repo with the edited configuration instead of updating the existing one. --with-certificates strings Comma separated list of files to read the custom certificates from. --with-runtime-keys strings Comma separated list of files to read customer APK signing public keys from. Each file becomes a key in /etc/apk/keys named after the file's basename, which must match the filename referenced by the repository's APKINDEX signature (.SIGN.RSA256.). --with-runtime-repositories strings Comma separated list of runtime APK repository URLs to write to /etc/apk/repositories in the image. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos build](/platform/chainctl/chainctl-docs/chainctl_images_repos_build/) - Manage custom image builds --- ### chainctl images repos build edit URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_build_edit.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos build edit Edit a build config, optionally creating a new repo with --save-as ### Synopsis Extend a Chainguard image with custom packages, environment variables, certificates, and runtime APK repositories. You can use Custom Assembly to customize any image you are entitled to by adding packages from Chainguard's repository, setting environment variables, adding OCI annotations or customizing user accounts and groups. Furthermore, Custom Assembly allows you to include any additional custom certificates in the image. They will be merged with the default certificate bundle, enabling the image to trust non-standard certificate authorities and connect to services secured with custom TLS certificates. You can also configure custom runtime APK repositories using --with-runtime-repositories. When set, these replace the default virtualapk.cgr.dev repositories in /etc/apk/repositories, allowing runtime package installation from your own APK mirrors. Finally, you can create variants by choosing to save the customized configuration as a new repository instead of modifying the existing one. How it works: You customize the image by editing a YAML configuration manifest. The command opens your editor with the current repository configuration (or a template for new repositories). To skip the interactive editor, use the --file flag to provide a pre-written configuration file. After editing, the command displays a diff of your changes for review. Upon confirmation, it updates the repository configuration and starts a custom build automatically. Customizable sections: contents.packages Add additional packages to install in the image (e.g., development tools, utilities). Packages must be available in Chainguard's package repository. Package names may include the {{major}} and {{minor}} template tokens, which expand to the corresponding components of the base image's main package version at build time. For example, "py{{major}}.{{minor}}-cryptography" becomes "py3.13-cryptography" on an image whose main package is python-3.13, and tracks the base image as its version moves. contents.runtime_repositories Add APK repositories to /etc/apk/repositories in the image for runtime package installation. When set, these replace the default virtualapk.cgr.dev repositories. Must be HTTPS URLs. contents.runtime_keyring Trust additional APK signing public keys for the runtime repositories (e.g., a re-signing mirror). Each entry has a name and a PEM PUBLIC KEY content; the key is written to /etc/apk/keys under its name, which must match the filename referenced by the repository's APKINDEX signature (.SIGN.RSA256.). Keys can also be loaded from files using the --with-runtime-keys flag. environment Set environment variables that will be available in the image. Variables with the 'CHAINGUARD_' or 'GUARDED_' prefix are reserved and cannot be used. annotations Add custom OCI annotations to the image for tracking build information, compliance, or metadata. Keys with the 'dev.chainguard' prefix are reserved and cannot be used. accounts Customize image users and groups. You can define custom users with specific UIDs/GIDs, home directories, and group memberships. You can also specify which user the image should run as. certificates Provide custom certificates that will be merged with the default certificate bundle in the image. This is useful for adding internal CA certificates. Certificates can be defined in the YAML manifest or loaded from files using the --with-certificates flag (can be specified multiple times). Both methods can be combined and all certificates are merged together. NOTE: This is a Beta feature that requires enrollment. Contact your Customer Success Team to enable this feature. guarded_entrypoint Set to true to wrap the image entrypoint with /usr/bin/guarded-entrypoint when the image is rebuilt. At container start the wrapper replaces each environment value written as a secret reference (cg+BACKEND://REF, for example cg+gsm://projects/acme/secrets/db/versions/latest) with the secret, runs any preflight checks, and then starts the app. preflight, fail_mode, and command_override require guarded_entrypoint: true in the same manifest. NOTE: Guarded Entrypoint, including preflight, fail_mode, and command_override, is a Beta feature. Contact Chainguard customer support to get access. preflight Readiness checks the wrapper runs before starting the app, up to 32. Each entry sets exactly one of tcp (a host:port to dial) or path (a file that must exist), and optionally timeout (default 30s), interval (default 500ms), and on_failure: fail (the default; the container does not start) or continue (the failure is logged). tcp and path expand ${VAR} from the resolved environment and cannot contain ',' or '='. fail_mode What the wrapper does when a secret reference cannot be resolved: closed (the default) stops the container; open logs the failure and starts the app anyway. command_override What the wrapper starts after preparing the environment. In default mode it runs command only when it receives no arguments at all (the image has no ENTRYPOINT or CMD and none are passed at run time), and otherwise runs the arguments it received; prepend runs command followed by those arguments; override runs command and drops them. prepend and override require a command. Each command entry expands ${VAR} from the resolved environment; an unset ${VAR} stops the container instead of expanding to empty. Write $$ for a literal $, for example $${PORT:-8080} to leave ${PORT:-8080} for a shell. The command is stored in the image config, which anyone who can pull the image can read, so never write a secret into it; set it in environment as a cg+BACKEND://REF reference. An expanded ${VAR} is visible in the process's command line, so prefer an app that reads the secret from its environment. Notice: Customer shall not provide Chainguard any personal data (or similarly regulated data) as part of the Custom Assembly tool, other than the personal data that Chainguard collects in the ordinary course of business, as further detailed in its [Privacy Notice](https://www.chainguard.dev/legal/privacy-notice). ``` chainctl images repos build edit [flags] ``` ### Examples ``` # Edit a repository interactively (prompts for repo selection) chainctl images repos build edit # Edit a specific repository chainctl images repos build edit --repo=my-custom-python # Edit and save as a new repository chainctl images repos build edit --repo=my-custom-python --save-as=my-new-python # Apply configuration from a file chainctl images repos build edit --repo=my-custom-python --file=config.yaml # Apply configuration from a file and save as new repository chainctl images repos build edit --file=config.yaml --save-as=my-new-python # Add custom certificates (interactive mode) chainctl images repos build edit --repo=my-custom-python --with-certificates=ca1.pem --with-certificates=ca2.pem # Add a customer APK signing key for a re-signing mirror (the file's basename # must match the key name the mirror's APKINDEX signature references) chainctl images repos build edit --repo=my-custom-python --with-runtime-keys=key-ee8fa0a3.rsa.pub # Keys can also be set in a --file manifest; the name must match the filename # referenced by the mirror's APKINDEX signature (.SIGN.RSA256.): # # contents: # runtime_keyring: # - name: key-ee8fa0a3.rsa.pub # content: | # -----BEGIN PUBLIC KEY----- # ... # -----END PUBLIC KEY----- # Combine file-based config with certificates chainctl images repos build edit --file=config.yaml --with-certificates=internal-ca.pem # Wrap the entrypoint with guarded-entrypoint from a --file manifest. # At start the wrapper resolves DB_PASSWORD from Secret Manager, waits # for the cache, and runs the command. The wrapper expands ${CACHE_HOST}; # $${PORT:-8080} reaches the shell as ${PORT:-8080}. The sh -c form needs # an image with a shell; in a shell-less image, list the program directly, # as in [python, /app/main.py]. # # guarded_entrypoint: true # environment: # DB_PASSWORD: cg+gsm://projects/acme/secrets/db/versions/latest # CACHE_HOST: redis # fail_mode: closed # preflight: # - tcp: ${CACHE_HOST}:6379 # timeout: 60s # command_override: # mode: override # command: # - sh # - -c # - exec my-app --cache ${CACHE_HOST} --port $${PORT:-8080} chainctl images repos build edit --repo=my-custom-app --file=config.yaml ``` ### Options ``` -f, --file string The name of the file containing the build config. --parent string The name or id of the parent location to apply build config. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --repo string The name or id of the repo to apply build config. --save-as string Create a new repo with the edited configuration instead of updating the existing one. --with-certificates strings Comma separated list of files to read the custom certificates from. --with-runtime-keys strings Comma separated list of files to read customer APK signing public keys from. Each file becomes a key in /etc/apk/keys named after the file's basename, which must match the filename referenced by the repository's APKINDEX signature (.SIGN.RSA256.). --with-runtime-repositories strings Comma separated list of runtime APK repository URLs to write to /etc/apk/repositories in the image. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos build](/platform/chainctl/chainctl-docs/chainctl_images_repos_build/) - Manage custom image builds --- ### chainctl images repos build list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_build_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos build list List build reports ``` chainctl images repos build list [--parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID] [flags] ``` ### Options ``` --parent string The name or id of the parent location to list build reports. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --recursive Search repositories recursively through all descendants instead of just children --repo string Search for a specific repo by name, or ID. A full repo ID identifies the repo on its own, so --parent is not needed. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos build](/platform/chainctl/chainctl-docs/chainctl_images_repos_build/) - Manage custom image builds --- ### chainctl images repos build logs URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_build_logs.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos build logs Get build logs ``` chainctl images repos build logs [flags] ``` ### Options ``` --build-id string The id of the build to get logs for, in full or its last segment, from the repo's 30 most recent builds. --parent string The name or id of the parent location to get build logs. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --repo string The name or id of the repo to get build logs. A full repo ID identifies the repo on its own, so --parent is not needed. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos build](/platform/chainctl/chainctl-docs/chainctl_images_repos_build/) - Manage custom image builds --- ### chainctl images repos create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos create Create an image repository. ``` chainctl images repos create {REPO_NAME} --parent ORGANIZATION_NAME | ORGANIZATION_ID | FOLDER_NAME | FOLDER_ID --tier=APPLICATION|BASE|FIPS|AI|DEVTOOLS|COMMERCIAL ``` ### Options ``` --description string Description for the repo (max 255 characters). --parent string The name or id of the parent location to create an image repo. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --source string Repository ID to sync from. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos](/platform/chainctl/chainctl-docs/chainctl_images_repos/) - Image repo related commands for the Chainguard platform. --- ### chainctl images repos delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos delete Remove an image repository. ``` chainctl images repos delete {REPO_NAME|REPO_ID} --parent ORGANIZATION_NAME | ORGANIZATION_ID ``` ### Options ``` --allow-missing Exit with status 0 if the repo does not exist --parent string The name or id of the parent location to remove an image repo. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos](/platform/chainctl/chainctl-docs/chainctl_images_repos/) - Image repo related commands for the Chainguard platform. --- ### chainctl images repos list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos list List image repositories. ``` chainctl images repos list [--repo=REPO_NAME] [--public | --parent=PARENT_NAME|PARENT_ID] [--updated-within=DURATION] [--show-dates] [--show-epochs] [--show-referrers] [--show-vcs-snapshots] [--active-only] ``` ### Options ``` --parent string The name or id of the parent location to list image repos. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --public List repos from the public Chainguard registry. --recursive Search repositories recursively through all descendants instead of just children --repo string Search for a specific repo by name. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos](/platform/chainctl/chainctl-docs/chainctl_images_repos/) - Image repo related commands for the Chainguard platform. --- ### chainctl images repos update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_repos_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images repos update Update image repositories. ``` chainctl images repos update {REPO_NAME|REPO_ID} --parent ORGANIZATION_NAME | ORGANIZATION_ID ``` ### Options ``` --bundles string Comma-separated list of bundles to assign to the repo. --description string Updated description for the repo. --expiration time Sync expiration time (e.g., 1970-01-01). --name string Updated name for the repo. --parent string The name or id of the parent location of the repo to update. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --source string Repository ID to sync from. --tier string Catalog tier. One of: [APPLICATION,BASE,FIPS,AI,DEVTOOLS,COMMERCIAL] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images repos](/platform/chainctl/chainctl-docs/chainctl_images_repos/) - Image repo related commands for the Chainguard platform. --- ### chainctl images tags URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_tags.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images tags Tags related commands for images. ### Synopsis Commands for listing and resolving image tags. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images](/platform/chainctl/chainctl-docs/chainctl_images/) - Images related commands for the Chainguard platform. * [chainctl images tags list](/platform/chainctl/chainctl-docs/chainctl_images_tags_list/) - List tags from repositories using --parent, --public, or --repo flags. * [chainctl images tags resolve](/platform/chainctl/chainctl-docs/chainctl_images_tags_resolve/) - Resolve tags for a specific image reference. --- ### chainctl images tags list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_tags_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images tags list List tags from repositories using --parent, --public, or --repo flags. ``` chainctl images tags list [--repo=REPO_NAME] [--public | --parent=PARENT_NAME|PARENT_ID] [--updated-within=DURATION] [--show-dates] [--show-epochs] [--show-referrers] [--show-vcs-snapshots] [--active-only] [--output=id|json|table] ``` ### Options ``` --active-only Whether to only show active tags. --all Return all tags that match the digest of the specified image reference. --parent string The name or id of the parent location to list image repos. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --public List repos from the public Chainguard registry. --recursive Search repositories recursively through all descendants instead of just children --repo string Search for a specific repo by name. --show-dates Whether to show date tags of the form latest-{date}. --show-epochs Whether to show epoch tags of the form 1.2.3-r4. --show-referrers Whether to show referrer tags of the form sha256-deadbeef.{sig,sbom,att}. --show-vcs-snapshots Whether to show VCS snapshot tags of the form 1.2.3_git20260914. --updated-within duration The duration within which an image must have been updated (0 disables the filter). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images tags](/platform/chainctl/chainctl-docs/chainctl_images_tags/) - Tags related commands for images. --- ### chainctl images tags resolve URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_images_tags_resolve.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl images tags resolve Resolve tags for a specific image reference. ``` chainctl images tags resolve IMAGE_REF ``` ### Options ``` --all Return all tags that match the digest of the specified image reference. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl images tags](/platform/chainctl/chainctl-docs/chainctl_images_tags/) - Tags related commands for images. --- ### chainctl libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries Ecosystem library related commands. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl libraries cache](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/) - Manage the Libraries resolution cache. * [chainctl libraries entitlements](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements/) - Manage entitlements to language ecosystem libraries. * [chainctl libraries go](/platform/chainctl/chainctl-docs/chainctl_libraries_go/) - Go module registry commands. * [chainctl libraries packages](/platform/chainctl/chainctl-docs/chainctl_libraries_packages/) - Inspect Libraries packages. * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. * [chainctl libraries remediate](/platform/chainctl/chainctl-docs/chainctl_libraries_remediate/) - Discover and apply Chainguard -cgr.N CVE remediations for a JavaScript project * [chainctl libraries storage](/platform/chainctl/chainctl-docs/chainctl_libraries_storage/) - Show the storage your organization's uploads use in Libraries. * [chainctl libraries update-hashes](/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes/) - Update lockfile integrity hashes with Chainguard Libraries checksums * [chainctl libraries verify](/platform/chainctl/chainctl-docs/chainctl_libraries_verify/) - A tool to analyze the use of Chainguard Libraries in various artifacts --- ### chainctl libraries cache URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_cache.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries cache Manage the Libraries resolution cache. ### Synopsis Manage your organization's Libraries resolution cache. Once your org is served a package version from a tier (Chainguard-built or upstream), that resolution is recorded and held stable so lockfile hashes keep verifying. Zapping retires recorded resolutions so the next fetch re-resolves to the current best tier; clients that hold lockfiles must regenerate them to pick up new hashes. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl libraries cache list](/platform/chainctl/chainctl-docs/chainctl_libraries_cache_list/) - List resolution cache entries. * [chainctl libraries cache opt-in](/platform/chainctl/chainctl-docs/chainctl_libraries_cache_opt-in/) - Opt the org back into the resolution cache. * [chainctl libraries cache opt-out](/platform/chainctl/chainctl-docs/chainctl_libraries_cache_opt-out/) - Opt the org out of the resolution cache. * [chainctl libraries cache status](/platform/chainctl/chainctl-docs/chainctl_libraries_cache_status/) - Show resolution cache status. * [chainctl libraries cache zap](/platform/chainctl/chainctl-docs/chainctl_libraries_cache_zap/) - Zap resolution cache entries. --- ### chainctl libraries cache list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_cache_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries cache list List resolution cache entries. ### Synopsis List the org's recorded resolutions: which tier each package version was served from. Live entries govern serving; retired entries were superseded by a zap and are kept until cleaned up. ``` chainctl libraries cache list [--ecosystem ECOSYSTEM] [--group G | --scope S | --package P] [--tier TIER] [--live] [--limit N] [--parent ORG] [--output=json|table] [flags] ``` ### Options ``` --ecosystem string Only list entries for this ecosystem (JAVA, PYTHON, JAVASCRIPT). --group string Only list entries under a Maven group prefix. Requires --ecosystem. --limit int32 Maximum number of entries to show. (default 100) --live Only list live entries (the ones that govern serving). --package string Only list entries for one package. Requires --ecosystem. --parent string The name or id of the organization whose resolution cache to list. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --registry string Only list entries for one registry. --scope string Only list entries under an npm scope prefix. Requires --ecosystem. --tier string Only list entries recording this tier (CHAINGUARD or UPSTREAM). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries cache](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/) - Manage the Libraries resolution cache. --- ### chainctl libraries cache opt-in URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_cache_opt-in.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries cache opt-in Opt the org back into the resolution cache. ### Synopsis Opt the org back into the resolution cache after an opt-out. Recording and enforcement resume from the retained entries. ``` chainctl libraries cache opt-in [--ecosystem ECOSYSTEM] [--parent ORG] [flags] ``` ### Options ``` --ecosystem string Change one ecosystem (JAVA, PYTHON, JAVASCRIPT). Omit for all ecosystems. --parent string The name or id of the organization whose opt-out to change. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries cache](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/) - Manage the Libraries resolution cache. --- ### chainctl libraries cache opt-out URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_cache_opt-out.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries cache opt-out Opt the org out of the resolution cache. ### Synopsis Opt the org out of the resolution cache: it stops recording resolutions and recorded ones stop being enforced, so every fetch resolves to the current best tier. Recorded entries are retained; opting back in resumes from them. ``` chainctl libraries cache opt-out [--ecosystem ECOSYSTEM] [--parent ORG] [flags] ``` ### Options ``` --ecosystem string Change one ecosystem (JAVA, PYTHON, JAVASCRIPT). Omit for all ecosystems. --parent string The name or id of the organization whose opt-out to change. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries cache](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/) - Manage the Libraries resolution cache. --- ### chainctl libraries cache status URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_cache_status.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries cache status Show resolution cache status. ### Synopsis Show the org's per-ecosystem resolution cache state: the current zap generation, how many zaps have run, opt-out state, and the most recent zap time. Ecosystems that have never been zapped or opted out are in the default state and are not listed. ``` chainctl libraries cache status [--parent ORG] [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the organization to report on. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries cache](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/) - Manage the Libraries resolution cache. --- ### chainctl libraries cache zap URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_cache_zap.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries cache zap Zap resolution cache entries. ### Synopsis Retire resolution cache entries at the chosen scope: a Maven group or npm scope prefix, a single package or version, one ecosystem, or the whole org. Zapped coordinates re-resolve to the current best tier on their next fetch, which changes artifact hashes for them — regenerate lockfiles afterwards. Use --dry-run first to see how many entries a zap would retire. Real zaps prompt for confirmation; pass --yes to skip the prompt in scripts. ``` chainctl libraries cache zap [--ecosystem ECOSYSTEM] [--group G | --scope S | --package P [--version V]] [--registry R] [--dry-run] [--yes] [--parent ORG] [flags] ``` ### Options ``` --dry-run Report how many entries the zap would retire without changing anything. --ecosystem string Scope the zap to one ecosystem (JAVA, PYTHON, JAVASCRIPT). Omit for an org-wide zap. --group string Zap a Maven group prefix (e.g. com.fasterxml.jackson). Requires --ecosystem. --package string Zap a single package (java: group:artifact). Requires --ecosystem. --parent string The name or id of the organization whose resolution cache to zap. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --registry string Narrow a package or prefix zap to one registry. --scope string Zap an npm scope prefix (e.g. @angular). Requires --ecosystem. --version string Zap a single version. Requires --package. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries cache](/platform/chainctl/chainctl-docs/chainctl_libraries_cache/) - Manage the Libraries resolution cache. --- ### chainctl libraries entitlements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries entitlements Manage entitlements to language ecosystem libraries. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl libraries entitlements create](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create/) - Create ecosystem library entitlements for an organization. * [chainctl libraries entitlements delete](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_delete/) - Delete an ecosystem library entitlement from an organization. * [chainctl libraries entitlements list](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_list/) - List entitlements of an organization. --- ### chainctl libraries entitlements create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries entitlements create Create ecosystem library entitlements for an organization. ``` chainctl libraries entitlements create --parent=PARENT --ecosystems=LANGUAGE1,LANGUAGE2,... [--output=json|table] [flags] ``` ### Options ``` --ecosystems strings The language ecosystems to entitle to the parent org. --parent string The name or id of the org to create an entitlement for. --policy string The policy to apply to the entitlement (chainguard, chainguard_and_upstream). Defaults to the ecosystem's default: chainguard_and_upstream for go and dotnet, chainguard otherwise. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries entitlements](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements/) - Manage entitlements to language ecosystem libraries. --- ### chainctl libraries entitlements delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries entitlements delete Delete an ecosystem library entitlement from an organization. ``` chainctl libraries entitlements delete --parent=PARENT --ecosystem=LANGUAGE [flags] ``` ### Options ``` --ecosystem string The language ecosystem to remove the entitlement for. --parent string The name or id of the org to delete an entitlement from. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries entitlements](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements/) - Manage entitlements to language ecosystem libraries. --- ### chainctl libraries entitlements list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries entitlements list List entitlements of an organization. ``` chainctl libraries entitlements list --parent=PARENT [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the org to list an entitlements for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries entitlements](/platform/chainctl/chainctl-docs/chainctl_libraries_entitlements/) - Manage entitlements to language ecosystem libraries. --- ### chainctl libraries go URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_go.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries go Go module registry commands. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl libraries go upload](/platform/chainctl/chainctl-docs/chainctl_libraries_go_upload/) - Upload a Go module version from a source directory. --- ### chainctl libraries go upload URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_go_upload.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries go upload Upload a Go module version from a source directory. ### Synopsis Upload a Go module to Chainguard Libraries for Go. The command packages the module at --source into a GOPROXY module zip (the same layout 'go mod download' produces), validates it locally, and uploads it to /go//@v/.zip. The version lands in your organization's own registry, which only your organization can resolve, alongside the shared catalog. The registry derives the .mod and .info files from the zip. The source directory must contain a go.mod whose module directive matches --module-path; VCS metadata (.git and friends), nested modules, and irregular files are excluded automatically. Uploads are immutable: once a (module, version) exists it cannot be replaced, so publish a new version instead. --version must be a canonical semantic version such as v1.2.3, and its major version must agree with the module path (a /v2 module publishes v2.x.y). Versions with a -cgr.N suffix are reserved for Chainguard's remediation pipeline and are refused. Authentication requires a token minted for the registry host that carries the Go push capability (CAP_LIBRARIES_GO_CREATE). Within an organization that is the built-in "owner" role or the dedicated "libraries.go.push" role. Credentials are resolved in this order: 1. --token 2. $CHAINCTL_AUTH_TOKEN, if its audience includes the registry host 3. the chainctl session for the registry host, as created by 'chainctl auth login --audience ' (a browser login is opened when no usable session exists) If a token was minted before the push capability was granted it still carries the old capability set; refresh it with 'chainctl auth login --audience --refresh'. Consumers resolve uploaded versions with GOPROXY=/go. Versions published this way never appear in sum.golang.org, so consumers must exclude the module from checksum-database lookups (GONOSUMDB or GOPRIVATE). ``` chainctl libraries go upload [flags] ``` ### Examples ``` # Publish v1.4.0 of a module from its checkout chainctl libraries go upload \ --module-path example.com/team/lib \ --version v1.4.0 \ --source ./lib # Build and validate the module zip without uploading chainctl libraries go upload --module-path example.com/team/lib --version v1.4.0 --source ./lib --dry-run # Publish to a non-production registry with an explicit token chainctl libraries go upload \ --ecosystems-url https://libraries.chainops.dev \ --token "$(chainctl auth token --audience libraries.chainops.dev)" \ --module-path example.com/team/lib --version v1.4.0 --source ./lib ``` ### Options ``` --dry-run Build and validate the module zip and report its size and go.sum hash without uploading. --ecosystems-url string URL of the Chainguard Libraries registry (defaults to https://libraries.cgr.dev). The /go registry path is appended automatically. --module-path string Module path to publish (the go.mod module directive), e.g. example.com/team/lib. Required. --source string Directory containing the module source, including its go.mod. Required. --token string Bearer token for the registry host. Overrides $CHAINCTL_AUTH_TOKEN and the chainctl session. Prefer passing it via the environment to keep it out of shell history. --version string Canonical semantic version to publish, e.g. v1.4.0. Required. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries go](/platform/chainctl/chainctl-docs/chainctl_libraries_go/) - Go module registry commands. --- ### chainctl libraries packages URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages Inspect Libraries packages. ### Synopsis Browse the Chainguard Libraries package catalog. Packages are served by Chainguard Libraries across language ecosystems; these commands list packages, their versions, and per-ecosystem counts, and report packages withheld by active Libraries policy. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl libraries packages blocked](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_blocked/) - List blocked Libraries packages. * [chainctl libraries packages count](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_count/) - Count Libraries packages per ecosystem. * [chainctl libraries packages list](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_list/) - List Libraries packages. * [chainctl libraries packages malware](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware/) - Inspect the Chainguard malware/greyware blocklist. * [chainctl libraries packages versions](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_versions/) - List the versions of a Libraries package. --- ### chainctl libraries packages blocked URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages_blocked.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages blocked List blocked Libraries packages. ### Synopsis List packages that were withheld by an active Libraries policy. Defaults to ENFORCE-mode events from the last 30 days. ``` chainctl libraries packages blocked [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--ecosystem ECOSYSTEM] [--package NAME] [--mode ENFORCE|PREVIEW] [--limit N] [--output=json|table] [flags] ``` ### Options ``` --ecosystem string Only show events for this ecosystem (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). --limit int32 The number of blocked packages per page in an interactive terminal; otherwise the maximum number to return. (default 50) --mode string Only show events in this mode (ENFORCE or PREVIEW). Defaults to ENFORCE. --package string Only show events whose package name matches (exact, case-insensitive). --parent string The name or id of the organization to scope events to. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries packages](/platform/chainctl/chainctl-docs/chainctl_libraries_packages/) - Inspect Libraries packages. --- ### chainctl libraries packages count URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages_count.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages count Count Libraries packages per ecosystem. ### Synopsis Report the total number of packages available in the Chainguard Libraries catalog, optionally scoped to a single ecosystem. ``` chainctl libraries packages count [--output=json|table] [flags] ``` ### Options ``` --ecosystem string The ecosystem to count packages for (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries packages](/platform/chainctl/chainctl-docs/chainctl_libraries_packages/) - Inspect Libraries packages. --- ### chainctl libraries packages list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages list List Libraries packages. ### Synopsis List packages in the Chainguard Libraries catalog for an ecosystem, optionally filtered by a search query. ``` chainctl libraries packages list --ecosystem ECOSYSTEM [--query QUERY] [--output=json|table] [flags] ``` ### Options ``` --ecosystem string The ecosystem to list packages for (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). --limit int32 The maximum number of packages to return; results are paginated automatically up to this limit. (default 50) --query string A search string to filter packages by name. If empty, all packages in the ecosystem are returned. --remediated Only return remediated packages. --upstream Also include upstream-registry packages. By default only Chainguard-built packages are listed. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries packages](/platform/chainctl/chainctl-docs/chainctl_libraries_packages/) - Inspect Libraries packages. --- ### chainctl libraries packages malware URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages malware Inspect the Chainguard malware/greyware blocklist. ### Synopsis Browse Chainguard's malware/greyware blocklist across language ecosystems. This is a global feed: every entry is blocked by Chainguard for all organizations, independent of any org's Libraries policy. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries packages](/platform/chainctl/chainctl-docs/chainctl_libraries_packages/) - Inspect Libraries packages. * [chainctl libraries packages malware list](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware_list/) - List the Chainguard malware/greyware blocklist. --- ### chainctl libraries packages malware list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages malware list List the Chainguard malware/greyware blocklist. ### Synopsis List Chainguard's malware/greyware blocklist across language ecosystems. Filter to a single package with --package (and optionally --version) to check whether a specific artifact is flagged; rows returned means blocked, empty means clear. ``` chainctl libraries packages malware list [--ecosystem ECOSYSTEM] [--package NAME] [--version VERSION] [--since TIME] [--before TIME] [--sort KEYS] [--exit-code] [--output=json|table] [flags] ``` ### Options ``` --before string Only show entries blocked strictly before this RFC3339 time. Combine with --since to query a bounded range. --ecosystem string Only show blocklist entries for this ecosystem (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). If empty, all ecosystems are returned. --exit-code Exit with a non-zero status if any entries match, so the command can be used as a CI gate. --limit int32 The maximum number of entries to return; results are paginated automatically up to this limit. (default 50) --package string Only show entries whose package name matches (exact). --since string Only show entries blocked at or after this RFC3339 time (e.g. 2026-05-01T00:00:00Z). --sort string Comma-separated keys to sort by, ascending, in precedence order (e.g. package,version). Keys: blocked, package, version (blocked is oldest-first; version is semver-aware). Defaults to newest-blocked first. --version string Only show entries matching this version. Requires --package. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries packages malware](/platform/chainctl/chainctl-docs/chainctl_libraries_packages_malware/) - Inspect the Chainguard malware/greyware blocklist. --- ### chainctl libraries packages versions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_packages_versions.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries packages versions List the versions of a Libraries package. ### Synopsis List all versions of a single package, identified by the id returned by 'chainctl libraries packages list'. ``` chainctl libraries packages versions PACKAGE_ID [--output=json|table] [flags] ``` ### Options ``` --upstream Also include upstream-registry versions. By default only Chainguard-built versions are listed. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries packages](/platform/chainctl/chainctl-docs/chainctl_libraries_packages/) - Inspect Libraries packages. --- ### chainctl libraries policy URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy Manage Libraries policies. ### Synopsis Libraries policies control which upstream packages your organization can pull. A policy configures automatic gates (cooldown, malware) and explicit block/allow lists; a binding activates a policy for an (organization, ecosystem) pair in either ENFORCE or PREVIEW mode. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl libraries policy binding](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding/) - Manage Libraries policy bindings. * [chainctl libraries policy create](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_create/) - Create a custom Libraries policy. * [chainctl libraries policy delete](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_delete/) - Delete a custom Libraries policy. * [chainctl libraries policy describe](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_describe/) - Describe a Libraries policy. * [chainctl libraries policy disable](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_disable/) - Disable a Libraries policy for an organization. * [chainctl libraries policy enable](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_enable/) - Enable a Libraries policy for an organization. * [chainctl libraries policy list](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_list/) - List Libraries policies. * [chainctl libraries policy update](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_update/) - Update a custom Libraries policy. --- ### chainctl libraries policy binding URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy binding Manage Libraries policy bindings. ### Synopsis A Libraries policy binding activates a policy for an (organization, ecosystem) pair in either ENFORCE or PREVIEW mode. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. * [chainctl libraries policy binding create](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_create/) - Create a Libraries policy binding. * [chainctl libraries policy binding delete](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_delete/) - Delete a Libraries policy binding. * [chainctl libraries policy binding list](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_list/) - List Libraries policy bindings. * [chainctl libraries policy binding update](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_update/) - Update a Libraries policy binding. --- ### chainctl libraries policy binding create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy binding create Create a Libraries policy binding. ### Synopsis Create a binding to activate a Libraries policy for an (organization, ecosystem) pair. The default mode is ENFORCE. ``` chainctl libraries policy binding create --policy POLICY [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--ecosystem ECOSYSTEM] [--mode ENFORCE|PREVIEW] [flags] ``` ### Options ``` --ecosystem string The ecosystem the binding applies to (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). --mode string The binding mode (ENFORCE or PREVIEW). Defaults to ENFORCE. --parent string The name or id of the organization to scope the binding to. --policy string The name or UIDP of the policy to bind. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy binding](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding/) - Manage Libraries policy bindings. --- ### chainctl libraries policy binding delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy binding delete Delete a Libraries policy binding. ### Synopsis Delete a Libraries policy binding to deactivate the policy for its bound (organization, ecosystem) pair. ``` chainctl libraries policy binding delete BINDING_ID [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy binding](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding/) - Manage Libraries policy bindings. --- ### chainctl libraries policy binding list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy binding list List Libraries policy bindings. ### Synopsis List active Libraries policy bindings to see which policies are enabled and in which mode. ``` chainctl libraries policy binding list [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the organization to list bindings for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy binding](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding/) - Manage Libraries policy bindings. --- ### chainctl libraries policy binding update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy binding update Update a Libraries policy binding. ### Synopsis Update the mode of an existing Libraries policy binding. ``` chainctl libraries policy binding update BINDING_ID --mode ENFORCE|PREVIEW [flags] ``` ### Options ``` --mode string The binding mode (ENFORCE or PREVIEW). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy binding](/platform/chainctl/chainctl-docs/chainctl_libraries_policy_binding/) - Manage Libraries policy bindings. --- ### chainctl libraries policy create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy create Create a custom Libraries policy. ### Synopsis Create a CUSTOM Libraries policy for an organization. A policy configures the gates applied when your organization pulls upstream packages. Use --cooldown-days to quarantine newly published versions for N days (0 disables the cooldown, 1-30 sets an explicit window, omit to inherit the system default), --block to always deny a package, and --allow to let a package override the cooldown and/or malware gates. Packages are identified by their package URL (purl). The purl namespace selects the ecosystem, so the same --block and --allow flags work for .NET, Go, Java, JavaScript, and Python: ``` .NET (NuGet) pkg:nuget/ Go (modules) pkg:golang/ Java (Maven) pkg:maven// JavaScript (npm) pkg:npm/ pkg:npm/%40/ (scoped packages) Python (PyPI) pkg:pypi/ ``` Append a version with @ to scope an entry to a single version (for example pkg:npm/lodash@4.17.20); omit the version to match every version of the package. --block and --allow are repeatable, so a single policy may list many packages across ecosystems. A newly created policy is inactive: activate it for an ecosystem with "chainctl libraries policy enable". ``` chainctl libraries policy create --name NAME [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--cooldown-days N] [--block ...] [--allow ...] [flags] ``` ### Examples ``` # Block a specific package and apply a 14-day cooldown (Python / PyPI) chainctl libraries policy create --name=trusted --parent=example.com \ --cooldown-days=14 --block=purl=pkg:pypi/evil # Block specific packages across ecosystems (repeat --block per package) chainctl libraries policy create --name=blocklist --parent=example.com \ --block=purl=pkg:pypi/evil \ --block=purl=pkg:npm/left-pad \ --block=purl=pkg:maven/com.example/bad-lib # Block a single version, leaving other versions of the package allowed chainctl libraries policy create --name=pin --parent=example.com \ --block=purl=pkg:npm/lodash@4.17.20 # Allow a package to override the malware gate (justification required) chainctl libraries policy create --name=trusted --parent=example.com \ --allow=purl=pkg:pypi/requests,override-malware=true,justification="vetted internally" # Allow a Java package to skip the cooldown window chainctl libraries policy create --name=trusted --parent=example.com \ --allow=purl=pkg:maven/org.apache.commons/commons-lang3,override-cooldown=true ``` ### Options ``` --allow stringArray A package permitted to override gates, as comma-separated key=value pairs: purl=[,override-cooldown=true][,override-malware=true][,justification="..."]. justification is required with override-malware. Repeatable. --block stringArray A package to always deny, as purl=. The purl namespace selects the ecosystem (pkg:nuget/, pkg:golang/, pkg:maven//, pkg:npm/, pkg:pypi/); append @ to block a single version. Repeatable. --cooldown-days int32 The cooldown window in days (0 disables, 1-30 explicit, omit to inherit the default). (default -1) --description string The description of the policy. --name string The name of the policy. --parent string The name or id of the organization to scope the policy to. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries policy delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy delete Delete a custom Libraries policy. ``` chainctl libraries policy delete POLICY [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [flags] ``` ### Options ``` --parent string The name or id of the organization that owns the policy. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries policy describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy describe Describe a Libraries policy. ### Synopsis Show a Libraries policy in full, including every block-list and allow-list entry. ``` chainctl libraries policy describe POLICY [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the organization. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries policy disable URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_disable.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy disable Disable a Libraries policy for an organization. ### Synopsis Disable a Libraries policy by deleting its binding. If --mode is omitted, both the ENFORCE and PREVIEW bindings for the (organization, ecosystem) are deleted. ``` chainctl libraries policy disable [POLICY] [--parent ORG] [--ecosystem ECOSYSTEM] [--mode ENFORCE|PREVIEW] [flags] ``` ### Options ``` --ecosystem string The ecosystem the binding applies to (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). --mode string The binding mode (ENFORCE or PREVIEW). --parent string The name or id of the organization to scope the binding to. --policy string The name or UIDP of the policy. Provide this or the positional argument, not both. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries policy enable URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_enable.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy enable Enable a Libraries policy for an organization. ### Synopsis Enable a Libraries policy by creating a binding. If a binding already exists for the (organization, ecosystem, mode), its mode is updated. The default mode is ENFORCE. ``` chainctl libraries policy enable [POLICY] [--parent ORG] [--ecosystem ECOSYSTEM] [--mode ENFORCE|PREVIEW] [flags] ``` ### Options ``` --ecosystem string The ecosystem the binding applies to (DOTNET, GO, JAVA, JAVASCRIPT, PYTHON). --mode string The binding mode (ENFORCE or PREVIEW). --parent string The name or id of the organization to scope the binding to. --policy string The name or UIDP of the policy. Provide this or the positional argument, not both. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries policy list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy list List Libraries policies. ### Synopsis List the Libraries policies available to an organization (SYSTEM policies and the organization's CUSTOM policies). ``` chainctl libraries policy list [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the organization. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries policy update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_policy_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries policy update Update a custom Libraries policy. ### Synopsis Update a CUSTOM Libraries policy for an organization. By default --block and --allow ADD to the policy's existing lists: a new --block purl is appended (duplicates are ignored) and a new --allow purl replaces any existing allow entry for the same purl. Use --remove-block and --remove-allow to drop entries, or --replace-block and --replace-allow to replace an entire list with the flag's entries (pass a replace flag with no entries to clear that list). Packages are identified by their package URL (purl); see "chainctl libraries policy create --help" for the supported purl formats. ``` chainctl libraries policy update POLICY [--cooldown-days N] [--block ...] [--allow ...] [--remove-block ...] [--remove-allow ...] [--replace-block] [--replace-allow] [flags] ``` ### Examples ``` # Add a package to the existing block list (other entries are kept) chainctl libraries policy update trusted --parent=example.com \ --block=purl=pkg:pypi/evil # Add or update an allow-list override for a package chainctl libraries policy update trusted --parent=example.com \ --allow=purl=pkg:npm/left-pad,override-cooldown=true # Remove entries from the block and allow lists by purl chainctl libraries policy update trusted --parent=example.com \ --remove-block=pkg:pypi/evil \ --remove-allow=pkg:npm/left-pad # Replace the entire block list, discarding the previous entries chainctl libraries policy update trusted --parent=example.com \ --replace-block --block=purl=pkg:pypi/only-this # Clear the allow list entirely chainctl libraries policy update trusted --parent=example.com --replace-allow ``` ### Options ``` --allow stringArray A package permitted to override gates, as comma-separated key=value pairs: purl=[,override-cooldown=true][,override-malware=true][,justification="..."]. Added to the existing allow list, replacing any existing entry for the same purl (use --replace-allow to replace the whole list instead). Repeatable. --block stringArray A package to always deny, as purl= (pkg:pypi/, pkg:npm/, pkg:maven//); append @ to block a single version. Added to the existing block list (use --replace-block to replace it instead). Repeatable. --cooldown-days int32 The cooldown window in days (0 disables, 1-30 explicit, omit to inherit the default). (default -1) --description string The updated description of the policy. --parent string The name or id of the organization that owns the policy. --remove-allow stringArray A package to remove from the allow list, as a purl or purl= (match the purl shown by 'describe'). Repeatable. --remove-block stringArray A package to remove from the block list, as a purl or purl= (match the purl shown by 'describe'). Repeatable. --replace-allow Replace the entire allow list with the --allow entries instead of adding to it; pass with no --allow to clear the allow list. --replace-block Replace the entire block list with the --block entries instead of adding to it; pass with no --block to clear the block list. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries policy](/platform/chainctl/chainctl-docs/chainctl_libraries_policy/) - Manage Libraries policies. --- ### chainctl libraries remediate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_remediate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries remediate Discover and apply Chainguard -cgr.N CVE remediations for a JavaScript project ### Synopsis remediate scans a JavaScript project's resolved dependency tree against the Chainguard OpenVEX feed, reports the vulnerabilities the feed cites against it with the Chainguard remediation (-cgr.N) that clears each one, then writes the direct-dependency pins and package-manager overrides that deliver them. Provide a project directory, or omit it to use the current directory. The package manager is detected from the lockfile: package-lock.json (npm), pnpm-lock.yaml (pnpm), yarn.lock (Yarn Classic and Berry), bun.lock/bun.lockb (Bun), or deno.lock (Deno). For Deno projects, chainctl requires Deno 2.7 or later, package.json, and deno.lock. It uses npm overrides in package.json to pin dependencies to -cgr.N versions. It rejects Deno projects with deno.json or deno.jsonc in the project root. A -cgr.N prerelease is never matched by a normal range such as ^1.3.1, so adoption is always deliberate: delivery is an exact pin plus a package-manager override, never a silent substitution. Modes: - No flag: plan only. Reports what would change and writes nothing. - --apply: write the planned changes, then synchronize the lockfile with the real package manager in an isolated copy of the project. The project is left untouched until the selected -cgr.N artifacts and the unchanged unrelated dependencies are both verified. - --check: CI gate. Writes nothing and exits non-zero when any package in the tree has a remediation available that the project has not adopted. --apply additionally needs a project .npmrc pointing at the Chainguard registry, because the lockfile synchronization resolves the -cgr.N artifacts through it. Run 'chainctl auth configure-npm' once per project to write one. Neither the default plan mode nor --check needs it. Discovery is registry-highest: for each exactly resolved version it selects the greatest -cgr.N published for that base version. Packages resolved from a non-public registry, and those whose lockfile entry cannot be proven to resolve through a public registry, are excluded. Each remediation is then classified against the OpenVEX feed. A feed statement fixing a vulnerability in -cgr.N means the upstream carries it, so a tree resolved to that base is reported as vulnerable, citing the CVE. A remediation with no statement against the resolved version is reported as a rebuild rather than a vulnerability; use --vulnerable-only to plan and gate on the vulnerable ones alone. Every finding cites the rebuild of its own base version that clears it; a finding is reported as backport-unavailable when the configured registry does not publish that rebuild. When the feed also clears a vulnerability at a later base version, that version is reported as a roll-forward option alongside the backport, never in place of it. Authentication matches 'chainctl libraries update-hashes': a libraries-scoped session ('chainctl auth login --audience=libraries.cgr.dev') is used directly; otherwise pass --token, --username/--password, or --parent to authenticate via 'chainctl auth pull-token'. With no credential source and no --parent, remediate prompts for an organization. ``` chainctl libraries remediate [project-dir] [flags] ``` ### Examples ``` # Show the remediations available for the project in the current directory chainctl libraries remediate # Apply them and synchronize the lockfile chainctl libraries remediate --apply # Fail a CI job when an unadopted remediation exists chainctl libraries remediate --check # Gate only on dependencies the OpenVEX feed cites a vulnerability against chainctl libraries remediate --check --vulnerable-only # Machine-readable plan for a project in another directory chainctl libraries remediate --check --format json ./services/api ``` ### Options ``` --apply Write the planned pins and overrides and synchronize the lockfile. Mutually exclusive with --check. --check Report available remediations and exit non-zero if any are unapplied, without writing changes. Use as a CI gate. Mutually exclusive with --apply. --ecosystems-url string URL for the Ecosystems Proxy (defaults to https://libraries.cgr.dev). The /javascript path segment is appended automatically. Candidates are still validated against tarball URLs under https://libraries.cgr.dev/javascript, so only production or a proxy that preserves those URLs will yield remediations. --format string Output format alias for --output: "json" or "none". Takes precedence over --output when both are set. --ignore-netrc Do not read credentials from ~/.netrc ($NETRC). --no-auth Send no authentication when discovering remediations. Overrides all ambient credential sources, including the ~/.netrc lookup that authenticates a --vex-url mirror. Does not affect --apply, whose lockfile synchronization still authenticates to the registry with the project .npmrc. Mutually exclusive with the explicit --token and --username/--password flags. --no-color Disable colored output --parent string Parent organization for authentication via 'chainctl auth pull-token'. Not needed when --token, --username/--password, the CHAINCTL_AUTH_TOKEN/CHAINCTL_REGISTRY_USERNAME env vars, or a matching ~/.netrc entry provides credentials. --password ps Basic-auth password. Must be paired with --username. Also readable from $CHAINCTL_REGISTRY_PASSWORD. Prefer the env-var form to avoid leaking the value via ps or shell history. --token string Literal bearer token to use as the basic-auth password (username is set to "token-user"). Mutually exclusive with --username/--password. --username string Basic-auth username. Must be paired with --password. Also readable from $CHAINCTL_REGISTRY_USERNAME. --vex-url string URL serving the Chainguard OpenVEX feed (defaults to https://libraries.cgr.dev). The /openvex/v1 path segment is appended automatically. Defaulted separately from --ecosystems-url so pointing that at a private proxy does not move the scan off the published feed. The published feed needs no credentials; a mirror that does is authenticated from a matching ~/.netrc entry, never from the registry credential flags. --vulnerable-only Restrict the plan, the write set, and the --check gate to packages the OpenVEX feed cites a vulnerability against, ignoring remediations that only offer a newer rebuild. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. --- ### chainctl libraries storage URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_storage.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries storage Show the storage your organization's uploads use in Libraries. ### Synopsis Show how much Libraries storage your organization's own uploads use, per ecosystem. Only artifacts your organization uploaded count; Chainguard-built and mirrored upstream artifacts do not. Go is the only ecosystem that reports usage today. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. * [chainctl libraries storage list](/platform/chainctl/chainctl-docs/chainctl_libraries_storage_list/) - List storage usage per ecosystem. --- ### chainctl libraries storage list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_storage_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries storage list List storage usage per ecosystem. ``` chainctl libraries storage list [--ecosystem ECOSYSTEM] [--parent ORG] [--output=json|table] [flags] ``` ### Options ``` --ecosystem string Only list storage usage for this ecosystem (GO). Omit for every ecosystem that reports usage. --parent string The name or id of the organization whose storage usage to list. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries storage](/platform/chainctl/chainctl-docs/chainctl_libraries_storage/) - Show the storage your organization's uploads use in Libraries. --- ### chainctl libraries update-hashes URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_update-hashes.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries update-hashes Update lockfile integrity hashes with Chainguard Libraries checksums ### Synopsis update-hashes reads package lockfiles and updates integrity hashes with checksums from Chainguard Libraries. Provide a path to a specific lockfile, or omit it to auto-detect a lockfile in the current directory. By default, Chainguard hashes are appended alongside existing hashes in supported formats (e.g. pip-tools, poetry). Use --replace to replace them. Note: formats that store a single hash per artifact (uv.lock, pdm.lock, pylock.toml) always replace — --replace has no effect on them. JavaScript formats: - package-lock.json (npm v2/v3) - yarn.lock (v1 and berry/v2+) - pnpm-lock.yaml - bun.lock Python formats: - requirements.txt (pip-tools, with --hash) - poetry.lock - pdm.lock - uv.lock - pylock.toml (PEP 751) - Pipfile.lock The ecosystem is auto-detected from the lockfile name. Use --ecosystem to override. For Python, hashes are fetched from the Chainguard Libraries "python" catalog by default. Use --remediated to fetch from "python-remediated" (CVE-patched packages), and --cuda to also include packages from the matching CUDA variant catalog. Authentication: If you log in scoped to the libraries registry ('chainctl auth login --audience=libraries.cgr.dev'), that session token is used directly — no --parent or prompt needed. A plain 'chainctl auth login' (console-api audience) does NOT work: the libraries registry requires a libraries.cgr.dev-scoped token and a console-api token cannot be re-audienced to it. In that case, with no other credential, update-hashes prompts for an organization and authenticates via 'chainctl auth pull-token'; pass --parent to skip the prompt. To authenticate without an issuer connection (e.g. from a rebuilder workflow), pass --token or set CHAINCTL_AUTH_TOKEN (its audience must include libraries.cgr.dev; mint one with 'chainctl auth token --audience libraries.cgr.dev'). To authenticate against a private proxy (e.g. Artifactory/JFrog) with real basic-auth credentials, use --username and --password (or set CHAINCTL_REGISTRY_USERNAME and CHAINCTL_REGISTRY_PASSWORD). Credentials are also read from ~/.netrc ($NETRC if set) for the registry's host; an explicit --parent takes precedence over a matching .netrc entry, and --ignore-netrc skips ~/.netrc entirely (useful when an ambient entry for npm/yarn/pip would otherwise shadow the Chainguard-native methods). To send no authentication at all (for a network-limited private registry that requires none and rejects any Authorization header), pass --no-auth. It overrides every ambient credential source — the CHAINCTL_AUTH_TOKEN / CHAINCTL_REGISTRY_USERNAME / CHAINCTL_REGISTRY_PASSWORD env vars, ~/.netrc, and 'chainctl auth pull-token' — so no credential can leak to the registry. It is mutually exclusive with the explicit --token and --username/--password flags (passing both is a contradiction and is rejected). Custom registry URLs: Use --registry-url to point at a private proxy whose path layout does not match libraries.cgr.dev (no /javascript or /python/simple suffix is appended; the value is used verbatim as the per-ecosystem base). When --registry-url is set, the Chainguard-specific token sources are not consulted (CHAINCTL_AUTH_TOKEN env var and 'chainctl auth pull-token') — sending the Chainguard JWT as a basic-auth password to a third-party host would be a credential leak. Authenticate with --token, --username/--password, $CHAINCTL_REGISTRY_USERNAME + $CHAINCTL_REGISTRY_PASSWORD, or a matching ~/.netrc entry instead. ``` chainctl libraries update-hashes [lockfile-path] [flags] ``` ### Examples ``` # Auto-detect lockfile in the current directory chainctl libraries update-hashes # Update hashes in a specific npm lockfile chainctl libraries update-hashes package-lock.json # Update hashes in a Python pip-tools requirements file chainctl libraries update-hashes path/to/requirements.txt # Preview changes without writing the file chainctl libraries update-hashes --dry-run package-lock.json # Replace hashes (instead of appending alongside existing ones) chainctl libraries update-hashes --replace uv.lock # Include CUDA variant packages for Python chainctl libraries update-hashes --cuda cu128 uv.lock # Query an unauthenticated private registry, sending no credentials chainctl libraries update-hashes --registry-url https://registry.internal/cg --no-auth uv.lock ``` ### Options ``` --cuda string CUDA variant to include alongside python (e.g. "cu124", "cu130") --dry-run Show what would change without writing --ecosystem string Ecosystem: "auto", "js", or "python" (default "auto") --ecosystems-url string URL for the Ecosystems Proxy (defaults to https://libraries.cgr.dev). Paths /javascript/{name}/{version} (JS) and /{python,python-remediated,cu###}/simple (Python) are appended automatically. Mutually exclusive with --registry-url. --fallback-registry-url string Registry URL used to synthesize tarball URLs for JS packages not found in Chainguard Libraries (e.g. https://registry.npmjs.org). Empty (the default) disables fallback synthesis; if any package requires a fallback URL, the command fails with a list of offenders. WARNING: pointing this at a public registry such as https://registry.npmjs.org can cause installation of malicious packages — prefer a private/internal registry you trust. --ignore-netrc Do not read credentials from ~/.netrc ($NETRC). Use this to keep an ambient .netrc entry (e.g. one configured for npm/yarn/pip) from shadowing the Chainguard-native methods: with this set, a stale or unrelated .netrc entry for the registry host is ignored, so authentication falls through to 'chainctl auth pull-token' (with --parent) or the interactive organization prompt. --include-all-scopes Ignore per-scope registries configured in .npmrc and reroute every package to Chainguard. JavaScript only. Mutually exclusive with --include-scope. --include-scope strings npm scope(s) (e.g. @myorg) to reroute to Chainguard even when .npmrc configures them to a different registry. By default, packages in a scope that .npmrc pins to a non-Chainguard registry are left untouched. Repeatable and/or comma-separated. JavaScript only. --no-auth Send no authentication to the registry. Use for a network-limited private registry that requires none and rejects any Authorization header. Overrides all ambient credential sources ($CHAINCTL_AUTH_TOKEN, $CHAINCTL_REGISTRY_USERNAME/$CHAINCTL_REGISTRY_PASSWORD, ~/.netrc, and 'chainctl auth pull-token'). Mutually exclusive with the explicit --token and --username/--password flags. --no-color Disable colored output --parent string Parent organization for authentication via 'chainctl auth pull-token'. Not needed when --token, --username/--password, the CHAINCTL_AUTH_TOKEN/CHAINCTL_REGISTRY_USERNAME env vars, or a matching ~/.netrc entry provides credentials. --password ps Basic-auth password. Must be paired with --username. Also readable from $CHAINCTL_REGISTRY_PASSWORD. Prefer the env-var form to avoid leaking the value via ps or shell history. --registry-url string Full base URL of the registry to query, used verbatim (no /javascript or /python/simple suffix is appended). Use this when pointing at a private proxy (Artifactory/JFrog) whose path layout differs from libraries.cgr.dev. Mutually exclusive with --ecosystems-url, --remediated, --cuda. NOTE: when this flag is set, Chainguard-specific token sources (CHAINCTL_AUTH_TOKEN, 'chainctl auth pull-token') are NOT consulted, to avoid leaking the Chainguard JWT to a third-party host. Authenticate with --token, --username/--password, $CHAINCTL_REGISTRY_USERNAME/$CHAINCTL_REGISTRY_PASSWORD, or ~/.netrc. --remediated Use python-remediated registry (Python only) --replace Replace integrity hashes instead of appending (no-op for formats that only support replacement) --token string Literal bearer token to use as the basic-auth password (username is set to "token-user"). Against libraries.cgr.dev this behaves like setting CHAINCTL_AUTH_TOKEN; under --registry-url, only --token is honored (the env var is ignored to avoid leaking the Chainguard JWT to a third-party host). Mutually exclusive with --username/--password. --username string Basic-auth username. Must be paired with --password. Use for private proxies (Artifactory/JFrog) that require real credentials. Also readable from $CHAINCTL_REGISTRY_USERNAME. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. --- ### chainctl libraries verify URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_libraries_verify.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl libraries verify A tool to analyze the use of Chainguard Libraries in various artifacts ### Synopsis verify analyzes various artifacts (directories, archives, packages) to analyze how much was built from source by Chainguard, based on SBOM data, signatures, and artifact inspection. You can specify one or more paths to analyze multiple artifacts in a single command. For container images, you can use: - Registry references (e.g., cgr.dev/chainguard/nginx:latest) - Local single-part images (e.g., redis:latest, nginx:alpine) - Docker archive format (docker-archive:/path/to/image.tar) - Local images with prefixes (localhost/myapp:latest) JavaScript package manager caches (npm, pnpm, Yarn Classic) are auto-detected in container images and local directories by their structure. Passing a JavaScript lockfile (package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, yarn.lock, bun.lock) reports which of its registry entries carry a digest covered by a Chainguard attestation, using the integrity hashes the lockfile records. No install or package cache is required. An entry counts as verified only when the attested digest is bound to the tarball bytes the entry permits — that is, when any install honoring the entry's integrity constraint must use the attested bytes. A lone integrity hash binds: the package manager rejects any tarball that does not match it. An entry listing several hashes does not, since any of them may be satisfied — those are reported as attested but not bound, unless the resolved URL names the same package on Chainguard's built route (libraries.cgr.dev/javascript/), which fixes the source. The upstream proxy route serves upstream bytes and does not bind. A lockfile rewritten by "libraries update-hashes" in its default append mode keeps the original registry hash alongside the Chainguard one. Such entries verify only while resolved points at the Chainguard built route, so behind a private proxy or custom --registry-url they report as not bound. Use "libraries update-hashes --replace" to record a single Chainguard hash per entry, which binds regardless of where the entry resolves from. No downloaded bytes are examined, and platform, optional dependencies, overrides, and registry configuration still affect what a package manager selects. Entries with no usable digest — linked and git dependencies, and Yarn Berry's non-SRI checksums — are reported at zero coverage. Entries the registry could not answer for — an outage, or rejected credentials — are reported as unchecked rather than unverified, since a service problem is not a provenance finding. Requests are retried before an entry is called unchecked. This report is informational: the exit status does not reflect coverage. Coverage counts entries Chainguard built from source, so a package that is simply not built from source is not a defect, and gating a build on the percentage is not the intended use. Per-entry results are available with "-o json --detailed". Remediated (CVE-patched) Java artifacts, whose versions carry a "-0.cgr." suffix (e.g. 3.5.0-0.cgr.2), are resolved from the java-remediated repository; other Java artifacts are resolved from the java repository. With --output-attestations, available SBOMs and attestations are saved under each verified artifact's repository path in --output-dir. Unsupported inputs produce no companion files. ``` chainctl libraries verify [path...] [flags] ``` ### Examples ``` # Analyze a local JAR file chainctl libraries verify myapp.jar # Analyze multiple files chainctl libraries verify build/libs/*.jar build/libs/*.war # Analyze a local Python virtual environment chainctl libraries verify ./venv/ # Analyze with JSON output chainctl libraries verify -o json build/libs/*.jar # Download available SBOMs and attestations alongside verification chainctl libraries verify commons-lang3-3.17.0.jar --output-attestations --output-dir ./attestations # Analyze container images chainctl libraries verify cgr.dev/chainguard/maven:latest # Analyze remote artifact chainctl libraries verify remote:example.com/maven2/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar # Verify a lockfile without installing anything chainctl libraries verify package-lock.json chainctl libraries verify pnpm-lock.yaml # Per-entry results for machine consumption chainctl libraries verify package-lock.json -o json --detailed # Verify npm cache (auto-detected by _cacache/index-v5/ structure) chainctl libraries verify "$(npm config get cache)" # Verify pnpm store (auto-detected; supports pnpm 10+ index dirs and the # pnpm 11 SQLite index, and the versioned path reported by newer pnpm) chainctl libraries verify "$(pnpm store path)" # Verify Yarn Classic (v1.x) cache chainctl libraries verify yarn: chainctl libraries verify yarn:~/Library/Caches/Yarn/v6 ``` ### Options ``` --concurrency int Number of artifacts verified in parallel: nested archives within a single input, or inputs across a multi-path run (0 = default of 16) -d, --detailed Show detailed per-artifact results --ecosystems-url string URL for the Ecosystems Proxy (defaults to https://libraries.cgr.dev) --no-color Disable colored output -o, --output string Output format (text, json, yaml) (default "text") --output-attestations Download available SBOMs and attestations for verified artifacts into --output-dir; ignored for unsupported ecosystems --output-dir string Base directory for files downloaded by --output-attestations, laid out under each artifact's repository path. No effect without --output-attestations (default ".") --parent string Parent organization for authentication --verbose Enable verbose output ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl libraries](/platform/chainctl/chainctl-docs/chainctl_libraries/) - Ecosystem library related commands. --- ### chainctl packages URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_packages.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl packages Interact with Chainguard packages ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl packages versions](/platform/chainctl/chainctl-docs/chainctl_packages_versions/) - Package version related commands for the Chainguard platform. --- ### chainctl packages versions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_packages_versions.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl packages versions Package version related commands for the Chainguard platform. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl packages](/platform/chainctl/chainctl-docs/chainctl_packages/) - Interact with Chainguard packages * [chainctl packages versions list](/platform/chainctl/chainctl-docs/chainctl_packages_versions_list/) - List version stream data for a managed package. --- ### chainctl packages versions list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_packages_versions_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl packages versions list List version stream data for a managed package. ### Synopsis List version stream data for a managed package. PACKAGE_NAME is the name of a Chainguard-managed version stream, not an arbitrary apk package name. Examples include 'bazel', 'airflow', 'python-3.13', and 'actions-runner'. For arbitrary apk packages such as 'bash' or 'curl', use 'apk info' or 'apk search' inside a Chainguard image instead. ``` chainctl packages versions list PACKAGE_NAME [--show-eol] [--show-active] [--show-fips] [--include-inactive] [--output=csv|json|table|wide] ``` ### Examples ``` # List version data for the bazel version stream chainctl packages versions list bazel # List a specific minor stream chainctl packages versions list python-3.13 ``` ### Options ``` --include-inactive Include only packages within the EOL grace period end date. No end date is considered inclusive. --show-active Show only active versions. --show-eol Show only EOL versions. --show-fips Show only FIPS versions. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl packages versions](/platform/chainctl/chainctl-docs/chainctl_packages_versions/) - Package version related commands for the Chainguard platform. --- ### chainctl policy URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy Manage policies. ### Synopsis Policies are a registry governance feature that controls which images your organization can pull. Each policy is a guardrail (for example, "block end-of-life images") that each image is evaluated against at pull. ## Availability Policies is an opt-in feature. To enable it for your organization, contact your Chainguard Customer Success representative. If your organization is not entitled, `chainctl policy list` will return an error. ## Concepts **Policy** — A reusable rule that determines whether an image is allowed. Each policy has a name, a description, and the resource types it applies to. Policies apply to registry repositories. `chainctl` currently manages bindings to system policies that ship with the platform. Use `chainctl policy list` to see which policies are available to your organization. **Binding** — A link between a policy and an organization. While a binding exists, the policy is active for image pulls under that organization. Without a binding, the policy has no effect. **Mode** — A binding's mode controls what happens when the policy denies an image: - `ENFORCE` — Block the pull. - `DRY_RUN` — Allow the pull but record the violation. Passing `--mode` is required when creating or enabling a binding. **Parameter** — A configurable value declared by a policy's schema (for example, `days` on the cooldown policy). Supply values when you enable a policy with `--param=KEY=VALUE` (repeatable). Omitted parameters fall back to the schema's declared default. Use `chainctl policy describe --policy NAME` to see which parameters a policy accepts and what defaults apply. ## What happens at pull time Each active policy is evaluated for every image pull. Policies are `enabled and disabled independently`, and multiple policies can be active at the same time. An image is allowed only when every active policy allows it. ### Examples ``` # Recommended rollout: start in DRY_RUN mode, review violations, then promote to ENFORCE. # 1. List the policies available to your organization. chainctl policy list --parent=example.com # 2. Inspect a specific policy chainctl policy describe --policy=cooldown --parent=example.com # 3. Activate a policy in DRY_RUN mode (records, does not block). # Note: if a policy declares configurable parameters and --param is omitted, # the schema's default value is applied. chainctl policy enable --policy=cooldown --parent=example.com --mode=DRY_RUN # 4. Review which policies are currently active. chainctl policy binding list --parent=example.com # 5. Evaluate a specific image against active policies chainctl policy check cgr.dev/example.com/python:latest # 6. Promote the policy to ENFORCE mode. chainctl policy enable --policy=cooldown --parent=example.com --mode=ENFORCE # 7. Update the parameters for your binding chainctl policy enable --policy=cooldown --parent=example.com --mode=ENFORCE --param=days=14 # 8. Disable if no longer needed. chainctl policy disable --policy=cooldown --parent=example.com ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl policy binding](/platform/chainctl/chainctl-docs/chainctl_policy_binding/) - Manage policy bindings. * [chainctl policy check](/platform/chainctl/chainctl-docs/chainctl_policy_check/) - Check an image against active policies. * [chainctl policy custom](/platform/chainctl/chainctl-docs/chainctl_policy_custom/) - Manage your custom policies. * [chainctl policy decision](/platform/chainctl/chainctl-docs/chainctl_policy_decision/) - Inspect policy decisions. * [chainctl policy describe](/platform/chainctl/chainctl-docs/chainctl_policy_describe/) - Describe a policy and its parameter schema. * [chainctl policy disable](/platform/chainctl/chainctl-docs/chainctl_policy_disable/) - Disable a policy. * [chainctl policy enable](/platform/chainctl/chainctl-docs/chainctl_policy_enable/) - Enable a policy for an organization. * [chainctl policy list](/platform/chainctl/chainctl-docs/chainctl_policy_list/) - List policies. * [chainctl policy override](/platform/chainctl/chainctl-docs/chainctl_policy_override/) - Manage policy overrides. --- ### chainctl policy binding URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_binding.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy binding Manage policy bindings. ### Synopsis A policy binding is a link between a policy and a group of image resources. When a binding exists, the linked policy is active for image pulls within that group; without a binding the policy has no effect. Currently, bindings support linking a policy to an organization ID, which applies the policy to all images under that organization. Use these commands to create or delete bindings. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. * [chainctl policy binding create](/platform/chainctl/chainctl-docs/chainctl_policy_binding_create/) - Create a policy binding. * [chainctl policy binding delete](/platform/chainctl/chainctl-docs/chainctl_policy_binding_delete/) - Delete a policy binding. * [chainctl policy binding list](/platform/chainctl/chainctl-docs/chainctl_policy_binding_list/) - List policy bindings. --- ### chainctl policy binding create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_binding_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy binding create Create a policy binding. ### Synopsis Create a binding to activate a policy for an organization. If the policy is already enabled with the same mode, this is a no-op. To change the mode of an existing binding, pass --mode explicitly. --mode is required and controls enforcement behavior: ENFORCE Blocks image pulls that violate the policy. DRY_RUN Records violations without blocking pulls. Binding to an organization applies the policy to all repos within it. Use --param=KEY=VALUE (repeatable) to supply parameter values declared by the policy's schema. For STRING_LIST parameters, items are comma-separated within a single --param value. ``` chainctl policy binding create --policy POLICY [--parent ORGANIZATION_NAME | ORGANIZATION_ID] --mode MODE [--param KEY=VALUE] [--output=json|table] [flags] ``` ### Examples ``` # Enforce a policy on all repos in an organization chainctl policy binding create --policy=no-eol --parent=engineering --mode=ENFORCE # Enable a policy in dry-run mode chainctl policy binding create --policy=no-eol --parent=engineering --mode=DRY_RUN # Create a binding with interactive organization selection chainctl policy binding create --policy=no-eol --mode=ENFORCE # Create a binding with a parameter value chainctl policy binding create --policy=cooldown --parent=engineering --mode=ENFORCE --param=days=14 ``` ### Options ``` --mode string The policy mode (ENFORCE or DRY_RUN). --param stringArray Parameter value as key=value. Repeatable. --parent string The name or id of the organization to scope the binding to. --policy string The name or UIDP of the policy to bind. --resources strings The resource types this binding applies to. (default [registry.chainguard.dev/Repo]) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy binding](/platform/chainctl/chainctl-docs/chainctl_policy_binding/) - Manage policy bindings. --- ### chainctl policy binding delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_binding_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy binding delete Delete a policy binding. ### Synopsis Delete a policy binding to deactivate a policy for its bound scope. Removing a binding disables the policy — image pulls will no longer be checked against it. You can pass a binding ID directly as a positional argument, or use --policy to specify the policy name and --parent to identify the organization if needed. ``` chainctl policy binding delete [BINDING_ID | --policy POLICY] [--parent ORG] [flags] ``` ### Examples ``` # Delete a binding by ID chainctl policy binding delete # Delete a binding by policy name chainctl policy binding delete --policy=no-eol --parent=engineering ``` ### Options ``` --parent string The name or id of the organization (required when deleting by policy). --policy string The name or id of the policy to disable. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy binding](/platform/chainctl/chainctl-docs/chainctl_policy_binding/) - Manage policy bindings. --- ### chainctl policy binding list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_binding_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy binding list List policy bindings. ### Synopsis List active policy bindings to see which policies are enabled and in which mode. Bindings are scoped to one organization: pass --parent, or omit it to use your configured default group, or (with no default configured) the single organization you can access — you are prompted when several are available. Each binding shows the policy it activates and the enforcement mode (enforced or dry-run). Each binding's parameters are cross-checked against the referenced policy's current parameters. The STATUS column reports OK, INVALID (parameters no longer satisfy the schema), or UNKNOWN. Rows flagged INVALID are elaborated below the table with the concrete reasons; the JSON output carries the same detail when at least one binding is invalidated. ``` chainctl policy binding list [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Examples ``` # List bindings for your default (or only) organization chainctl policy binding list # List bindings for a specific organization chainctl policy binding list --parent=engineering ``` ### Options ``` --parent string The name or id of the organization to list bindings for. --resource-type string Only list entries for this resource type (shorthand: Repo, Python, Java, Javascript; or a full type). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy binding](/platform/chainctl/chainctl-docs/chainctl_policy_binding/) - Manage policy bindings. --- ### chainctl policy check URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_check.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy check Check an image against active policies. ### Synopsis Evaluate an image against any active policies and print the result for each. Exit status is non-zero if any policy returned DENIED or ERROR, regardless of the policy's mode, so this command is suitable for use in CI. ``` chainctl policy check IMAGE_REF [flags] ``` ### Examples ``` # Check an image by tag chainctl policy check cgr.dev/example.com/python:latest # Check an image by digest chainctl policy check cgr.dev/example.com/python@sha256:abc... ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. --- ### chainctl policy custom URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_custom.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy custom Manage your custom policies. ### Synopsis Manage the lifecycle of your organization's custom policies: validate a manifest before submitting, create a new policy, update an existing one, and delete when it's no longer needed. A custom policy is a Rego expression paired with a single supported resource type and an optional parameter schema. Each policy is identified by (name, resource type) within an organization. The supported resource type is set at create time and cannot be changed afterwards. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. * [chainctl policy custom create](/platform/chainctl/chainctl-docs/chainctl_policy_custom_create/) - Create a custom policy. * [chainctl policy custom delete](/platform/chainctl/chainctl-docs/chainctl_policy_custom_delete/) - Delete a custom policy. * [chainctl policy custom update](/platform/chainctl/chainctl-docs/chainctl_policy_custom_update/) - Update a custom policy. * [chainctl policy custom validate](/platform/chainctl/chainctl-docs/chainctl_policy_custom_validate/) - Validate a custom policy manifest or expression without persisting it. --- ### chainctl policy custom create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_custom_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy custom create Create a custom policy. ### Synopsis Create a custom policy from a YAML manifest, or from a raw Rego expression as a shortcut for parameterless policies. Two modes are supported: --file: creates a policy from a full YAML manifest (name, description, expression, supported_resource_type, and optional parameters). Use this when your policy declares parameters. --expression: creates a parameterless policy directly from a raw .rego file. --name and --resource-type are required in this mode; --description is optional. Policies with parameters must be authored as a manifest and passed via --file. Policy expressions must be declared under `package chainguard.policies` and must define an `allow` rule (start from `default allow := false` and add an `allow if { ... }` block); validation rejects a module defining neither. A minimal manifest looks like: ```yaml name: my-policy description: What this policy enforces. supported_resource_type: registry.chainguard.dev/Repo@v1 expression_type: EXPRESSION_TYPE_REGO parameters: # optional; omit for parameterless policies - name: days type: PARAMETER_TYPE_INTEGER # STRING | INTEGER | BOOLEAN | STRING_LIST description: Minimum age in days. default: 7 minimum: 1 maximum: 365 expression: | package chainguard.policies import rego.v1 default allow := false allow if { # replace with your real conditions input.parameters.days >= 1 } ``` Each parameter accepts: name, type (a PARAMETER_TYPE_* value), description, default, minimum, maximum, allowed_values, required, deprecated. --resource-type accepts a shorthand (Repo, DotNet, Go, Java, Javascript, Python) or a full type: - Repo -> registry.chainguard.dev/Repo@v1 - DotNet -> libraries.chainguard.dev/DotNetPackage@v1 - Go -> libraries.chainguard.dev/GoPackage@v1 - Java -> libraries.chainguard.dev/JavaPackage@v1 - Javascript -> libraries.chainguard.dev/NPMPackage@v1 - Python -> libraries.chainguard.dev/PythonPackage@v1 The resource type is set at create time and cannot be changed later. Within an organization, (name, resource type) is unique. The same name may be reused across different resource types. A manifest whose rule reads the same for every ecosystem may name them all at once with the repeated `supported_resource_types` in place of the scalar `supported_resource_type`: ```yaml supported_resource_types: - libraries.chainguard.dev/NPMPackage@v1 - libraries.chainguard.dev/PythonPackage@v1 - libraries.chainguard.dev/JavaPackage@v1 ``` That creates one policy per entry, all sharing the manifest's name. A resource type that already carries a policy of this name is skipped and reported, so a re-run fills in whichever types are missing. For an authoritative live example, inspect a system policy with `chainctl policy describe --policy -o json`. ``` chainctl policy custom create --file POLICY.yaml | --expression POLICY.rego --name NAME --resource-type TYPE [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Examples ``` # Create a custom policy from a full manifest chainctl policy custom create --file policy.yaml --parent example.com # Create a parameterless policy from a raw Rego expression chainctl policy custom create --expression allow.rego --name allow-all \ --resource-type Repo --parent example.com ``` ### Options ``` --description string Human-readable description of the policy. Only valid with --expression. -e, --expression string Path to a Rego expression (.rego) file. Shortcut for parameterless policies; requires --name and --resource-type. -f, --file string Path to a policy manifest YAML file. --name string Policy name. Required with --expression. --parent string The name or id of the organization to create the policy under. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --resource-type string The resource type the policy applies to (shorthand: Repo, Python, Java, Javascript; or a full type). Required with --expression. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy custom](/platform/chainctl/chainctl-docs/chainctl_policy_custom/) - Manage your custom policies. --- ### chainctl policy custom delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_custom_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy custom delete Delete a custom policy. ### Synopsis Delete a custom policy by name or UIDP. Use --force to skip the confirmation prompt. Deletion is permanent, once a policy is deleted it cannot be recovered. Deletion cascades: every binding that referenced the policy is removed, and every override waiving this policy is removed as well. It is reported the number of bindings and overrides that will be affected before you confirm. If the target policy's name is not unique within the organization (more than 1 policy can share a name when their resource types differ), pass --resource-type to disambiguate. Ignored when --policy is given by UIDP. ``` chainctl policy custom delete --policy NAME_OR_ID [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--force] [flags] ``` ### Examples ``` # Delete a custom policy by name chainctl policy custom delete --policy cooldown-30 --parent example.com # Delete by UIDP, skipping confirmation chainctl policy custom delete --policy 720a...c81 --force # Disambiguate a policy name shared across resource types chainctl policy custom delete --policy cooldown-30 --resource-type Python --parent example.com ``` ### Options ``` --force Skip the confirmation prompt. --parent string The name or id of the organization the policy belongs to. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --policy string The name or UIDP of the custom policy to delete. --resource-type string Resource type used to disambiguate a policy referenced by name (shorthand: Repo, Python, Java, Javascript; or a full type). Ignored when the policy is given by UIDP. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy custom](/platform/chainctl/chainctl-docs/chainctl_policy_custom/) - Manage your custom policies. --- ### chainctl policy custom update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_custom_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy custom update Update a custom policy. ### Synopsis Update a custom policy in one of two modes: --file: full replacement from a YAML manifest. The manifest supplants the entire policy definition. The resulting policy is exactly what the manifest declares (name, description, expression, supported_resource_type, and parameters). Flag mode: partial update via individual flags. Only the fields you pass are changed; everything else is preserved. Flag mode is restricted to policies that do NOT declare parameter schemas. For parameterized policies, use --file. The supported resource type is immutable. A --file update whose manifest declares a different resource type than the current policy is rejected. Update addresses one policy, so a manifest using the repeated `supported_resource_types` to name several — the form `create` fans out — is rejected here; update each resource type in turn instead. If the target policy's name is not unique within the organization (more than 1 policy can share a name when their resource types differ), pass --resource-type to disambiguate. Ignored when --policy is given by UIDP. See `chainctl policy custom create --help` for the manifest fields and the required Rego package and `allow` rule. ``` chainctl policy custom update --policy NAME_OR_ID --file POLICY.yaml | [--name NAME] [--description DESC] [--expression FILE.rego] [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Examples ``` # Full replacement from a manifest chainctl policy custom update --policy cooldown-30 --file policy.yaml --parent example.com # Rename only chainctl policy custom update --policy cooldown-30 --name cooldown-strict --parent example.com # Change just the description chainctl policy custom update --policy cooldown-30 --description "block pulls from images newer than 30 days" # Replace the expression from a new .rego (parameterless policies only) chainctl policy custom update --policy cooldown-30 --expression new-cooldown.rego # Disambiguate a policy name shared across resource types chainctl policy custom update --policy cooldown-30 --resource-type Python --description "python-only variant" ``` ### Options ``` --description string New description. Flag mode only; mutually exclusive with --file. -e, --expression string Path to a new Rego expression (.rego) file. Flag mode only; mutually exclusive with --file. -f, --file string Path to a policy manifest YAML file. Full replacement — supplants the entire policy definition. --name string New policy name. Flag mode only; mutually exclusive with --file. --parent string The name or id of the organization the policy belongs to. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --policy string The name or UIDP of the custom policy to update. --resource-type string Resource type used to disambiguate a policy referenced by name (shorthand: Repo, Python, Java, Javascript; or a full type). Ignored when the policy is given by UIDP. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy custom](/platform/chainctl/chainctl-docs/chainctl_policy_custom/) - Manage your custom policies. --- ### chainctl policy custom validate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_custom_validate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy custom validate Validate a custom policy manifest or expression without persisting it. ### Synopsis Validate a policy without creating it to catch problems. Two modes are supported: --file: validates a full policy manifest (YAML). Expression and parameters are both checked. --expression: validates just a Rego expression (a raw .rego file). Useful during authoring for a fast parse + compile check. Pass --resource-type to say which resource types the expression is for; a manifest declares its own. An expression is validated against the input document its resource type is evaluated with, so the same expression can be valid for one type and read an undefined field under another. A manifest naming several types is checked against each, and a diagnostic that holds for only some of them names those. Prints structured diagnostics on errors, indicating if the policy is invalid. Expression errors include the line and column in the Rego source; parameter-schema errors identify the offending field (e.g. parameter_schemas[0].name). Policy expressions must be declared under `package chainguard.policies` and must define an `allow` rule; validation rejects a module that defines neither. ``` chainctl policy custom validate --file POLICY.yaml | --expression POLICY.rego [flags] ``` ### Examples ``` # Check an existing policy layout for reference chainctl policy describe --policy cooldown --parent example.com -o json # Validate a manifest before creating the policy chainctl policy custom validate --file policy.yaml # Validate just the Rego expression chainctl policy custom validate --expression policy.rego --resource-type registry.chainguard.dev/Repo@v1 # Validate an expression for several library ecosystems at once chainctl policy custom validate --expression policy.rego \ --resource-type libraries.chainguard.dev/NPMPackage@v1 \ --resource-type libraries.chainguard.dev/PythonPackage@v1 ``` ### Options ``` -e, --expression string Path to a Rego expression (.rego) file. Validates the expression only; skips parameters. -f, --file string Path to a policy manifest YAML file. --resource-type stringArray Resource type to validate the expression against (e.g. registry.chainguard.dev/Repo@v1). Required with --expression, repeat for several, and rejected with --file. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy custom](/platform/chainctl/chainctl-docs/chainctl_policy_custom/) - Manage your custom policies. --- ### chainctl policy decision URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_decision.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy decision Inspect policy decisions. ### Synopsis A policy decision records the outcome of evaluating one policy against one image digest at pull time. Decisions are recorded for both enforced and dry-run policies, so you can review what a policy blocked, or would have blocked, before promoting it to enforce mode. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. * [chainctl policy decision list](/platform/chainctl/chainctl-docs/chainctl_policy_decision_list/) - List policy decisions. --- ### chainctl policy decision list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_decision_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy decision list List policy decisions. ### Synopsis List recorded policy decisions to see which image digests a policy allowed or denied at pull time. Each decision shows the repository, the digest, the policy that evaluated it, the mode, the outcome, and when it was pulled. Decisions are scoped to one organization: pass --parent, or omit it to use your configured default group, or (with no default configured) the single organization you can access — you are prompted when several are available. Within that scope, filter by --repo for a single repository, by --policy for a single policy, by --mode or --result for a subset of outcomes, and by --since for a time window. Decisions are listed most recent first. By default the 20 most recent are shown; use --limit (1-100) to change how many are returned. For a multi-arch image the ARTIFACT ID column may show the index digest, while pulls are enforced against the per-platform child manifest; run "chainctl policy check" to find the child digest an override must target. The table view shows the artifact in short form; -o json returns the full value. Use --show-decision-details to print each decision as an expanded block, grouped by artifact. Every verdict is rendered so a --artifact-id lookup shows the full decision picture. Use --show-artifact-ids to print the unique full artifact IDs from the result set, one per line. With -o json the output is an object with an "items" array (one entry per decision) and a string "totalCount"; read .items[] rather than treating the output as a top-level array. ``` chainctl policy decision list [--parent ORG] [--repo REPO] [--artifact-id ARTIFACT] [--policy POLICY] [--mode MODE] [--result RESULT] [--since Nd] [--limit N] [--show-decision-details] [--show-artifact-ids] [--output=json|table] [flags] ``` ### Examples ``` # List decisions for your default (or only) organization chainctl policy decision list # List decisions for a specific organization chainctl policy decision list --parent=engineering # List what the cooldown policy would have blocked in the last day chainctl policy decision list --parent=engineering --policy=cooldown --result=DENIED --since=1d # List all decisions recorded for a single repository chainctl policy decision list --parent=engineering --repo=nginx # Show the 50 most recent decisions chainctl policy decision list --parent=engineering --limit=50 # Print each decision as an expanded block with per-policy reasons chainctl policy decision list --show-decision-details # List the unique full artifact IDs from the result set chainctl policy decision list --show-artifact-ids # Show every policy's verdict and reasons for a single artifact chainctl policy decision list --artifact-id=sha256: --show-decision-details # List decisions as JSON for scripting; the payload is an object, so read .items[] chainctl policy decision list --parent=engineering -o json | jq '.items[]' ``` ### Options ``` --artifact-id string Only show decisions for this artifact: an image digest for container policies (sha256:...) or a PURL for library policies (pkg:npm/left-pad@1.3.0). --limit int Maximum number of decisions to return, most recent first (1-100). (default 20) --mode string Only show decisions evaluated in this mode (ENFORCE or DRY_RUN; the POLICY_MODE_ prefixed value from -o json is also accepted). --parent string The name or id of the organization to list decisions for. --policy string Only show decisions for this policy (name or UIDP). --repo string Only show decisions for this repository. --resource-type string Resource type used to disambiguate a policy referenced by name (shorthand: Repo, Python, Java, Javascript; or a full type). Ignored when the policy is given by UIDP. --result string Only show decisions with this outcome (ALLOWED, DENIED, or ERROR; the RESULT_ prefixed value from -o json is also accepted). --show-artifact-ids Print the unique full artifact IDs from the result set, one per line. --show-decision-details Print each decision as an expanded block that lists every reason line-by-line. --since string Only show decisions pulled within a window given as a positive whole number of days followed by d, e.g. 7d. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy decision](/platform/chainctl/chainctl-docs/chainctl_policy_decision/) - Inspect policy decisions. --- ### chainctl policy describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy describe Describe a policy and its parameter schema. ### Synopsis Show the full definition of a policy: its description, type, resource type, and the set of configurable parameters it accepts. The output includes a copyable example invocation suitable for `chainctl policy enable`. Use this command to discover what's configurable on a system policy before enabling it for your organization. ``` chainctl policy describe --policy POLICY [--parent ORG] [--output=json|table] [flags] ``` ### Examples ``` # Describe the cooldown policy chainctl policy describe --policy=cooldown --parent=example.com # JSON output (useful for scripts that need the full schema) chainctl policy describe --policy=cooldown --parent=example.com -o json ``` ### Options ``` --parent string The name or id of the organization to scope the lookup to. --policy string The name or UIDP of the policy to describe. --resource-type string Resource type used to disambiguate a policy referenced by name (shorthand: Repo, Python, Java, Javascript; or a full type). Ignored when the policy is given by UIDP. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. --- ### chainctl policy disable URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_disable.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy disable Disable a policy. ### Synopsis Disable a policy by deleting its binding. This is a shortcut for "policies binding delete". ``` chainctl policy disable --policy POLICY [--parent ORG] [flags] ``` ### Examples ``` # Disable a policy by name chainctl policy disable --policy=no-eol --parent=example.com ``` ### Options ``` --parent string The name or id of the organization (required when deleting by policy). --policy string The name or id of the policy to disable. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. --- ### chainctl policy enable URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_enable.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy enable Enable a policy for an organization. ### Synopsis Enable a policy by creating a binding for an organization. If the policy is already enabled, its mode (or parameter values) is updated. This is a shortcut for "policies binding create". --mode is required and must be either ENFORCE or DRY_RUN. Pass --param=KEY=VALUE (repeatable) to supply values for policies that declare a parameter schema; omitted parameters fall back to the schema's declared default. ``` chainctl policy enable --policy POLICY [--parent ORG] --mode MODE [--param KEY=VALUE] [--output=json|table] [flags] ``` ### Examples ``` # Enable a policy in DRY_RUN mode chainctl policy enable --policy=no-eol --parent=example.com --mode=DRY_RUN # Enable a policy in enforce mode chainctl policy enable --policy=no-eol --parent=example.com --mode=ENFORCE # Enable a policy with a parameter value chainctl policy enable --policy=cooldown --parent=example.com --mode=ENFORCE --param=days=14 ``` ### Options ``` --mode string The policy mode (ENFORCE or DRY_RUN). --param stringArray Parameter value as key=value. Repeatable. --parent string The name or id of the organization to scope the binding to. --policy string The name or UIDP of the policy to bind. --resources strings The resource types this binding applies to. (default [registry.chainguard.dev/Repo]) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. --- ### chainctl policy list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy list List policies. ### Synopsis List the policies available for an organization. Each available policy is shown with its name, resource type, and description. Several policies can share a name across resource types, so --resource-type narrows the list to one of them. ``` chainctl policy list [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Examples ``` # List all policies for an organization chainctl policy list --parent=example.com # List policies using interactive organization selection chainctl policy list # List only the policies that apply to Python packages chainctl policy list --parent=example.com --resource-type=Python # List policies in JSON format chainctl policy list --parent=example.com -o json ``` ### Options ``` --parent string The name or id of the organization. Defaults to the default.group config value (env: CHAINGUARD_DEFAULT_GROUP). --resource-type string Only list entries for this resource type (shorthand: Repo, Python, Java, Javascript; or a full type). ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. --- ### chainctl policy override URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_override.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy override Manage policy overrides. ### Synopsis A policy override is an admin-granted waiver that flips a DENIED policy result to ALLOWED for one specific artifact. It does not change the policy or its binding; it records a deliberate, attributable exception that the engine applies after evaluation. Each override names exactly one policy and one artifact. Creating an override requires the policies.override.create capability, which is typically held by organization owners. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy](/platform/chainctl/chainctl-docs/chainctl_policy/) - Manage policies. * [chainctl policy override create](/platform/chainctl/chainctl-docs/chainctl_policy_override_create/) - Create a policy override. * [chainctl policy override delete](/platform/chainctl/chainctl-docs/chainctl_policy_override_delete/) - Delete a policy override. * [chainctl policy override list](/platform/chainctl/chainctl-docs/chainctl_policy_override_list/) - List policy overrides. --- ### chainctl policy override create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_override_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy override create Create a policy override. ### Synopsis Create an override that waives a policy for one specific artifact. The override flips the policy's result to ALLOWED for the artifact identified by --artifact-id, under the policy named by --policy. Which form the artifact takes follows from the policy's resource type: an image digest for a Repo policy, written as sha256: followed by exactly 64 lowercase hex characters, or a PURL for a library policy, such as pkg:npm/left-pad@1.3.0. A malformed value is rejected locally before any API call, as is a digest aimed at a library policy or a PURL aimed at an image policy. A --reason is required to record why the waiver was granted. An override matches exactly one artifact. For a multi-arch image, pulls are enforced against the per-platform child manifest, not the index digest, so override the child digest that "chainctl policy check" reports as enforced; overriding the index digest alone may not unblock the pull. A library override matches either one exact version, when the PURL carries one, or every version of the package, when it does not. Because a policy is identified by its name and resource type together, the same name can exist for images and for each library ecosystem. Pass --resource-type to say which one you mean. Creating an override requires the policies.override.create capability, a separate capability typically held by organization owners. ``` chainctl policy override create --policy POLICY --artifact-id ARTIFACT --reason REASON [--parent ORG] [--output=json|table] [flags] ``` ### Examples ``` # Waive the no-eol policy for a specific image digest chainctl policy override create --policy=no-eol --parent=engineering \ --artifact-id=sha256:<64-hex-digest> --reason="approved exception, ticket OPS-42" # Waive the cooldown gate for one npm package version chainctl policy override create --policy=cooldown --resource-type=Javascript \ --parent=engineering --artifact-id=pkg:npm/left-pad@1.3.0 \ --reason="hotfix dependency, approved by SEC, OPS-42" # Waive the malware gate for one PyPI package version chainctl policy override create --policy=malware --resource-type=Python \ --parent=engineering --artifact-id=pkg:pypi/acme-telemetry@1.4.2 \ --reason="scanner false positive confirmed by SEC, OPS-42" ``` ### Options ``` --artifact-id string The artifact to waive: an image digest for a Repo policy, as sha256: followed by exactly 64 lowercase hex characters, or a PURL for a library policy, e.g. pkg:npm/left-pad@1.3.0. For a multi-arch image use the per-platform child digest that "chainctl policy check" reports, not the index digest. --parent string The name or id of the organization to scope the override to. --policy string The name or UIDP of the policy to override. --reason string The justification for the override. --resource-type string Resource type used to disambiguate a policy referenced by name (shorthand: Repo, Python, Java, Javascript; or a full type). Ignored when the policy is given by UIDP. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy override](/platform/chainctl/chainctl-docs/chainctl_policy_override/) - Manage policy overrides. --- ### chainctl policy override delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_override_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy override delete Delete a policy override. ### Synopsis Delete a policy override to revoke a waiver. Deletion happens immediately and is not confirmed: there is no prompt and no --force flag, so the override is removed as soon as you run the command. Once removed, the targeted artifact is again subject to the policy's result. Pass the override's full ID (a UIDP) as the positional argument; it is globally unique, so no --parent is needed. Use "chainctl policy override list" to find it. ``` chainctl policy override delete OVERRIDE_ID [flags] ``` ### Examples ``` # Delete an override by ID chainctl policy override delete ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy override](/platform/chainctl/chainctl-docs/chainctl_policy_override/) - Manage policy overrides. --- ### chainctl policy override list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_policy_override_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl policy override list List policy overrides. ### Synopsis List active policy overrides to see which artifacts have been waived and for which policy. Overrides are scoped to one organization: pass --parent, or omit it to use your configured default group, or (with no default configured) the single organization you can access — you are prompted when several are available. Each override shows its ID, the policy it waives, the targeted artifact, and the reason. ``` chainctl policy override list [--parent ORGANIZATION_NAME | ORGANIZATION_ID] [--output=json|table] [flags] ``` ### Examples ``` # List overrides for your default (or only) organization chainctl policy override list # List overrides for a specific organization chainctl policy override list --parent=engineering ``` ### Options ``` --parent string The name or id of the organization to list overrides for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl policy override](/platform/chainctl/chainctl-docs/chainctl_policy_override/) - Manage policy overrides. --- ### chainctl skills URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills Skills registry related commands. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl skills accept-terms](/platform/chainctl/chainctl-docs/chainctl_skills_accept-terms/) - Accept legal terms required to publish skills. * [chainctl skills delete](/platform/chainctl/chainctl-docs/chainctl_skills_delete/) - Remove a published version of a skill. * [chainctl skills describe](/platform/chainctl/chainctl-docs/chainctl_skills_describe/) - Show metadata for a published skill. * [chainctl skills entitlements](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements/) - Manage skills entitlements for an organization. * [chainctl skills harden](/platform/chainctl/chainctl-docs/chainctl_skills_harden/) - Submit a skill for server-side hardening. * [chainctl skills install](/platform/chainctl/chainctl-docs/chainctl_skills_install/) - Download a skill and install it into agent directories. * [chainctl skills list](/platform/chainctl/chainctl-docs/chainctl_skills_list/) - List skills published by an org. * [chainctl skills pull](/platform/chainctl/chainctl-docs/chainctl_skills_pull/) - Download a published skill to a local directory. * [chainctl skills push](/platform/chainctl/chainctl-docs/chainctl_skills_push/) - Package a skill directory and publish it to uploads.cgr.dev. * [chainctl skills registry](/platform/chainctl/chainctl-docs/chainctl_skills_registry/) - Publish and install skills in any OCI registry by reference. * [chainctl skills status](/platform/chainctl/chainctl-docs/chainctl_skills_status/) - Check a harden job or wait for it to finish. * [chainctl skills uninstall](/platform/chainctl/chainctl-docs/chainctl_skills_uninstall/) - Remove a skill from agent directories on the local machine. * [chainctl skills validate](/platform/chainctl/chainctl-docs/chainctl_skills_validate/) - Check a local skill directory's format (not its authenticity) without making network calls. * [chainctl skills verify](/platform/chainctl/chainctl-docs/chainctl_skills_verify/) - Verify that Chainguard signed a published skill. * [chainctl skills versions](/platform/chainctl/chainctl-docs/chainctl_skills_versions/) - List all published versions (tags) for a skill. --- ### chainctl skills accept-terms URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_accept-terms.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills accept-terms Accept legal terms required to publish skills. ### Synopsis Accept the legal documents required to publish skills to skills.cgr.dev. An org owner must run this once per org before any 'chainctl skills push' will succeed. Re-running after acceptance is a no-op. --group accepts either the org name (e.g. "acme.com") or its UIDP; omit it for an interactive picker. For CI use cases where an interactive TUI isn't available, pass --yes. By using --yes you confirm you have read and agreed to the legal documents referenced in https://www.chainguard.dev/legal/agent-skills and https://www.chainguard.dev/legal/supplemental-dpa. ``` chainctl skills accept-terms [flags] ``` ### Options ``` --group string Name or UIDP of the org to accept terms for --yes Accept legal terms non-interactively. By using this flag you confirm you have read and agreed to the documents referenced in https://www.chainguard.dev/legal/agent-skills and https://www.chainguard.dev/legal/supplemental-dpa. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills delete Remove a published version of a skill. ### Synopsis Remove a published version of a skill. Delete a single version: org/name:tag A tag is required to prevent accidental deletion of "latest", and deleting the "latest" tag requires additional confirmation. Digest references are not supported. When you delete a skill's last remaining version, the now-empty skill entry is removed too, so it no longer lingers in "skills list" with no pullable content. Clear an already-empty skill: org/name A tagless reference is accepted only for a skill that has no versions left (the residue of versions deleted before this cleanup existed). A skill that still has versions requires an explicit tag. ``` chainctl skills delete [flags] ``` ### Options ``` -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills describe URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_describe.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills describe Show metadata for a published skill. ``` chainctl skills describe [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills entitlements URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_entitlements.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills entitlements Manage skills entitlements for an organization. ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. * [chainctl skills entitlements create](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements_create/) - Create a skills entitlement for an organization. * [chainctl skills entitlements delete](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements_delete/) - Delete the skills entitlement from an organization. * [chainctl skills entitlements list](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements_list/) - List skills entitlements of an organization. --- ### chainctl skills entitlements create URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_entitlements_create.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills entitlements create Create a skills entitlement for an organization. ### Synopsis Create a skills entitlement for an organization. This enables the org to push and pull skill artifacts via skills.cgr.dev. The command is idempotent: re-running it on an already-entitled org converges to the correct state without returning an error. ``` chainctl skills entitlements create --parent=PARENT [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the org to create a skills entitlement for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills entitlements](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements/) - Manage skills entitlements for an organization. --- ### chainctl skills entitlements delete URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_entitlements_delete.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills entitlements delete Delete the skills entitlement from an organization. ### Synopsis Delete the skills entitlement from an organization. WARNING: This removes the skills folder and entitlement record for the org. All skill repos under the org become inaccessible. There is no recovery path. ``` chainctl skills entitlements delete --parent=PARENT [flags] ``` ### Options ``` --parent string The name or id of the org to delete the skills entitlement from. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills entitlements](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements/) - Manage skills entitlements for an organization. --- ### chainctl skills entitlements list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_entitlements_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills entitlements list List skills entitlements of an organization. ``` chainctl skills entitlements list --parent=PARENT [--output=json|table] [flags] ``` ### Options ``` --parent string The name or id of the org to list skills entitlements for. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills entitlements](/platform/chainctl/chainctl-docs/chainctl_skills_entitlements/) - Manage skills entitlements for an organization. --- ### chainctl skills harden URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_harden.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills harden Submit a skill for server-side hardening. ### Synopsis Submit a skill to the Skills API for server-side hardening. Pass a local path or use --folder to package and upload a directory; SKILL.md supplies the skill name. An uploads registry reference uses an existing artifact. --digest uses an artifact already uploaded to uploads.cgr.dev/ORG/NAME and requires --name. The digest must be sha256:<64-hex>, not a tag or a full reference. --group is required and accepts an organization name or UIDP. Prints the job ID and status after submission. With --wait, polls until the job finishes, reporting the pipeline stage as it runs, then downloads the result to ./hardened/NAME and prints its reference and digest. .hardened-artifact.json in the download records the published reference and digest. HARDENING.md contains the report and scanner findings; its source digest is the digest of the submitted skill, not of the hardened result. If the post-hardening scan rejects the rewrite, your submitted skill is published unmodified with that report instead: it downloads to ./unhardened/NAME and the command exits nonzero, naming the scanner and rule that rejected it. Any other failed job exits nonzero and names the stage it failed in. --timeout bounds the command when waiting. A timeout or interrupt leaves the server-side job running. Submitting unchanged content to the same organization as the same user returns the same job ID. Use skills status to check a saved job ID or resume waiting without uploading again. To rerun a failed job with the same input, pass --retry-of JOB_ID. Repeating that retry returns the same new attempt; to retry again, pass the failed attempt's new job ID. ``` chainctl skills harden [] --group [flags] ``` ### Examples ``` chainctl skills harden --group my-org ./my-skill --wait --timeout 30m chainctl skills harden --folder ./my-skill --group my-org chainctl skills harden uploads.cgr.dev/my-org/my-skill:latest --group my-org --wait chainctl skills harden --digest sha256:<64-hex> --name my-skill --group my-org chainctl skills status --group my-org --id --wait ``` ### Options ``` --digest string SHA256 digest of an artifact already in the uploads registry. --folder string Local skill directory containing SKILL.md. -g, --group string Target organization name or UIDP (required). --name string Uploaded skill name (required with --digest). --retry-of string Failed job ID to retry with this same skill content. --timeout duration Maximum command duration with --wait (0 means no timeout). --wait Wait for hardening to finish. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills install URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_install.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills install Download a skill and install it into agent directories. ### Synopsis Download a skill and place it into the skills directories of detected agents. is a skill reference, optionally host-qualified: a bare org/name (or org/owner/name) resolves to the hardened catalog on skills.cgr.dev, while an uploads.cgr.dev/org/name reference installs a private skill straight from the uploads namespace (the same reference `chainctl skills list --source uploads` prints). By default, a shared canonical copy is written to .agents/skills// and agent-specific symlinks are created. Use --copy to write independent copies. With --verify, the fetched digest must pass the same signature check as `chainctl skills verify` before any file or symlink is written; otherwise the command exits nonzero and installs nothing. A skill on a registry whose signatures chainctl cannot verify, such as a staging or development registry, fails too, unless --allow-unverifiable-host is also given. ``` chainctl skills install [flags] ``` ### Options ``` -a, --agent stringArray Target specific agents by ID (repeatable). Use --agent '*' for all known agents. --allow-unverifiable-host With --verify, accept a skill from a registry whose signatures chainctl cannot verify, such as a staging or development registry, with a warning instead of failing. --copy Copy files per agent instead of using a shared canonical copy + symlinks. --global Install to global (~/) directories instead of project-local. --verify Check Chainguard's signature on the fetched skill, as 'chainctl skills verify' does, before writing anything. Fails unless verified, including for a skill on a registry whose signatures chainctl cannot verify. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills list URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_list.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills list List skills published by an org. ### Synopsis List skills and their tags, including skills without a latest tag. By default, list the immediate skills and folders in the skills registry. Use --recursive to include skills in nested folders, or --source uploads to list uploads. Referrer tags are hidden by default; use --show-referrers to include them. ``` chainctl skills list [flags] ``` ### Options ``` -g, --group string Org or folder to list, e.g. "chainguard" or "chainguard/github" (default: current context). -r, --recursive Recurse into nested folders and list every skill by its full path. --show-referrers Whether to show referrer tags of the form sha256-deadbeef.{sig,sbom,att}. --source string Which namespace to list: "skills" (hardened, skills.cgr.dev), "uploads" (user uploads, uploads.cgr.dev), or "all". (default "skills") ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills pull URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_pull.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills pull Download a published skill to a local directory. ### Synopsis Download a published skill to a local directory. The reference accepts org/name:tag or org/name@sha256:DIGEST. The optional directory sets the download destination. The download includes .hardened-artifact.json, which records the skill's registry reference and digest; packing the directory again leaves that file out. With --verify, the fetched digest must pass the same signature check as `chainctl skills verify` before anything is written; otherwise the command exits nonzero and writes nothing. A skill on a registry whose signatures chainctl cannot verify, such as a staging or development registry, fails too, unless --allow-unverifiable-host is also given. With -o json, a successful pull prints {"location": } plus, with --verify, "verification": the result `chainctl skills verify -o json` prints. A failed --verify prints that result on its own and exits 1. ``` chainctl skills pull [] [flags] ``` ### Examples ``` # Pull into a specific directory: chainctl skills pull chainguard/github/lint ./my-skills/lint # Pull only if Chainguard signed the skill: chainctl skills pull --verify chainguard/github/lint ``` ### Options ``` --allow-unverifiable-host With --verify, accept a skill from a registry whose signatures chainctl cannot verify, such as a staging or development registry, with a warning instead of failing. --force Overwrite destination directory if it already exists. --verify Check Chainguard's signature on the fetched skill, as 'chainctl skills verify' does, before writing anything. Fails unless verified, including for a skill on a registry whose signatures chainctl cannot verify. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills push URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_push.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills push Package a skill directory and publish it to uploads.cgr.dev. ### Synopsis Package a skill directory and publish it to uploads.cgr.dev. Reads SKILL.md from the specified directory (default: current directory), validates the skill, and pushes an OCI artifact to uploads.cgr.dev//:. ``` chainctl skills push [] [flags] ``` ### Options ``` --dry-run Build and validate without pushing. -g, --group string Org or folder to publish under, e.g. "chainguard" or "chainguard/github" (default: current chainctl context). -t, --tag stringArray Version tag to apply (repeatable, e.g. -t v1.0.0 -t latest). (default [latest]) ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills registry URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_registry.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills registry Publish and install skills in any OCI registry by reference. ### Synopsis Publish and install skills in any OCI registry by reference. These commands address skills by full OCI reference (e.g. cgr.dev/my-org/my-skill:v1) rather than by Chainguard org path. Skills are packed in the same artifact format as "chainctl skills push". Registries authenticate with your docker credentials (~/.docker/config.json). For Chainguard registries, run "chainctl auth configure-docker" first. A skill is an ordinary OCI artifact, so crane handles everything else: list tags, copy, tag, or read the manifest and config. A skill's files are a single tar layer, which "crane export" writes as-is. ### Examples ``` # Install a Chainguard skill (its signature is verified): chainctl skills registry install \ skills.cgr.dev/chainguard/my-folder/my-skill # Publish the skill in the current directory, then install it. Your own # skills aren't Chainguard-signed, so installing them needs --no-verify: chainctl skills registry publish cgr.dev/my-org/my-skill:v1 chainctl skills registry install --no-verify cgr.dev/my-org/my-skill:v1 # Inspect a skill's files without installing it: crane export cgr.dev/my-org/my-skill:v1 - | tar -tf - crane export cgr.dev/my-org/my-skill:v1 - | tar -xOf - SKILL.md ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. * [chainctl skills registry install](/platform/chainctl/chainctl-docs/chainctl_skills_registry_install/) - Install the skill at an OCI reference into agent directories. * [chainctl skills registry publish](/platform/chainctl/chainctl-docs/chainctl_skills_registry_publish/) - Package a skill directory and publish it to an OCI reference. --- ### chainctl skills registry install URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_registry_install.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills registry install Install the skill at an OCI reference into agent directories. ### Synopsis Install the skill at an OCI reference into agent directories. The artifact must be a skill (as published by "chainctl skills push" or "chainctl skills registry publish"). Like "chainctl skills install", a shared canonical copy is written under .agents/skills/ and agent-specific symlinks are created; use --copy to write independent copies. The installed directory is named for the last segment of the repository path (e.g. my-skill for skills.cgr.dev/chainguard/my-folder/my-skill); use --name to choose another. The install is recorded in the skills CLI lock file (skills-lock.json in the working directory, or ~/.agents/.skill-lock.json with --global) with the repository, tag, and image digest it came from. Installing fails if the lock records that name from a different source. The skill's signature is checked, as by "chainctl skills verify", before anything is written; installing fails if it doesn't pass. Only skills on skills.cgr.dev can be verified, so installing from any other registry requires --no-verify. The install locations are shown for confirmation before anything is downloaded; pass --yes to skip the prompt. ``` chainctl skills registry install [flags] ``` ### Examples ``` # Install into the detected agents' project directories: chainctl skills registry install \ skills.cgr.dev/chainguard/my-folder/my-skill # Install globally for Claude Code only: chainctl skills registry install --global --agent claude-code \ skills.cgr.dev/chainguard/my-folder/my-skill # Install under a different name: chainctl skills registry install --name team-skill \ skills.cgr.dev/chainguard/my-folder/my-skill # Install from a registry chainctl can't verify signatures on: chainctl skills registry install --no-verify cgr.dev/my-org/my-skill:v1 # Install without confirming the locations: chainctl skills registry install --yes \ skills.cgr.dev/chainguard/my-folder/my-skill ``` ### Options ``` -a, --agent stringArray Target specific agents by ID (repeatable). Use --agent '*' for all known agents. --copy Copy files per agent instead of using a shared canonical copy + symlinks. --global Install to global (~/) directories instead of project-local. --name string Name to install the skill under (default: the last segment of the repository path). --no-verify Install without checking the skill's signature. Required for registries chainctl can't verify, which is any but skills.cgr.dev. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills registry](/platform/chainctl/chainctl-docs/chainctl_skills_registry/) - Publish and install skills in any OCI registry by reference. --- ### chainctl skills registry publish URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_registry_publish.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills registry publish Package a skill directory and publish it to an OCI reference. ### Synopsis Package a skill directory and publish it to an OCI reference. Reads and validates SKILL.md from the skill directory (default: current directory, or --dir), packs it as a skill artifact, and publishes it to . Prints the published reference by digest. ``` chainctl skills registry publish [flags] ``` ### Examples ``` # Publish the skill in the current directory: chainctl skills registry publish cgr.dev/my-org/my-skill:v1 # Publish the skill in another directory: chainctl skills registry publish --dir ./my-skill cgr.dev/my-org/my-skill:v1 ``` ### Options ``` --dir string Skill directory to package (must contain SKILL.md). (default ".") ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills registry](/platform/chainctl/chainctl-docs/chainctl_skills_registry/) - Publish and install skills in any OCI registry by reference. --- ### chainctl skills status URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_status.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills status Check a harden job or wait for it to finish. ### Synopsis Check the job ID printed by skills harden without uploading or submitting the skill again. Progress names the pipeline stage once the job is running. --wait tracks the job to completion and downloads the hardened skill and report to ./hardened/NAME, with .hardened-artifact.json recording the published reference and digest. A timeout or interrupt leaves the job running. A skill the post-hardening scan rejected downloads unmodified to ./unhardened/NAME and exits nonzero, naming the scanner and rule that rejected it. Other failures exit nonzero and name the stage the job failed in. ``` chainctl skills status --group --id [flags] ``` ### Examples ``` chainctl skills status --group my-org --id chainctl skills status --group my-org --id --wait --timeout 30m ``` ### Options ``` -g, --group string Target organization name or UIDP (required). --id string Harden job ID (required). --timeout duration Maximum command duration with --wait (0 means no timeout). --wait Wait for hardening to finish and download the result. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills uninstall URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_uninstall.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills uninstall Remove a skill from agent directories on the local machine. ### Synopsis Remove a skill from agent directories on the local machine. is the skill name (no org or tag needed — operates on local files only). By default uninstall removes the project-local copy. If a global copy also exists, it is left in place and a warning is printed; re-run with --global to remove it. uninstall is a local-only operation and does not modify the registry. ``` chainctl skills uninstall [flags] ``` ### Options ``` -a, --agent stringArray Remove from specific agents only (default: all installed locations). --global Remove from global directories instead of project-local. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills validate URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_validate.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills validate Check a local skill directory's format (not its authenticity) without making network calls. ### Synopsis Check a local skill directory for spec compliance without making network calls. validate checks format only: SKILL.md frontmatter, the name, description, compatibility, and allowed-tools fields, the directory size, and which files would be published. It does not check authenticity, and a passing result says nothing about who published a skill. To check that Chainguard signed a published skill, run `chainctl skills verify `. ``` chainctl skills validate [] [flags] ``` ### Options ``` --strict Also report warnings for optional recommended fields. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills verify URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_verify.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills verify Verify that Chainguard signed a published skill. ### Synopsis Verify that Chainguard's publishing pipeline signed a published skill for the organization that owns it. The reference accepts org/name:tag or org/name@sha256:DIGEST. A tag is resolved once and the digest is verified; the output prints that digest so you can pin it. Verification passes only when a sigstore bundle attached to the digest chains to the public-good Sigstore trusted root, was issued by https://issuer.enforce.dev to exactly the owning organization's SKILLS service principal, and has a verified transparency-log entry and timestamp. The signing identity of Chainguard's own organizations (public, chainguard) is built into chainctl; for any other organization, chainctl reads its SKILLS binding with your credentials, which requires the viewer role (or higher) on that organization. The result is one of: verified, unsigned, failed, or skipped. Skills on a staging or development registry are skipped with a warning; uploads are never Chainguard-signed and always fail. The command exits 0 for verified and skipped, and 1 for unsigned and failed (the status field tells them apart); an error that stops verification from running also exits nonzero. ``` chainctl skills verify [flags] ``` ### Examples ``` # Verify the latest version of a skill: chainctl skills verify chainguard/github/lint # Verify a pinned digest and print the result as JSON: chainctl skills verify chainguard/github/lint@sha256: -o json ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl skills versions URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_skills_versions.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl skills versions List all published versions (tags) for a skill. ### Synopsis List all published versions (tags) for a skill. is the skill name without a tag: org/name ``` chainctl skills versions [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl skills](/platform/chainctl/chainctl-docs/chainctl_skills/) - Skills registry related commands. --- ### chainctl starter URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_starter.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl starter Manage catalog starter organizations ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control * [chainctl starter add-images](/platform/chainctl/chainctl-docs/chainctl_starter_add-images/) - Add catalog images to your catalog starter organization. * [chainctl starter init](/platform/chainctl/chainctl-docs/chainctl_starter_init/) - Initialize a new catalog starter organization. * [chainctl starter request-access](/platform/chainctl/chainctl-docs/chainctl_starter_request-access/) - Request access to a catalog starter organization. * [chainctl starter status](/platform/chainctl/chainctl-docs/chainctl_starter_status/) - Show catalog starter organization status, quota, and image readiness. --- ### chainctl starter add-images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_starter_add-images.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl starter add-images Add catalog images to your catalog starter organization. ### Synopsis Add catalog images to your catalog starter organization. The target organization is the single kind=starter org the caller's identity is bound to — auto-discovered, no flags needed. Images are resolved against the catalog by exact name match. The server picks the active starter entitlement that has remaining capacity. The total cap across all starter images is enforced by the server. ``` chainctl starter add-images IMAGE_NAME [IMAGE_NAME ...] [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl starter](/platform/chainctl/chainctl-docs/chainctl_starter/) - Manage catalog starter organizations --- ### chainctl starter init URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_starter_init.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl starter init Initialize a new catalog starter organization. ### Synopsis Initialize a new catalog starter organization. A catalog starter organization gives a team free access to a small set of Chainguard images for evaluation. Each starter org is entitled to a limited number of images of your choice (base or application); the exact quota is shown by 'chainctl starter status'. Other Chainguard platform features may be unavailable to starter organizations; refer to the documentation for the current scope. This command is interactive. It will: 1. Prompt you to authenticate with a supported identity provider. 2. Register a new Chainguard identity for your email address. 3. Create a new starter organization. With a business email address it is named after your email domain. With a personal email address you name it yourself, at a prompt or with --name, and its registry and package paths use its ID instead of the name. Once your organization exists, the Chainguard platform will automatically entitle it to choose images to be added. It should be ready within a few minutes. Authentication requirements: * Use a business email address to get an organization named after your company's domain. Where personal email providers (Gmail, Yahoo, etc.) are accepted, the organization isn't tied to any domain. * Authentication must use one of: - Email and password; or - Google, only if your business uses a Google Workspace account registered to your business domain. If this command fails and you think the organization already exists, you may follow up by running 'chainctl starter request-access' to request access. ``` chainctl starter init [flags] ``` ### Options ``` --name string Name of the organization, when you sign up with a personal email address. It can't look like a domain name. Ignored for an organization named after your email domain. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl starter](/platform/chainctl/chainctl-docs/chainctl_starter/) - Manage catalog starter organizations --- ### chainctl starter request-access URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_starter_request-access.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl starter request-access Request access to a catalog starter organization. ### Synopsis Request access to an existing catalog starter organization. If a catalog starter organization for your email domain already exists, this command sends an access request to the organization's owners. They can choose whether to invite you; if they do, you will receive an email with instructions to join. You must be authenticated as a Chainguard identity (e.g. via 'chainctl auth login') before running this command. The candidate organization is derived from the email domain on your authenticated session. For privacy, this command intentionally returns the same response whether or not a matching organization exists. If your domain has no associated starter organization, no request is sent and no error is reported. ``` chainctl starter request-access [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl starter](/platform/chainctl/chainctl-docs/chainctl_starter/) - Manage catalog starter organizations --- ### chainctl starter status URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_starter_status.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl starter status Show catalog starter organization status, quota, and image readiness. ### Synopsis Show the status of your catalog starter organization. Reports the registry path, account provisioning status, image quota usage, and per-image readiness — INITIALIZING until the catalog syncer has both created the repo and mirrored at least one tag, READY afterwards. Use this command to check whether a newly-added image is pullable yet, or to confirm your starter org has finished provisioning before running 'chainctl starter add-images'. ``` chainctl starter status [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl starter](/platform/chainctl/chainctl-docs/chainctl_starter/) - Manage catalog starter organizations --- ### chainctl update URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_update.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl update Update chainctl. ### Synopsis Update chainctl to the latest release. The downloaded binary's signature is verified before it is installed; a binary that fails verification is never installed. Verification requires network access to the download host (dl.enforce.dev) and, at least on first use, to the Sigstore TUF CDN (tuf-repo-cdn.sigstore.dev). If those hosts are unreachable (for example, behind a restrictive proxy), the update fails and the current binary is left in place; as a fallback, download the latest release directly from https://dl.enforce.dev/chainctl/latest/. ``` chainctl update [--yes] [--force] ``` ### Options ``` --force Skip the version check and update chainctl regardless of the current version. -y, --yes Automatic yes to prompts; assume "yes" as answer to all prompts and run non-interactively. ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control --- ### chainctl version URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/platform/chainctl/chainctl-docs/chainctl_version.md Last Modified: October 15, 2026 Tags: chainctl, Reference, Product ## chainctl version Prints the version ``` chainctl version [flags] ``` ### Options inherited from parent commands ``` --api string The url of the Chainguard platform API. (default "https://console-api.enforce.dev") --audience string The Chainguard token audience to request. (default "https://console-api.enforce.dev") --config string A specific chainctl config file. Uses CHAINCTL_CONFIG environment variable if a file is not passed explicitly. --console string The url of the Chainguard platform Console. (default "https://console.chainguard.dev") --force-color Force color output even when stdout is not a TTY. -h, --help Help for chainctl --issuer string The url of the Chainguard STS endpoint. (default "https://issuer.enforce.dev") --log-level string Set the log level (debug, info) (default "ERROR") -o, --output string Output format. One of: [csv, env, go-template, id, json, markdown, none, table, terse, tree, wide] -v, --v int Set the log verbosity level. ``` ### SEE ALSO * [chainctl](/platform/chainctl/chainctl-docs/chainctl/) - Chainguard Control --- ## Software Security ### Chainguard Libraries for Java URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202607.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Libraries AI frontier models are accelerating the discovery of vulnerabilities in Java applications at a pace that teams can't keep up with. If your team is stuck on older Java versions that are hard to upgrade, you need support to remediate those vulnerabilities so you can get time back in your day to build. In the July 2026 Learning Lab, Manfred Moser shares the details behind Chainguard Libraries for Java, with an overview of how to start using remediated versions directly with Apache Maven. Manfred covers and demonstrates the following new features: - CVE remediation for Spring Framework and other libraries - Protected fallback to Maven Central - Cooldown and other policies The lab uses a hands-on demo project to migrate a typical Maven build to Chainguard Libraries. [Watch the video on YouTube](https://www.youtube.com/watch?v=c6n_SQgx4xI) ## Sections - [0:00](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=0s) Introduction - [2:42](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=162s) Software supply chain and open source components - [6:20](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=380s) History of software supply chain security issues and attacks - [8:30](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=510s) Chainguard Libraries and Chainguard Factory overview - [11:30](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=690s) Details about Chainguard Libraries - [17:36](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=1056s) CVE remediation - [24:30](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=1470s) What is remediated? - [25:47](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=1547s) Browsing remediated libraries - [30:14](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=1814s) Fallback and policies - [35:24](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=2124s) Start of demos - [35:50](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=2150s) chainctl demo - [40:47](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=2447s) Simple artifact download scripts demo - [43:08](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=2588s) Example application and Maven settings.xml file - [46:02](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=2762s) Building and running the example application - [50:10](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=3010s) Advanced pom.xml modification - [55:43](https://www.youtube.com/watch?v=c6n_SQgx4xI&t=3343s) Final Q&A ## Questions The following questions were asked by the audience during the event, and responses are added since time ran out to answer them all: ### How does Chainguard differ from eg: "pip-audit" for Python? or npm run audit for Node? ..etc. `pip-audit` and `npm audit` are reactive scanners that flag known vulnerabilities in dependencies you already pulled from public registries. Chainguard Libraries is preventive: dependencies are rebuilt from source in the hardened Chainguard Factory and served with malware scanning and cooldown controls, closing off the build and distribution attack surface a scanner cannot see. The two are complementary. The VEX feed from Chainguard includes information about remediated CVEs on Chainguard Libraries and can be used by scanners. ### For backported fixes for Java specifically, does Chainguard have an n-? support model as to which versions fixes will be backported to? There is no fixed n-minus matrix. Backports target critical and high CVEs on older versions that customers still run and that upstream no longer patches, prioritized by usage and demand. Request specific libraries through your account team. Typically n-? policies also are not meaningful since they are heavily impacted by the release cadence of upstream projects and the presence of multiple maintained major version streams. Chainguard focuses on the customer demand for backports. ### When you say. "source" - is it the source Github repo? or the public registry (Eg: PyPI for python) Source means the upstream code in a source code manager such as GitHub or GitLab, not the binary from a registry like PyPI, npm, or Maven Central. Chainguard verifies the project's source URL and rebuilds from that code, so packages without a valid, verifiable source URL are out of scope and no binary is produced. ### I don't see "Manage pull tokens" option under my Profile for Libraries Creating pull tokens requires the ecosystem entitlement and the matching `pull_token_creator` role, such as `libraries.java.pull_token_creator`. Without both, the option does not appear. Tokens live under **Overview** on the **Manage pull tokens** tab, not a personal profile menu. Ask an administrator to confirm your entitlement and role, or create tokens with `chainctl auth pull-token`. ### Is there a way to update the policy, change cooldown period Yes. With the Owner role, and upstream fallback enabled, create a policy with a cooldown value and enforce it: ```bash chainctl libraries policy create --name=java-trust --cooldown-days=0 chainctl libraries policy enable java-trust --ecosystem=JAVA --mode=ENFORCE ``` The default is 7 days, and `--cooldown-days=0` deactivates cooldown. One custom policy per ecosystem carries all your cooldown, block, and override rules. ### Any support for other tech - Golang, Rust Chainguard Libraries currently covers the Java, JavaScript, and Python ecosystems. Future plans are considering Golang, Rust, .NET, and others. ### How do you deal with custom / non-standard / non-one-step builds? E.e. build that require (undocumented) properties set? In the context of Chainguard Libraries on the Chainguard side, we determine how to build libraries with the help of our expert engineers and our agentic infrastructure. In the context of customers using Chainguard Libraries on their own projects, you must troubleshoot and debug these details with the help of Apache Maven and other tools. ### For the fix using this as an example do these fixes get pushed to the open source package instead of a private Chainguard repo? If not how come? Remediated fixes ship from the dedicated `java-remediated` Chainguard repository with a `-0.cgr.N` suffix, not upstream as the released artifact. These backports target older versions upstream no longer patches. Chainguard cannot publish them under the same coordinates on Maven Central. Chainguard does collaborate with upstream projects to contribute fixes as source code where possible and the project is interested. ### If we are overriding versions how do we ensure integrity and stability of the application? Overriding dependency versions with CVE-remediated Java libraries allows you to keep the version difference of the change minimal and isolated to the CVE fixes. Nevertheless you must test your application thoroughly to ensure the integrity and stability of your application. When upgrading more libraries, and introducing larger version drifts, this need for verification and potentially adjusted code becomes more and more critical, and can result in significant necessary changes, that must be tested and verified like any other application change. ### How do you ensure that the dependencies that you use during build do not contain any malicious code? When using Chainguard Libraries for Java with the fallback to upstream Maven Central, you can rely on the extensive analysis and scanning of the artifacts performed by Chainguard. ## Resources - [Demo project on GitHub](https://github.com/chainguard-demo/chainguard-libraries-java) - [Slide deck](/downloads/learning-lab-202607.pdf) - [Chainguard Libraries documentation](/chainguard/libraries/introduction/overview/) --- ### Build safely with AI URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202605.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Libraries The May 2026 Learning Lab with Erika Heidi explores AI-native threat models and how to mitigate risks involved with AI-assisted coding, leveraging Cursor and Chainguard for a secure AI SLDC. [Watch the video on YouTube](https://www.youtube.com/watch?v=EDbPhANkMsU) ## Sections - [0:00](https://www.youtube.com/watch?v=EDbPhANkMsU) Introduction and agenda - [3:35](https://www.youtube.com/watch?v=EDbPhANkMsU&t=215s) "This Changes Everything" - [4:19](https://www.youtube.com/watch?v=EDbPhANkMsU&t=259s) How the threat model changed with AI - [8:17](https://www.youtube.com/watch?v=EDbPhANkMsU&t=497s) Your local dev environment at risk - [9:49](https://www.youtube.com/watch?v=EDbPhANkMsU&t=589s) AI-native attack vectors - [12:30](https://www.youtube.com/watch?v=EDbPhANkMsU&t=750s) Closing the trust gap - [14:14](https://www.youtube.com/watch?v=EDbPhANkMsU&t=854s) Two principles for a safe SLDC with AI assistance - [23:06](https://www.youtube.com/watch?v=EDbPhANkMsU&t=1386s) Vibecoding with trusted sources - [27:26](https://www.youtube.com/watch?v=EDbPhANkMsU&t=1646s) The Chainguard plugin for Cursor - [29:03](https://www.youtube.com/watch?v=EDbPhANkMsU&t=1743s) Demo - building an SBOM visualizer - [31:27](https://www.youtube.com/watch?v=EDbPhANkMsU&t=1887s) The initial prompt - [33:50](https://www.youtube.com/watch?v=EDbPhANkMsU&t=2030s) The resulting vibecoded app - [34:20](https://www.youtube.com/watch?v=EDbPhANkMsU&t=2060s) Migrating to Chainguard Libraries - [36:43](https://www.youtube.com/watch?v=EDbPhANkMsU&t=2203s) Verifying coverage - [38:26](https://www.youtube.com/watch?v=EDbPhANkMsU&t=2306s) Recap of what we neutralized - [40:15](https://www.youtube.com/watch?v=EDbPhANkMsU&t=2415s) Other Chainguard skills to try out on Cursor - [48:45](https://www.youtube.com/watch?v=EDbPhANkMsU&t=2925s) Wrapping Up ## Resources - [Slide deck](/downloads/learning-lab-202605.pdf) - [Chainguard plugin for Cursor](https://cursor.com/marketplace/chainguard) - [Chainguard Containers](/chainguard/containers/overview/) - [Chainguard Libraries](/chainguard/libraries/introduction/overview/) --- ### Securing CI/CD with Chainguard URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202604.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Actions The April 2026 Learning Lab with Erika Heidi goes through how attackers exploit vulnerable GitHub Actions workflows, and how Chainguard can protect your CI/CD pipelines from these threats. [Watch the video on YouTube](https://www.youtube.com/watch?v=D9tORVR4H9g) ## Sections - [0:00](https://www.youtube.com/watch?v=D9tORVR4H9g) Introduction and agenda - [5:31](https://www.youtube.com/watch?v=D9tORVR4H9g&t=331s) Timeline of CI/CD software supply chain incidents - [11:25](https://www.youtube.com/watch?v=D9tORVR4H9g&t=685s) Open Source and CI/CD as the new target - [12:47](https://www.youtube.com/watch?v=D9tORVR4H9g&t=767s) 2026: the year of AI-assisted attacks - [15:16](https://www.youtube.com/watch?v=D9tORVR4H9g&t=916s) Unpacking the Trivy Compromise - [19:57](https://www.youtube.com/watch?v=D9tORVR4H9g&t=1197s) Secret exfiltration live demo - [36:17](https://www.youtube.com/watch?v=D9tORVR4H9g&t=2177s) What could unfold from here - [39:04](https://www.youtube.com/watch?v=D9tORVR4H9g&t=2344s) Strategies to mitigate risks - [39:24](https://www.youtube.com/watch?v=D9tORVR4H9g&t=2364s) Repository inspection for insecure defaults - [44:03](https://www.youtube.com/watch?v=D9tORVR4H9g&t=2643s) Minimize attack surface - [48:48](https://www.youtube.com/watch?v=D9tORVR4H9g&t=2928s) Pull from trusted sources - [52:21](https://www.youtube.com/watch?v=D9tORVR4H9g&t=3141s) Pin by digest - [54:28](https://www.youtube.com/watch?v=D9tORVR4H9g&t=3268s) Use short lived tokens (ban PATs) - [55:32](https://www.youtube.com/watch?v=D9tORVR4H9g&t=3332s) Use Chainguard Actions - [58:55](https://www.youtube.com/watch?v=D9tORVR4H9g&t=3535s) Closing notes ## Resources - [Slide deck](/downloads/learning-lab-securing-cicd-202604.pdf) - [Chainguard Containers](/chainguard/containers/overview/) - [Chainguard Libraries](/chainguard/libraries/introduction/overview/) - [Chainguard Actions](https://www.chainguard.dev/actions) - [Digestabot](https://github.com/marketplace/actions/update-the-image-digest) - [Octo-STS](https://github.com/apps/octo-sts) --- ### Software supply chain attacks and Chainguard Libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202603.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Libraries The March 2026 Learning Lab with Manfred Moser focuses on the history, trends, and future developments around software supply chain attacks. Manfred talks about the role libraries for application developers and demonstrates numerous new features and use cases for Chainguard Libraries. [Watch the video on YouTube](https://www.youtube.com/watch?v=MPKP8YSoheE) ## Sections - [0:00](https://www.youtube.com/watch?v=MPKP8YSoheE) Introduction and agenda - [1:45](https://www.youtube.com/watch?v=MPKP8YSoheE&t=105s) Software supply chain overview - [7:25](https://www.youtube.com/watch?v=MPKP8YSoheE&t=445s) History of attacks and overview of different methods - [17:59](https://www.youtube.com/watch?v=MPKP8YSoheE&t=1079s) Future threads and trends - [21:37](https://www.youtube.com/watch?v=MPKP8YSoheE&t=1297s) Statistics about intensifying attacks - [22:54](https://www.youtube.com/watch?v=MPKP8YSoheE&t=1374s) Incidents and fire drills - [25:05](https://www.youtube.com/watch?v=MPKP8YSoheE&t=1505s) Impact from Minecraft and log4j on Maven Central - [26:42](https://www.youtube.com/watch?v=MPKP8YSoheE&t=1602s) Chainguard Libraries overview - [32:21](https://www.youtube.com/watch?v=MPKP8YSoheE&t=1941s) Chainguard Libraries for Python, Java, and JavaScript - [35:17](https://www.youtube.com/watch?v=MPKP8YSoheE&t=2117s) Demo - Browsing libraries in the console UI - [40:38](https://www.youtube.com/watch?v=MPKP8YSoheE&t=2438s) Demo - Pull token creation in console UI and with chainctl - [42:13](https://www.youtube.com/watch?v=MPKP8YSoheE&t=2533s) Demo - Browsing Python simple index - [45:50](https://www.youtube.com/watch?v=MPKP8YSoheE&t=2750s) Demo - Browsing Java repository - [49:25](https://www.youtube.com/watch?v=MPKP8YSoheE&t=2965s) Demo - Example projects for JavaScript and Python - [56:35](https://www.youtube.com/watch?v=MPKP8YSoheE&t=3395s) Demo - Verification with chainctl - [58:47](https://www.youtube.com/watch?v=MPKP8YSoheE&t=3527s) Chainguard Repository - [1:01:16](https://www.youtube.com/watch?v=MPKP8YSoheE&t=3676s) Upcoming events ## Resources - [Slide deck](/downloads/learning-lab-chainguard-libraries-202603.pdf) - [Chainguard Libraries documentation](/chainguard/libraries/introduction/overview/) --- ### Shipping safer container runtimes in 2026 URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202512.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Containers The December 2025 Learning Lab with Erika Heidi focuses on strategies to improve the security of your software supply chain and ship safer container runtimes in 2026. [Watch the video on YouTube](https://www.youtube.com/watch?v=z5SNwBC4T-Q) ## Sections - [00:00](https://www.youtube.com/watch?v=z5SNwBC4T-Q) Intro - [02:52](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=172s) Why Devs need to care - [04:48](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=288s) XZ Utils incident - [09:43](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=583s) tj-actions/changed-files incident - [12:42](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=762s) Sha1-Hulud Second Coming - [17:11](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=1031s) Trending threat models - [32:42](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=1962s) Mitigating risks - [45:02](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=2702s) Concrete Dev actions this sprint - [50:47](https://www.youtube.com/watch?v=z5SNwBC4T-Q&t=3047s) Chainguard Containers: CVE Comparisons ## Resources - [Slide deck](/downloads/learning-lab-202512.pdf) - [Chainguard Containers overview](https://edu.chainguard.dev/chainguard/containers/overview/) - [Chainguard Libraries overview](https://edu.chainguard.dev/chainguard/libraries/introduction/overview/) - [Migrating to Chainguard Containers](https://edu.chainguard.dev/chainguard/containers/migration/migrations-overview/) - [Inspecting Chainguard Containers](https://edu.chainguard.dev/chainguard/containers/troubleshooting/inspecting-containers/) - [Using Digestabot with Chainguard Containers](https://edu.chainguard.dev/chainguard/containers/security-and-compliance/updating-containers/digestabot/) - [Using Renovate with Chainguard Containers](https://edu.chainguard.dev/chainguard/containers/security-and-compliance/updating-containers/renovate/) - [Images Directory](https://images.chainguard.dev/) --- ### Chainguard OS on Raspberry Pi URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202511.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard OS The November 2025 Learning Lab with Erika Heidi covers the [release of Chainguard OS for the Raspberry Pi](https://www.chainguard.dev/unchained/a-gift-for-the-open-source-community-chainguards-cve-free-raspberry-pi-images-beta), showing how Chainguard OS has evolved to power new environments. [Watch the video on YouTube](https://www.youtube.com/watch?v=SvUU2n2mQ7M) ## Sections - [0:46](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=46s) Presentation Starts - [2:55](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=175s) How We Got Here: Wolfi and Chainguard OS - [5:47](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=347s) Presenting Chainguard OS for the Raspberry Pi - [8:42](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=522s) How to Set Up your Raspberry Pi with Chainguard OS - [11:23](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=683s) Grype scan on the Raspberry images - [14:47](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=887s) Demo Overview: Guardcraft Pi - [18:10](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=1090s) Grype scan on the Guardcraft image - [20:59](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=1259s) Live Demo: Minecraft server on the Raspberry Pi - [24:14](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=1454s) Demo Overview: Open Source LLM server - [30:51](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=1851s) Grype scan on the wolfi-llama image - [32:35](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=1955s) Live Demo: Open Source Llama.cpp Server - [35:03](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=2103s) Live image description with Qwen3-VL and Llama.cpp - [39:18](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=2358s) Live Demo: ALT description CLI tool using Llama.cpp server API - [41:24](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=2484s) What's Next - [42:22](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=2542s) New Compliance Features for Chainguard VMs - [44:20](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=2660s) Chainguard VMs Roadmap - [45:40](https://www.youtube.com/watch?v=SvUU2n2mQ7M&t=2740s) Announcing the next Learning Labs ## Quickstart To get started with Chainguard OS on the Raspberry Pi, and to be able to run the demos in this presentation, you'll need: - A Raspberry Pi 5 - Power source for the Raspberry Pi 5 - MicroSD card (and reader) - Ethernet connection - Micro-hdmi to hdmi cable (to connect your Pi to a display ) - USB Keyboard You also need to download the Chainguard Raspberry Pi Docker image by filling in [the request form](http://images.chainguard.dev/rpi). ### Creating the startup disk Unpack the image contents: ```shell gunzip rpi-generic-docker-arm64-*.raw.gz ``` Create the disk - this assumes your microSD card reader is on `/dev/sda`: ```shell sudo dd if=rpi-generic-docker-arm64-*.raw of=/dev/sda bs=1M ``` After the disk is ready, plug it into the Pi and connect the board to the power source, ethernet cable, micro-hdmi, and keyboard. You can log in with user **linky** and password **linky**. Then, you can run `ip addr` to find out your local network IP address and connect to the Pi via SSH from another computer. ## Demo 1: Guardcraft Minecraft server on Raspberry Pi On the first demo, Erika demonstrates how to build a minimal Minecraft Java server with Chainguard Containers, running Chainguard OS on the Raspberry Pi. From the Raspberry Pi, clone the [Guardcraft](https://github.com/chainguard-demo/guardcraft-server) repository: ```shell git clone https://github.com/chainguard-demo/guardcraft-server.git && cd guardcraft-server ``` Build the image: ```shell docker build . -t guardcraft-server ``` Then, run the Minecraft server with: ```shell docker-compose up ``` This starts a Minecraft Java server using default settings configured via environment variables on the `docker-compose.yaml` file. You can connect from any compatible client on your local network. ## Demo 2: Llama.cpp LLM server on Raspberry Pi On the second demo, Erika shows how to build a Llama.cpp container image using Chainguard Containers, and how to run the Llama.cpp server with vision-capable LLMs for generating rich ALT image descriptions, on the Raspberry Pi with Chainguard OS. ### Step 1: Building the wolfi-llama image From the Raspberry Pi, clone the [Wolfi-llama](https://github.com/erikaheidi/wolfi-llama) repository: ```shell git clone https://github.com/erikaheidi/wolfi-llama.git && cd wolfi-llama ``` Next, run the command to build the `wolfi-llama` container image. This step compiles Llama.cpp from source, which may take several minutes to complete. ```shell docker build . -t wolfi-llama ``` ### Step 2: Downloading LLMs into the Pi For this demo, we're using the [Qwen3-VL](https://huggingface.co/unsloth/Qwen3-VL-2B-Instruct-GGUF/tree/main) open source LLM, since that has vision capabilities. We picked the 2B-Instruct version since that runs well on the Raspberry Pi. Access the `models` directory from the repository. This is where the model files should be stored in order to be shared with the container when the server is running: ```shell cd models/ ``` Download the LLM model from Huggingface: ```shell curl -L -O https://huggingface.co/unsloth/Qwen3-VL-2B-Instruct-GGUF/resolve/main/Qwen3-VL-2B-Instruct-Q8_0.gguf?download=true ``` Next, download the `mmproj` file for that model, since that is required for advanced image features: ```shell curl -L -O https://huggingface.co/unsloth/Qwen3-VL-2B-Instruct-GGUF/resolve/main/mmproj-F32.gguf?download=true ``` When download is complete, you can run the server. ### Step 3: Running the Llama server The `docker-compose.yaml` file includes a custom **command** directive that includes all options required for running the server using the models you just downloaded. These values are hardcoded so you don't need to type a long `docker run` command every time you want to get the server up and running. As a reference, here is the command that you'll be running via `docker-compose`: ```shell docker run --rm --device /dev/dri/card1 --device /dev/dri/renderD128 \ -v ${PWD}/models:/models -p 8000:8000 wolfi-llama:latest --no-mmap --no-warmup \ -m /models/Qwen3-VL-2B-Instruct-Q8_0.gguf --mmproj /models/mmproj-F32.gguf \ --port 8000 --host 0.0.0.0 -n 512 \ --temp 0.7 \ --top-p 0.8 \ --top-k 20 \ --presence-penalty 1.5 ``` To start the server, run: ```shell docker-compose up ``` When the server is up and running, you can access the chatbot interface from your browser by pointing it to the Raspberry Pi IP address in your local network, on port `8000`. ## Resources - [Slide deck](/downloads/learning-lab-raspberrypi-202511.pdf) - Blog: [Chainguard’s FIPS-validated, hardened VM images: compliance without the complexity](https://www.chainguard.dev/unchained/chainguards-fips-validated-hardened-vm-images-compliance-without-the-complexity) - Blog: [A Gift for the Open Source Community: Chainguard’s CVE-Free Raspberry Pi Images (Beta)](https://www.chainguard.dev/unchained/a-gift-for-the-open-source-community-chainguards-cve-free-raspberry-pi-images-beta) - Tutorial: [Tutorial: Running Open Source LLMs on a Raspberry Pi 5 with Llama.cpp](https://eheidi.dev/posts/raspberry-llama/) - Repository: [Guardcraft demo](https://github.com/chainguard-demo/guardcraft-server) - Repository: [Wolfi-Llama demo](https://github.com/erikaheidi/wolfi-llama) --- ### Chainguard Libraries for JavaScript and CVE remediation for Python libraries URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202510.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Libraries The October 2025 Learning Lab with Manfred Moser covers Chainguard Libraries for JavaScript and CVE remediation with Chainguard Libraries for Python. It starts with an overview about libraries and the JavaScript ecosystem and moves on to a demo with npm and pnpm. In the second section Manfred explains the approach for CVE remediation and shows an example project with remediation and scanning with grype. [Watch the video on YouTube](https://www.youtube.com/watch?v=6V7IHtYekwM) ## Sections * [0:00](https://www.youtube.com/watch?v=6V7IHtYekwM) Introduction * [1:55](https://www.youtube.com/watch?v=6V7IHtYekwM&t=115s) Agenda * [2:54](https://www.youtube.com/watch?v=6V7IHtYekwM&t=174s) Secure container and the role of libraries * [5:20](https://www.youtube.com/watch?v=6V7IHtYekwM&t=320s) Software supply chain for libraries and security * [6:51](https://www.youtube.com/watch?v=6V7IHtYekwM&t=411s) Chainguard Libraries and the JavaScript ecosystem * [14:33](https://www.youtube.com/watch?v=6V7IHtYekwM&t=873s) Malware in the npm ecosystem * [18:39](https://www.youtube.com/watch?v=6V7IHtYekwM&t=1119s) Chainguard Libraries for JavaScript * [25:57](https://www.youtube.com/watch?v=6V7IHtYekwM&t=1557s) Demoes with npm and pnpm * [33:50](https://www.youtube.com/watch?v=6V7IHtYekwM&t=2030s) Troubleshooting results and additional demos * [41:34](https://www.youtube.com/watch?v=6V7IHtYekwM&t=2494s) Chainguard Libraries for Python * [43:39](https://www.youtube.com/watch?v=6V7IHtYekwM&t=2619s) CVE remediation process and examples * [52:03](https://www.youtube.com/watch?v=6V7IHtYekwM&t=3123s) Demoes with uv and grype * [56:25](https://www.youtube.com/watch?v=6V7IHtYekwM&t=3385s) Further resources * [57:35](https://www.youtube.com/watch?v=6V7IHtYekwM&t=3455s) Next up * [58:18](https://www.youtube.com/watch?v=6V7IHtYekwM&t=3498s) Questions * [1:06:00](https://www.youtube.com/watch?v=6V7IHtYekwM&t=3960s) Wrapping up ## JavaScript demo The demonstration of Chainguard Libraries for JavaScript walks through the [minimal example for pnpm](/chainguard/libraries/javascript/build-configuration/#pnpm-minimal) with direct access to the registry and with access to a local repository manager, and shows an equivalent project with npm as well. ## CVE remediation demo The example project to showcase the CVE remediation for Python packages uses a simple project setup for use with `uv`. The pyproject.toml file contains all relevant configuration: ```toml [project] name = "uv-example" version = "0.1.0" description = "An example project with uv for Chainguard Libraries" readme = "README.md" requires-python = ">=3.13" dependencies = [ "flask==2.0.0", # fix with 2.0.0+cgr.1 "werkzeug==3.0.2", # fix in 3.0.2+cgr.1 "urllib3==1.26.7", # fix in 1.26.7+cgr.1 "setuptools==77.0.3" # fix in 77.0.3+cgr.1 ] [[tool.uv.index]] name = "nexus" #url = "http://localhost:8081/repository/python-all/simple/" url = "http://localhost:8081/repository/python-all-remediated/simple/" ``` Note the declared dependencies just using the plain semantic version. These versions are resolved to the specific versions with the CVE fixes in place if the remediated packages are available. The script sets up a new virtual environment, builds the project with uv, and then scans for vulnerabilities with grype: ```shell rm -rf bin dist uv.lock python3 -m venv . source ./bin/activate uv cache clean rm -rf lib include uv build uv lock grype . ``` ## Resources * [Slide deck](/downloads/learning-lab-javascript-cve-202510.pdf) * [Chainguard Libraries product site](https://www.chainguard.dev/libraries) * [Chainguard Libraries documentation](/chainguard/libraries/) * [Chainguard Libraries for JavaScript documentation](/chainguard/libraries/javascript/) * [Chainguard Libraries for Python documentation](/chainguard/libraries/python/) * [CVE remediation for Chainguard Libraries](/chainguard/libraries/policies-and-security/cve-remediation/) * [Vulnerability scanners and Chainguard Libraries](/chainguard/libraries/policies-and-security/scanners/) --- ### Static Chainguard container images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202509.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Containers The September 2025 Learning Lab, led by Adrian Moat, focuses on minimizing the attack surface of container images by adopting Chainguard's static and minimal images, which boast zero known CVEs. Adrian demonstrated a container build for a Go-based application and explains more complex use cases. [Watch the video on YouTube](https://www.youtube.com/watch?v=4Cjy_iBNr3I) ## Sections - [00:01](http://www.youtube.com/watch?v=4Cjy_iBNr3I&) Welcome and introductions - [03:34](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=214) Talk outline and prerequisites - [04:34](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=274) Understanding CVEs - [07:34](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=454) Introducing Chainguard Containers - [08:58](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=538) Vulnerability and size comparison - [10:14](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=614) Why Chainguard images are more secure - [11:46](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=706) Practical demo: Migrating a Go container image - [15:01](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=901) Initial build - [17:59](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=1079) Migration to Chainguard base image - [20:45](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=1245) Multi-stage build and static image optimization - [27:39](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=1659) Static vs. dynamic binaries - [30:16](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=1816) Chainguard Container variants - [32:34](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=1954) Distroless Containers and dev images - [33:30](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=2010) Debugging Distroless containers demonstration - [44:57](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=2697) Key takeaways and wrap-up - [46:44](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=2804) Next Learning Lab announcement - [47:50](http://www.youtube.com/watch?v=4Cjy_iBNr3I&t=2870) Resources for further learning ## Resources - [Slide deck](/downloads/learning-lab-static-images-202509.pdf) --- ### Getting started with Chainguard's Dockerfile Converter URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202508.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Containers, dfc The August 2025 Learning Lab with Erika Heidi covers [DFC](/chainguard/containers/migration/migration-tools/dockerfile-conversion/), or Dockerfile Converter, an open source tool created by the Chainguard team to facilitate migration to Chainguard Containers. In this session, learn how to install and use DFC to effectively convert your Dockerfiles to use minimal container images from Chainguard. Erika demonstrates how to use various flags to customize DFC's output and also how to connect the DFC MCP server to your AI assistant to have DFC functionality integrated within your current AI workflow. [Watch the video on YouTube](https://www.youtube.com/watch?v=HzZRFpnKKIU) ## Sections - [0:00](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=0s) Introduction - [1:28](https://youtu.be/HzZRFpnKKIU?t=88) What is DFC? - [3:42](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=222s) Why Convert to Chainguard Containers - [5:26](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=326s) How DFC works - [9:22](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=562s) Tag Mappings - [12:54](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=774s) Installation - [15:45](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=945s) Demo 1: Basic Usage - [21:50](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=1310s) Customizing DFC's Output - [23:45](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=1425s) Demo 2: Customizing with Flags - [28:55](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=1735s) Using DFC as a Go library - [30:05](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=1805s) Using the DFC MCP server with an AI code assistant - [32:19](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=1939s) Demo 3: Multi-stage Dockerfile conversion - [36:29](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=2189s) Demo 4: Using DFC's MCP server with Claude Code - [41:00](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=2460s) Q&A starts - [52:54](https://www.youtube.com/watch?v=HzZRFpnKKIU&t=3174s) Closing Notes ## Demo 1 In the first demo, Erika demonstrates DFC's basic usage with a few inline conversions: Converting a single `FROM` line: ```shell echo "FROM node" | dfc - ``` Converting a single `RUN` line: ```shell echo "RUN apt-get update && apt-get install -y nano" | dfc - ``` Erika also demonstrates how to run DFC to convert a whole Dockerfile. You can use this Python Dockerfile as a reference: ```Dockerfile FROM python:3.9 ADD main.py . RUN pip install requests beautifulsoup4 python-dotenv CMD ["python", "./main.py"] ``` To convert this Dockerfile, run: ```shell dfc Dockerfile ``` ## Demo 2 In the second demo, Erika shows how to use various flags to customize output produced by DFC. ### Specifying the org To specify the org and overwrite the `ORG` placeholder, you can use the `--org` flag: ```shell dfc Dockerfile --org chainguard ``` ### Using a custom mappings file Sometimes, it might be useful to overwrite default mappings for images and packages. For example, let's consider the following Dockerfile for a `php-fpm` environment: ```Dockerfile FROM php:fpm RUN apt-get update && apt-get install -y \ git \ curl \ libxml2-dev \ zip \ unzip # Install Composer and set up application COPY --from=composer:latest /usr/bin/composer /usr/bin/composer RUN mkdir /application COPY . /application/ RUN cd /application && composer install ``` With default settings, DFC will use Chainguard's `php:latest-dev` image for this environment, but we'd like it to use `php:latest-fpm-dev` instead. Create a mappings file such as this: ```custom-mappings.yaml images: php:fpm: php:latest-fpm-dev ``` Then you can provide it alongside the `--mappings` flag when running DFC: ```shell dfc --mappings="custom-mappings.yaml" Dockerfile ``` ## Demo 3 The third demo shows how to convert multi-stage builds and how to connect the DFC MCP server to your AI assistant, using Claude Code as example. ### Converting multi-stage Dockerfiles Consider the following Dockerfile as example: ```shell FROM python:3.9 as builder WORKDIR /app RUN apt update && apt install -y curl git ENV PATH="/venv/bin:$PATH" RUN python -m venv /app/venv COPY requirements.txt /app RUN pip install --no-cache-dir -r requirements.txt FROM python:3.9-slim WORKDIR /app ENV PATH="/venv/bin:$PATH" COPY main.py /app COPY --from=builder /app/venv /venv CMD ["python", "/app/main.py"] ``` This example builds a runtime in two stages. To convert, run DFC as usual: ```shell dfc Dockerfile --org chainguard ``` The expected result: ```shell FROM cgr.dev/chainguard/python:3.9-dev AS builder USER root WORKDIR /app RUN apk add --no-cache curl git ENV PATH="/venv/bin:$PATH" RUN python -m venv /app/venv COPY requirements.txt /app RUN pip install --no-cache-dir -r requirements.txt FROM cgr.dev/chainguard/python:3.9-dev USER root WORKDIR /app RUN apk add --no-cache curl git ENV PATH="/venv/bin:$PATH" COPY main.py /app COPY --from=builder /app/venv /venv CMD ["python", "/app/main.py"] ``` ### DFC MCP server (Claude Code) To build the MCP server that is included with DFC, access the project directory, then enter the `mcp-server` folder and run: ```shell go build -o mcp-server . ``` To add the DFC MCP server to Claude Code **per project**, run: ```shell claude mcp add dfc -- ~/dfc/mcp-server/mcp-server ``` To add the DFC MCP server to Claude Code **system-wide** for your user, run: ```shell claude mcp add dfc -s user -- ~/dfc/mcp-server/mcp-server ``` After that, you'll be able to ask Claude to convert your Dockerfiles to use Chainguard Containers, and the task should be proxied through the DFC MCP server. ## Resources - [Slide deck](/downloads/learning-lab-dfc-202508.pdf) - [Getting Started with DFC Course](https://courses.chainguard.dev/getting-started-with-chainguards-dockerfile-converter) - [DFC documentation](https://edu.chainguard.dev/chainguard/containers/migration/migration-tools/dockerfile-conversion/) - [DFC on GitHub](https://github.com/chainguard-dev/dfc) --- ### AI with hardened container images URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202507.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Containers, AI The July 2025 Learning Lab with Patrick Smyth covers AI with Hardened Container Images. In this session, learn how to secure AI workloads by reducing vulnerabilities in container images by over 90%. Patrick demonstrates hands-on techniques for training an animal detection model using PyTorch with hardened container images, creating minimal and secure deployments, and running AI frameworks with zero CVEs. [Watch the video on YouTube](https://www.youtube.com/watch?v=JGSc6BwjbRI) ## Sections - [0:00](https://www.youtube.com/watch?v=JGSc6BwjbRI) Introduction and updates - [2:02](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=122s) Preparation: Docker pull instructions for demo - [3:39](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=219s) Chainguard! Who are we? - [4:34](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=274s) CVE system fundamentals - [6:48](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=408s) "Boss assigned me to fix Ubuntu" problem - [7:41](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=461s) Introduction to Chainguard Containers - [8:54](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=534s) Zero CVE containers: Real results and comparisons - [11:10](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=670s) How we achieve zero CVEs: Minimal, Fresh, Advisory, Patch - [13:24](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=804s) AI container challenges: Size and complexity - [14:59](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=899s) PyTorch container analysis: CVEs, packages, and executables - [16:21](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=981s) Demo introduction: Image classification with PyTorch - [17:59](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=1079s) Demo walkthrough and repository overview - [19:28](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=1168s) Demo: Running the training command - [22:01](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=1321s) Demo: Downloading test image and running inference - [23:20](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=1400s) Recent developments in Chainguard AI containers - [25:09](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=1509s) Other AI containers: TensorFlow, KServe, Triton backends - [26:46](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=1606s) Q&A - [35:18](https://www.youtube.com/watch?v=JGSc6BwjbRI&t=2118s) Chainguard AI course and additional resources ## Demo In the demo, Patrick trains and runs inference on an image classification model using PyTorch and Chainguard's hardened container image. The model classifies images of octopuses, whales, and penguins, demonstrating how to work with AI workloads securely. [Demo repository: PyTorch getting started](https://github.com/chainguard-dev/pytorch-getting-started/blob/main/learning-lab.md) ### Training the model First, create a directory for the project and download the necessary files: ```bash mkdir -p ~/image_classification && cd ~/image_classification && \ curl https://codeload.github.com/chainguard-dev/pytorch-getting-started/tar.gz/main | \ tar -xz --strip=1 pytorch-getting-started-main/ ``` Then run the training script inside a Chainguard PyTorch container: ```bash docker run --user root --rm -it \ --platform linux/amd64 \ -v "$PWD/:/home/nonroot/octopus-detector" \ cgr.dev/chainguard/pytorch:latest \ "/home/nonroot/octopus-detector/image_classification.py" ``` This command generates a model file named `octopus_whale_penguin_model.pt`. ### Running inference To test the trained model, first download a test image: ```bash curl https://raw.githubusercontent.com/chainguard-dev/pytorch-getting-started/main/inference-images/octopus.jpg > ~/image_classification/octopus.jpg ``` Then run the classification: ```bash cd ~/image_classification && \ docker run --user root --rm -it \ --platform linux/amd64 \ -v "$PWD:/home/nonroot/octopus-detector" \ cgr.dev/chainguard/pytorch:latest \ "/home/nonroot/octopus-detector/image_classification.py" \ "/home/nonroot/octopus-detector/octopus.jpg" ``` The demo showcases how Chainguard's hardened PyTorch image provides the same functionality as traditional images while eliminating vulnerabilities and reducing the attack surface. ## Resources - [Slide deck](/downloads/learning-lab-ai-images.pdf) - [Demo repository](https://github.com/chainguard-dev/pytorch-getting-started) - [Chainguard AI/ML Supply Chain Security Course](https://courses.chainguard.dev/securing-ai) - [Getting started with the PyTorch Chainguard Container](https://edu.chainguard.dev/chainguard/containers/getting-started/ai-and-data/pytorch/) - [PyTorch container overview](https://images.chainguard.dev/directory/image/pytorch/overview) - [Beyond Zero: Eliminating Vulnerabilities in PyTorch Container Images (PyTorch 2024)](https://www.youtube.com/watch?v=1klynk1dxYA) --- ### Chainguard Libraries for Python URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202506.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Libraries, Python The June 2025 Learning Lab with Patrick Smyth covers Chainguard Libraries for Python. Open source libraries help you move fast, but pulling in external dependencies can introduce supply chain risk. This session covers fundamental concepts of Chainguard Libraries, package managers and dependencies, PyPI and build tools, configuring repository managers, and running example application builds. [Watch the video on YouTube](https://www.youtube.com/watch?v=h_nzhPY_vDA) ## Sections - [0:00](https://www.youtube.com/watch?v=h_nzhPY_vDA) Introduction and welcome - [0:54](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=54s) Patrick Smyth introduction and background - [1:47](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=107s) Chainguard! Who are we? - [2:47](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=167s) Chainguard Containers and the "boss assigned me to fix Ubuntu" problem - [4:12](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=252s) Introduction to Chainguard Libraries for Python - [5:04](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=304s) Python libraries fundamentals - modules, packages, and libraries - [6:34](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=394s) The dependency graph problem and modern ecosystem challenges - [8:57](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=537s) PyPI (Python Package Index) overview and infrastructure - [10:53](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=653s) Supply chain attacks on the rise and threats to the Python ecosystem - [11:39](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=699s) Supply chain meme calendar - an attack every month this year - [13:54](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=834s) Anatomy of supply chain attacks and attack vectors - [17:43](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1063s) Chainguard Libraries! - [19:34](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1174s) Chainguard Factory overview and operational security - [21:33](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1293s) Case study: Ultralytics YOLO December 2024 attack - [23:22](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1402s) Technical caveats and requirements for Chainguard Libraries - [25:06](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1506s) Demo introduction and Flask project overview - [27:48](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1668s) Accessing demo materials on Chainguard Academy - [29:00](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1740s) Demo: Cloning and setting up the Flask project - [31:17](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1877s) Demo: Creating virtual environment and installing from PyPI - [33:06](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=1986s) Demo: Running Flask application and testing with libCheck tool - [34:28](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2068s) Demo: Configuring pip for Chainguard Libraries via repository manager - [36:19](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2179s) Demo: Installing dependencies from Chainguard Libraries - [37:02](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2222s) Demo: Verification with libCheck - [38:22](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2302s) Demo: Containerizing the demo application - [40:25](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2425s) Demo: Building and running containerized Flask application - [41:41](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2501s) Additional configuration options and documentation resources - [42:19](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2539s) Q&A: Repository manager setup and configuration - [43:26](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2606s) Q&A: Architecture support and glibc requirements - [44:34](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2674s) Q&A: libCheck tool open source plans and detailed output - [46:05](https://www.youtube.com/watch?v=h_nzhPY_vDA&t=2765s) Q&A: CVE scanning with Grype and vulnerability management ## Demo In the demo, Patrick switches a Flask application to use Chainguard Libraries for Python, sourcing dependencies from a repository manager (Artifactory) set up to pull first from the Chainguard Libraries for Python index with a fallback to the Python Package Index (PyPI). [Demo Flask application](https://github.com/chainguard-dev/cg-images-python-migration/tree/libraries-demo) Patrick demonstrates two approaches. First, he modifies the `~/.pip/pip.conf` file to pull from the virtual repository set up in the repository manager: ``` [global] index-url = ``` After changing this global setting, Patrick installs and runs the application from a virtual environment, then uses Chainguard's `libCheck` tool to test the provenance of the packages in the virtual environment. Chainguard is in the process of releasing this tool under an open source license. Patrick also updates the demo application's `requirements.txt` file and build and run the application from a Chainguard Container. ## Resource links - [Slide deck](/downloads/learning-lab-python-libraries.pdf) - [Chainguard Libraries](https://www.chainguard.dev/libraries) - [Chainguard Libraries documentation](/chainguard/libraries/introduction/overview/) - [Chainguard Libraries for Python documentation](/chainguard/libraries/python/overview/) - [Python global configuration](/chainguard/libraries/python/global-configuration/) - [Python build configuration](/chainguard/libraries/python/build-configuration/) - [Python Package Index (PyPI)](https://pypi.org/) - [pip documentation](https://pip.pypa.io/) - [Python Packaging User Guide](https://packaging.python.org/) - [Cheese Must Stand: Defending the Python Library Ecosystem in 2025 at PyCon 2025](https://www.youtube.com/watch?v=5cdC5pVkCvU) --- ### Chainguard Libraries for Java URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/software-security/learning-labs/ll202505.md Last Modified: September 28, 2026 Tags: Learning Labs, Chainguard Libraries, Overview The May 2025 Learning Lab with Manfred Moser covers Chainguard Libraries for Java. It starts with an overview about libraries and the Java ecosystem and progresses to a demo with Apache Maven and Sonatype Nexus. [Watch the video on YouTube](https://www.youtube.com/watch?v=z42b2_lePNI) ## Sections - [0:00](https://www.youtube.com/watch?v=z42b2_lePNI) Introduction and agenda - [2:38](https://www.youtube.com/watch?v=z42b2_lePNI&t=158s) Chainguard and containers - [3:47](https://www.youtube.com/watch?v=z42b2_lePNI&t=227s) Chainguard Factory - [4:57](https://www.youtube.com/watch?v=z42b2_lePNI&t=297s) Concepts - from containers to libraries - [9:00](https://www.youtube.com/watch?v=z42b2_lePNI&t=540s) Java and Java libraries - [12:45](https://www.youtube.com/watch?v=z42b2_lePNI&t=765s) Software supply chain of libraries and attacks - [19:27](https://www.youtube.com/watch?v=z42b2_lePNI&t=1167s) Dependency supply in Java - [20:30](https://www.youtube.com/watch?v=z42b2_lePNI&t=1230s) Repository concept and Maven Central - [24:32](https://www.youtube.com/watch?v=z42b2_lePNI&t=1472s) Chainguard Libraries for Java and repository manager intro - [28:17](https://www.youtube.com/watch?v=z42b2_lePNI&t=1697s) Developer tools - [29:21](https://www.youtube.com/watch?v=z42b2_lePNI&t=1761s) Demo start and setup with chainctl - [32:55](https://www.youtube.com/watch?v=z42b2_lePNI&t=1975s) Sonatype Nexus configuration - [37:30](https://www.youtube.com/watch?v=z42b2_lePNI&t=2250s) Maven configuration - [40:41](https://www.youtube.com/watch?v=z42b2_lePNI&t=2441s) Example project setup, build, and results - [44:57](https://www.youtube.com/watch?v=z42b2_lePNI&t=2697s) Dependency list and tree - [47:00](https://www.youtube.com/watch?v=z42b2_lePNI&t=2820s) Results and verification - [49:37](https://www.youtube.com/watch?v=z42b2_lePNI&t=2977s) Summary - [50:43](https://www.youtube.com/watch?v=z42b2_lePNI&t=3043s) Up next - [52:55](https://www.youtube.com/watch?v=z42b2_lePNI&t=3175s) Questions ## Demo Following are some of the commands used in the demo. More information can be found in the slide deck, the linked resources, and the video. Creating a pull token: ```shell chainctl auth login chainctl libraries entitlements list chainctl auth pull-token --repository=java --ttl=1h ``` Cleaning up the local Maven repository cache: ```shell rm -rf ~/.m2/repository ``` Building [Trino Gateway](https://github.com/trinodb/trino-gateway) from source and looking at dependencies: ```shell cd trino-gateway ./mvnw clean install -DskipTests=true ./mvnw dependency:list ./mvnw dependency:tree ``` ## Resource links - [Chainguard Libraries](https://www.chainguard.dev/libraries) - [Chainguard Libraries documentation](/chainguard/libraries/introduction/overview/) - [Chainguard Libraries for Java documentation](/chainguard/libraries/java/overview/) - [Slide deck](/downloads/learning-lab-chainguard-libraries-java.pdf) - [Apache Maven](https://maven.apache.org/) - [Sonatype Nexus Repository](https://www.sonatype.com/products/sonatype-nexus-repository) - [Apache Maven dependency plugin](https://maven.apache.org/plugins/maven-dependency-plugin/) ---