Define your images
Each image is defined by an image.yml file located at images/<image-name>/image.yml.
A JSON schema is published to SchemaNest and can be referenced directly at schema-nest.timo-reymann.de/api/schema/json-schema/containerhive-image/latest.
Example
tags:
- name: 3.13.7
versions:
python: 3.13.7
build_args:
base_tag: bookworm
versions:
uv: 0.8.22
poetry: 2.2.1
build_args:
foo: bar
secrets:
api_key:
source: env
value: API_KEY
variants:
- name: slim
tag_suffix: -slim
versions:
python: 3.13.7-slim
build_args:
variant: slim
platforms:
- linux/amd64
depends_on:
- base-image
platforms:
- linux/amd64
- linux/arm64
Fields
tags
List of tags to build for this image. Each tag can override versions and build args.
| Field | Type | Description |
|---|---|---|
name |
string | Tag name |
versions |
map | Version overrides for this tag |
build_args |
map | Build arg overrides for this tag |
labels |
map | Custom OCI labels for this tag. Overrides image-level labels. See labels |
tag_ranges
Generates tags automatically from an external version source (a JSON feed, an OCI
registry, or GitHub releases), instead of listing every tag by hand. Resolution happens
at discovery time (ch generate, ch build, ...) and requires network access unless a
warm cache is available.
tag_ranges:
- tag_name: "{{.major}}.{{.minor}}"
source:
type: json
url: "https://nodejs.org/dist/index.json"
transform: |
$[lts != false].{
"version": $substring(version, 1),
"npm": npm,
"openssl": openssl
}
select:
major: { last: 3 }
minor: { last: 3 }
patch: latest
filter:
exclude_prerelease: true
max_major_increment: 5
versions:
nodejs: "{{.full}}"
npm: "{{.extra.npm}}"
openssl: "{{.extra.openssl}}"
For the Node.js LTS feed, this generates tags like 24.11, 24.10, 24.9, 22.22, 22.21,
20.20, 20.19 — the 3 highest majors, the 3 highest minors within each, and the latest
patch of each minor — each with its own NODEJS_VERSION, NPM_VERSION and
OPENSSL_VERSION build args.
| Field | Type | Description |
|---|---|---|
tag_name |
string | Required. Go template for the generated tag name. Available data: .major, .minor, .patch, .full (normalized version), .raw (exactly as the source reported it), .prerelease, .extra.<key> (source-specific metadata). |
source |
object | External version source. Mutually exclusive with generator. See below. |
generator |
string | Named generator to use instead of an explicit source (currently only semver-matrix). Mutually exclusive with source. |
params |
map | Parameters for the named generator. |
select |
object | How many versions to keep per semantic version level. Required when source is set. See below. |
filter |
object | Filters applied to fetched versions before selection. See below. |
max_tags |
integer | Hard cap on generated tags for this range. Exceeding it is an error. Defaults to 50. |
versions |
map | Versions for generated tags. Values are Go templates rendered per resolved version (same syntax as tag_name). |
build_args |
map | Build args for generated tags, rendered the same way. |
labels |
map | Custom OCI labels for generated tags, rendered the same way. |
source
| Field | Type | Description |
|---|---|---|
type |
string | Required. json, registry, dockerhub (alias of registry), or github. |
url |
string | HTTP(S) URL returning JSON (type: json). |
transform |
string | JSONata expression mapping the document to a list of {version, ...extras} objects (type: json). ContainerHive uses the JSONata 1.5.4 subset — an expression copied from a newer JSONata Exerciser may not compile. |
headers |
map | Extra request headers for type: json. Values support $VAR / ${VAR} environment expansion, so a token never has to be inlined. |
image |
string | Repository whose tags are listed, e.g. library/node or ghcr.io/owner/image (type: registry, dockerhub). Uses the same registry credentials as ch login. |
repo |
string | GitHub repository as owner/name (type: github). |
kind |
string | What to list for type: github: releases (default, drafts excluded) or tags. |
token_env |
string | Name of the environment variable holding an auth token. Never put the token itself here. Falls back to GITHUB_TOKEN / GH_TOKEN for type: github. Anonymous GitHub API access is rate-limited to 60 requests/hour. |
ttl |
string | Cache TTL override for this source, as a Go duration (e.g. 6h). |
select
Selects how many versions survive at each semantic version level, applied hierarchically: majors first, then minors within each kept major, then patches within each kept minor. Each level accepts:
select:
major: latest # keep the single highest value (equivalent to { last: 1 })
minor: all # keep every distinct value
patch: 2 # keep the 2 highest distinct values (equivalent to { last: 2 })
# patch: { last: 2 } # same as above, mapping form
An omitted level defaults to latest, never all — a config that forgets a level
can't silently explode the tag count.
filter
Filters are applied, in this fixed order, before selection:
| Field | Type | Description |
|---|---|---|
exclude_prerelease |
boolean | Drop versions with a semantic version prerelease component. Defaults to true. |
exclude_suffixes |
list | Drop versions whose raw string contains any of these substrings — for non-semver flavours like -alpine. exclude_prerelease already covers -rc/-beta/-alpha. |
min_version / max_version |
string | Drop versions outside this inclusive range. |
max_major_increment |
integer | Sanity guard, not a window: drops a major (and everything above it) only if it's separated from the rest of the set by a gap larger than this. [18, 20, 22, 24] with 5 keeps everything (gaps of 2); adding 99 drops it (a gap of 75). 0 disables the guard. |
generator: semver-matrix
A shorthand for the common "last N majors, last M minors" shape, expanding to an
equivalent source/select/filter:
tag_ranges:
- generator: semver-matrix
params:
source: dockerhub
image: library/node
majors: "3"
minors: "3"
tag_name: "{{.major}}.{{.minor}}"
versions:
nodejs: "{{.full}}"
params: source (required — any source.type), image / repo / url /
transform (matching the chosen source type), majors / minors / patches (each
defaulting to 1). Explicit select or filter on the range override the generator's.
Collision rules
- A static tag in
tagsalways wins over a generated tag of the same name — this is the pin/escape hatch. - Two different
tag_rangesproducing the same tag name is a hard error: there's no defensible winner. - Within one range, if the
tag_nametemplate is coarser than theselectgranularity (e.g."{{.major}}.{{.minor}}"withpatch: { last: 2 }), the highest version wins the collision by design — the template is what sets the granularity. - A generated tag colliding with another tag's variant-suffixed form (
tag + variant.tag_suffix) is a hard error.
Known limitations
tag_rangesfeed intoversions, which become build args, which are part of the BuildKit cache key — so an upstream release can change what a tag builds to without a new commit. The same commit can produce different images over time.- A tag-level
build_argsentry (generated or static) can currently be silently overwritten by an image-levelbuild_argsentry of the same key — see thesecrets-and-versionsdocs for the merge precedence. This is a pre-existing issue, tracked separately, not specific totag_ranges.
versions
Default version variables available in Dockerfiles and templates.
build_args
Default build arguments passed to the Dockerfile.
secrets
Secrets made available during the build.
| Field | Type | Description |
|---|---|---|
source |
string | env (from environment variable) or plain (literal value) |
value |
string | Environment variable name or literal value |
variants
Variants allow building multiple flavors of the same image (e.g. slim, debug).
| Field | Type | Description |
|---|---|---|
name |
string | Variant name |
tag_suffix |
string | Suffix appended to tag names |
versions |
map | Version overrides |
build_args |
map | Build arg overrides |
platforms |
list | Platform overrides |
labels |
map | Custom OCI labels for this variant. Overrides tag- and image-level labels. See labels |
Each variant can have its own Dockerfile, test.yml.gotpl, and rootfs/ directory in a subdirectory named after the variant.
depends_on
List of image names this image depends on. ContainerHive resolves dependencies automatically from FROM references in Dockerfiles, but explicit dependencies can be declared here.
platforms
Override the project-level platform list for this specific image.
labels
Custom OCI image labels applied to every tag and variant of this image. Values are merged with project-, tag-, and variant-level labels following the precedence chain documented in Configure your project › labels.
labels:
com.acme.layer: image
com.acme.image: python
tags:
- name: 3.13.7
labels:
com.acme.layer: tag # overrides image-level entry for this tag
variants:
- name: slim
tag_suffix: -slim
labels:
com.acme.layer: variant # overrides image and tag for the variant build
Standard auto-derived OCI keys (title, version, created, etc.) always win over custom map entries with the same key.
latest_alias
Configure an alias pointing to the highest semantic version tag. This allows you to automatically retag the highest semantic version as a configurable alias (e.g., latest, stable).
| Field | Type | Description |
|---|---|---|
tag |
string | Required. The alias tag name (e.g., latest, stable) |
on_missing |
string | Optional. Behavior when no semantic tags are found: error (default), warning, or silent |
Example:
In this example, the highest semantic version tag will be retagged as latest. If no semantic tags are found, a warning will be logged instead of failing.
Report inclusion
The resolved alias is also included in the report output (ch report) as a separate latestAlias property on each image and variant (JSON: { "name": "latest", "target": "1.2.3" }). In the web report, it renders as a badge below the tag tabs, with its own pull command. The alias is never merged into the tags array.
Directory structure
Each image directory can contain:
images/<image-name>/
├── image.yml # Image definition (required)
├── Dockerfile[.gotpl] # Build instructions (required)
├── test.yml[.gotpl] # Container structure tests (optional)
├── rootfs/ # Files to copy into the image (optional)
└── <variant-name>/ # Variant subdirectory (optional)
├── Dockerfile[.gotpl]
├── test.yml[.gotpl]
└── rootfs/
The .gotpl extension is optional for Dockerfiles and test files. Plain files are copied as-is. Use .gotpl when you need Go template features (e.g. resolve_base, conditionals). See Write your Dockerfiles for details.