dbuild
dbuild is the primary build engine for the Daemonless project. It provides a unified interface for building, testing, and publishing FreeBSD OCI container images, ensuring consistency between local development and CI/CD environments.
Overview
In the Daemonless ecosystem, dbuild replaces manual podman build commands and legacy shell scripts. It understands the project structure, handles FreeBSD-specific logic (like variant detection and architecture mapping), and enforces quality gates through integrated testing.
flowchart LR
subgraph dbuild ["dbuild (The Engine)"]
direction LR
B[Build] --> T[Test]
T --> P[Push]
end
subgraph CIT ["CIT (The Gates)"]
direction TB
T1[Shell]
T2[Port]
T3[Health]
T4[Screenshot]
end
T -.-> CIT
dbuild manages the full container lifecycle. When it reaches the test phase, it invokes the CIT quality gates to validate the image before allowing a push.
In CI, dbuild ci-run executes the full pipeline (build, test, push, sbom) as a single command.
Key Features
- Multi-variant builds: Automatically detects
ContainerfileandContainerfile.<variant>(e.g.,.pkg), or use explicit variant definitions for full control. - Integrated CIT: Built-in Container Integration Testing with cumulative modes: shell, port, health-check, and visual regression (screenshots).
- Architecture awareness: Maps common names (
x86_64,arm64,riscv) to FreeBSD conventions (amd64,aarch64,riscv64). - CI first: Designed to run identically on local machines, GitHub Actions, and Woodpecker CI.
- SBOM generation: Produces SBOMs using
trivyand native FreeBSDpkgdata. - Docker Hub mirroring: Automatically mirrors images to Docker Hub when credentials are configured.
- Skip directives: Control CI behavior via commit messages (e.g.,
[skip test],[skip push:dockerhub]).
Installation
dbuild is a Python-based tool. It requires Python 3.11+ and Podman.
# Clone the dbuild repository
git clone https://github.com/daemonless/dbuild
cd dbuild
# Install locally
pip install .
# For visual regression (screenshot) testing support
pip install ".[dev]"
Project Structure
dbuild expects a specific layout in each image repository:
myapp/
Containerfile # upstream binary build (:latest)
Containerfile.pkg # FreeBSD package build (:pkg)
root/ # files copied into the container
.daemonless/
config.yaml # build + test configuration
baseline.png # screenshot baseline (shared)
baseline-latest.png # screenshot baseline (per-variant)
compose.yaml # multi-service test stack
Commands
Global Flags
These flags apply to all subcommands:
| Flag | Description |
|---|---|
-v, --verbose |
Enable debug logging |
--variant TAG |
Filter to a single variant by tag |
--arch ARCH |
Override target architecture |
--registry URL |
Override container registry URL |
--push |
Shorthand: build then push |
dbuild info
Human-readable overview of the detected configuration, variants, and test settings. This is an alias for dbuild detect --format human.
dbuild detect
Outputs the build matrix as structured data. Used by CI systems to generate dynamic build matrices.
dbuild detect # JSON output (default)
dbuild detect --format github # GitHub Actions matrix format
dbuild detect --format woodpecker # Woodpecker CI matrix format
dbuild detect --format gitlab # GitLab CI matrix format
dbuild build
Builds container images for all detected variants.
dbuild build # Build all variants
dbuild build --variant latest # Build only :latest
dbuild build --arch aarch64 # Build for specific architecture
dbuild build --push # Build and push to registry
Each image is tagged as {registry}/{image}:build-{tag} during the build phase. After building, dbuild extracts the application version and applies OCI labels.
dbuild test
Runs Container Integration Tests (CIT) against the built images.
dbuild test # Run all tests
dbuild test --variant pkg # Test specific variant
dbuild test --json results.json # Export results to JSON
While dbuild manages the container lifecycle (start, stop, cleanup), the test mode (shell, port, health, screenshot) defines the success criteria. For a deep dive into configuring these tests and how visual regression works, see the Quality Gates (CIT) guide.
dbuild push
Tags and pushes built images to the registry. Non-amd64 builds get an architecture suffix (e.g., :latest-aarch64).
If DOCKERHUB_USERNAME and DOCKERHUB_TOKEN are set, images are also mirrored to Docker Hub. Use [skip push:dockerhub] in commit messages to skip the mirror.
dbuild manifest
Creates and pushes multi-arch OCI manifest lists. Only needed for images built on multiple architectures.
For each variant tag (and its aliases), this creates a manifest combining the per-architecture images. Architecture suffixes follow Docker convention: amd64 has no suffix, aarch64 uses -arm64, riscv64 uses -riscv64.
dbuild sbom
Generates an SBOM for each built variant, saved to sbom-results/.
The SBOM includes FreeBSD packages (from pkg query) and application dependencies detected by trivy (Go, Node, Python, Rust, .NET, Java, PHP, Ruby).
dbuild init
Scaffolds a new project with a starter Containerfile and .daemonless/config.yaml. Skips files that already exist.
dbuild init # Basic scaffold
dbuild init --woodpecker # Also generate .woodpecker.yaml
dbuild init --github # Also generate GitHub Actions workflow
CI Commands
These are primarily used inside CI pipelines:
| Command | Description |
|---|---|
dbuild ci-run |
Full pipeline: build, test, push, sbom. Add --prepare to run setup first. |
dbuild ci-prepare |
Set up FreeBSD CI environment (install podman, ocijail, networking). Add --compose to also install podman-compose. Requires root. |
dbuild ci-test-env |
Read-only preflight checks: verifies required tools, podman runtime, networking, and jail annotations. |
Configuration Reference
Config File Locations
dbuild looks for configuration in this order:
.dbuild.yaml(project root).daemonless/config.yaml(project root)/usr/local/etc/daemonless.yaml(global config, for shared variant definitions)
If no local config is found, dbuild auto-detects variants from Containerfile* names.
Full Config Example
# Image type: "app" (default) or "base"
# "app" extracts version from /app/version
# "base" extracts version from freebsd-version
type: app
# Build configuration
build:
auto_version: true # extract version from built image
pkg_name: myapp # FreeBSD package name (for SBOM)
architectures: [amd64, aarch64] # target architectures
ignore: ["Containerfile.old"] # exclude from auto-detection
# Explicit variants (disables auto-detection when defined)
variants:
- tag: latest
containerfile: Containerfile
default: true
- tag: pkg
containerfile: Containerfile.pkg
aliases: ["stable"] # additional tags pushed to registry
- tag: pkg-latest
containerfile: Containerfile.pkg
args:
BASE_VERSION: "15-latest" # override Containerfile ARG defaults
# Container integration test configuration
# See the Quality Gates (CIT) guide for full details
cit:
mode: health
port: 8080
health: /api/health
wait: 120
annotations:
- "org.freebsd.jail.allow.mlock=true"
For the complete cit: configuration reference, see the Quality Gates (CIT) guide.
Variant Resolution
When build.variants is defined in the local config, those variants are used exclusively -- auto-detection is skipped entirely. You must list all variants you want, including latest and pkg.
When build.variants is not defined:
- Auto-detect from
Containerfile(tag:latest) andContainerfile.*(tag: suffix) - Append extra variants from the global config (
/usr/local/etc/daemonless.yaml) whose Containerfile exists in the project - Skip duplicates (same tag)
Per-Variant Options
Each variant supports these fields:
| Field | Default | Description |
|---|---|---|
tag |
(required) | Tag name (e.g., latest, pkg, pkg-latest) |
containerfile |
Containerfile |
Path to the Containerfile |
args |
{} |
Build args passed to podman build --build-arg |
aliases |
[] |
Additional tags pushed to the registry |
default |
false |
Mark as the default variant for dbuild info |
pkg_name |
(from build) | Override FreeBSD package name for this variant |
auto_version |
(from build) | Override version extraction for this variant |
Build Args
dbuild always passes FREEBSD_ARCH={arch} as a build arg. Additional args from variant.args are merged in. If GITHUB_TOKEN is set in the environment, it is forwarded as a build secret.
CI/CD Integration
dbuild ci-run Pipeline
ci-run is the single entry point for CI. It runs this sequence:
graph TD
A[Source Code] --> B[dbuild build]
B --> C[dbuild test]
C -->|Pass| D{PR?}
C -->|Fail| E[Abort]
D -->|No| F[dbuild push]
D -->|Yes| G[Done]
F --> H[dbuild sbom]
F --> I[dbuild manifest]
H --> G
I --> G
- Prepare (optional, with
--prepare) -- install tools and configure networking - Build -- all variants; exit on failure
- Test -- all variants; abort remaining steps on failure
- PR check -- if this is a pull request, stop here (skip push and sbom)
- Push -- tag and push to registry (+ Docker Hub mirror)
- SBOM -- generate and save SBOMs
Skip Directives
Add these tags to commit messages to control CI behavior:
| Directive | Effect |
|---|---|
[skip test] |
Skip the test phase |
[skip push] |
Build and test, but do not push |
[skip push:dockerhub] |
Push to primary registry, skip Docker Hub mirror |
[skip sbom] |
Skip SBOM generation |
CI Environment Setup
dbuild ci-prepare installs everything needed to build on a fresh FreeBSD VM:
- Configures the FreeBSD latest package repo
- Installs podman, buildah, skopeo, jq, trivy, python3, py311-pyyaml
- Installs patched ocijail (for .NET jail annotation support)
- Cleans stale container state
- Loads pf and enables IP forwarding
Use --compose to also install podman-compose for compose-based tests.
Preflight Checks
Run dbuild ci-test-env to verify your environment is ready. It checks:
- Required tools: podman, buildah, skopeo, jq, trivy
- Optional tools: podman-compose
- Podman runtime (expects ocijail)
- Networking: pf loaded, IP forwarding enabled
- Jail annotations: tests mlock and sysvipc support
Returns exit code 0 if all required checks pass, 1 if any fail.
Architecture Support
dbuild maps common architecture names to FreeBSD conventions:
| Input | FreeBSD |
|---|---|
amd64, x86_64, x64 |
amd64 |
aarch64, arm64 |
aarch64 |
riscv64, riscv |
riscv64 |
Non-amd64 builds get an architecture suffix on registry tags (e.g., :latest-aarch64). Multi-arch manifests combine these into a single tag using dbuild manifest.
Environment Variables
| Variable | Default | Description |
|---|---|---|
DBUILD_REGISTRY |
Auto-detected from git remote, fallback localhost |
Target container registry |
GITHUB_TOKEN |
Registry authentication + build secret | |
GITHUB_ACTOR |
Registry login username | |
DOCKERHUB_USERNAME |
Enable Docker Hub mirroring | |
DOCKERHUB_TOKEN |
Docker Hub authentication token | |
CHROME_BIN |
/usr/local/bin/chrome |
Path to Chrome/Chromium for screenshot tests |
Registry Auto-Detection
If DBUILD_REGISTRY is not set, dbuild derives the registry from the git remote URL:
https://github.com/daemonless/radarrorgit@github.com:daemonless/radarr.gitbecomesghcr.io/daemonless- If no git remote is found, falls back to
localhost