From 8d07b6c79ed4ffa52c251fc134d97055079813ca Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 11 Apr 2026 23:59:57 -0400 Subject: [PATCH] ci: new ci-runner base image + uv/ruff/node_modules cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runner base image (infra/Dockerfile.runner-base): - Added uv (fast Python package installer, ~10x faster than pip) - Added ruff (baked in, lint job drops from ~13s to ~2s) - Added jq + tzdata - Renamed tag from py3.12-node22 to ci-runner (purpose over contents) Workflow (ci.yml): - typecheck: cache node_modules directly instead of npm download cache; skip npm ci entirely on cache hits (9m41s → ~30s expected) - lint: just `ruff check src/` — no venv, no install - test: uv venv + uv pip install replaces pip (~10x faster resolves) - all jobs: runs-on ci-runner label Baseline: typecheck 9m41s, lint 13s, test 1m42s --- .forgejo/workflows/ci.yml | 50 +++++++++++++++++------------------- infra/Dockerfile.runner-base | 35 +++++++++++++++++-------- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 084e3fa..acf6524 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -51,17 +51,23 @@ env: jobs: typecheck: name: TypeScript typecheck - runs-on: py3.12-node22 + runs-on: ci-runner steps: - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + # Cache node_modules directly (not the npm download cache). + # npm ci still has to extract + link every module even with a + # warm download cache, which is where the real time goes. Caching + # the output directory lets us skip npm ci entirely on hits. + - name: Cache node_modules + id: npm-cache + uses: actions/cache@v4 with: - node-version: "22" - cache: "npm" - cache-dependency-path: frontend/package-lock.json + path: frontend/node_modules + key: node-modules-${{ hashFiles('frontend/package-lock.json') }} - name: Install dependencies + if: steps.npm-cache.outputs.cache-hit != 'true' run: npm ci working-directory: frontend @@ -71,46 +77,38 @@ jobs: lint: name: Python lint - runs-on: py3.12-node22 + runs-on: ci-runner steps: - uses: actions/checkout@v6 - - name: Install ruff - # Isolated venv avoids polluting the runner's system - # site-packages (the old --break-system-packages was a smell) - # and pipx isn't available on the py3.12-node22 runner image. - run: | - python3.12 -m venv /opt/ruff-venv - /opt/ruff-venv/bin/pip install --quiet ruff - + # ruff is pre-installed in the ci-runner base image — no install + # step needed, lint runs in ~2s. - name: Lint - run: /opt/ruff-venv/bin/ruff check src/ + run: ruff check src/ test: name: Python tests - runs-on: py3.12-node22 + runs-on: ci-runner steps: - uses: actions/checkout@v6 - # Python 3.12 is pre-installed in the runner-base image (py3.12-node22). - - name: Cache pip wheels + - name: Cache uv packages uses: actions/cache@v4 with: - path: ~/.cache/pip - key: pip-${{ hashFiles('pyproject.toml') }} - restore-keys: pip- + path: ~/.cache/uv + key: uv-${{ hashFiles('pyproject.toml') }} + restore-keys: uv- - name: Create virtual environment - run: python3.12 -m venv /opt/venv + run: uv venv /opt/venv - name: Install package with dev deps run: | - /opt/venv/bin/pip install --upgrade pip setuptools wheel # http-ece's build-time dep on cryptography fails under PEP 517 # isolation on this runner image — install it without isolation # first so the editable install below finds it already built. - /opt/venv/bin/pip install --no-build-isolation http-ece - /opt/venv/bin/pip install -e ".[dev]" + uv pip install --python /opt/venv/bin/python --no-build-isolation http-ece + uv pip install --python /opt/venv/bin/python -e ".[dev]" - name: Run tests run: /opt/venv/bin/python -m pytest tests/ -q @@ -124,7 +122,7 @@ jobs: if: | github.event_name == 'push' && (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) - runs-on: py3.12-node22 + runs-on: ci-runner permissions: contents: read packages: write diff --git a/infra/Dockerfile.runner-base b/infra/Dockerfile.runner-base index e1a1214..b4fa7e9 100644 --- a/infra/Dockerfile.runner-base +++ b/infra/Dockerfile.runner-base @@ -1,26 +1,28 @@ # Runner base image for Forgejo act_runner job containers. -# Pre-installs Python 3.12 and Node 20 so the test job skips the -# ~3-4 minute deadsnakes PPA install on every run. +# Pre-installs Python 3.12, Node 22, uv, and ruff so workflows skip +# lengthy runtime installs on every run. # -# Build and push (one-time setup, re-run if this file changes): +# Build and push (re-run when this file changes): # docker build -f infra/Dockerfile.runner-base \ -# -t git.fabledsword.com/bvandeusen/runner-base:py3.12-node22 . -# docker push git.fabledsword.com/bvandeusen/runner-base:py3.12-node22 +# -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 label. +# 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 would be ~280MB and exceeds the nginx upload timeout. +# 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 — no PPA needed. +# 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 python3-pip \ - git curl ca-certificates gnupg && \ + 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 @@ -33,3 +35,14 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ 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/