Harden zot registry, pt 1 (#231)

## Summary
- Enable OIDC + API key authentication on zot with anonymous pull preserved
- Enforce tag immutability for version tags
- Adopt commit-SHA-based container image tagging

Details in the [[harden-zot-registry]] Mikado chain (`mise run docs-mikado harden-zot-registry`).

## Test plan
- [ ] Anonymous pull still works
- [ ] Unauthenticated push fails (401)
- [ ] CI container builds pass with new auth and tagging
- [ ] `mise run services-check` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/231
This commit is contained in:
Erich Blume 2026-02-20 22:50:01 -08:00
commit 0e2c10176d
28 changed files with 743 additions and 30 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

@ -1,20 +1,80 @@
# Nix-built ntfy push notification server
# Replaces the multi-stage Dockerfile (Node + Go + Alpine) with nixpkgs ntfy-sh
# Builds v2.17.0 from forge mirror (nixpkgs has 2.15.0)
# Built with dockerTools.buildLayeredImage for efficient layer caching
{ pkgs ? import <nixpkgs> { } }:
let
version = "2.17.0";
src = pkgs.fetchgit {
url = "https://forge.ops.eblu.me/eblume/ntfy.git";
rev = "v${version}";
hash = "sha256-/dxILAkye1HwYcybnx1WrMRK2jXZMrxal2ZKm6y2bWc=";
};
ui = pkgs.buildNpmPackage {
inherit src version;
pname = "ntfy-sh-ui";
npmDepsHash = "sha256-d73rymqCKalsjAwHSJshEovmUHJStfGt8wcZYN49sHY=";
prePatch = ''
cd web/
'';
installPhase = ''
runHook preInstall
mv build/index.html build/app.html
rm build/config.js
mkdir -p $out
mv build/ $out/site
runHook postInstall
'';
};
ntfy = pkgs.buildGoModule {
inherit src version;
pname = "ntfy-sh";
vendorHash = "sha256-/mQ+UwBYz78mPVVwYgsSYatE00ce2AKXJdx+nl6oT8E=";
doCheck = false;
ldflags = [
"-s"
"-w"
"-X main.version=${version}"
];
postPatch = ''
sed -i 's# /bin/echo# echo#' Makefile
'';
# Copy pre-built web UI; skip docs (create placeholder for go:embed)
preBuild = ''
cp -r ${ui}/site/ server/
mkdir -p server/docs && touch server/docs/placeholder
'';
meta = with pkgs.lib; {
description = "Send push notifications to your phone or desktop via PUT/POST";
homepage = "https://ntfy.sh";
license = licenses.asl20;
mainProgram = "ntfy";
};
};
in
pkgs.dockerTools.buildLayeredImage {
name = "blumeops/ntfy";
tag = "latest";
contents = [
pkgs.ntfy-sh
ntfy
pkgs.cacert
pkgs.tzdata
];
config = {
Entrypoint = [ "${pkgs.ntfy-sh}/bin/ntfy" ];
Entrypoint = [ "${ntfy}/bin/ntfy" ];
Env = [
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
"TZDIR=${pkgs.tzdata}/share/zoneinfo"

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} \