# Chainguard Academy - Full Content
Generated: October 16, 2026 at 1: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-athena-mention.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
### Find Chainguard replacements with chainctl images discover
URL: https://chainguard-docs-preview-git-athena-mention.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/).
---
### Chainguard Checks overview
URL: https://chainguard-docs-preview-git-athena-mention.chainguard.app/chainguard/checks/overview.md
Last Modified: October 13, 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.
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.
## 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
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-athena-mention.chainguard.app/chainguard/workspaces/overview.md
Last Modified: October 13, 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.
## 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
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-athena-mention.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, see [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/).
## 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_`. You can see them with `docker inspect`. 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. See [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. See [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 it. `#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 anything that can read the spec can see. 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 image's ENTRYPOINT and CMD, and any arguments passed at run time, are dropped. |
`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.
`command` is stored in the image configuration and 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. See also [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, or 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.
The lists of the supported and refused entrypoints for each image come from a generated report. See the [lists of supported and refused entrypoints](https://PLACEHOLDER.invalid/guarded-entrypoint-supported-and-refused-lists).
For how a refusal appears in `chainctl`, see [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/)
---
### Managing Custom Assembly Overlays with chainctl
URL: https://chainguard-docs-preview-git-athena-mention.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, see [Overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/).
## 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, see [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, see [Version templates in package names](/chainguard/containers/custom-assembly/overlays/#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, see [How overlapping bindings combine](/chainguard/containers/custom-assembly/overlays/#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 see 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, see [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/)
* [Managing 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-athena-mention.chainguard.app/chainguard/containers/building-and-modifying/adding-packages.md
Last Modified: October 8, 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.
Container images that include apk — such as a -dev variant — can search the repository from inside a running container. For a free container image, no authentication is needed:
docker run --rm --entrypoint sh cgr.dev/chainguard/wolfi-base:latest \
-c 'apk update > /dev/null && apk search -e "mongo*"'
To search the packages your organization is entitled to, start a -dev variant of one of your organization’s images with an HTTP_AUTH variable so that apk can reach your Private APK Repository:
Replace $CONTAINER with the name of the image. If you omit it, chainctl prompts you to choose. It also prompts for the organization when you have access to more than one; pass --parent=<organization> to skip that prompt.
chainctl opens the configuration in your default text editor. Add the package under contents.packages:
contents:
packages:
- yarn
- wget
- bash
Save and close the file. chainctl prints a diff and asks you to confirm:
/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 start the build.
To save the result as a new image rather than changing the existing one, add --save-as $NEW_NAME. For the rest of what you can set in this file, including environment variables, annotations, and custom user accounts, see Using chainctl to manage Custom Assembly resources.
Both apply and edit accept a configuration file, which skips the editor and the prompt. Use this form in CI/CD and anywhere you keep image configuration in version control.
To save the result as a new image, add --save-as $NEW_NAME. This works when you target a single repository; it isn’t available when you target several at once with repeated --repo flags or a wildcard.
## 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-athena-mention.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-athena-mention.chainguard.app/chainguard/guardener/github/getting-started.md
Last Modified: October 8, 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-athena-mention.chainguard.app/chainguard/actions/overview.md
Last Modified: October 5, 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.
## 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-athena-mention.chainguard.app/chainguard/agent-skills/overview.md
Last Modified: September 28, 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 — 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-athena-mention.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-athena-mention.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-athena-mention.chainguard.app/chainguard/containers/reference/catalog-starter.md
Last Modified: September 29, 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, and create pull tokens, but does not include permission to invite other users to the organization or access 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-athena-mention.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-athena-mention.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-athena-mention.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-athena-mention.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.0com.examplejib-demo1.0.0jar2121UTF-8yyyyMMddHHmmssorg.springframework.bootspring-boot-starter-web3.5.6org.springframework.bootspring-boot-maven-plugin3.5.6repackage
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.toolsjib-maven-plugin3.4.6packagedockerBuildcgr.dev/chainguard/jre:latestlinuxamd64linuxarm64linky8080USE_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.toolsjib-maven-plugin3.4.6packagebuildcgr.dev/chainguard/jre:latestlinuxamd64linuxarm64ttl.sh/jib-demo-${maven.build.timestamp}:20m8080USE_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.toolsjib-maven-plugin3.4.6packagebuildTarcgr.dev/chainguard/jre:latestlinuxamd64linky8080USE_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-athena-mention.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-athena-mention.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-athena-mention.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:
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:
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-athena-mention.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.
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
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.
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.
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.
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
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.
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.
## 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. Remediated versions are:
* Published in a separate repository:
`https://libraries.cgr.dev/python-remediated/simple/` for Python and
`https://libraries.cgr.dev/java-remediated/` for Java.
* Given a local version suffix -- like `+cgr.N` for Python (for example,
2.0.0+cgr.1) and `-0.cgr.N` for Java -- so dependency resolvers can
distinguish them from non‑remediated upstream versions while still preferring
the remediated build during resolution.
## 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-athena-mention.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.commonscommons-lang33.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-athena-mention.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
### 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-athena-mention.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, see [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/). 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-athena-mention.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-athena-mention.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.
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:
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:
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:
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-athena-mention.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 ContainersChainguard 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.
---
### How Chainguard issues Security Advisories
URL: https://chainguard-docs-preview-git-athena-mention.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.

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-athena-mention.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-athena-mention.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.
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-athena-mention.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-athena-mention.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-athena-mention.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 ContainersChainguard 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.
---
### Strategies for minimizing your CVE risk
URL: https://chainguard-docs-preview-git-athena-mention.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-athena-mention.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-athena-mention.chainguard.app/chainguard/containers/security-and-compliance/working-with-scanners/false-results.md
Last Modified: October 8, 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](/software-security/cves/infamous-cves/), 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-athena-mention.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 ContainersChainguard 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.
---
### Registry overview
URL: https://chainguard-docs-preview-git-athena-mention.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-athena-mention.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-athena-mention.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-athena-mention.chainguard.app/chainguard/checks/getting-started.md
Last Modified: October 13, 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.
## 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 repository on github.com that you can push to, and a local clone of it.
## Step 1: Install or update chainctl
You sign up with `chainctl`. If you don't have it yet, follow the [chainctl installation guide](/platform/chainctl-usage/how-to-install-chainctl/).
This guide needs `chainctl` 0.2.380 or later. Earlier releases can't sign you up as this guide describes. If you already have `chainctl`, update it to the latest release, because Chainguard Checks changes often during the beta:
```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.
## Step 2: Install the Chainguard App
Chainguard Checks receives pull request events and reports results through the Chainguard App, a GitHub App that also backs [Chainguard Workspaces](/chainguard/workspaces/overview/) and [Guardener](/chainguard/guardener/).
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.
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.
## Step 3: Sign up and turn on Chainguard Checks
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 with a monthly compute allowance that resets on the first day of each month (UTC). [Availability and limits](/chainguard/checks/overview/#availability-and-limits) describes what the allowance includes. 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 an email address and password or with a Google account, not with 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. With a work email address, the organization is named after your email domain. With a personal email address, you name it.
- 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. Asks which GitHub account 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 the monthly allowance, 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 to Chainguard Workspaces sessions, unless it's connected already or your organization doesn't allow it. Sessions then act on GitHub as you, through a separate GitHub App named Chainguard Develop. Chainguard Checks doesn't need this, so you can decline, and connect later as described in [Let sessions act on GitHub as you](/chainguard/workspaces/credentials/#let-sessions-act-on-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` says so and asks for a different GitHub user or organization 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).
You can answer some of the questions ahead of time 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. |
| `--name` | Names the new organization when you sign up with a personal email address. The name can't look like a domain name. The command ignores `--name` for a work email address, and fails if you already belong to an organization. |
For example, to link the `acme` GitHub organization:
```shell
chainctl develop init --github-org acme
```
To turn on Chainguard Checks in an organization you already belong to, replace `$ORGANIZATION` with the organization's name:
```shell
chainctl develop init --parent $ORGANIZATION
```
If `chainctl develop init` fails, refer to [Troubleshoot Chainguard Workspaces](/chainguard/workspaces/troubleshooting/#chainctl-develop-init-fails).
If your company already has a Chainguard organization, `chainctl develop init` can't create a second one for your work 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 Chainguard Checks in it with `chainctl develop init --parent $ORGANIZATION`. If the organization already has Chainguard Checks through your Chainguard account team, the command keeps that allowance.
An owner of an organization that has Chainguard Checks through the account team can instead accept only the Chainguard Checks terms, with the following command:
```shell
chainctl checks accept-terms --parent $ORGANIZATION
```
Until an owner accepts the terms, Chainguard Checks runs no checks for the organization, and the summary check on each pull request fails with **Run creation failed**. 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.
## Step 4: 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 <
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.
## 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.
- Optional: [Claude Code](https://code.claude.com/docs/en/overview) installed and logged in on your laptop.
## Step 1: Install or update chainctl
Workspaces uses the `chainctl develop` command. If you don't have `chainctl` yet, follow the [chainctl installation guide](/platform/chainctl-usage/how-to-install-chainctl/).
This guide needs `chainctl` 0.2.380 or later. Earlier releases can't sign you up as this guide describes or run Claude Code in a session. If you already have `chainctl`, update it to the latest release, because Workspaces changes often during the beta:
```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.
## Step 2: Install the Chainguard App
By default, sessions fetch and push your repository through the Chainguard App, a GitHub App that also backs [Chainguard Checks](/chainguard/checks/overview/) and [Guardener](/chainguard/guardener/). The sign-up in the next step links your Chainguard organization to a GitHub account.
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 Workspaces.
4. Review the requested permissions and select **Install**.
## Step 3: Sign up and turn on Checks and Workspaces
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 with a free monthly allowance that resets on the first day of each month (UTC). Run it 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 an email address and password or with Google. Other sign-in methods, such as GitHub, can't create one.
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. With a work email address, the organization is named after your email domain. With a personal email address, you name it, and its registry paths use its ID instead of its name.
- If you belong to exactly one organization on the Starter or Team plan, it uses that one.
- Otherwise, it stops and asks you to name an organization with `--parent`.
3. Asks you to accept the terms that Checks and Workspaces are offered under, if your organization hasn't accepted them yet: the Code Analysis Terms, the Secured Platform Terms, the Master Service and License Agreement (MSLA), and the Terms of Service and Data Privacy Agreement that cover linking the Chainguard App.
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` for your own GitHub user, or enter the name of a GitHub organization you own, such as `acme`, not a URL. It then opens your browser so you can authorize the link. If the Chainguard App isn't installed on the account, it offers to open the page that installs it, then authorizes and links again.
5. Turns on Checks and Workspaces with the monthly allowance, and prints each product's limits, which [Availability and limits](/chainguard/workspaces/overview/#availability-and-limits) describes. If Chainguard already granted your organization an allowance, it keeps that one.
6. Offers to connect your GitHub account, if you haven't yet and your organization allows it, so that the sessions you create act on GitHub as you. The connection uses Chainguard Develop, a GitHub App separate from the Chainguard App. If you agree, it opens your browser again. If you run the command in your repository's directory, it also offers to install Chainguard Develop on the GitHub accounts your repository needs. 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/).
A GitHub account can be linked to only one Chainguard organization at a time. When you authorize the link, GitHub might ask you to authorize single sign-on for your GitHub organizations. Linking your own GitHub user doesn't need it, so you can select **Continue**. To link an organization that requires SAML single sign-on, authorize single sign-on for that organization.
You can answer some of the questions ahead of time with flags. For example, to link the `acme` GitHub organization instead of your own GitHub user:
```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).
If your company already has a Chainguard organization, `chainctl develop init` can't create another one for your email domain, so ask an owner of that organization to add you, as described in [Give teammates access](#give-teammates-access). If the command stops with an error, refer to [`chainctl develop init` fails](/chainguard/workspaces/troubleshooting/#chainctl-develop-init-fails).
If your organization already has Workspaces through your Chainguard account team, someone with the `owner` role must accept the terms before anyone can open sessions in it, as described in [`chainctl develop accept-terms`](/chainguard/workspaces/chainctl-develop-reference/#chainctl-develop-accept-terms).
## Step 4: 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 5: 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 6: 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 7: 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
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`. Refer to [Roles](/chainguard/workspaces/chainctl-develop-reference/#roles).
### 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:
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 develop` 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 [Step 1](#step-1-install-or-update-chainctl), and open a session, as described in [Step 4](#step-4-open-a-session). 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 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. An invite can give only a role whose permissions you also have, and of those roles, only `limited_owner` can open sessions.
To let your teammate open sessions, run step 1 of [Add a teammate](#add-a-teammate) with `--role limited_owner` instead of `--role viewer`, replacing `$EMAIL` with your teammate's email address:
```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. Your teammate accepts the invite as described in step 2 of [Add a teammate](#add-a-teammate).
Invites in a Starter organization have the following limits:
- 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.
- If you signed up with a personal email address, you can't invite anyone.
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.
## 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-athena-mention.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/). 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 image's ENTRYPOINT and CMD, and any arguments you pass at run time, are dropped. 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. See [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`. See [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/)
* [How Guarded Entrypoint works](/chainguard/containers/custom-assembly/guarded-entrypoint/how-it-works/)
* [Troubleshoot a wrapped container](/chainguard/containers/custom-assembly/guarded-entrypoint/troubleshooting/)
---
### Managing Custom Assembly Overlays with Terraform
URL: https://chainguard-docs-preview-git-athena-mention.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, see [Overview of Custom Assembly Overlays](/chainguard/containers/custom-assembly/overlays/).
The provider doesn't support [all-repos bindings](/chainguard/containers/custom-assembly/overlays/#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, see [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, see [Version templates in package names](/chainguard/containers/custom-assembly/overlays/#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, see 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, see [Supported customizations](/chainguard/containers/custom-assembly/overlays/#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/)
* [Managing 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-athena-mention.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-athena-mention.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-athena-mention.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-athena-mention.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.
- **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-athena-mention.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-athena-mention.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 ContainersChainguard 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.
### 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-athena-mention.chainguard.app/chainguard/agent-skills/skills-registry.md
Last Modified: September 28, 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.364** 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.
```
`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.
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.
```
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.
### 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.
### 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"` |
| Install a hardened skill | `chainctl skills install "$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-athena-mention.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-athena-mention.chainguard.app/chainguard/chainguard-repository/container-policies.md
Last Modified: September 28, 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-athena-mention.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-athena-mention.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-athena-mention.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-athena-mention.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 ContainersChainguard 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-athena-mention.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-athena-mention.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-athena-mention.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 <
## 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-athena-mention.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-athena-mention.chainguard.app/chainguard/libraries/javascript/global-configuration.md
Last Modified: September 28, 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.
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-athena-mention.chainguard.app/chainguard/containers/concepts/lifecycle-and-eol/eol-grace-period.md
Last Modified: October 8, 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](/chainguard/containers/concepts/lifecycle-and-eol/how-eol-software-accumulates-cves/). 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.
> **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:
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.
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 conceptual article on [How end-of-life software accumulates vulnerabilities](/chainguard/containers/concepts/lifecycle-and-eol/how-eol-software-accumulates-cves/) is helpful for understanding the risk involved with using end-of-life software by outlining how EOL images accrue vulnerabilities and where they accumulate.
---
### Understanding Chainguard's container image categories
URL: https://chainguard-docs-preview-git-athena-mention.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-athena-mention.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-athena-mention.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-athena-mention.chainguard.app/chainguard/libraries/introduction/quickstart.md
Last Modified: September 28, 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.
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.
Run the following chainctl libraries command to create an entitlement for libraries:
The available ecosystems are JAVA, JAVASCRIPT, and PYTHON.
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/):
> 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.
JavaScript
Repository
manager: Add the Chainguard Libraries registry as a remote repository
and configure it as the first choice for package resolution, with npm as a
fallback only where necessary.
Check out minimal example projects for
npm,
pnpm,
Yarn,
Yarn
Classic,
and
Bun to understand how to use these repositories.
Migrating an existing JavaScript project? If you have an
existing lockfile with upstream hashes, use chainctl libraries update-hashes
to update checksums to Chainguard’s automatically, without regenerating your
lockfile from scratch. The command authenticates to Chainguard Libraries; see
the authentication options in the
JavaScript
build configuration page.
Python
Repository manager: Add Chainguard Libraries as a remote repository in your
repository manager.
Direct access: Configure
your tool to retrieve artifacts directly from the Chainguard Libraries for
Python.
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 for libraries with CVE
remediation
In addition to malware-resistance, Chainguard Libraries for Python 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
uv and pip to understand how to use these repositories.
Migrating an existing Python project? If you have an
existing lockfile with upstream hashes, use chainctl libraries update-hashes
to update checksums to Chainguard’s automatically, without regenerating your
lockfile from scratch. The command authenticates to Chainguard Libraries; see
the authentication options in the
Python build configuration page.
> **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-athena-mention.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 WolfiWolfi 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 ContainersChainguard 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.
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:
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:
## 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-athena-mention.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-athena-mention.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.
---
### Strategies and tooling for updating containers
URL: https://chainguard-docs-preview-git-athena-mention.chainguard.app/chainguard/containers/security-and-compliance/updating-containers/strategies-tools-updating-images.md
Last Modified: September 28, 2026
Tags: Chainguard Containers
When it comes to keeping a system secure, one of the most important measures you can take is to regularly apply updates. In modern, containerized infrastructures, this normally means updating containers to use only the latest container images that are still maintained. A casual observer might expect such a standard and important task to have agreed-on best practices and standardized tooling, but they might be surprised by the wide variety of different solutions and opinions on this problem.
This conceptual article will delve into some of the options and offer guidance on which might work best for readers. This article assumes that you are familiar with [semantic versioning](https://semver.org/) (SemVer) and image tagging. If you aren't acquainted with these concepts, check out this guide on [Considerations for keeping containers up to date](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/).
## Updating means risk
The core issue with applying updates is that it's a fundamentally risky endeavor; any update to software risks a change to behavior and system breakages. It's a common practice to avoid major updates for weeks or even months in order to ensure bugs have been worked out before upgrading.
Larger software projects (like PostgreSQL, Java, and Node.js) often have multiple versions of their project in support at the same time. This means that users can stay on an older version and avoid the more risky updates while still getting security patches. Although this approach is helpful to operations teams, it is only practical on large projects with paid maintainers that can spend time backporting fixes. Smaller projects will often struggle with just keeping the main version up to date.
## Not updating means more risk
End of Life (EOL) software [presents a host of security risks](/chainguard/containers/concepts/lifecycle-and-eol/how-eol-software-accumulates-cves/). Upgrading may require a great deal of work and proper testing, but it's a small price to pay to keep your systems from being at risk and accruing technical debt.
If your application has an automated test suite with good coverage, you can be confident that any breakages caused by upgrades will be caught before deployment to production. Of course, whenever a test failure occurs, there will be work required to address it. Putting off that work by delaying upgrades will only mean that more work is required in the future as further changes pile up.
Another way organizations test and reduce the risk of breaking changes introduced by updates is through the use of staging environments where changes are tried out before being pushed to production. An alternative approach to this is [testing in production](https://increment.com/testing/i-test-in-production/), which usually involves using techniques like [feature flags](https://www.honeycomb.io/blog/what-is-a-feature-flag-best-practices-and-use-cases) and staged updates to verify the effects of changes before they impact the majority of users.
## Knowing when updates are available
The primary way of knowing when a new image is available is through the registry itself. Many registries will offer a webhook callback service ([Docker Hub](https://docs.docker.com/docker-hub/webhooks/), for example), but this is typically only for your own repositories. If you want to get notified when a public repository is updated, you'll generally have to use a third-party service like [NewReleases](https://newreleases.io/).
If you're trying to find out how outdated the images in your Kubernetes cluster are, you might find the [version-checker](https://github.com/jetstack/version-checker) project to be useful. This is a Kubernetes utility that will create an inventory of your current images and produce a chart showing how out of date they are. The dashboard can form part of a full solution with notifications for outdated software being sent to cluster administrators for mitigation.
## Updating solutions
This section outlines some commonly employed solutions for keeping images up to date. We'll only consider solutions that involve automation — you could argue that `kubectl set image` is an updating solution, but it would only be scalable as part of an automated pipeline.
### Using `latest` or major version tags
One common strategy is to only use the `latest` tag or major version tags. This means having something like the following in your Dockerfile:
``` Dockerfile
FROM cgr.dev/chainguard/redis:3
```
Or, if you're using a Kubernetes manifest, you might have a line like this:
``` Kubernetes
image: cgr.dev/chainguard/redis:latest
```
The tag used will determine the jump in the updated version; `latest` will jump major versions so typically a major or minor tag is chosen to limit the size of changes.
The goal for this strategy is to update the image whenever it is rebuilt or redeployed. However, the reality is that this approach is still dependent on caching and configuration (especially the [image pull policy](https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy)) and it may involve a considerable time to redeployment.
One major issue with this approach is that you lack control and reproducibility over the images that will be deployed. In Kubernetes you can end up with different pods in the same deployment running different versions of the application because nodes pulled the image at slightly different times. Debugging can become difficult, as you can't easily recreate the system. You can't even say for sure what is running in the cluster, which will be a big problem when you need to respond to a security situation.
The advantage of this approach is that it is relatively simple, requires little maintenance, and will keep up to date with changes over time, meaning it's often appropriate for simple projects, or example code. However, it's recommended that you don't deploy the `latest` tag to production, as doing so can present its own risks.
### Keel
[Keel](https://keel.sh/) is a Kubernetes Operator that will automatically update Kubernetes manifests and Helm charts. It has multiple options for finding updates — typically using webhooks from registries and falling back to polling for new versions. Updates are controlled through [policies](https://keel.sh/docs/#policies) which cover the normal cases.
### GitOps: Flux and ArgoCD
[GitOps](https://medium.com/weaveworks/what-is-gitops-really-e77329f23416) is a set of practices that leverage Git as a single source of truth for infrastructure automation. The two leading GitOps solutions for updating images are Flux and ArgoCD (though there are also newer solutions gaining traction including [fleet](https://github.com/rancher/fleet) and [kluctl](https://kluctl.io/)).
#### Flux
[Flux](https://fluxcd.io/flux/guides/image-update/) uses an [`ImageRepository` custom
resource](https://fluxcd.io/flux/components/image/imagerepositories/) that polls repositories for updates. There is also support for webhooks via the [Notification Controller](https://fluxcd.io/flux/components/notification/). An [`ImagePolicy` custom
resource](https://fluxcd.io/flux/components/image/imagepolicies/) defines what tags we're interested in — typically you will use a SemVer policy, such as `range: 5.0.x`, to get minor updates.
An [`ImageUpdateAutomation` resource](https://fluxcd.io/flux/components/image/imageupdateautomations/) then defines how to handle updates, such as by checking an update commit directly or committing to a new branch and opening a GitHub pull request for manual approval. There is also support for reverting updates and suspending automation to support incident response.
#### ArgoCD
ArgoCD has a separate [Image Updater](https://github.com/argoproj-labs/argocd-image-updater) project that can be used to automate updates. Rather than creating new resources, ArgoCD relies on annotations being added to existing manifests. Update strategies are similar to Flux, with support for SemVer and regular expressions to filter tags. Unlike Flux, there is currently no support for webhooks, but this could change in the future.
### ImageStreams
OpenShift has the concept of [ImageStreams](https://docs.openshift.com/container-platform/4.15/openshift_images/image-streams-manage.html) for handling updates. ImageStreams are a "virtual view" on top of images. Deployments and builds can listen for ImageStream notifications to automatically update for new versions.
The underlying data for an ImageStream comes from registries, but decoupling this data means it is possible to have different versions in the ImageStream and on the registry. This in turn allows for processes such as rolling back a deployment without retagging images on the registry. The ImageStream itself is represented as a custom resource which contains a history of previous digests, ensuring that rollbacks are possible even when tags are overwritten (assuming the image isn't deleted).
### Frizbee and digestabot
A best practice in supply chain security is to specify GitHub actions and container images by their *digest*. The digest is a content-based SHA of the image that is guaranteed to always refer to exactly the same version of the action or code, and it is also guaranteed to not have changed. In other words, digests are *immutable*, meaning that they can't be changed to point to something else. The disadvantages are that digests aren't human-readable and you need to keep updating them to stay up to date.
However, It is possible to get something human-readable as well as immutable. The following are valid image references which specify both a meaningful tag and an immutable digest:
```
cgr.dev/chainguard/wolfi-base:latest@sha256:3eff851ab805966c768d2a8107545a96218426cee1e5cc805865505edbe6ce92
```
```
redis:7@sha256:01afb31d6d633451d84475ff3eb95f8c48bf0ee59ec9c948b161adb4da882053
```
[Frizbee](https://github.com/stacklok/frizbee), a tool from Stacklok, will update image references to the most up-to-date digest. For the above example, it will ask the registry for the digest of the `cgr.dev/chainguard/wolfi-base:latest` image and update it if it doesn't match.
At Chainguard, we take a similar approach with [Digestabot](https://github.com/chainguard-dev/digestabot), a GitHub Action that looks up digests in this format and opens a pull request to update them.
Digestabot authenticates to your private `cgr.dev` registry with an assumable identity. Refer to [Using Digestabot with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/digestabot/) for configuration details.
### Dependabot
Dependabot is GitHub's tool for monitoring dependencies. It can be used with GitHub, but [can also be self-hosted](https://github.com/dependabot/dependabot-core?tab=readme-ov-file#how-to-run-dependabot).
Dependabot is designed to work with a variety of different package ecosystems, as well as container images referenced in Dockerfiles and Kubernetes manifests. Dependabot runs on a schedule and will open pull requests to update dependencies when it finds outdated versions.
Dependabot authenticates to your private `cgr.dev` registry with a pull token. Refer to [Using Dependabot with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/dependabot/) for configuration details.
### Renovate
[Renovate](https://github.com/renovatebot/renovate) is a similar solution to Dependabot and opens pull requests to update out-of-date dependencies. The major difference is that Renovate is a self-hosted application that supports multiple repositories, like GitLab, Azure, and Bitbucket, instead of just GitHub.
Refer to [Using Renovate with Chainguard Containers](/chainguard/containers/security-and-compliance/updating-containers/renovate/) for configuration details.
## Conclusion
Something as important as keeping packages up to date has more approaches and tooling than one might expect. This article has shied away from offering any clear recommendations, but this is a matter where every organization will need to choose a solution that suits its own needs.
We encourage you to check out each of the solutions listed in this article and judge them on their own merits. We also suggest you read our other articles on handling EOL software, including [Considerations for keeping containers up to date](/chainguard/containers/security-and-compliance/updating-containers/considerations-for-image-updates/).
---
### Using Grype to scan software artifacts
URL: https://chainguard-docs-preview-git-athena-mention.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
```

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-athena-mention.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.
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-athena-mention.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-athena-mention.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-athena-mention.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
On the Repositories page, find your repository.
Hover your cursor over the repository's row and click the ellipsis (⋯) all the way to the right.
Select Set me up.
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:
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
On the Repositories page, find your repository.
Hover your cursor over the repository's row and click the ellipsis (⋯) all the way to the right.
Select Set me up.
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:
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
On the Repositories page, find your repository.
Hover your cursor over the repository's row and click the ellipsis (⋯) all the way to the right.
Select Set me up.
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:
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-athena-mention.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-athena-mention.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-athena-mention.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-athena-mention.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.
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-athena-mention.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 ContainersChainguard 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.
---
### Resolve a scanner finding for a CVE Chainguard has fixed
URL: https://chainguard-docs-preview-git-athena-mention.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-athena-mention.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
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
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
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`.
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
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 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 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
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
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
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
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
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
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
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
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
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
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
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
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
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
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-athena-mention.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
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
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
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
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
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
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
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
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-athena-mention.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/) 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 skips its preflight checks. 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. See [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.
To see 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. See [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 see 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. The `Reason` column is filled only for failures that Chainguard 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. See [Turn off Guarded Entrypoint](/chainguard/containers/custom-assembly/guarded-entrypoint/#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, see the [lists of 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-athena-mention.chainguard.app/chainguard/guardener/github/configuration.md
Last Modified: October 8, 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. |
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-athena-mention.chainguard.app/chainguard/agent-skills/public-registry.md
Last Modified: September 28, 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, 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.364** 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.
## 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
```
## 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////