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:
mkdir traefik && cd traefik
dbuild init \
--freebsd-port net/traefik \
--port 8080 \
--variants latest,pkg,pkg-latest \
--github
What just happened?
dbuild scaffolded a complete project structure for you:
traefik/
├── .daemonless/
│ └── config.yaml # Build variants and CIT test config
├── .github/workflows/build.yaml # GitHub Actions CI pipeline
├── compose.yaml # Image metadata + deployment example
├── Containerfile.j2 # Template for :latest (upstream binary)
├── Containerfile.pkg.j2 # Template for :pkg and :pkg-latest
└── root/
├── etc/services.d/traefik/run # s6 service supervisor script
└── healthz # Optional health check script
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:
name: traefik
x-daemonless:
title: "Traefik"
icon: ":material-server-network:" # Browse icons at pictogrammers.com
category: "Infrastructure" # See config reference for categories
description: "Modern HTTP reverse proxy and load balancer for FreeBSD."
upstream_url: "https://github.com/traefik/traefik" # Must be the SOURCE repo
web_url: "https://traefik.io/"
freshports_url: "https://www.freshports.org/net/traefik/"
upstream_binary: true # Tells dbuild :latest uses binaries
user: "bsd"
docs:
env:
PUID: "User ID for the application process"
PGID: "Group ID for the application process"
volumes:
/config: "Traefik configuration directory"
ports:
80: "HTTP"
443: "HTTPS"
8080: "Dashboard / API"
services:
traefik:
image: ghcr.io/daemonless/traefik:latest
# ... rest of deployment config ...
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:
ARG BASE_VERSION=15
FROM ghcr.io/daemonless/base:${BASE_VERSION}
ARG UPSTREAM_URL="https://api.github.com/repos/traefik/traefik/releases/latest"
ARG UPSTREAM_JQ=".tag_name"
# [dbuild] labels will be automatically injected here
RUN pkg update && pkg install -y ca_root_nss jq && pkg clean -ay
# Fetch version and binary using 'fetch' (FreeBSD native)
RUN TRAEFIK_VERSION=$(fetch -qo - "${UPSTREAM_URL}" | jq -r "${UPSTREAM_JQ}") && \
fetch -qo /tmp/traefik.tar.gz \
"https://github.com/traefik/traefik/releases/download/${TRAEFIK_VERSION}/traefik_${TRAEFIK_VERSION}_freebsd_amd64.tar.gz" && \
tar xzf /tmp/traefik.tar.gz -C /usr/local/bin traefik && \
chmod +x /usr/local/bin/traefik && \
mkdir -p /app && echo "${TRAEFIK_VERSION}" > /app/version && \
rm /tmp/traefik.tar.gz
RUN mkdir -p /config && chown -R bsd:bsd /config
COPY root/ /
5. Generate and Build
The core dbuild loop is Generate → Build → Test.
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
# Build the default variant
dbuild build
# Build all variants defined in .daemonless/config.yaml
dbuild build --variant latest --variant pkg --variant pkg-latest
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:
cit:
mode: health
port: 8080
health: /ping
ready: "Configuration loaded" # Watch logs for this string
Run the test:
If the app doesn't bind to port 8080 or the/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
name: Build FreeBSD Container
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
# This delegates EVERYTHING to the shared daemonless engine
uses: daemonless/dbuild/.github/workflows/daemonless-build.yaml@main
with:
image_name: traefik
secrets: inherit
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.