Using chainctl to Manage Custom Assembly Resources
How to use chainctl to manage Custom Assembly resources.
Chainguard’s Custom Assembly is a tool that allows customers to create customized container images with extra packages and annotations added. This enables customers to reduce their risk exposure by creating container images that are tailored to their internal organization and application requirements while still having few-to-zero CVEs. It can be managed in the Chainguard Console, with chainctl, with the API, or via CI/CD.
This guide shows how to use Chainguard Custom Assembly as code via CI/CD, storing your configuration in Git and using automation to apply changes and trigger builds. The examples in this guide focus on GitHub Actions, as seen in Chainguard’s custom-assembly-as-code demo repository.
NOTE:
chainctlis an API client that handles common tasks like authentication and applying configuration files. You can manage Custom Assembly interactively usingchainctl. Runningchainctlnon-interactively is a common pattern for implementing GitOps workflows.
Before getting started, you should have:
Also note that Chainguard’s demo workflow uses octo-sts, a tool that generates short-lived GitHub tokens instead of using long-lived Personal Access Tokens (PATs). While octo-sts is optional for Custom Assembly builds, it’s recommended for workflows that need GitHub API access alongside Chainguard operations.
Custom Assembly uses apko overlay YAML files to customize images. You can use them to define changes such as additional packages to install, environment variables, and annotations.
This example overlay file shows the configuration options available for customizing Chainguard images:
contents:
packages:
- curl
- jq
environment:
APP_ENV: production
LOG_LEVEL: info
annotations:
org.opencontainers.image.title: "Python App with Tools"
org.opencontainers.image.description: "Custom Python image with curl and jq"
accounts:
run-as: "appuser"
users:
- username: "appuser"
uid: 65532
gid: 65532
homedir: "/home/appuser"
groups:
- groupname: "appgroup"
gid: 65532
members:
- "appuser"
certificates:
additional:
- name: "certificate name"
content: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----We recommend organizing your configuration YAML files in a dedicated directory. For example:
your-repo/
├── .github/
│ └── workflows/
│ └── build-custom-images.yaml
├── ca-images-iac/
│ ├── python-app.yaml
│ ├── nginx-custom.yaml
│ └── node-api.yaml
└── README.mdIn this example, the ca-images-iac/ directory contains the apko overlay files, while the workflow file defines how and when builds are triggered.
First, create an identity that your CI/CD platform can assume. The process varies by platform, but here’s a GitHub Actions example:
chainctl iam identities create github-actions-identity \
--description="GitHub Actions identity for Custom Assembly" \
--claim=repository=your-org/your-repo \
--claim=event_name=pushThis creates an identity that GitHub Actions workflows in your-org/your-repo can assume when triggered by push events.
The identity needs permission to build Custom Assembly images. You can create a least-privilege custom role that contains the repo.update and repo.create permissions, then grant the necessary permission using chainctl:
# Get your identity ID
IDENTITY_ID=$(chainctl iam identities list -o json | jq -r '.items[] | select(.name=="github-actions-identity") | .id')
# Grant image build permissions
chainctl iam role-bindings create \
--identity=$IDENTITY_ID \
--role=custom-role \
--group=your-group-idYou’ll need your identity ID for your CI/CD workflow configuration. Save it for use in the next section:
chainctl iam identities list -o tableRegardless of which CI/CD platform you use, Custom Assembly builds are triggered with the same chainctl command:
chainctl images repos build apply --file ca-images-iac/custom-jre.yaml \
--parent your-parent-group \
--repo your-repo \
--yesThis command follows the example repo structure that appears earlier on this page, where ca-images-iac is the directory that contains the apko overlay files.
This command:
--yes flag, making it suitable for automated workflowsThis section provides a complete example for automating Custom Assembly builds with GitHub Actions.
Create .github/workflows/build-custom-images.yaml in your repository. This example is based on Chainguard’s custom-assembly-as-code demo:
# Trigger builds automatically when the specified file changes. Only runs on pushes to the main branch. Use a wildcard to trigger on any file in a specified directory.
name: build
on:
push:
branches: [main]
paths:
- 'ca-images-iac/custom-jre.yaml'
workflow_dispatch:
env:
CUSTOM_IMAGE: "cgr.dev/your-org/your-image"
# Top-level permissions set to principle of least privilege. Job-level permissions grant only what's needed.
permissions: {}
jobs:
build-custom-image-as-code:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
id-token: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@20cf305ff2072d973412fa9b1e3a4f227bda3c76 # v2.14.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: main
- name: Setup Go environment
uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
with:
cache: false
# Use octo-sts for GitHub authentication (no PAT needed)
- uses: octo-sts/action@6177b4481c00308b3839969c3eca88c96a91775f # v1.0.0
id: octo-sts
with:
scope: your-org/your-repo
identity: build
- name: Install Crane
run: go install github.com/google/go-containerregistry/cmd/crane@latest
- name: Install Cosign
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0
# Authenticate to Chainguard using assumable identity
- uses: chainguard-dev/setup-chainctl@8d93dcbef466d3cf3533f67084f52eb74ef9d262 # v0.2.4
with:
identity: "your-org-id/your-identity-id"
- name: 'Auth to Registry'
run: |
chainctl auth configure-docker
chainctl auth status
# Verify existing image signature before rebuilding. Find these identity IDs in your organization's "Assumed Identities" settings.
- name: Verify signature && pull existing image
id: cosign-verify
continue-on-error: false
run: |
# Images are signed by either CATALOG_SYNCER or APKO_BUILDER identity in your org.
# Find these values in your organization settings under "Assumed Identities"
CATALOG_SYNCER="your-org-id/catalog-syncer-id"
APKO_BUILDER="your-org-id/apko-builder-id"
cosign verify \
--certificate-oidc-issuer=https://issuer.enforce.dev \
--certificate-identity-regexp="https://issuer.enforce.dev/(${CATALOG_SYNCER}|${APKO_BUILDER})" \
$CUSTOM_IMAGE:latest | jq
# Extract and display packages from the SBOM attestation.
- name: Print created time and list packages
id: crane-config
continue-on-error: false
run: |
echo "Created time: $(crane config $CUSTOM_IMAGE:latest | jq -r .created)"
crane manifest $CUSTOM_IMAGE:latest | \
jq -r '.manifests[] | \
select (.platform.architecture=="amd64") | \
.digest' | \
xargs -I {} cosign verify-attestation --type=spdx \
--certificate-oidc-issuer=https://issuer.enforce.dev \
--certificate-identity-regexp="https://issuer.enforce.dev/(${CATALOG_SYNCER}|${APKO_BUILDER})" \
$CUSTOM_IMAGE@{} 2> /dev/null | \
jq -r .payload | base64 -d | jq '.predicate' | \
jq '.packages[] | select(.externalRefs[]?.referenceCategory == "PACKAGE_MANAGER") | \
.externalRefs[] | select(.referenceCategory == "PACKAGE_MANAGER") | .referenceLocator'
# Apply the apko configuration file to trigger the build. The --yes flag skips the confirmation prompt.
- name: Trigger custom build
id: start-custom-build
continue-on-error: false
run: |
chainctl image repo build apply -f ca-images-iac/custom-jre.yaml \
--parent your-parent-group --repo your-repo --yesBefore deploying your CI/CD workflow to production, test it thoroughly to ensure builds complete successfully and authentication works correctly. Start by triggering a manual build and reviewing the logs for each step. Verify that images are built with the expected packages and configurations, and confirm that signatures and attestations are properly generated. Testing in a non-production environment or with a dedicated test repository helps catch configuration issues early without impacting your production image builds.
Before using the GitHub action in this guide, make sure to update the placeholders:
your-org/your-repo: Your GitHub repository (e.g., acme/infrastructure)your-org-id/your-identity-id: Your full Chainguard identity IDCUSTOM_IMAGE: "cgr.dev/your-org/your-image": Your image registry pathCATALOG_SYNCER="your-org-id/catalog-syncer-id": Your catalog syncer identityAPKO_BUILDER="your-org-id/apko-builder-id": Your APKO builder identity--parent your-parent-group --repo your-repo: Your Chainguard group and repo namesca-images-iac/custom-jre.yaml: Your repo’s directory that holds the apko overlay files, and the overlay file nameTo test your GitHub Action:
Last updated: 2025-07-15 11:07