Build CFEngine packages inside Docker containers using build scripts. Requires only Docker and Python 3 on the host.
# Build a community agent .deb for Ubuntu 22
./build-in-container.py --platform ubuntu-22 --project community --role agent --build-type DEBUG
# Build a nova hub release package for Debian 12
./build-in-container.py --platform debian-12 --project nova --role hub --build-type RELEASEIn the examples above, we run the script from inside buildscripts/ (with
buildscripts as our current working directory). This is not required — if not
specified, defaults will:
- Look for sources relative to the script (parent directory of
build-in-container.py). - Place cache files in the user's home directory
(
~/.cache/cfengine/buildscripts). - Use the current working directory for output packages (
./output/).
./build-in-container.py --platform PLATFORM --project PROJECT --role ROLE --build-type TYPE [OPTIONS]
| Option | Description |
|---|---|
--platform |
Target platform (e.g. ubuntu-22, debian-12) |
--project |
community or nova (not required for --push-image) |
--role |
agent or hub (not required for --push-image) |
--build-type |
DEBUG or RELEASE (not required for --push-image) |
None of the above arguments are required for --update.
| Option | Default | Description |
|---|---|---|
--output-dir |
./output |
Where to write output packages |
--cache-dir |
~/.cache/cfengine/buildscripts |
Dependency cache directory |
--build-number |
1 |
Build number for package versioning |
--version |
auto | Override version string |
--rebuild-image |
Force rebuild of Docker image (bypasses Docker layer cache) | |
--push-image |
Build image and push to registry, then exit | |
--update |
Fetch latest image versions from registry and update platforms.json | |
--shell |
Drop into a bash shell inside the container for debugging | |
--list-platforms |
List available platforms and exit | |
--source-dir |
parent of buildscripts/ |
Root directory containing repos |
| Name | Base image |
|---|---|
ubuntu-20 |
ubuntu:20.04 |
ubuntu-22 |
ubuntu:22.04 |
ubuntu-24 |
ubuntu:24.04 |
debian-11 |
debian:11 |
debian-12 |
debian:12 |
Adding a new Debian/Ubuntu platform requires only a new entry in platforms.json.
Adding a non-debian based platform (e.g.,
RHEL/CentOS) requires a new container/Dockerfile.rhel plus platform entries.
The system has three components:
-
build-in-container.py(Python) -- the orchestrator that runs on the host. Parses arguments, builds the Docker image, and launches the container with the correct mounts and environment variables. -
build-in-container-inner.sh(Bash) -- runs inside the container. Copies source repos from the read-only mount, then calls the existing build scripts in order. -
container/Dockerfile.debian-- parameterized Dockerfile shared by all Debian/Ubuntu platforms via aBASE_IMAGEbuild arg.
| Host path | Container path | Mode | Purpose |
|---|---|---|---|
Source repos (parent of buildscripts/) |
/srv/source |
read-only | Protects host repos from modification |
~/.cache/cfengine/buildscripts/ |
/home/builder/.cache/buildscripts_cache |
read-write | Dependency cache shared across builds |
./output/ |
/output |
read-write | Output packages copied here |
The inner script runs these steps in order:
- autogen -- runs
autogen.shin each repo - install-dependencies -- builds and installs bundled dependencies
- mission-portal-deps -- (hub only) installs PHP/npm/LESS assets
- configure -- runs
./configurewith platform-appropriate flags - compile -- compiles and installs to the dist tree
- package -- creates
.debor.rpmpackages
By default, the script pulls a pre-built image from the container registry
(ghcr.io/cfengine). If the pull fails (e.g. no network, image not yet
published), it falls back to building the image locally.
Use --rebuild-image to skip the registry and force a local rebuild — useful
when iterating on the Dockerfile. The local build tracks the Dockerfile content
hash and skips rebuilding when nothing has changed.
Images are hosted at ghcr.io/cfengine and versioned per-platform via
image_version in platforms.json. To push a new image:
# Build and push a single platform
./build-in-container.py --platform ubuntu-22 --push-image--push-image always builds with --no-cache to pick up the latest upstream
packages, then pushes to the registry. However, you must be logged in to
ghcr.io first. You can log in with a personal access token (classic) that has
the write:packages scope. Alternatively, trigger the GitHub Actions workflow
which handles authentication automatically.
The build-base-images.yml workflow builds and pushes images for every
supported platform. It runs weekly (Sunday at midnight UTC) and can also be
triggered manually via workflow_dispatch.
After the workflow pushes new images, update platforms.json to use them:
# Update all platforms to the latest registry version
./build-in-container.py --update
# Update a single platform
./build-in-container.py --update --platform ubuntu-22The update-base-images.yml workflow automates this step. It runs weekly
(Monday at midnight UTC) and can also be triggered manually. It calls
./build-in-container.py --update and opens a pull request with any
platforms.json changes. This workflow requires contents: write and
pull-requests: write permissions.
The workflow authenticates to ghcr.io using the automatic GITHUB_TOKEN
provided by GitHub Actions. For this to work:
- The repository must grant
GITHUB_TOKENwrite access to packages. In the GitHub repository settings, go to Actions → General → Workflow permissions and select Read and write permissions. - After the first push, each package defaults to private. To allow anonymous
pulls, go to the package on GitHub (your org → Packages), open Package
settings, and change the visibility to Public. This is a one-time step
per package — new tags (e.g. from bumping
image_version) inherit the existing visibility.
- Edit
container/Dockerfile.debianas needed - Test locally with
--rebuild-image - Commit and merge the Dockerfile change
- Push new images by triggering the
build-base-images.ymlworkflow - Trigger the
update-base-images.ymlworkflow to open a PR updatingplatforms.json
# Drop into a shell inside the container
./build-in-container.py --platform ubuntu-22 --project community --role agent --build-type DEBUG --shellThe shell session has the same mounts and environment as a build run. The
container is ephemeral (--rm), so any changes are lost on exit.