# Using chainctl to manage Custom Assembly resources

URL: https://chainguard-docs-preview-git-1016-404s.chainguard.app/chainguard/containers/custom-assembly/custom-assembly-chainctl.md
Last Modified: October 8, 2026
Tags: Chainguard Containers, Procedural, Custom Assembly

How to use chainctl to manage Custom Assembly resources.


Chainguard's [Custom Assembly](/chainguard/containers/custom-assembly/overview/) is a tool that allows customers to create customized containers with extra packages and annotations added. This enables customers to reduce their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs.

You can use [`chainctl`, Chainguard's command-line interface tool](/platform/chainctl/), to further customize your Custom Assembly builds and retrieve information about them. This guide provides an overview of the relevant `chainctl` commands and outlines how you can edit the configuration of Custom Assembly containers, as well as retrieve a list of a customized image's builds and its build logs.

> **Note**: This tutorial highlights using `chainctl` to interact with Custom Assembly resources. However, you can also interact with Custom Assembly using [the Chainguard Console](/chainguard/containers/custom-assembly/custom-assembly-console/), as well as [the Chainguard API](/chainguard/containers/custom-assembly/custom-assembly-api-demo/).

## Adding packages to a customized container image

`chainctl` offers two ways to change the packages in a Custom Assembly container image. The `edit` subcommand opens the build configuration in your text editor, and the `apply` subcommand takes a configuration file you've already written. Both produce the same result, and both start a build once you confirm the change.

For a shorter, task-first version of these procedures, along with how to find a package name and how to confirm the package reached the finished image, see [Adding a package to a Chainguard Container](/chainguard/containers/building-and-modifying/adding-packages/).

### Editing packages interactively

To edit one of your organization's Custom Assembly container images, you can run the `chainctl images repos build edit` command:

```shell
chainctl images repos build edit --repo $CONTAINER
```

This example includes the `--repo` argument, which points to the name of the image you want to customize. If you omit it, `chainctl` prompts you to select a container image interactively. It also prompts for the organization when you have access to more than one; pass `--parent=<organization>` to skip that prompt.

This command will open up a file with your machine's default text editor. This file will contain a structure like the following:

```yaml
contents:
  packages:
  - yarn
  - wget
```

Edit this file by adding or removing any packages you like. Then, save and close the file. Note that you can undo all the customization and return the image to its original state by removing every entry listed under `packages:`.

Before applying the change, `chainctl` will outline the changes you made and prompt you to confirm that you want to move forward with the change:

```output
/tmp/3352123767.yaml (-deletion / +addition):

 contents:
   packages:
   - yarn
-  - wget
+  - bash

Applying build config to $CONTAINER
Are you sure?
Do you want to continue? [y,N]:
```

Enter `y` to apply the changes.

Following that, you'll be able to see the updated builds in the Chainguard Console, though it may take a few minutes for these changes to populate.

### Applying packages non-interactively

To edit a customized container image without any interactivity, you can use the `apply` subcommand. This method requires you to have a YAML file listing the desired packages, like the example created with this command:

```shell
cat > build.yaml <<EOF
contents:
  packages:
    - bash
    - curl
    - mysql
EOF
```

Then include this file in the `apply` command by adding the `-f` argument:

```shell
chainctl images repos build apply -f build.yaml --repo $CONTAINER --yes
```

This command will again ask you to confirm that you want to apply the new configuration. To make this example completely declarative, this example includes `--yes` to automatically confirm the changes:

```output
Applying build config to custom-assembly
  (*v1.CustomOverlay)(Inverse(protocmp.Transform, protocmp.Message{
      "@type": s"chainguard.platform.registry.CustomOverlay",
      "contents": protocmp.Message{
          "@type": s"chainguard.platform.registry.ImageContents",
          "packages": []string{
-             "wolfi-base",
+             "bash",
-             "go",
+             "curl",
+             "mysql",
          },
      },
  }))

Are you sure?
Do you want to continue? [y,N]:
```

This approach is useful in cases where you would prefer to avoid any kind of interactivity, as in a CI/CD or other automation system.

To see what a configuration file would change without changing anything, replace `--yes` with `--dry-run`. The command prints the same diff and then exits with a non-zero status if there's anything to apply, which makes it usable as a drift check in a pipeline:

```shell
chainctl images repos build apply -f build.yaml --repo $CONTAINER --dry-run
```

The `edit` subcommand also accepts a configuration file through its own `-f` argument. Passing a file to `edit` skips the text editor but still prompts you to confirm the diff:

```shell
chainctl images repos build edit -f build.yaml --repo $CONTAINER
```

### Using the `--save-as` option

When customizing a Chainguard Container with Custom Assembly, you have the option to either customize the image itself or create a new image based on the original with your customizations applied to it.

For example, say your organization has access to Chainguard's [`node` container image](https://images.chainguard.dev/directory/image/node/versions). If you use Custom Assembly to customize the `node` image without creating a new image, then the customizations applied to it will also apply to any users in your organization that are already consuming the image. Anyone who runs `docker pull cgr.dev/example.com/node` will download the customized image instead of the original, uncustomized one.

By creating a new image with Custom Assembly, you can customize the image without impacting any of the users or workflows already consuming it. You could also create multiple customized images based on the `node` container image to support specific functions.

To use `chainctl` to create new customized container images with Custom Assembly, you must include the `--save-as` option, like this:

```shell
chainctl images repos build edit --repo $CONTAINER --save-as $NEW_NAME
```

The following example command creates a new image named `custom-node` after applying the customizations:

```shell
chainctl images repos build edit --repo node --save-as custom-node
```

Once you run this example, the new container image would be accessible from the following URL:

```url
cgr.dev/example.com/custom-node
```

The `apply` subcommand accepts `--save-as` as well, so you can create a new image without any interactivity:

```shell
chainctl images repos build apply -f build.yaml --repo node --save-as custom-node --yes
```

Note that you **must** pass the new image's name when using the `--save-as` option; `chainctl` will return an error if you don't include a new name. Additionally, `--save-as` applies to a single source repository. It isn't available when you target several repositories at once, either by passing `--repo` more than once or by using a wildcard.

## Adding custom annotations and environment variables

Custom Assembly lets you extend Chainguard Containers with your own metadata and runtime defaults by adding custom annotations and environment variables through `chainctl`.

### Custom annotations

Chainguard's container images include metadata in the form of *annotations*. These annotations provide important information about the container image's origin, contents, and characteristics.

With Custom Assembly, you can add custom annotations to your Chainguard Containers using `chainctl`. The process is the same as the one outlined previously for adding packages. First run a command like the following:

```shell
chainctl images repos build edit --repo $CONTAINER
```

In the text editor, add an `annotations` section to the bottom of the file like the following example:

```yaml
contents:
  packages:
    - jq
    - git
    - curl

annotations:
  "com.example.team": "platform-team"
  "com.example.build-timestamp": "2025-10-15T10:30:00Z"
```

After saving and confirming these changes, Custom Assembly will add two custom annotations to the container image.

You can also apply custom annotations declaratively using the `apply` subcommand, as outlined previously.

Note that Custom Assembly blocks `org.opencontainers` and `dev.chainguard` annotations from being changed.

### Custom environment variables

Chainguard's container images often come with a set of predefined environment variables. These are useful for setting certain configuration details that are available to the container at runtime.

You can follow the same procedure for adding custom annotations to add custom environment variables to your Custom Assembly container images. Start by running a `chainctl images repos build edit` command:

```shell
chainctl images repos build edit --repo $CONTAINER
```

In the text editor, add an `environment` section like the following example:

```yaml
contents:
  packages:
    - jq
    - git
    - curl

environment:
  NODE_ENV: production
  API_URL: https://api.example.com
  PORT: "3000"
  LOG_LEVEL: info
  CACHE_TTL: "300"
```

After saving and confirming these changes, Custom Assembly will add these five custom environment variables to the container image. As with packages and annotations, you can also apply custom environment variables declaratively using the `apply` subcommand, as outlined previously.

Be aware that Custom Assembly blocks any environment variable that begins with `CHAINGUARD_` from being added or changed. This is to prevent conflicts with configuration details managed by Chainguard. The `GUARDED_` prefix is reserved too.

To set a variable from a secret store when the container starts, use a secret reference such as `cg+vault://secret/data/app#password` as the value. A reference resolves only when Guarded Entrypoint is turned on for the repo. Refer to [Guarded Entrypoint for Custom Assembly](/chainguard/containers/custom-assembly/guarded-entrypoint/overview/).

## Custom runtime repositories

Custom Assembly lets you replace the default APK repository URLs written to `/etc/apk/repositories` in your assembled images. This is useful if your organization mirrors Chainguard's APK feed to an internal registry and requires containers to resolve packages from internal infrastructure at runtime.

> **Note**: This feature only affects which repository URLs are written into the image for runtime use. Build-time package resolution always uses Chainguard's `apk.cgr.dev` repositories. For more details on limitations and compatibility, refer to the [Custom runtime repositories overview](/chainguard/containers/custom-assembly/overview/#custom-runtime-repositories).

To add custom runtime repositories, use `chainctl images repos build edit` as with other customizations:

```shell
chainctl images repos build edit --repo $CONTAINER
```

In the text editor, add a `runtime_repositories` field under `contents`:

```yaml
contents:
  packages:
    - curl
    - jq
  runtime_repositories:
    - https://apk-mirror.example.com/chainguard
    - https://apk-mirror.example.com/extras
```

After saving and confirming the changes, the resulting image's `/etc/apk/repositories` will contain only your custom URLs:

```
https://apk-mirror.example.com/chainguard
https://apk-mirror.example.com/extras
```

The default `virtualapk.cgr.dev` entries aren't present in the image.

You can also apply custom runtime repositories declaratively using the `apply` subcommand:

```shell
cat > build.yaml <<EOF
contents:
  packages:
    - curl
    - jq
  runtime_repositories:
    - https://apk-mirror.example.com/chainguard
    - https://apk-mirror.example.com/extras
EOF
```

```shell
chainctl images repos build apply -f build.yaml --repo $CONTAINER --yes
```

To remove custom runtime repositories and revert to the default `virtualapk.cgr.dev` URLs, edit the configuration and remove the `runtime_repositories` field entirely.

### Validation rules

Custom runtime repository URLs are validated when the configuration is applied. URLs that do not pass validation will be rejected. The following rules apply:

* Each URL must use HTTPS (`https://`). HTTP, `file://`, and other schemes aren't allowed.
* At least one URL must be provided if the `runtime_repositories` field is set.
* Duplicate URLs aren't allowed.
* Loopback addresses (`127.0.0.0/8`, `::1`) aren't allowed.
* Private network addresses (RFC 1918: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) aren't allowed.
* Link-local addresses (`169.254.0.0/16`, `fe80::/10`) aren't allowed.
* Hostnames ending in `.internal`, `.local`, or `.svc.cluster.local` aren't allowed.
* `localhost` isn't allowed.
* Hostnames that resolve to any of the blocked IP ranges listed earlier aren't allowed either.

### Custom runtime keys

Custom Assembly images trust only Chainguard's APK signing key by default. If your mirror re-signs packages with its own key — as Artifactory virtual repositories and Sonatype Nexus Alpine repositories do — runtime `apk add` commands reject packages from that mirror. Custom runtime keys let you embed your mirror's public signing key in the image's `/etc/apk/keys` directory so that `apk` can verify re-signed packages.

To add a runtime key, pass the public key file to the `--with-runtime-keys` option:

```shell
chainctl images repos build edit --repo $CONTAINER --with-runtime-keys=key-ee8fa0a3.rsa.pub
```

Each file becomes a key in `/etc/apk/keys` named after the file's basename. The name must match the filename referenced by your repository's APKINDEX signature (`.SIGN.RSA256.<name>`), because `apk` looks up the key by that name during verification. `chainctl` uses filenames verbatim and returns an error if two files share the same basename.

You can also define runtime keys in the editor or in a YAML manifest by adding a `runtime_keyring` field under `contents`. Each entry has a `name` and the key's PEM `content`:

```yaml
contents:
  packages:
    - curl
    - jq
  runtime_repositories:
    - https://apk-mirror.example.com/chainguard
  runtime_keyring:
    - name: key-ee8fa0a3.rsa.pub
      content: |
        -----BEGIN PUBLIC KEY-----
        ...
        -----END PUBLIC KEY-----
```

Apply the manifest with the `apply` subcommand as with other customizations. In the confirmation diff, each key appears as the SHA-256 digest of its content rather than the raw PEM text. To confirm that a digest matches a local key file, run `sha256sum <keyfile>`.

When you run the `edit` subcommand, the editor buffer includes any existing runtime keys. Keep the `runtime_keyring` section to preserve the keys, edit an entry to replace a key, or delete the field to remove all custom keys from the image.

#### Key validation rules

Runtime keys are validated when the configuration is applied. The following rules apply:

* Each entry must contain exactly one PEM-encoded RSA public key: a single `PUBLIC KEY` block with no other content before or after it. Certificates, OpenSSH keys, and EC keys aren't accepted.
* Key names must be unique.
* Key names can't be empty, begin with a dot, or contain path separators.
* Key names can't begin with `chainguard` or `wolfi`; these prefixes are reserved.
* The combined size of all keys can't exceed 512 KB.

## Retrieving information about Custom Assembly containers

You can also use the `list` subcommand to retrieve every one of a customized image's builds from the past 24 hours:

```shell
chainctl images repos build list --repo $REPO
```

This command is useful for quickly determining which builds were successful or failed:

```
           START TIME           |        COMPLETION TIME        | RESULT  |                    TAGS
--------------------------------+-------------------------------+---------+---------------------------------------------
  Thu, 01 May 2025 10:10:40 PDT | Thu, 01 May 2025 10:10:45 PDT | Success | 20, 20.19, 20.19.1
  Thu, 01 May 2025 10:10:34 PDT | Thu, 01 May 2025 10:10:46 PDT | Success | 22-slim, 22.15-slim, 22.15.0-slim
  Thu, 01 May 2025 10:10:33 PDT | Thu, 01 May 2025 10:10:41 PDT | Success | 23, 23.11, 23.11.0, latest

. . .
```

Lastly, you can also retrieve the logs for a given build with the `logs` subcommand:

```shell
chainctl images repos build logs --repo $REPO
```

This command will prompt you to select the build report you want to view. These are organized in reverse chronological order by the time of each build:

```output
    Select a build report to view logs:

    Wed, 16 Apr 2025 16:36:52 PDT - Wed, 16 Apr 2025 16:37:08 PDT Success (18-dev, 18.20-dev, 18.20.8-dev)
  > Wed, 16 Apr 2025 16:36:45 PDT - Wed, 16 Apr 2025 16:37:00 PDT Success (20-dev, 20.19-dev, 20.19.0-dev)
    Wed, 16 Apr 2025 16:36:42 PDT - Wed, 16 Apr 2025 16:36:52 PDT Success (18, 18.20, 18.20.8)
    Wed, 16 Apr 2025 16:36:41 PDT - Wed, 16 Apr 2025 16:36:51 PDT Success (22-slim, 22.14-slim, 22.14.0-slim)
    Wed, 16 Apr 2025 16:36:32 PDT - Wed, 16 Apr 2025 16:36:57 PDT Success (22-dev, 22.14-dev, 22.14.0-dev)
    Wed, 16 Apr 2025 16:36:32 PDT - Wed, 16 Apr 2025 16:36:44 PDT Success (20-slim, 20.19-slim, 20.19.0-slim)
    Wed, 16 Apr 2025 16:36:29 PDT - Wed, 16 Apr 2025 16:36:41 PDT Success (23-slim, 23.11-slim, 23.11.0-slim)
    Wed, 16 Apr 2025 16:36:19 PDT - Wed, 16 Apr 2025 16:36:29 PDT Success (23, 23.11, 23.11.0, latest)
    Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:42 PDT Success (23-dev, 23.11-dev, 23.11.0-dev, latest-dev)
    Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:31 PDT Success (20, 20.19, 20.19.0)
    Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:31 PDT Success (22, 22.14, 22.14.0)
    Wed, 16 Apr 2025 16:36:09 PDT - Wed, 16 Apr 2025 16:36:18 PDT Success (18-slim, 18.20-slim, 18.20.8-slim)
    Wed, 16 Apr 2025 16:35:35 PDT - Wed, 16 Apr 2025 16:35:47 PDT Success

    ••••••••••••••

    ↑/k up • ↓/j down • / filter • q quit • ? more

```

Highlight your chosen build report and select it by pressing `ENTER`. This will open up the build's logs:

```output
2025-04-17T16:00:08-07:00[INFO]Building image with locked configuration: {Contents:{BuildRepositories:[] RuntimeRepositories:[https://apk.cgr.dev/45a0c61eEXAMPLEf050c5fb9ac06a69eed764595]
```

## Making bulk changes across repos

You are not required to make changes to custom assembly repos one at a time. Instead, you can make bulk changes to customize across multiple repos at the same time.

For example, if you wanted to apply custom certs at scale across every repo you own with "kubernetes-*" you could use this:

```shell
chainctl images repos build apply \
  --repo="kubernetes-*" \
  --with-certificates=ca.pem
```

You can use `chainctl images repos build apply` to do things like:

* Pass `--repo` multiple times: `--repo=python --repo=nginx --repo=node`
* Use wildcards: `--repo="python-*"` or `--repo="*"` (all repos under the parent)
* Combine with `--dry-run` to see which repos would change (drift detection) without actually updating anything.

For more information, refer to [chainctl images repos build apply](/platform/chainctl/chainctl-docs/chainctl_images_repos_build_apply/).

## Customizing specific tags

The `chainctl images repos build` commands apply a customization to every tag in a repository. To apply a customization to some tags only, use the `chainctl images overlays` commands. For details, refer to [Manage Custom Assembly Overlays with chainctl](/chainguard/containers/custom-assembly/overlays/chainctl/).

## Learn more

You can also use `chainctl` to add custom certificates to your Custom Assembly images. Refer to our guide on [Adding custom certificates with Custom Assembly](/chainguard/containers/custom-assembly/custom-assembly-certs/#using-chainctl-to-add-custom-certificates-using-custom-assembly) for more information.

Additionally, you can interact with Custom Assembly with the [Chainguard API](/platform/api/spec/). Our tutorial on [Using the Chainguard API to manage Custom Assembly resources](/chainguard/containers/custom-assembly/custom-assembly-api-demo/) outlines how to run a demo application that updates the configuration of a Custom Assembly container through the Chainguard API.

