For the complete documentation index, see llms.txt.

Chainguard Checks examples

Complete Chainguard Checks configurations for Java with Maven, Rust, container image builds with Docker, a monorepo, and checks that use Postgres and Redis.
  6 min read

This page gives complete Chainguard Checks configurations for common kinds of projects. Copy the one closest to your project, then change its packages, commands, and network access to match. Each example passes chainctl checks validate. For what each key does, refer to the Chainguard Checks configuration reference, and for services and caches, refer to Variations, inputs, services, and caches.

Keep the following in mind when you adapt an example:

  • A check reaches only the hosts in its network.egress list. If your build downloads from somewhere else, such as a private package registry, add that host. Refer to Find the hosts a check needs.
  • A cache does nothing until your default branch’s .chainguard/ci/identities.yaml file defines cache pools, as described in Set up cache pools. The examples that use caches also run after merging, with trigger: [presubmit, postsubmit], so that runs on your default branch fill the cache that pull requests start from.
  • Chainguard Workspaces sessions can use these files for their packages, but they don’t start services or mount caches other than gobuild. Refer to Configure a Chainguard Workspaces session’s environment.

Java with Maven

This file installs a JDK and Maven, and runs the build’s tests with mvn verify:

.chainguard/ci.yaml
version: 1
environment:
  packages: [openjdk-21-default-jdk, maven-3.9]
  caches:
    java: {}
checks:
  test:
    cmd: mvn --batch-mode verify
    trigger: [presubmit, postsubmit]
    network:
      egress: [repo.maven.apache.org]

The example works as follows:

  • openjdk-21-default-jdk installs OpenJDK 21 and sets JAVA_HOME, which Maven needs. For another Java version, use its package, such as openjdk-25-default-jdk.
  • --batch-mode keeps Maven’s download progress out of the check’s log.
  • Maven downloads plugins and dependencies from Maven Central, repo.maven.apache.org. Add any other repositories that your pom.xml or settings.xml names.
  • The java cache keeps the files that Maven downloads, so later runs download only new ones. For Gradle builds, which still ask each repository for checksums, refer to Cache types.

Rust

This file installs a Rust toolchain and runs the project’s tests:

.chainguard/ci.yaml
version: 1
environment:
  packages: [rust-1.99, build-base]
  caches:
    cargo: {}
checks:
  test:
    cmd: cargo test --locked
    trigger: [presubmit, postsubmit]
    network:
      egress: [index.crates.io, static.crates.io]

The example works as follows:

  • rust-1.99 installs rustc and cargo. Choose the package for the Rust version your project uses. Chainguard Checks installs the toolchain from the package, not with rustup.
  • build-base installs a C compiler and linker, which rustc uses to link binaries, and which crates with C code need.
  • --locked makes the check fail if Cargo.lock doesn’t match Cargo.toml, so commit Cargo.lock.
  • Cargo reads the crates.io index from index.crates.io and downloads crates from static.crates.io.
  • The cargo cache keeps downloaded crates and the build output in target/, so later runs rebuild only what changed. Refer to Cache types.

Build a container image

To build an image from your repository’s Dockerfile, run the Docker daemon as a service. This example uses the directory layout, with the docker service from Run Docker in .chainguard/ci/services/docker/. Copy both of that section’s files, docker.yaml and docker.sh. Then add a check file that uses the service:

.chainguard/ci/image.yaml
version: 1
environment:
  services: [docker]
checks:
  build:
    script: |
      docker build --tag example:ci .
      docker run --rm example:ci
    network:
      egress:
        - cgr.dev
        - 9236a389bd48b984df91adc1bc924620.r2.cloudflarestorage.com

The check builds the image and then runs it once, so a container that can’t start fails the check. The check appears on pull requests as presubmit / image / build.

The two hosts in egress serve images from cgr.dev, such as a FROM cgr.dev/chainguard/wolfi-base line in your Dockerfile. If your Dockerfile pulls from another registry or downloads files while it builds, add those hosts too. The Docker daemon needs root, so a check that uses the service must run as root, the default.

A monorepo

In a repository that holds several projects, give each project its own check file, and run each check only when its project changes. This example has a Go service in services/api/ and a Node.js application in web/, and uses the directory layout:

.chainguard/ci/
├── api.yaml
├── web.yaml
└── env/
    ├── go.yaml
    └── node.yaml

Each project’s environment goes in its own file in env/:

.chainguard/ci/env/go.yaml
version: 1
environment:
  packages: [go]
  caches:
    gobuild: {}
    gomod: {}
.chainguard/ci/env/node.yaml
version: 1
environment:
  packages: [nodejs-22, npm]

Each check file names its environment, the project’s directory, and the files that should run it:

.chainguard/ci/api.yaml
version: 1
checks:
  test:
    environment: go
    cmd: go test ./...
    workdir: services/api
    trigger: [presubmit, postsubmit]
    when:
      changed: ["services/api/**", ".chainguard/ci/api.yaml", ".chainguard/ci/env/go.yaml"]
    network:
      egress: [proxy.golang.org, sum.golang.org]
.chainguard/ci/web.yaml
version: 1
checks:
  test:
    environment: node
    workdir: web
    steps:
      - name: install
        cmd: npm ci
      - name: test
        cmd: npm test
    when:
      changed: ["web/**", ".chainguard/ci/web.yaml", ".chainguard/ci/env/node.yaml"]
    network:
      egress: [registry.npmjs.org]

The example works as follows:

  • The checks appear on pull requests as presubmit / api / test and presubmit / web / test, so each project’s result is separate.
  • workdir runs each check’s commands in its project’s directory, relative to the repository’s root.
  • when.changed runs a check only when a pull request changes a file that matches one of its patterns. Each list includes the check’s own configuration files, so a change to a check runs it. Refer to Run when files change.
  • Every check names its environment, so the directory needs no env/default.yaml.
  • Only the Go check runs after merging, because only its environment has caches to fill.

Postgres and Redis

A check can start several services, such as a database and a cache, before its commands run. This example runs a Go project’s integration tests against Postgres and Redis, in the directory layout:

.chainguard/ci/
├── integration.yaml
└── services/
    ├── postgres/
    │   ├── postgres.yaml
    │   └── postgres.sh
    └── redis/
        ├── redis.yaml
        └── redis.sh

For the postgres service, copy both files from Services. Define the redis service beside it:

.chainguard/ci/services/redis/redis.yaml
version: 1
name: redis
packages: [busybox, redis-8.8, redis-cli-8.8]
cmd: [sh, ./redis.sh]
timeout: 60s
exports:
  REDIS_URL: redis://127.0.0.1:6379

The script starts Redis without saving data to disk, waits until it answers, prints ::service-ready::, and then waits on Redis:

.chainguard/ci/services/redis/redis.sh
#!/bin/sh
set -eu
redis-server --bind 127.0.0.1 --port 6379 --save "" --appendonly no &
daemon=$!
trap 'kill "$daemon" 2>/dev/null || true' EXIT
trap 'exit 1' HUP INT TERM
until redis-cli -h 127.0.0.1 -p 6379 ping >/dev/null 2>&1; do
  kill -0 "$daemon" 2>/dev/null || exit 1
  sleep 0.1
done
printf '\n::service-ready::\n'
wait "$daemon"
exit 1

The check names both services in its environment:

.chainguard/ci/integration.yaml
version: 1
environment:
  packages: [go]
  services: [postgres, redis]
checks:
  integration:
    cmd: go test ./integration/...
    network:
      egress: [proxy.golang.org, sum.golang.org]

The example works as follows:

  • The check’s commands start after both services print ::service-ready::.
  • The services’ exports give the check’s commands PGHOST, PGUSER, and REDIS_URL, so your tests can read their connection details from the environment.
  • Both services listen on 127.0.0.1, inside the check’s VM, so the check needs no egress entry to reach them. Its egress list covers only the Go module downloads.
  • The redis-8.8 and redis-cli-8.8 packages come from Chainguard’s Extra Packages repository. Keep the two packages on the same version.

Last updated: 2026-11-12 14:46