Base Images — GitLab CI
This guide walks through a production-ready GitLab CI pipeline for a base-images repository.
Repository structure
Section titled “Repository structure”base-images/├── imglife.yaml # imglife configuration├── eol-data.yaml # EOL cache (managed by CI)├── README.md # status report (managed by CI)├── images/│ ├── alpine/│ │ └── Dockerfile.tmpl│ └── golang/│ └── Dockerfile.tmpl└── .gitlab-ci.ymlThe pipeline
Section titled “The pipeline”workflow: rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web"
variables: IMGLIFE_IMAGE: registry.gitlab.com/imglife-project/imglife:latest IMGLIFE_LOG_FORMAT: text
stages: - sync - build - eol-update - status - cleanup
# ── Sync mirrors ──────────────────────────────────────────────────────────────
sync: stage: sync image: $IMGLIFE_IMAGE script: - imglife sync rules: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web"
# ── Build base images ─────────────────────────────────────────────────────────# Option A: standard docker:dind (requires privileged runner)
build-dind: stage: build image: $IMGLIFE_IMAGE services: - docker:26-dind variables: DOCKER_TLS_CERTDIR: /certs before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" - docker buildx create --name imglife-builder --use --bootstrap script: - imglife build rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Option B: rootless BuildKit (no privileged runner)# Uncomment and configure BUILDKITD_ADDR in your runner environment
# build-rootless:# stage: build# image: $IMGLIFE_IMAGE# script:# - imglife build# variables:# DOCKER_HOST: unix:///run/buildkit/buildkitd.sock# IMGLIFE_BUILDER: "" # use default docker CLI# rules:# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# Option C: output-dir + Kaniko (no Docker socket required)# build-kaniko:# stage: build# image: $IMGLIFE_IMAGE# script:# - imglife build --output-dir /tmp/contexts# - |# for dir in /tmp/contexts/*/; do# [ -f "$dir/build.json" ] || continue# tag=$(jq -r .tag "$dir/build.json")# /kaniko/executor \# --context "dir://$dir" \# --destination "$tag"# done# rules:# - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# ── Refresh EOL data (weekly schedule recommended) ───────────────────────────
eol-update: stage: eol-update image: $IMGLIFE_IMAGE before_script: - git config user.email "ci-bot@$CI_SERVER_HOST" - git config user.name "CI Bot" - git remote set-url origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" script: - imglife eol update - | if git diff --quiet eol-data.yaml; then echo "EOL data unchanged — nothing to commit." else git add eol-data.yaml git commit -m "chore(lifecycle): update EOL data [skip ci]" git push origin HEAD:$CI_DEFAULT_BRANCH fi rules: - if: $CI_PIPELINE_SOURCE == "schedule"
# ── Generate status report ────────────────────────────────────────────────────
status: stage: status image: $IMGLIFE_IMAGE before_script: - git config user.email "ci-bot@$CI_SERVER_HOST" - git config user.name "CI Bot" - git remote set-url origin "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" script: - imglife status --output README.md - | if git diff --quiet README.md; then echo "Status unchanged — nothing to commit." else git add README.md git commit -m "chore(status): update image status report [skip ci]" git push origin HEAD:$CI_DEFAULT_BRANCH fi rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - if: $CI_PIPELINE_SOURCE == "schedule"
# ── Cleanup stale tags ────────────────────────────────────────────────────────
cleanup: stage: cleanup image: $IMGLIFE_IMAGE script: - imglife cleanup rules: - if: $CI_PIPELINE_SOURCE == "schedule" when: manual allow_failure: trueCI variables to configure
Section titled “CI variables to configure”In GitLab, go to Settings > CI/CD > Variables and add:
| Variable | Description | Protected | Masked |
|---|---|---|---|
GITLAB_TOKEN | GitLab PAT with api, read_packages, write_packages, read_registry, write_registry | Yes | Yes |
IMGLIFE_DOCKER_IO_USERNAME | Docker Hub username (to avoid rate limits) | No | No |
IMGLIFE_DOCKER_IO_PASSWORD | Docker Hub password or access token | Yes | Yes |
The CI job token (CI_JOB_TOKEN) is used automatically for the OCI registry. GITLAB_TOKEN is needed for git push (status/EOL commits) and Package Registry operations.
imglife.yaml for this pipeline
Section titled “imglife.yaml for this pipeline”registry: url: https://gitlab.example.com project_id: 42
sync: entries: - source: docker.io/library/alpine tag_regex: '^3\.\d+\.\d+$' keep_last: 3 target: $CI_REGISTRY/mirrors/alpine lifecycle: product: alpine extract: minor
- source: docker.io/library/golang tag_regex: '^1\.\d+\.\d+-alpine3\.\d+$' keep_last: 2 target: $CI_REGISTRY/mirrors/golang lifecycle: product: go extract: minor
build: core_version: "1.0.0" registry: $CI_REGISTRY/bases platforms: [linux/amd64, linux/arm64]
images: - name: alpine folder: images/alpine type: core mirror_image: $CI_REGISTRY/mirrors/alpine mirror_tag: "3.21.3" version: "3.21.3"
- name: golang folder: images/golang type: core mirror_image: $CI_REGISTRY/mirrors/golang mirror_tag: "1.22.3-alpine3.21" version: "1.22.3-alpine3.21"
retention: keep_last: 5 max_age_days: 90
lifecycle: eol_provider: endoflife eol_target: git eol_data_file: eol-data.yaml
status: client_zone: | Pipeline runs daily at 06:00 UTC. Contact `#platform-images` for questions.Scheduled pipeline
Section titled “Scheduled pipeline”Create a schedule in CI/CD > Schedules to run the pipeline daily or weekly:
- Daily at 06:00 UTC: runs
sync,eol-update,status - Weekly: runs
cleanup(or trigger manually after reviewing--listoutput)
Multi-arch setup
Section titled “Multi-arch setup”For multi-architecture builds with docker:dind, ensure your GitLab runner has QEMU registered:
before_script: - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - docker buildx create --name imglife-builder --use --bootstrapOr use a dedicated linux/arm64 runner added to the buildx builder — no QEMU required.