Native Dagger container builds + Navidrome v0.61.1 (#330)
All checks were successful
Build Container / detect (push) Successful in 3s
Build Container / build-dagger (navidrome) (push) Successful in 22m26s

## 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
This commit is contained in:
Erich Blume 2026-04-11 17:11:56 -07:00
commit c86b5d7772
33 changed files with 422 additions and 929 deletions

View file

@ -1,57 +0,0 @@
# Navidrome music server
# Three-stage build: UI (Node), backend (Go+taglib), runtime (Alpine)
ARG CONTAINER_APP_VERSION=v0.60.3
ARG NAVIDROME_VERSION=${CONTAINER_APP_VERSION}
FROM node:22-alpine AS ui-build
ARG NAVIDROME_VERSION
RUN apk add --no-cache git
RUN git clone --depth 1 --branch ${NAVIDROME_VERSION} \
https://forge.ops.eblu.me/mirrors/navidrome.git /app
WORKDIR /app/ui
RUN npm ci
RUN npm run build
FROM golang:alpine3.22 AS build
ARG NAVIDROME_VERSION
RUN apk add --no-cache build-base git taglib-dev zlib-dev
RUN git clone --depth 1 --branch ${NAVIDROME_VERSION} \
https://forge.ops.eblu.me/mirrors/navidrome.git /app
WORKDIR /app
# Copy pre-built UI assets
COPY --from=ui-build /app/ui/build /app/ui/build
ENV CGO_ENABLED=1
ENV CGO_CFLAGS_ALLOW="--define-prefix"
RUN go build -tags=netgo \
-ldflags="-w -s -X github.com/navidrome/navidrome/consts.gitTag=${NAVIDROME_VERSION}" \
-o /navidrome .
FROM alpine:3.22
ARG CONTAINER_APP_VERSION
LABEL org.opencontainers.image.title="Navidrome"
LABEL org.opencontainers.image.description="Navidrome is a self-hosted music server and streamer"
LABEL org.opencontainers.image.version="${CONTAINER_APP_VERSION}"
LABEL org.opencontainers.image.source="https://forge.eblu.me/eblume/blumeops"
LABEL org.opencontainers.image.vendor="blumeops"
RUN apk add --no-cache ca-certificates tzdata taglib ffmpeg \
&& addgroup -g 1000 navidrome \
&& adduser -u 1000 -G navidrome -D navidrome
COPY --from=build /navidrome /usr/bin/navidrome
EXPOSE 4533
USER 1000
CMD ["/usr/bin/navidrome"]

View file

@ -0,0 +1,54 @@
"""Navidrome music server — native Dagger build.
Three-stage build: Node (UI), Go (backend with taglib + FTS5), Alpine (runtime).
Source cloned from forge mirror.
"""
import dagger
from blumeops.containers import (
alpine_runtime,
clone_from_forge,
go_build,
node_build,
oci_labels,
)
VERSION = "v0.61.1"
async def build(src: dagger.Directory) -> dagger.Container:
source = clone_from_forge("navidrome", VERSION)
# Stage 1: Build UI assets
ui = node_build(source, "ui")
# Stage 2: Build Go backend with CGO (taglib) and FTS5
backend = go_build(
source.with_directory("ui/build", ui.directory("/app/ui/build")),
"/navidrome",
tags="netgo,sqlite_fts5",
ldflags=f"-w -s -X github.com/navidrome/navidrome/consts.gitTag={VERSION}",
cgo_enabled=True,
extra_apk=["taglib-dev", "zlib-dev"],
)
# Stage 3: Runtime
runtime = alpine_runtime(
extra_apk=["ca-certificates", "tzdata", "taglib", "ffmpeg"],
uid=1000,
gid=1000,
username="navidrome",
)
runtime = oci_labels(
runtime,
title="Navidrome",
description="Navidrome is a self-hosted music server and streamer",
version=VERSION,
)
return (
runtime.with_file("/usr/bin/navidrome", backend.file("/navidrome"))
.with_exposed_port(4533)
.with_user("1000")
.with_default_args(args=["/usr/bin/navidrome"])
)