Skip to content

CI base images

TruePPM’s CI runs its Python-dependent jobs against three baked base images rather than installing system packages and dependency wheels on every pipeline. The images pre-bake libpq-dev, gcc, and all dev-dependency wheels, so the per-job pip install -e is a fast editable re-link with no apt-get and no wheel downloads — this shaves roughly three minutes off each of the API jobs (and the same cold-install cost off web:integration).

This page is for maintainers of the TruePPM GitLab project — it concerns the project’s own container registry, not a self-hosted TruePPM deployment.

ImageBuilt byDockerfile
registry.gitlab.com/trueppm/trueppm/ci-api:py3.11ci:build-api-image.gitlab/ci-images/api.Dockerfile
registry.gitlab.com/trueppm/trueppm/ci-scheduler:py3.11ci:build-scheduler-image.gitlab/ci-images/scheduler.Dockerfile
registry.gitlab.com/trueppm/trueppm/ci-integration:nobleci:build-integration-image.gitlab/ci-images/integration.Dockerfile

All three are rebuilt when their Dockerfile or the relevant pyproject.toml changes, plus on the weekly scheduled pipeline so the baked wheels stay current with security updates. The ci-integration:noble image layers Python, libpq-dev, gcc, and the scheduler/API dev dependencies on top of the Playwright base image; it is used by the web:integration job.

Every rebuild pushes a new image SHA under the same :py3.11 tag, which leaves the previously-tagged SHA in the registry as an untagged layer. Nothing reaps those untagged layers automatically, so they accumulate indefinitely. At one scheduled rebuild per week per image — plus pyproject.toml churn — the registry is tens of gigabytes deep within a single release cycle.

It is also a quiet supply-chain risk: a stale untagged SHA that a long-running MR pipeline still references can pin a vulnerable transitive dependency that the latest :py3.11 tag has since dropped.

GitLab’s per-project container registry cleanup policy solves both problems by periodically deleting untagged images while keeping the most recent tagged ones. The policy is currently disabled on the project; enable it with these settings:

SettingValueEffect
cadence1dRun the cleanup daily
enabledtrueTurn the policy on
keep_n10Keep the 10 most recent matching images per repository
older_than7dOnly remove images older than 7 days
name_regex_delete.*Consider every tag for deletion…
name_regex_keep`(py3.11noble)`

older_than: 7d is deliberately generous so an in-flight MR pipeline that pinned a now-untagged SHA has a week to finish before that layer is reaped.

Set it through the API with glab:

Terminal window
glab api --method PUT projects/trueppm%2Ftrueppm \
-f 'container_expiration_policy_attributes[cadence]=1d' \
-f 'container_expiration_policy_attributes[enabled]=true' \
-f 'container_expiration_policy_attributes[keep_n]=10' \
-f 'container_expiration_policy_attributes[older_than]=7d' \
-f 'container_expiration_policy_attributes[name_regex_delete]=.*' \
-f 'container_expiration_policy_attributes[name_regex_keep]=(py3\.11|noble)'

The same settings are available in the GitLab UI under Settings → Packages and registries → Clean up image tags.

Terminal window
# 1. Confirm the policy is enabled with the expected settings.
glab api projects/trueppm%2Ftrueppm | jq .container_expiration_policy
# 2. Confirm a run is scheduled — next_run_at should be in the future.
glab api projects/trueppm%2Ftrueppm \
| jq '.container_expiration_policy.next_run_at'

After the next scheduled run (or after manually re-running ci:build-api-image), the prior image SHA should show as untagged in the registry UI, and untagged layers older than the older_than window should be gone.

  • Contributing guide — branching, commits, testing.
  • Release process — version bump, changelog, tag, publish.
  • The image build jobs live in .gitlab-ci.yml (ci:build-api-image, ci:build-scheduler-image, ci:build-integration-image) with their Dockerfiles under .gitlab/ci-images/.