Generate CI pipelines
ContainerHive generates CI pipeline configurations from your project definition, so you don't have to maintain pipeline YAML by hand.
Generating CI pipelines
Use template ci to generate a pipeline for your CI provider:
ch template ci --provider gitlab --output .gitlab-ci.yml
ch template ci --provider github --output .github/workflows/build.yml
Supported providers
| Provider | Flag value | Output |
|---|---|---|
| GitLab CI | gitlab |
GitLab CI pipeline YAML |
| GitHub Actions | github |
GitHub Actions workflow YAML |
How it works
- ContainerHive discovers your project and resolves image dependencies.
- Images are grouped into stages based on their dependency depth — images at the same depth can build in parallel.
- For each image, the pipeline includes jobs for building (per-platform), creating multi-arch manifests, and running tests.
- If an image has a
latest_aliasconfigured, the highest semantic version tag is automatically retagged as the specified alias (e.g.,latest,stable). - The generated pipeline references the ContainerHive container image to run all commands.
- Before builds, Dockerfiles are linted with hadolint (enabled by default). Lint failures gate the build stage.
- After successful builds, an HTML and JSON report is generated and published to GitHub Pages or GitLab Pages.
Options
| Flag | Description |
|---|---|
--provider |
CI provider (gitlab or github) — required |
--output |
Output file path (default: stdout) |
--template-dir |
Custom template directory to override built-in templates |
--artifacts |
Upload/download build artifacts between jobs |
--version |
ContainerHive CLI version to use (default: current version) |
--image-name |
Container image name for the CLI (default: containerhive/containerhive) |
Example with custom image
ch template ci \
--provider github \
--output .github/workflows/build.yml \
--image-name ghcr.io/containerhive/containerhive \
--version v1.0.0 \
--artifacts
Custom templates
For use cases beyond CI pipelines, the template custom command renders any Go template with full access to the project
context.
The --output value is itself rendered as a Go template with the same context as the template file, so the
destination can be derived from the project:
When the output path references .Image, the template is rendered once per image and one file is written per
image — .Image is bound to the current image in both the output path and the template content. Without
.Image, a single render writes a single file (and .Image is not available in the content). Missing parent
directories in the output path are created automatically.
Template context
Custom templates receive a CIContext with the following fields:
| Field | Type | Description |
|---|---|---|
Images |
list | All images (see below) |
Platforms |
list | All unique platforms across all images |
Stages |
list | Ordered build stages (build-<image>, test-<image>, manifest-<image>) |
Config |
object | Registry and cache configuration (see below) |
Artifacts |
bool | Whether build artifacts are passed between jobs (set by template ci --artifacts) |
TemplateOptions |
map | Merged template options (defaults + hive.yml), same values the option(key) function returns |
HasTagRanges |
bool | Whether any image defines tag_ranges |
TagRangeUnits |
int | Total version-cache tag units across all images (0 unless HasTagRanges is true) |
Command |
string | The ch template ci command line — only set by template ci, empty in custom templates |
Version |
string | CH CLI version used in CI templates — only set by template ci |
ImageName |
string | Container image name for the CH CLI — only set by template ci |
ProjectPath |
string | --project value when it is not . — only set by template ci |
Each image in Images provides:
| Field | Type | Description |
|---|---|---|
Name |
string | Image name |
Tags |
list | All tags including variant-suffixed tags (base + variants) |
Aliases |
map | Alias name to the exact tag it points at: semantic version series aliases (1.2.3 → 1.2, 1) and latest_alias, including variant-suffixed ones. Same resolution as the registry retagging step |
Dependencies |
list | Names of images this image depends on |
Depth |
int | Dependency depth (0 = no dependencies) |
Platforms |
list | Target platforms |
ReadmePath |
string | Path to the image's README.md, empty if it has none. Load the content with read_file |
ShardUnits |
int | Number of shard units (base + variant tags) — the useful max |
BuildShards |
int | Effective parallel for build jobs (min(ci_build_shards, ShardUnits)) |
TestShards |
int | Effective parallel for test jobs (min(ci_test_shards, ShardUnits)) |
Config exposes the project registry and cache configuration:
| Field | Type | Description |
|---|---|---|
Config.Registry.Address |
string | Container registry address |
Config.Registry.DockerMediaTypes |
bool | Force Docker-scheme media types (null = auto-detect) |
Config.RegistryHost() |
string | Registry hostname without path (e.g. docker.io/timoreymann → docker.io) |
Config.Cache.Type |
string | Cache backend: s3 or registry |
Config.Cache.Endpoint |
string | S3 endpoint URL |
Config.Cache.Bucket |
string | S3 bucket name |
Config.Cache.Region |
string | S3 region |
Config.Cache.AccessKeyId |
string | S3 access key ID |
Config.Cache.SecretAccessKey |
string | S3 secret access key — available, but avoid rendering it into committed files |
Config.Cache.UsePathStyle |
bool | Use path-style S3 URLs |
Config.Cache.Ref |
string | Registry cache ref (e.g. registry:5000/cache) |
Config.Cache.Insecure |
bool | Allow insecure registry connections |
Note
Config.Registry and Config.Cache are pointers and render as <no value> when unset — guard with
{{ with .Config.Cache }}...{{ end }}. Registry.DockerMediaTypes is a tri-state: unset means auto-detect.
The context also provides helper methods, callable like functions:
| Method | Description |
|---|---|
ChCmd() |
The ch command prefix, including -p <project> when a project path is set |
Dist() |
The dist directory path, prefixed with the project path when set |
VersionCacheDir() |
The tag_ranges version cache path, prefixed with the project path when set |
Template functions
Templates have access to Sprig functions plus:
| Function | Description |
|---|---|
resolve_base(name, tag) |
Produces an internal image reference (__hive__/name:tag) for inter-image dependencies |
option(key) |
Returns the value of a template option from hive.yml, or empty string if not set |
read_file(path) |
Returns the content of a file, or an empty string for an empty path — e.g. {{ .Image.ReadmePath \| read_file }}. Fails if the file does not exist |
Example custom template
{{- range .Images }}
Image: {{ .Name }}
Tags: {{ range .Tags }}{{ . }}, {{ end }}
Platforms: {{ range .Platforms }}{{ . }}, {{ end }}
Dependencies: {{ range .Dependencies }}{{ . }}, {{ end }}
{{ end }}
Overriding built-in CI templates
You can override the built-in CI templates by providing a custom template directory:
The directory should contain .gotpl files following the same structure as the built-in templates. Files in the custom
directory take precedence over the built-in ones.
Built-in template structure
templates/<provider>/
├── pipeline.yml.gotpl (or workflow.yml.gotpl for GitHub)
├── build-job.yml.gotpl
├── lint-job.yml.gotpl
├── manifest-job.yml.gotpl
└── test-job.yml.gotpl
The entrypoint template includes the job templates as partials using {{ template "build-job.yml.gotpl" . }}.
The generate command
Before building or generating CI configs, run generate to discover the project and render all configurations to
dist/:
This processes hive.yml and all image.yml definitions, renders Dockerfiles and test templates, and prepares the
dist/ directory for subsequent build, test, and sbom commands.
Report generation
By default, the generated CI pipeline includes a report job that runs after all build jobs complete. This job
generates:
dist/report.html— Interactive HTML report with image details, tags, platforms, and SBOM datadist/report.json— Machine-readable JSON report
GitHub Pages
The HTML report is automatically deployed to GitHub Pages. Your repository must have Pages enabled in settings with the source set to "GitHub Actions".
GitLab Pages
The report is uploaded as a GitLab Pages artifact and exposed as "ContainerHive Report" in the merge request widget. The JSON report is also available as an artifact.
Lint job
By default, the generated CI pipeline includes a lint job that runs after the generate stage and before any build
stages. This job lints all plain Dockerfiles in the project using hadolint
via ch lint.
GitLab Code Quality report
For GitLab CI, the lint job produces a Code Quality report
artifact that GitLab renders inline on merge requests. The report uses the codeclimate output format:
GitHub Actions annotations
For GitHub Actions, use the github-actions output format to emit annotations inline on pull request diffs:
Lint failures still cause the job to fail and gate subsequent build jobs. Without the --format flag, findings only
appear in the workflow log — using --format github-actions makes them visible on the changed lines in the PR.
Disabling linting
To disable the lint job entirely, set ci_lint to false in your hive.yml:
Reducing SBOM size
syft attaches CPEs to every component by default, which is a meaningful contributor to SBOM size on images with
many packages. Set ci_sbom_generate_cpes to false to pass --no-cpe to ch sbom in the generated GitLab CI
pipeline, trading CPE-based vulnerability matching for a smaller artifact — useful if you're running into GitLab's
SBOM artifact size limits:
Cancelling outdated pipelines
By default, when a new pipeline is triggered on the same branch, in-progress pipelines from earlier commits are
automatically cancelled to save CI resources. This behaviour is controlled by the ci_cancel_outdated option.
GitHub Actions
The generated workflow uses a concurrency group scoped to the workflow and branch. On a push to the default branch,
a new run cancels the previous in-progress run. Pull request runs use a unique group per run, so PR behaviour is
unchanged (no cancellation, no queueing).
Requires actions: read permission (included in the generated workflow).
GitLab CI
The generated pipeline sets workflow:auto_cancel:on_new_commit: interruptible (GitLab 16.9+) and marks all jobs as
interruptible: true via the default block (GitLab 15.9+). This forces auto-cancel of superseded in-progress
pipelines on the same ref, regardless of the project-level "Auto-cancel redundant pipelines" setting.
Note: GitLab's auto-cancel mechanism applies per ref. A new push to a merge request also cancels that MR's superseded pipeline. There is no clean way to restrict cancellation to the default branch only.
To disable, set ci_cancel_outdated to false in your hive.yml:
Sharding build and test jobs
For GitLab CI, ci_build_shards and ci_test_shards set an upper bound on parallel: N for each build/test job. The
actual value is capped per image to the number of shard units (base + variant tags) that image has, so images with fewer
tags than the configured value never spawn empty jobs that no-op. See Split work across shards for the
full picture; the template option is set the same way as ci_lint above:
Registry retention
Every generated pipeline pushes per-platform intermediate images and, when using --build-id, snapshot manifests and
aliases into the registry. These accumulate over time — even for MRs that are never merged. Since ch test and
ch finalize need to pull these images, skipping pushes is not an option. Instead, configure a registry-level
retention policy to automatically clean up snapshot images after a few days.
See Clean up registry images with retention policies for per-provider setup instructions.