Files
python-repositories/docs/ci-image.md
T
Brian Bjarke JensenandCursor 3b05260323
Code Quality Pipeline / code-quality (pull_request) Failing after 1s
Test Python Package / unit-tests (pull_request) Failing after 0s
Test Python Package / integration-tests (pull_request) Failing after 0s
PR Title Check / check-title (pull_request) Successful in 6s
Test Python Package / coverage-report (pull_request) Has been skipped
Add nightly CI base image and migrate workflows to python-repositories-ci.
Pre-build Python, uv, and locked deps in a Gitea container image so daily jobs skip repeated bootstrap; document runner registry auth and ci-bot credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 22:21:22 +02:00

5.0 KiB

CI base image

This repository uses a per-repo Docker image for Gitea Actions jobs instead of installing Python and uv on every run. Infrastructure images (redis, minio, etc.) are cached cluster-wide via Harbor (see homelab-platform docs/harbor-registry-mirror.md).

Image contents

Built from docker/ci/Dockerfile:

  • Node.js 20 (required by act_runner job containers)
  • Python 3.12 (installed via uv python install) and pinned uv 0.7.0
  • Docker CLI (integration tests via testcontainers)
  • Dev dependencies from uv.lock (uv sync --all-extras --no-install-project)

Published to the Gitea container registry:

  • gitea.lille-vemmelund.dk/brian/python-repositories-ci:latest
  • gitea.lille-vemmelund.dk/brian/python-repositories-ci:YYYYMMDDHHmm (timestamped rollback tag)

Rebuild triggers

ci-image.yml runs on:

  • Nightly cron (0 2 * * * UTC)
  • Manual workflow_dispatch
  • Push to main when pyproject.toml, uv.lock, or docker/ci/** change

Workflow usage

Python jobs use runs-on: python-repositories-ci and a fast incremental sync:

runs-on: python-repositories-ci
steps:
  - uses: actions/checkout@v4
  - name: Sync dependencies
    env:
      UV_LINK_MODE: copy
      UV_INDEX_GITEA_USERNAME: ci-bot
      UV_INDEX_GITEA_PASSWORD: ${{ secrets.CI_RUNNER_TOKEN }}
    run: uv sync --all-extras --frozen

ci-image.yml uses runs-on: ubuntu-latest so it can bootstrap before the custom image exists.

Registry authentication

act_runner pulls the job image before any workflow step runs, so a docker login step inside a job cannot authenticate that pull. Authentication must be configured on the runner (or via a job-level container.credentials block — not used here).

k8s Gitea runners (automatic)

The homelab-platform runners mount Gitea registry credentials automatically:

No manual docker login is required on the in-cluster runners once the secret exists. To create or rotate credentials:

export KUBECONFIG=/path/to/homelab-cluster/talos/_out/kubeconfig
export GITEA_REGISTRY_USERNAME='ci-bot'
export GITEA_REGISTRY_PASSWORD='personal-access-token-with-read-package'
bash scripts/create-gitea-registry-secret.sh

Then restart runner pods so they pick up the updated secret. See homelab-platform docs/gitea-actions-runners.md for full runner setup.

Verify on a running pod:

kubectl -n gitea-runners exec homelab-cluster-gitea-runner-0 -c runner -- \
  test -f /root/.docker/config.json && echo "registry auth mounted"

Standalone runners (e.g. Unraid homelab)

The 4th runner is outside the k8s cluster and does not get the automatic mount. Configure registry auth manually on that host:

docker login gitea.lille-vemmelund.dk -u ci-bot -p <token>

Also add the python-repositories-ci label to that runner's act_runner config.

ci-image.yml push login

The build workflow still runs docker login before docker push. That step authenticates DinD inside the job for pushing the image to Gitea — a different code path from act_runner pulling the job container.

Runner label

Jobs use the python-repositories-ci act_runner label, configured in homelab-platform platform/gitea-runners/values.yaml:

python-repositories-ci:docker://gitea.lille-vemmelund.dk/brian/python-repositories-ci:latest

After label changes, roll runner pods so they re-register with Gitea.

Bootstrap order

  1. Ensure homelab-platform runners have gitea-registry-dockerconfig and the python-repositories-ci label (see homelab-platform docs).
  2. Merge ci-image.yml, docker/ci/Dockerfile, and workflow migrations to main.
  3. Run Build CI Image manually once (Actions → Build CI Image → Run workflow).
  4. Confirm a test workflow job starts on python-repositories-ci.

Until step 3 completes, jobs targeting python-repositories-ci will fail because the image does not exist in the Gitea registry yet.

Local build

echo "$CI_RUNNER_TOKEN" > /tmp/uv_token
docker build -f docker/ci/Dockerfile \
  --secret id=uv_token,src=/tmp/uv_token \
  -t python-repositories-ci:local .
rm -f /tmp/uv_token