Pin container versions and add uniform CONTAINER_APP_VERSION

Every container Dockerfile now declares ARG CONTAINER_APP_VERSION=X.Y.Z
as the first ARG, enabling uniform version parsing for the sync check.
Containers that use the version in build commands chain it to a semantic
ARG (e.g., ARG NAVIDROME_VERSION=${CONTAINER_APP_VERSION}).

Version sources:
- cv: 1.0.3 (latest Forgejo generic package release)
- quartz: 1.28.2 (nginx stable, pinned FROM tag)
- devpi: 6.19.1 / 5.0.1 (devpi-server + devpi-web from PyPI)
- nettest: 0.1.0 (internal, no upstream)
- All others: existing versions carried forward

Mark pin-container-versions Mikado card as complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-02-20 19:58:26 -08:00
commit d368a07876
17 changed files with 69 additions and 69 deletions

View file

@ -6,6 +6,8 @@
#
# The container downloads the tarball on startup, extracts it, and serves with nginx.
ARG CONTAINER_APP_VERSION=1.0.3
FROM nginx:alpine
# Install curl for downloading release assets

View file

@ -1,7 +1,15 @@
ARG CONTAINER_APP_VERSION=6.19.1
FROM python:3.12-slim
ARG CONTAINER_APP_VERSION
ARG DEVPI_SERVER_VERSION=${CONTAINER_APP_VERSION}
ARG DEVPI_WEB_VERSION=5.0.1
# Install devpi-server and devpi-web
RUN pip install --no-cache-dir devpi-server devpi-web
RUN pip install --no-cache-dir \
devpi-server==${DEVPI_SERVER_VERSION} \
devpi-web==${DEVPI_WEB_VERSION}
# Create non-root user
RUN useradd -r -u 1000 devpi && mkdir -p /devpi && chown devpi:devpi /devpi

View file

@ -9,9 +9,13 @@
# Usage: Configure runner with label like:
# docker:docker://registry.ops.eblu.me/blumeops/forgejo-runner:latest
ARG CONTAINER_APP_VERSION=0.19.11
FROM debian:bookworm-slim
ARG TARGETARCH
ARG CONTAINER_APP_VERSION
ARG DAGGER_VERSION=${CONTAINER_APP_VERSION}
# Install base dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
@ -51,7 +55,6 @@ RUN ARCH="${TARGETARCH:-$(dpkg --print-architecture)}" \
&& argocd version --client
# Install Dagger CLI (for running Dagger CI pipelines)
ARG DAGGER_VERSION=0.19.11
RUN ARCH="${TARGETARCH:-$(dpkg --print-architecture)}" \
&& curl -fsSL -o /tmp/dagger.tar.gz \
"https://dl.dagger.io/dagger/releases/${DAGGER_VERSION}/dagger_v${DAGGER_VERSION}_linux_${ARCH}.tar.gz" \

View file

@ -1,7 +1,8 @@
# Homepage - self-hosted services dashboard
# Two-stage build: Node.js build, Alpine runtime
ARG HOMEPAGE_VERSION=v1.10.1
ARG CONTAINER_APP_VERSION=v1.10.1
ARG HOMEPAGE_VERSION=${CONTAINER_APP_VERSION}
FROM node:24-slim AS builder

View file

@ -1,10 +1,13 @@
# kiwix-serve container
# Downloads pre-built binary from kiwix mirror
ARG CONTAINER_APP_VERSION=3.8.1
FROM alpine:3.22
ARG TARGETPLATFORM
ARG KIWIX_VERSION=3.8.1
ARG CONTAINER_APP_VERSION
ARG KIWIX_VERSION=${CONTAINER_APP_VERSION}
RUN set -e && \
apk --no-cache add dumb-init curl && \

View file

@ -1,10 +1,13 @@
# Minimal kubectl container
# Multi-arch build: downloads correct binary for target platform
ARG CONTAINER_APP_VERSION=v1.34.4
FROM alpine:3.22 AS downloader
ARG TARGETARCH
ARG KUBECTL_VERSION=v1.34.4
ARG CONTAINER_APP_VERSION
ARG KUBECTL_VERSION=${CONTAINER_APP_VERSION}
RUN apk add --no-cache curl && \
# Detect architecture - use TARGETARCH if set, otherwise detect from uname

View file

@ -1,7 +1,8 @@
# Miniflux RSS feed reader
# Based on upstream packaging/docker/alpine/Dockerfile
ARG MINIFLUX_VERSION=2.2.17
ARG CONTAINER_APP_VERSION=2.2.17
ARG MINIFLUX_VERSION=${CONTAINER_APP_VERSION}
FROM golang:alpine3.22 AS build

View file

@ -1,7 +1,8 @@
# Navidrome music server
# Three-stage build: UI (Node), backend (Go+taglib), runtime (Alpine)
ARG NAVIDROME_VERSION=v0.60.3
ARG CONTAINER_APP_VERSION=v0.60.3
ARG NAVIDROME_VERSION=${CONTAINER_APP_VERSION}
FROM node:22-alpine AS ui-build

View file

@ -4,6 +4,8 @@
# - Docker on indri (during CI build)
# - Minikube pods (manual testing)
ARG CONTAINER_APP_VERSION=0.1.0
FROM alpine:3.22
RUN apk add --no-cache \

View file

@ -1,7 +1,8 @@
# ntfy push notification server
# Three-stage build: Web UI (Node), server (Go+SQLite), runtime (Alpine)
ARG NTFY_VERSION=v2.17.0
ARG CONTAINER_APP_VERSION=v2.17.0
ARG NTFY_VERSION=${CONTAINER_APP_VERSION}
ARG NTFY_COMMIT=a03a37feb1869e84e3af0dd6190bdc7183f211ec
FROM node:22-alpine AS web-build

View file

@ -6,7 +6,10 @@
#
# The container downloads the tarball on startup, extracts it, and serves with nginx.
FROM nginx:alpine
ARG CONTAINER_APP_VERSION=1.28.2
ARG NGINX_VERSION=${CONTAINER_APP_VERSION}
FROM nginx:${NGINX_VERSION}-alpine
# Install curl for downloading release assets
RUN apk add --no-cache curl

View file

@ -1,7 +1,8 @@
# TeslaMate - Tesla data logger
# Based on upstream Dockerfile
ARG TESLAMATE_VERSION=v2.2.0
ARG CONTAINER_APP_VERSION=v2.2.0
ARG TESLAMATE_VERSION=${CONTAINER_APP_VERSION}
FROM elixir:1.18-otp-26 AS builder

View file

@ -1,9 +1,12 @@
# Transmission BitTorrent daemon
# Simpler alternative to linuxserver image
ARG CONTAINER_APP_VERSION=4.0.6-r4
FROM alpine:3.22
ARG TRANSMISSION_VERSION=4.0.6-r4
ARG CONTAINER_APP_VERSION
ARG TRANSMISSION_VERSION=${CONTAINER_APP_VERSION}
RUN apk add --no-cache \
transmission-daemon=${TRANSMISSION_VERSION} \

View file

@ -26,13 +26,13 @@ Discovered during analysis of [[adopt-commit-based-container-tags]]: the new com
A uv-script mise task that validates version consistency across sources:
1. **Dockerfile ARG** — parse `ARG <NAME>_VERSION=<value>` (strip `v` prefix if present). This is the primary version declaration for Dockerfile containers.
1. **Dockerfile ARG** — parse `ARG CONTAINER_APP_VERSION=<value>` (strip `v` prefix if present). Every container Dockerfile declares this as its canonical version.
2. **`service-versions.yaml`** — `current-version` field for the matching service name (strip `v` prefix). Must agree with the Dockerfile ARG.
3. **Nix derivation** — for nix-only containers (authentik), extract the version via `dagger call nix-version` (from [[add-dagger-nix-build]]). For dual containers (nettest, ntfy), the Dockerfile ARG is the primary check target; the nix version is informational.
The check also validates:
- Every `hybrid` service in `service-versions.yaml` has a non-null `current-version`
- Every container with a Dockerfile has a parseable `*_VERSION` ARG (after [[pin-container-versions]] is complete)
- Every container with a Dockerfile has a parseable `CONTAINER_APP_VERSION` ARG (after [[pin-container-versions]] is complete)
Report mismatches as errors. Exit non-zero if any are found.
@ -57,7 +57,7 @@ Fill in `current-version` for all hybrid services that currently have `null`. Th
The CI workflow (from [[adopt-commit-based-container-tags]]) extracts the version at build time — no VERSION file needed:
- **Dockerfile builds**: `grep -oP 'ARG \w+_VERSION=\K\S+' containers/$CONTAINER/Dockerfile | head -1`
- **Dockerfile builds**: `grep -oP 'ARG CONTAINER_APP_VERSION=\K\S+' containers/$CONTAINER/Dockerfile`
- **Nix builds**: `dagger call nix-version --src=. --package=<pkg>` or `nix eval --raw nixpkgs#<pkg>.version`
## Key Files

View file

@ -39,7 +39,7 @@ Both the Dockerfile and Nix workflows fire for each trigger, each bailing out if
Each container's version is extracted at build time from existing declarations — no separate VERSION file:
- **Dockerfile builds**: parsed from `ARG <NAME>_VERSION=<value>` in the Dockerfile
- **Dockerfile builds**: parsed from `ARG CONTAINER_APP_VERSION=<value>` in the Dockerfile
- **Nix builds**: extracted via `dagger call nix-version` or `nix eval`
The [[add-container-version-sync-check]] pre-commit check ensures these declarations stay in sync with `service-versions.yaml`. See [[pin-container-versions]] for the work to ensure every container has a parseable version.

View file

@ -1,7 +1,6 @@
---
title: Pin Container Versions
modified: 2026-02-20
status: active
tags:
- how-to
- containers
@ -15,69 +14,38 @@ Ensure every container has an explicit, parseable version declaration so that [[
## Context
Discovered during analysis of [[adopt-commit-based-container-tags]]: most containers already have version ARGs (miniflux, navidrome, ntfy, etc.), but several do not. Without explicit versions in the build files, there is nothing for a VERSION file to sync against.
Discovered during analysis of [[adopt-commit-based-container-tags]]: containers needed a uniform, parseable version declaration for the sync check. Most containers already had version ARGs (miniflux, navidrome, ntfy, etc.), but with inconsistent naming (`NAVIDROME_VERSION`, `MINIFLUX_VERSION`, etc.), and several containers (devpi, cv, quartz, nettest) had none.
## Containers Needing Work
## What Was Done
### devpi — Pin pip dependencies
Currently installs `devpi-server` and `devpi-web` without version pins:
Every container Dockerfile now declares `ARG CONTAINER_APP_VERSION=X.Y.Z` as its first ARG, providing a uniform parsing target. Containers that use the version in build commands chain it to a semantic ARG:
```dockerfile
RUN pip install --no-cache-dir devpi-server devpi-web
ARG CONTAINER_APP_VERSION=v0.60.3
ARG NAVIDROME_VERSION=${CONTAINER_APP_VERSION}
```
Add version ARGs and pin:
```dockerfile
ARG DEVPI_SERVER_VERSION=6.12.1
ARG DEVPI_WEB_VERSION=4.2.2
RUN pip install --no-cache-dir \
devpi-server==${DEVPI_SERVER_VERSION} \
devpi-web==${DEVPI_WEB_VERSION}
```
The VERSION file will track `devpi-server` as the primary version.
### cv — Add internal version ARG
Thin nginx wrapper that downloads content at runtime. No upstream app to track — the version reflects the container definition itself:
```dockerfile
ARG CV_VERSION=0.1.0
```
Bump when the Dockerfile or scripts change.
### quartz — Add internal version ARG
Same pattern as cv:
```dockerfile
ARG QUARTZ_VERSION=0.1.0
```
### nettest — Already handled
Utility container with no upstream. Will use `0.1.0` in VERSION file. No Dockerfile ARG needed since there's nothing to pin — the Dockerfile just installs Alpine packages at whatever version Alpine ships. The sync check can skip the Dockerfile ARG validation for containers without a `*_VERSION` ARG.
### forgejo-runner — Already handled
Has `ARG DAGGER_VERSION=0.19.11` as its primary version. Good enough.
Specific changes:
- **devpi**: Pinned devpi-server==6.19.1 and devpi-web==5.0.1
- **cv**: `CONTAINER_APP_VERSION=1.0.3` (matches latest Forgejo package release)
- **quartz**: `CONTAINER_APP_VERSION=1.28.2` (pinned nginx:1.28.2-alpine base)
- **nettest**: `CONTAINER_APP_VERSION=0.1.0` (internal, no upstream)
- **All others**: Existing versions carried forward with new uniform ARG pattern
## Key Files
| File | Change |
|------|--------|
| `containers/devpi/Dockerfile` | Pin devpi-server and devpi-web versions |
| `containers/cv/Dockerfile` | Add `ARG CV_VERSION` |
| `containers/quartz/Dockerfile` | Add `ARG QUARTZ_VERSION` |
| `containers/*/Dockerfile` | Add `ARG CONTAINER_APP_VERSION` to all 13 containers |
| `service-versions.yaml` | Populate `current-version` for devpi, cv, docs |
## Verification
- [ ] Every container Dockerfile either has a `*_VERSION` ARG or is documented as version-exempt (nettest)
- [ ] `devpi` container builds with pinned versions
- [ ] `cv` and `quartz` containers still build and serve correctly
- [x] Every container Dockerfile has `ARG CONTAINER_APP_VERSION=X.Y.Z`
- [x] ARG chaining tested with Docker build (nginx:1.28.2-alpine)
- [x] devpi container pins pip package versions
- [x] cv version matches Forgejo package release (1.0.3)
- [x] quartz pins nginx base image to stable (1.28.2)
## Related

View file

@ -169,22 +169,22 @@ services:
- name: devpi
type: hybrid
last-reviewed: null
current-version: null
current-version: "6.19.1"
upstream-source: https://github.com/devpi/devpi/releases
- name: cv
type: hybrid
last-reviewed: null
current-version: null
current-version: "1.0.3"
upstream-source: null
notes: Personal static site, no upstream
- name: docs
type: hybrid
last-reviewed: null
current-version: null
current-version: "1.28.2"
upstream-source: https://github.com/jackyzha0/quartz/releases
notes: Quartz static site generator
notes: Quartz static site generator; container version tracks nginx base
- name: forgejo-runner
type: hybrid