## Summary
- Move Dagger module from `.dagger/` to repo root (`src/blumeops/`), rename `blumeops-ci` → `blumeops`
- Replace opaque `docker_build()` with native Dagger pipelines that surface full build errors per step
- Migrate navidrome as the first container (`containers/navidrome/container.py`)
- Upgrade navidrome from v0.60.3 to v0.61.1 (major artwork overhaul, SQLite FTS5 search, server-managed transcoding)
- Add `dagger call container-version` for CI version extraction without Dockerfile parsing
- All mise tasks (`container-list`, `container-version-check`, `container-build-and-release`) updated for hybrid mode
- Legacy `docker_build()` fallback preserved for all other containers
## Motivation
When navidrome v0.61.0 added a new Go build tag (`sqlite_fts5`), `docker_build()` showed only "exit code: 1". We had to run `docker build --progress=plain` manually to find `undefined: buildtags.SQLITE_FTS5`. Native Dagger pipelines show the full error inline.
## Container build dispatch needed
After merge, dispatch container build for navidrome:
```
mise run container-build-and-release navidrome --ref 470b4bd
```
## Deploy steps
1. Wait for container build to complete
2. Back up navidrome-data PVC (non-reversible DB migrations)
3. `argocd app set navidrome --revision main && argocd app sync navidrome`
4. Verify at https://dj.ops.eblu.me
## Future
Remaining containers migrate incrementally in follow-up PRs using the same pattern.
Reviewed-on: #330
104 lines
4 KiB
Markdown
104 lines
4 KiB
Markdown
---
|
|
title: Dagger
|
|
modified: 2026-04-11
|
|
tags:
|
|
- reference
|
|
- ci-cd
|
|
- dagger
|
|
---
|
|
|
|
# Dagger
|
|
|
|
Build engine for BlumeOps CI/CD pipelines. Replaces shell-based build scripts with Python functions that run identically locally and in CI.
|
|
|
|
## Quick Reference
|
|
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| **Module** | `blumeops` |
|
|
| **Engine Version** | v0.20.1 |
|
|
| **SDK** | Python |
|
|
| **Source** | `src/blumeops/main.py` |
|
|
| **Config** | `dagger.json` (source: `.`) |
|
|
|
|
## Functions
|
|
|
|
| Function | Signature | Description |
|
|
|----------|-----------|-------------|
|
|
| `build` | `(src, container_name) → Container` | Build a container — uses native pipeline (`container.py`) if available, falls back to `docker_build()` for Dockerfile containers |
|
|
| `container_version` | `(container_name) → str` | Return the `VERSION` from a container's `container.py` (empty string if no `container.py`) |
|
|
| `publish` | `(src, container_name, version, registry?) → str` | Build and push to registry (default: `registry.ops.eblu.me`) |
|
|
| `build_nix` | `(src, container_name) → File` | Build a nix container from `containers/<name>/default.nix`, return docker-archive tarball |
|
|
| `nix_version` | `(package) → str` | Extract the version of a nixpkgs package |
|
|
| `build_docs` | `(src, version) → File` | Build Quartz docs site, return docs tarball |
|
|
| `flake_lock` | `(src, flake_path?) → File` | Resolve flake inputs, return updated `flake.lock` |
|
|
| `flake_update` | `(src, flake_path?) → File` | Update all flake inputs to latest, return `flake.lock` |
|
|
|
|
## Container Build Types
|
|
|
|
Containers can be built in three ways:
|
|
|
|
| Build file | How it works | Error visibility |
|
|
|------------|-------------|-----------------|
|
|
| `container.py` | Native Dagger pipeline (preferred) | Full per-step output |
|
|
| `Dockerfile` | `docker_build()` fallback (legacy) | Opaque — errors swallowed |
|
|
| `default.nix` | `nix-build` on ringtail runner | Full nix output |
|
|
|
|
New containers for indri (k8s runner) should use `container.py`. Ringtail containers should continue using `default.nix`. Existing Dockerfile containers are migrated incrementally during [[review-services|service reviews]]. See `containers/navidrome/container.py` for the reference pattern.
|
|
|
|
## CLI Examples
|
|
|
|
```bash
|
|
# Build a container
|
|
dagger call build --src=. --container-name=devpi
|
|
|
|
# Drop into container shell for inspection
|
|
dagger call build --src=. --container-name=devpi terminal
|
|
|
|
# Debug a failure interactively
|
|
dagger call --interactive build --src=. --container-name=devpi
|
|
|
|
# Publish a container to zot
|
|
dagger call publish --src=. --container-name=devpi --version=v1.1.0
|
|
|
|
# Build a nix container (no local nix required)
|
|
dagger call build-nix --src=. --container-name=ntfy export --path=./ntfy.tar.gz
|
|
|
|
# Check a nixpkgs package version
|
|
dagger call nix-version --package=authentik
|
|
|
|
# Build docs tarball locally
|
|
dagger call build-docs --src=. --version=dev export --path=./docs-dev.tar.gz
|
|
|
|
# Debug a docs build failure
|
|
dagger call --interactive build-docs --src=. --version=dev
|
|
|
|
# Update all ringtail flake inputs
|
|
dagger call flake-update --src=. --flake-path=nixos/ringtail \
|
|
export --path=nixos/ringtail/flake.lock
|
|
```
|
|
|
|
## Secrets
|
|
|
|
Dagger has a first-class `Secret` type — values are never logged or cached. Pass secrets from environment variables using the `env:VAR` syntax:
|
|
|
|
```bash
|
|
dagger call release-docs \
|
|
--src=. --version=v1.6.0 \
|
|
--forgejo-token=env:FORGEJO_TOKEN \
|
|
--argocd-token=env:ARGOCD_TOKEN
|
|
```
|
|
|
|
In [[forgejo]] Actions, secrets are injected as env vars. Locally, mise tasks call `op read` to populate them.
|
|
|
|
## Caveats
|
|
|
|
- **Pre-1.0 API** — Current version is v0.20.x. Pin the CLI version and test upgrades on a branch before adopting. See [[upgrade-dagger]] for the upgrade procedure.
|
|
- **Privileged container** — The Dagger engine requires privileged container access. The Forgejo runner's DinD sidecar provides this.
|
|
|
|
## Related
|
|
|
|
- [[forgejo]] — CI/CD trigger layer
|
|
- [[zot]] — Container registry (publish target)
|
|
- [[docs]] — Documentation site (build target)
|
|
- [[manage-lockfile]] — Ringtail flake lockfile management
|