ci: consume shared ci-python:3.14 image; bump runtime to 3.14

Migrate to the FabledRulebook CI-Runner contract:

- .forgejo/workflows/ci.yml: all four jobs (typecheck/lint/test/build)
  now schedule on the `python-ci` runner label and run inside
  container.image: git.fabledsword.com/bvandeusen/ci-python:3.14
  (Python 3.14 + Node 24 + ruff + uv + Docker CLI). Dropped the inline
  uv install in the test job — uv is now baked into the image.
- Dockerfile: production runtime bumped to python:3.14-slim so test
  results stay representative against what we ship.
- ci-requirements.md: new file at repo root declaring image deps and
  per-job installs (per FabledRulebook ci-runners.md).
- infra/Dockerfile.runner-base: deleted. The in-repo runner base
  (Ubuntu 24.04 + Python 3.12 + Node 22) is superseded by the shared
  ci-python image. The runner-host deployment files
  (runner-compose.yml + act-runner-config.yml) stay as deployment-shape
  documentation; source of truth is the deployed config.
- docs/development.md: CI/CD + Runner sections refreshed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 20:16:26 -04:00
parent 5d2d27c499
commit 3f1bcc3360
5 changed files with 76 additions and 60 deletions
+13 -5
View File
@@ -64,7 +64,9 @@ jobs:
name: TypeScript typecheck
# Skip on main merge-commit pushes — see workflow header comment.
if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v')
runs-on: ci-runner
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
steps:
- uses: actions/checkout@v6
@@ -86,11 +88,13 @@ jobs:
lint:
name: Python lint
if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v')
runs-on: ci-runner
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
steps:
- uses: actions/checkout@v6
# ruff is pre-installed in the ci-runner base image — no install
# ruff is pre-installed in the ci-python image — no install
# step needed, lint runs in ~2s.
- name: Lint
run: ruff check src/
@@ -98,7 +102,9 @@ jobs:
test:
name: Python tests
if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v')
runs-on: ci-runner
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
steps:
- uses: actions/checkout@v6
@@ -132,7 +138,9 @@ jobs:
# pushes skip here too, so no production image is ever built from a
# raw main push (only from the v* tag the release creates).
if: github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/v')
runs-on: ci-runner
runs-on: python-ci
container:
image: git.fabledsword.com/bvandeusen/ci-python:3.14
permissions:
contents: read
packages: write
+2 -1
View File
@@ -8,7 +8,8 @@ COPY frontend/ .
RUN npm run build
# Stage 2: Python runtime
FROM python:3.12-slim AS runtime
# Tracks CI image (ci-python:3.14) so test results stay representative.
FROM python:3.14-slim AS runtime
WORKDIR /app
COPY pyproject.toml .
+49
View File
@@ -0,0 +1,49 @@
# CI Requirements — FabledScribe
> Spec lives in [`docs/process.md`](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/process.md)
> in the CI-Runner repo.
## Runtime image
```
git.fabledsword.com/bvandeusen/ci-python:3.14
```
Used by all four jobs in `.forgejo/workflows/ci.yml`: typecheck (Vue/TS),
lint (ruff), test (pytest), build (docker buildx).
## Image deps used
- python 3.14
- node 24 (used for `npm ci` + `vue-tsc` in the typecheck job, and as the
frontend builder stage inside the production `Dockerfile`)
- ruff (lint job runs `ruff check src/` with zero install overhead)
- docker CLI + buildx (build job pushes the production image to the
Forgejo registry)
## Per-job tool installs
Anything CI installs at job time that isn't in the image. Promotion
candidates if more than one project needs them.
- `uv` — installed inline in the test job (`curl -LsSf
https://astral.sh/uv/install.sh | sh`). **Temporary**: belongs in the
ci-python image so every consumer doesn't re-install on cold start.
Tracked at [CI-Runner](https://git.fabledsword.com/bvandeusen/CI-runner).
- `http-ece` is `--no-build-isolation`-installed before the editable
package install because http-ece doesn't declare `setuptools` as a
build dep and uv creates bare venvs without it. Not promotion-worthy
(one project, one wheel).
## Notes
- Production runtime image (`Dockerfile`) also tracks Python 3.14 — the
CI image and runtime image stay aligned by design so test results are
representative.
- Build wall time: dominated by `pytest` (full async test suite). Cold
ci-python pulls add ~30s; not a blocker.
- Registry-backed BuildKit layer cache (`type=registry,ref=…:cache,mode=max`)
gives ~80% speedup on warm builds — see the build job comment.
- `pyproject.toml` keeps `requires-python = ">=3.12"` permissive so the
package can still be installed against older interpreters by external
consumers, but the CI/runtime target is 3.14.
+12 -6
View File
@@ -57,7 +57,9 @@ Migration conventions:
### Pipeline
CI runs on Forgejo Actions with a custom runner base image (`py3.12-node22`):
CI runs on Forgejo Actions, consuming the shared
[`ci-python:3.14`](https://git.fabledsword.com/bvandeusen/CI-runner) image
via `container.image` (Python 3.14 + Node 24 + ruff + uv + Docker CLI):
| Trigger | Jobs | Docker tags pushed |
|---------|------|--------------------|
@@ -77,13 +79,17 @@ CI runs on Forgejo Actions with a custom runner base image (`py3.12-node22`):
git checkout dev && git merge main && git push origin dev
```
### Custom Runner
### Runner
Runner base image: `infra/Dockerfile.runner-base` (Ubuntu 24.04 + Python 3.12 + Node 22 LTS).
Runner config: `infra/act-runner-config.yml` (label: `py3.12-node22`).
Runner compose: `infra/runner-compose.yml`.
CI jobs schedule against the `python-ci` runner label and run inside the
shared `git.fabledsword.com/bvandeusen/ci-python:3.14` image (see
`ci-requirements.md` for what this project relies on from the image).
The runner deployment lives outside this repo; image bumps happen in
[CI-Runner](https://git.fabledsword.com/bvandeusen/CI-runner) via Renovate.
To activate a new runner registration, copy `infra/act-runner-config.yml` to the runner's config directory, delete the `.runner` registration file in the runner container, and restart the stack.
`infra/runner-compose.yml` + `infra/act-runner-config.yml` document the
runner-host deployment shape; the source of truth is the deployed
config on the runner host.
### Docker Registry
-48
View File
@@ -1,48 +0,0 @@
# Runner base image for Forgejo act_runner job containers.
# Pre-installs Python 3.12, Node 22, uv, and ruff so workflows skip
# lengthy runtime installs on every run.
#
# Build and push (re-run when this file changes):
# docker build -f infra/Dockerfile.runner-base \
# -t git.fabledsword.com/bvandeusen/runner-base:ci-runner .
# docker push git.fabledsword.com/bvandeusen/runner-base:ci-runner
#
# Then redeploy the act_runner stack in Portainer to pick up the new image.
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=UTC
# Split into separate RUN steps so each pushed layer is smaller — one
# combined layer exceeds the nginx upload timeout on the self-hosted
# Forgejo registry.
# Python 3.12 (ships in Ubuntu 24.04 main repos) + core tools
RUN apt-get update -qq && \
apt-get install -y -qq \
python3.12 python3.12-dev python3.12-venv \
git curl ca-certificates gnupg jq tzdata && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Node 22 LTS via NodeSource
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y -qq nodejs && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Docker CLI — daemon comes from the host socket mount, only CLI needed.
# docker.io from Ubuntu repos is sufficient for docker login + buildx.
RUN apt-get update -qq && \
apt-get install -y -qq docker.io && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# uv — fast Python package installer/resolver (~10x faster than pip).
# Used by test jobs instead of pip for venv creation + dep installs.
RUN curl -LsSf https://astral.sh/uv/install.sh | env INSTALLER_NO_MODIFY_PATH=1 sh && \
mv /root/.local/bin/uv /usr/local/bin/ && \
mv /root/.local/bin/uvx /usr/local/bin/
# ruff — Python linter. Baked in so the lint job is just
# `ruff check src/` with zero install overhead.
RUN curl -LsSf https://astral.sh/ruff/install.sh | env INSTALLER_NO_MODIFY_PATH=1 sh && \
mv /root/.local/bin/ruff /usr/local/bin/