Building Your First Image: A Complete Workflow
This guide walks through the full lifecycle of creating a new daemonless container image, from initialization to GitHub Actions CI/CD.
Why use dbuild?
If you're building FreeBSD container images, dbuild provides a standardized, high-trust workflow that bridges the gap between local development and CI/CD:
- Jinja2 Power: Use templates (
.j2) to keep yourContainerfileDRY across multiple variants (e.g.,:latestvs:pkg). - Integrated Testing (CIT): Automatically verify that your container actually works (port checks, health endpoints, even screenshots) before pushing.
- GitHub-First: Seamlessly integrates with GitHub Actions and GHCR.io with zero-config reusable workflows.
- Local/CI Parity: The exact same
dbuild buildanddbuild testcommands run on your laptop and in the cloud.
1. Prerequisites
- FreeBSD 14+ or 15+
dbuildand Podman installed:
ghcr.io)
- Optional: A Woodpecker CI instance for self-hosted builds.
2. Initialize the Project
Create an empty directory for your image and run dbuild init. We'll use Traefik as our example:
What just happened?
dbuild scaffolded a complete project structure for you:
3. The "Single Source of Truth": compose.yaml
In the daemonless ecosystem, compose.yaml isn't just for deployment—it's the source of truth for your image's metadata and documentation.
Open compose.yaml and refine the x-daemonless section:
4. Crafting the Templates (.j2)
dbuild uses Jinja2 templates to generate standard Containerfiles. This allows you to inject dynamic labels and reuse logic.
Edit Containerfile.j2 (:latest variant)
Replace the placeholder download logic with Traefik's real GitHub releases:
5. Generate and Build
The core dbuild loop is Generate → Build → Test.
flowchart TD
Init["$ dbuild init\nScaffold a new project"] --> Compose
Init --> Template
Init --> Config
Compose["📝 compose.yaml\nTitle, description, ports, env vars, docs"]
Template["📝 Containerfile.j2\nHow the image is built"]
Config["📝 .daemonless/config.yaml\nBuild variants and CIT settings"]
Compose --> Generate
Template --> Generate
Config --> Generate
Generate["$ dbuild generate\nRenders templates → Containerfile + README"]
Generate --> Build
Build["$ dbuild build\nBuilds OCI image with podman"]
Build --> Test
Test["$ dbuild test\nRuns CIT: shell → port → health → screenshot"]
Test --> CIT{Pass?}
CIT -- "✅ yes" --> Push["$ dbuild push\nPushes to ghcr.io/daemonless/app:latest"]
CIT -- "❌ no" --> Fix["📝 Fix Containerfile.j2\nor .daemonless/config.yaml"]
Fix --> Build
style Init fill:#2563eb,color:#fff,stroke:none
style Generate fill:#2563eb,color:#fff,stroke:none
style Build fill:#2563eb,color:#fff,stroke:none
style Test fill:#2563eb,color:#fff,stroke:none
style Push fill:#2563eb,color:#fff,stroke:none
style Compose fill:#7c3aed,color:#fff,stroke:none
style Template fill:#7c3aed,color:#fff,stroke:none
style Config fill:#7c3aed,color:#fff,stroke:none
style Fix fill:#7c3aed,color:#fff,stroke:none
Step A: Generate
Turn your templates and compose.yaml into real files:
Containerfile and Containerfile.pkg
* Generates a standardized README.md from your compose.yaml
Step B: Build Locally
6. Test with CIT (Container Integration Test)
Don't just assume it works. dbuild test spins up the container and runs the checks defined in .daemonless/config.yaml:
Run the test:
/ping endpoint fails, the build is considered "failed".
7. GitHub Integration: dbuild + github
This is where dbuild shines. The .github/workflows/build.yaml file generated by dbuild init --github uses a reusable workflow.
The Workflow File
Why use the Reusable Workflow?
- Managed FreeBSD Runners: It automatically handles spinning up FreeBSD VMs on GitHub Actions.
- Automatic Matrix: It calls
dbuild detectto build all your variants in parallel. - GHCR.io Auth: It uses your
GITHUB_TOKENto automatically push toghcr.io/your-user/traefik. - SBOM Generation: Automatically generates and attaches CycloneDX Software Bill of Materials.
GitHub "Pro Tips"
- Registry: By default, it pushes to
ghcr.io/{github_actor}/{image_name}. - Commit Directives: Control CI behavior directly from your commit messages:
[skip test]— Skip CIT (useful for docs-only changes).[skip push]— Build and test, but don't push to the registry.
8. Summary of Commands
| Command | Description |
|---|---|
dbuild init |
Scaffold a new project |
dbuild generate |
Update Containerfiles from templates |
dbuild build |
Build the container image(s) |
dbuild test |
Run Integration Tests (CIT) |
dbuild push |
Push to registry (GHCR/DockerHub) |
dbuild info |
Show detected variants and config |
Next Steps
- Learn more about Quality Gates (CIT).
- Explore Multi-Arch Builds for amd64 and aarch64.
- Check the Config Reference for advanced
.daemonless/config.yamloptions.