Skip to content

Base Images — GitLab CI

This guide walks through a production-ready GitLab CI pipeline for a base-images repository.

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.yml
.gitlab-ci.yml
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: true

In GitLab, go to Settings > CI/CD > Variables and add:

VariableDescriptionProtectedMasked
GITLAB_TOKENGitLab PAT with api, read_packages, write_packages, read_registry, write_registryYesYes
IMGLIFE_DOCKER_IO_USERNAMEDocker Hub username (to avoid rate limits)NoNo
IMGLIFE_DOCKER_IO_PASSWORDDocker Hub password or access tokenYesYes

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.

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.

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 --list output)

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 --bootstrap

Or use a dedicated linux/arm64 runner added to the buildx builder — no QEMU required.