Shared CI tooling + base images
  • Python 52%
  • Shell 25.6%
  • Dockerfile 22.4%
Find a file
statevault 4f7d3ccf70 docs(readme): catalog the base images + canonical pipeline shape
README was still describing the pre-launch "vendored templates vs
include:" design discussion. Replaces with the post-2026-05-13
reality: 4 base images, ci-lint + bao-checks + bump.sh tooling,
canonical kaniko+sm-zot pattern, :v1 pinning rule, pitfalls.

Cross-references the new aienv runbooks
(setup-new-loco-pipeline, bump-loco-base-image,
debug-pipeline-failure) and the 4 new memory entries documenting
the same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:38:19 +02:00
.woodpecker feat(python313): add 3.13 variant of shared-python-base 2026-05-13 13:15:22 +02:00
containers feat(python313): add 3.13 variant of shared-python-base 2026-05-13 13:15:22 +02:00
schema feat(schema): vendor v1.json + accept '+' wildcard in path globs 2026-05-01 18:45:44 +02:00
scripts feat(ci-lint): drift-check consumer ci.yml against bundled template 2026-05-13 11:59:07 +02:00
templates feat(templates): canonical Python CI shape 2026-05-13 11:45:08 +02:00
.gitignore feat: initial ci-shared bootstrap 2026-05-01 16:22:54 +02:00
build.sh feat(ci): add release.yml for tag-triggered Kaniko build 2026-05-04 13:05:47 +02:00
Containerfile feat(ci-lint): drift-check consumer ci.yml against bundled template 2026-05-13 11:59:07 +02:00
README.md docs(readme): catalog the base images + canonical pipeline shape 2026-05-13 13:38:19 +02:00

ci-shared

Shared CI tooling for every loco/* workload. Carries the per-language base images, the bao tooling, the version bumper, and the CI-template drift-checker.

What's in here

ci-shared/
├── Containerfile                       # builds ci-shared:v1.X.Y (alpine + bao+release tools)
├── containers/
│   ├── rust/Containerfile              # → shared-rust-base
│   ├── python/Containerfile            # → shared-python-base (3.12)
│   ├── python-3.13/Containerfile       # → shared-python-base-3.13
│   └── go/Containerfile                # → shared-go-base
├── scripts/
│   ├── bump.sh                         # Conventional-Commits version bumper
│   ├── bao-manifest-check              # validate bao.yml against the v1 schema
│   ├── bao-lint                        # forbidden-pattern grep linter
│   ├── ci-lint                         # drift-check ci.yml against templates/python-ci.yml
│   └── reverse-coverage                # nightly bao.yml ↔ policies drift
├── templates/
│   ├── ci.example.yml                  # generic single-workflow starter
│   └── python-ci.yml                   # canonical Python CI shape
├── schema/
│   └── v1.json                         # frozen bao-manifest schema
└── .woodpecker/
    ├── release.yml                     # ci-shared:v1.X.Y on `vX.Y.Z` tags
    ├── release-rust-base.yml           # on `base-rust-vX.Y.Z` tags
    ├── release-python-base.yml         # on `base-python-vX.Y.Z` tags
    ├── release-python-3.13-base.yml    # on `base-python-3.13-vX.Y.Z` tags
    └── release-go-base.yml             # on `base-go-vX.Y.Z` tags

Images in the loco registry

Image Tag prefix FROM Pre-installed
loco/shared-ci-shared vX.Y.Z alpine:3.21 bash, curl, git, py3-yaml/jsonschema + the 5 scripts above + schema + templates
loco/shared-rust-base base-rust-vX.Y.Z rust:1.93-slim clang, mold, libssl/libsqlite/pkg-config, sccache 0.10.0 (sha-verified), jq
loco/shared-python-base base-python-vX.Y.Z python:3.12-slim pyflakes, ruff, pip-audit, pytest, build, build-essential, libffi/libssl/libsqlite-dev, jq
loco/shared-python-base-3.13 base-python-3.13-vX.Y.Z python:3.13-slim same as 3.12 variant
loco/shared-go-base base-go-vX.Y.Z golang:1.25-alpine git, gcc, musl-dev, make, helm v3.16.4, jq

Consumers always pin :v<MAJOR> (e.g. :v1), never an exact patch. The Forgejo registry has a "keep last 3 versions" retention; exact pins break on the next prune. See aienv memory feedback_forgejo_package_keep3.

Release flow

Each image is its own release pipeline gated on its tag prefix — they're independent. Bump one without affecting the others.

# bump ci-shared:v1.x.y itself (alpine image)
git tag -a v1.0.10 -m "..."
git push origin v1.0.10

# bump a base image
git tag -a base-rust-v1.0.2 -m "..."
git push origin base-rust-v1.0.2

Full procedure + caveats: ~/aienv/docs/runbooks/bump-loco-base-image.md.

How consumers wire it up

For a fresh consumer: ~/aienv/docs/runbooks/setup-new-loco-pipeline.md.

In short, every consumer's .woodpecker/ci.yml:

  • pins shared-ci-shared:v1 for the bao-checks, bump-prepare, bump-push steps
  • pins the relevant shared-{lang}-base:v1 for lint/audit/test steps
  • uses the canonical kaniko+sm-zot pattern in build-image (pin v1.20.0-debug, sm-zot cache + mirror, --compressed-caching=false)

The canonical Python step shape lives at templates/python-ci.yml. Run ci-lint .woodpecker/ci.yml (against the bundled template) to spot drift.

Pitfalls

  • Don't push 3+ base tags in rapid succession. Hit a Woodpecker DB unique-constraint race once (2026-05-13, base-python-v1.0.0 got stuck in error with empty steps). Either space the tag pushes by ~30s or, if you get stuck, nudge the file's content hash on a new commit and retag with the next patch. See the pinned commit ea55fbd for the dodge.
  • kaniko 1.22+ breaks sm-zot. All release pipelines pin executor:v1.20.0-debug. Don't "modernise" without first upgrading sm-zot.
  • Project-specific Python libs don't go in the base image. Each consumer pip-installs its own requirements.txt. shared-python-base carries lint/audit/test tooling + system headers only.

Cross-references

  • aienv runbook: setup-new-loco-pipeline.md
  • aienv runbook: bump-loco-base-image.md
  • aienv runbook: debug-pipeline-failure.md
  • aienv memory: reference_loco_shared_base_images
  • aienv memory: reference_kaniko_sm_zot_pattern
  • aienv memory: feedback_forgejo_package_keep3
  • aienv memory: feedback_kaniko_sm_zot_cache_pin