# Using GitOps to Manage Custom Assembly Resources

URL: https://edu.chainguard.dev/chainguard/chainguard-images/features/ca-docs/custom-assembly-gitops.md
Last Modified: January 29, 2026
Tags: Chainguard Containers, Procedural, Custom Assembly

How to use GitOps to manage Custom Assembly resources.

Chainguard&rsquo;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&rsquo;s custom-assembly-as-code demo repository.
NOTE: chainctl is an API client that handles common tasks like authentication and applying configuration files. You can manage Custom Assembly interactively using chainctl. Running chainctl non-interactively is a common pattern for implementing GitOps workflows.
Prerequisites Before getting started, you should have:
A Chainguard organization with access to Custom Assembly Permission to manage Custom Assembly configuration for your organization/repository A CI/CD platform in place In this guide, we use GitHub Actions as an example. A Git repository to host your apko configuration files A configured assumable identity for your CI workload If you have not yet set up CI identities, see Chainguard&rsquo;s tutorials for creating and assuming identities. The full IDs for your catalog_syncer and apko_builder identities. Also note that Chainguard&rsquo;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&rsquo;s recommended for workflows that need GitHub API access alongside Chainguard operations.
Understanding apko overlay files Custom Assembly uses apko overlay YAML files to customize images. You can use them to define changes such as additional packages to install, environment variables, and annotations.
This example overlay file shows the configuration options available for customizing Chainguard images:
contents: packages: - curl - jq environment: APP_ENV: production LOG_LEVEL: info annotations: org.opencontainers.image.title: &#34;Python App with Tools&#34; org.opencontainers.image.description: &#34;Custom Python image with curl and jq&#34; accounts: run-as: &#34;appuser&#34; users: - username: &#34;appuser&#34; uid: 65532 gid: 65532 homedir: &#34;/home/appuser&#34; groups: - groupname: &#34;appgroup&#34; gid: 65532 members: - &#34;appuser&#34; certificates: additional: - name: &#34;certificate name&#34; content: | -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- Repository structure 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.
Step 1: Create an assumable identity First, create an identity that your CI/CD platform can assume. The process varies by platform, but here&rsquo;s a GitHub Actions example:
chainctl iam identities create github-actions-identity \ --description=&#34;GitHub Actions identity for Custom Assembly&#34; \ --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.
Step 2: Grant permissions 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 &#39;.items[] | select(.name==&#34;github-actions-identity&#34;) | .id&#39;) # Grant image build permissions chainctl iam role-bindings create \ --identity=$IDENTITY_ID \ --role=custom-role \ --group=your-group-id Step 3: Note Your identity ID You&rsquo;ll need your identity ID for your CI/CD workflow configuration. Save it for use in the next section:
chainctl iam identities list -o table Trigger builds via chainctl in CI/CD workflows Regardless 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:
Reads your apko overlay configuration from the YAML file Applies it to build a custom image Pushes the result to your Chainguard registry Skips the interactive confirmation via the --yes flag, making it suitable for automated workflows GitHub Actions example This section provides a complete example for automating Custom Assembly builds with GitHub Actions.
Create .github/workflows/build-custom-images.yaml in your repository. This example is based on Chainguard&rsquo;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: - &#39;ca-images-iac/custom-jre.yaml&#39; workflow_dispatch: env: CUSTOM_IMAGE: &#34;cgr.dev/your-org/your-image&#34; # Top-level permissions set to principle of least privilege. Job-level permissions grant only what&#39;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: &#34;your-org-id/your-identity-id&#34; - name: &#39;Auth to Registry&#39; run: | chainctl auth configure-docker chainctl auth status # Verify existing image signature before rebuilding. Find these identity IDs in your organization&#39;s &#34;Assumed Identities&#34; settings. - name: Verify signature &amp;&amp; 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 &#34;Assumed Identities&#34; CATALOG_SYNCER=&#34;your-org-id/catalog-syncer-id&#34; APKO_BUILDER=&#34;your-org-id/apko-builder-id&#34; cosign verify \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp=&#34;https://issuer.enforce.dev/(${CATALOG_SYNCER}|${APKO_BUILDER})&#34; \ $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 &#34;Created time: $(crane config $CUSTOM_IMAGE:latest | jq -r .created)&#34; crane manifest $CUSTOM_IMAGE:latest | \ jq -r &#39;.manifests[] | \ select (.platform.architecture==&#34;amd64&#34;) | \ .digest&#39; | \ xargs -I {} cosign verify-attestation --type=spdx \ --certificate-oidc-issuer=https://issuer.enforce.dev \ --certificate-identity-regexp=&#34;https://issuer.enforce.dev/(${CATALOG_SYNCER}|${APKO_BUILDER})&#34; \ $CUSTOM_IMAGE@{} 2&gt; /dev/null | \ jq -r .payload | base64 -d | jq &#39;.predicate&#39; | \ jq &#39;.packages[] | select(.externalRefs[]?.referenceCategory == &#34;PACKAGE_MANAGER&#34;) | \ .externalRefs[] | select(.referenceCategory == &#34;PACKAGE_MANAGER&#34;) | .referenceLocator&#39; # 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 --yes Testing your workflow Before deploying your CI/CD workflow to production, test it thoroughly to ensure builds complete successfully and authentication works correctly. Start by triggering a manual build and reviewing the logs for each step. Verify that images are built with the expected packages and configurations, and confirm that signatures and attestations are properly generated. Testing in a non-production environment or with a dedicated test repository helps catch configuration issues early without impacting your production image builds.
Testing the GitHub Action example Before using the GitHub action in this guide, make sure to update the placeholders:
your-org/your-repo: Your GitHub repository (e.g., acme/infrastructure) your-org-id/your-identity-id: Your full Chainguard identity ID CUSTOM_IMAGE: &quot;cgr.dev/your-org/your-image&quot;: Your image registry path CATALOG_SYNCER=&quot;your-org-id/catalog-syncer-id&quot;: Your catalog syncer identity APKO_BUILDER=&quot;your-org-id/apko-builder-id&quot;: Your APKO builder identity --parent your-parent-group --repo your-repo: Your Chainguard group and repo names ca-images-iac/custom-jre.yaml: Your repo&rsquo;s directory that holds the apko overlay files, and the overlay file name To test your GitHub Action:
In GitHub, go to Actions tab &gt; Select workflow &gt; Run workflow. View the detailed logs for each step. Confirm that the images appear in your Chainguard registry. Additional resources Custom Assembly Overview apko Overview Assumable Identity Documentation Demo Repository: custom-assembly-as-code Chainguard Support 
