Files
python-repositories/docs/ci-image.md
T
Brian Bjarke JensenandCursor 33efbd1005
PR Title Check / check-title (pull_request) Successful in 6s
Test Python Package / integration-tests (pull_request) Failing after 30s
Code Quality Pipeline / code-quality (pull_request) Failing after 1m12s
Test Python Package / unit-tests (pull_request) Failing after 1m28s
Test Python Package / coverage-report (pull_request) Has been skipped
Update CI registry owner from brian to lille-vemmelund.
Align CI image paths, package index URLs, and lockfile entries with the new Gitea owner name.

Co-authored-by: Cursor <[email protected]>
2026-07-10 09:32:39 +02:00

5.9 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 from .python-version) 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/lille-vemmelund/python-repositories-ci:latest
  • gitea.lille-vemmelund.dk/lille-vemmelund/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/lille-vemmelund/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. Seed the registry with a first image (see below).
  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.

Seed the image

After merge to main: Actions → Build CI ImageRun workflow.

Before merge (e.g. PR branch): the workflow file is not on main yet — build and push locally with scripts/ci/build-ci-image.sh:

export CI_RUNNER_TOKEN='ci-bot-personal-access-token'
bash scripts/ci/build-ci-image.sh --push

Run from the repo root on the branch you want to test. Runners pull gitea.lille-vemmelund.dk/lille-vemmelund/python-repositories-ci:latest from the registry; they do not care which git branch built it.

Override registry settings if needed:

export REGISTRY=gitea.lille-vemmelund.dk
export REGISTRY_USER=ci-bot
export CI_RUNNER_TOKEN='...'
bash scripts/ci/build-ci-image.sh --push

Local build

Build only (no registry login or push):

export CI_RUNNER_TOKEN='ci-bot-personal-access-token'
bash scripts/ci/build-ci-image.sh --local

Or manually:

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