Miniflux 2.2.19 + container.py migration + ty typechecker (#331)
All checks were successful
Build Container / detect (push) Successful in 3s
Build Container / build-dagger (miniflux) (push) Successful in 1m3s

## Summary

- Upgrade miniflux from 2.2.17 to 2.2.19 (security hardening, performance improvements)
- Migrate miniflux from Dockerfile to native Dagger container.py build
- Refactor `alpine_runtime()` helper to support existing users (nobody/65534)
- Add `ty` (Astral) Python typechecker to prek hooks

## Test plan

- [ ] `dagger call build --src=. --container-name=miniflux` succeeds
- [ ] `dagger call container-version --container-name=miniflux` returns 2.2.19
- [ ] `mise run container-version-check` passes
- [ ] `ty check` passes cleanly
- [ ] `prek run --all-files` passes
- [ ] CI builds container successfully
- [ ] Miniflux healthcheck passes after deploy from branch

Reviewed-on: #331
This commit is contained in:
Erich Blume 2026-04-12 08:54:32 -07:00
commit 138e23d525
12 changed files with 162 additions and 54 deletions

View file

@ -10,4 +10,4 @@ resources:
images:
- name: registry.ops.eblu.me/blumeops/miniflux
newTag: v2.2.17-613f05d
newTag: v2.2.19-e08c95c

View file

@ -1,35 +0,0 @@
# Miniflux RSS feed reader
# Based on upstream packaging/docker/alpine/Dockerfile
ARG CONTAINER_APP_VERSION=2.2.17
ARG MINIFLUX_VERSION=${CONTAINER_APP_VERSION}
FROM golang:alpine3.22 AS build
ARG MINIFLUX_VERSION
RUN apk add --no-cache build-base git make
# Clone specific version
RUN git clone --depth 1 --branch ${MINIFLUX_VERSION} \
https://forge.ops.eblu.me/mirrors/miniflux.git /go/src/app
WORKDIR /go/src/app
RUN make miniflux
FROM alpine:3.22
ARG CONTAINER_APP_VERSION
LABEL org.opencontainers.image.title="Miniflux"
LABEL org.opencontainers.image.description="Miniflux is a minimalist and opinionated feed reader"
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"
EXPOSE 8080
ENV LISTEN_ADDR=0.0.0.0:8080
RUN apk --no-cache add ca-certificates tzdata
COPY --from=build /go/src/app/miniflux /usr/bin/miniflux
USER 65534
CMD ["/usr/bin/miniflux"]

View file

@ -0,0 +1,61 @@
"""Miniflux RSS feed reader — native Dagger build.
Two-stage build: Go (backend with PIE), Alpine (runtime).
Source cloned from forge mirror.
"""
import dagger
from dagger import dag
from blumeops.containers import (
alpine_runtime,
clone_from_forge,
oci_labels,
)
VERSION = "2.2.19"
async def build(src: dagger.Directory) -> dagger.Container:
source = clone_from_forge("miniflux", VERSION)
# Stage 1: Build Go backend (PIE mode, matching upstream Makefile)
ldflags = f"-s -w -X 'miniflux.app/v2/internal/version.Version={VERSION}'"
backend = (
dag.container()
.from_("golang:alpine3.22")
.with_exec(["apk", "add", "--no-cache", "build-base", "git"])
.with_directory("/app", source)
.with_workdir("/app")
.with_env_variable("CGO_ENABLED", "1")
.with_exec(
[
"go",
"build",
"-buildmode=pie",
f"-ldflags={ldflags}",
"-o",
"/miniflux",
".",
]
)
)
# Stage 2: Runtime (uses Alpine's built-in nobody:65534)
runtime = alpine_runtime(
extra_apk=["ca-certificates", "tzdata"],
create_user=False,
)
runtime = oci_labels(
runtime,
title="Miniflux",
description="Miniflux is a minimalist and opinionated feed reader",
version=VERSION,
)
return (
runtime.with_file("/usr/bin/miniflux", backend.file("/miniflux"))
.with_exposed_port(8080)
.with_env_variable("LISTEN_ADDR", "0.0.0.0:8080")
.with_user("65534")
.with_default_args(args=["/usr/bin/miniflux"])
)

View file

@ -0,0 +1 @@
Add `ty` (Astral) Python typechecker to prek hooks, configured for Dagger SDK and container.py modules. Add `type: mise` to service-versions.yaml for tracking development tool versions (dagger, ansible-core, prek, pulumi, ty) through the standard service review process.

View file

@ -0,0 +1 @@
Upgrade miniflux from 2.2.17 to 2.2.19 and migrate from Dockerfile to native Dagger container.py build (second container after navidrome). Refactor `alpine_runtime()` with `create_user` parameter to support Alpine's built-in nobody user. Pin all mise.toml tool versions to explicit versions instead of "latest".

View file

@ -1,7 +1,7 @@
---
title: Review Services
modified: 2026-03-24
last-reviewed: 2026-03-07
modified: 2026-04-12
last-reviewed: 2026-04-12
tags:
- how-to
- maintenance
@ -66,6 +66,16 @@ Versioned NixOS services (forgejo-runner, snowflake, k3s) are pinned via a `nixp
4. Deploy via `mise run provision-ringtail`
5. Update `service-versions.yaml` with the new version
### Mise Tools (`type: mise`)
Development tools managed via `mise.toml` with pinned versions. These are local CLI tools (dagger, pulumi, prek, ty, ansible-core) rather than deployed services.
1. Check the upstream releases page for new versions
2. Review the changelog for breaking changes
3. Update the pinned version in `mise.toml`
4. Run `mise install` to verify the new version installs correctly
5. Update `service-versions.yaml` with the new version
### Private Forge Repos (`upstream-source` under `forge.eblu.me/eblume/`)
Some services are built from private repos on the forge rather than tracking an external upstream project. When `upstream-source` points to a `forge.eblu.me/eblume/` repo:

View file

@ -5,7 +5,7 @@
# ///
#MISE description="Review the most stale service for version freshness"
#USAGE flag "--limit <limit>" default="15" help="Number of services to show in the table"
#USAGE flag "--type <type>" help="Filter by service type (argocd, ansible, nixos)"
#USAGE flag "--type <type>" help="Filter by service type (argocd, ansible, nixos, fly, mise)"
"""Review the most stale service for version freshness.
Reads ``docs/reference/services/service-versions.yaml`` and sorts services
@ -197,6 +197,13 @@ def main(
"• Update: dagger call flake-update --src=. export --path=nixos/ringtail/flake.lock\n",
"• Deploy: mise run provision-ringtail\n",
]
elif svc_type == "mise":
checklist_parts += [
"\n[bold]Mise Tool Update:[/bold]\n",
"• Update pinned version in mise.toml\n",
"• Run: mise install to verify\n",
"• Check for breaking changes in release notes\n",
]
checklist_parts += [
"\n[bold]Health Check:[/bold]\n",

View file

@ -1,5 +1,6 @@
[tools]
"pipx:ansible-core" = { version = "latest", uvx = "true", uvx_args = "--with botocore --with boto3" }
prek = "latest"
pulumi = "latest"
"pipx:ansible-core" = { version = "2.20.1", uvx = "true", uvx_args = "--with botocore --with boto3" }
prek = "0.3.4"
pulumi = "3.215.0"
dagger = "0.20.1"
ty = "0.0.29"

View file

@ -77,6 +77,18 @@ repo = "https://github.com/astral-sh/ruff-pre-commit"
rev = "v0.15.7"
hooks = [{ id = "ruff", args = ["--fix"] }, { id = "ruff-format" }]
# Python - ty type checker
[[repos]]
repo = "local"
[[repos.hooks]]
id = "ty-check"
name = "ty type check"
entry = "ty check"
language = "system"
types = ["python"]
pass_filenames = false
# Shell scripts - shellcheck and shfmt
[[repos]]
repo = "https://github.com/shellcheck-py/shellcheck-py"

View file

@ -10,3 +10,10 @@ build-backend = "uv_build"
[tool.uv.sources]
dagger-io = { path = "sdk", editable = true }
[tool.ty.environment]
python-version = "3.13"
extra-paths = ["sdk/src"]
[tool.ty.src]
exclude = ["pulumi/", "containers/transmission-exporter/"]

View file

@ -5,7 +5,7 @@
#
# Fields:
# name - kebab-case service identifier
# type - argocd | ansible | nixos | fly
# type - argocd | ansible | nixos | fly | mise
# last-reviewed - date (YYYY-MM-DD) or null
# current-version - deployed version string or null
# upstream-source - URL to upstream releases/changelog
@ -184,8 +184,8 @@ services:
- name: miniflux
type: argocd
last-reviewed: 2026-03-02
current-version: "2.2.17"
last-reviewed: 2026-04-12
current-version: "2.2.19"
upstream-source: https://github.com/miniflux/v2/releases
- name: teslamate
@ -393,3 +393,40 @@ services:
current-version: "v1.14.1"
upstream-source: https://github.com/grafana/alloy/releases
notes: COPY --from in fly/Dockerfile for log shipping and metrics
# --- Mise-managed development tools ---
- name: dagger
type: mise
last-reviewed: 2026-04-12
current-version: "0.20.1"
upstream-source: https://github.com/dagger/dagger/releases
notes: Dagger CI/CD engine; pinned in mise.toml
- name: ansible-core
type: mise
last-reviewed: 2026-04-12
current-version: "2.20.1"
upstream-source: https://github.com/ansible/ansible/releases
notes: Installed via pipx/uvx with botocore and boto3
- name: prek
type: mise
last-reviewed: 2026-04-12
current-version: "0.3.4"
upstream-source: https://github.com/j178/prek/releases
notes: Pre-commit hook runner (Rust reimplementation)
- name: pulumi-cli
type: mise
last-reviewed: 2026-04-12
current-version: "3.215.0"
upstream-source: https://github.com/pulumi/pulumi/releases
notes: IaC CLI for tailscale and gandi stacks
- name: ty
type: mise
last-reviewed: 2026-04-12
current-version: "0.0.29"
upstream-source: https://github.com/astral-sh/ty/releases
notes: Astral Python typechecker (beta); prek hook

View file

@ -131,20 +131,26 @@ def alpine_runtime(
uid: int = 65534,
gid: int = 65534,
username: str = "app",
create_user: bool = True,
) -> dagger.Container:
"""Standard Alpine 3.22 runtime base with non-root user."""
"""Standard Alpine 3.22 runtime base.
When create_user is True (default), creates a non-root user with the given
uid/gid/username. Set create_user=False to use an existing user (e.g.
Alpine's built-in nobody:65534).
"""
packages = extra_apk or []
setup_cmds = []
if packages:
setup_cmds.append(f"apk add --no-cache {' '.join(packages)}")
if create_user:
setup_cmds.append(f"addgroup -g {gid} {username}")
setup_cmds.append(f"adduser -u {uid} -G {username} -D {username}")
return (
dag.container()
.from_("alpine:3.22")
.with_exec(["sh", "-c", " && ".join(setup_cmds)])
)
ctr = dag.container().from_("alpine:3.22")
if setup_cmds:
ctr = ctr.with_exec(["sh", "-c", " && ".join(setup_cmds)])
return ctr
def oci_labels(