CI image builds
Building container images in CI
Section titled “Building container images in CI”Build container images with plain docker build in docker-in-docker, pinned to
the Incus runners with tags: [dind]. Those are the only runners where a
privileged build is safe — their gitlab-runner sits inside an unprivileged Incus
container, so the build is namespaced and cannot reach the host; the unprivileged
bare-metal runners no longer carry the dind tag. Rationale:
gitlab-runners.md.
build:image: tags: [dind] image: docker:28 services: - docker:28-dind before_script: - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" "$CI_REGISTRY" --password-stdin script: - docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" . - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"The runners set DOCKER_BUILDKIT=1, so BuildKit features work out of the box:
- Secrets (private package index, etc.):
docker build --secret id=TOKENwithRUN --mount=type=secret,id=TOKENin the Dockerfile — credentials never land in the image. - Cache:
--cache-from "$IMAGE" --build-arg BUILDKIT_INLINE_CACHE=1. - Multi-stage target:
--target STAGE.
Don’t reach for kaniko or a standalone buildctl job — plain docker build on
the dind runners covers it.
Any job that runs containers
Section titled “Any job that runs containers”The same applies to any job that needs a live Docker daemon — a docker compose
stack for end-to-end tests, for example: give it tags: [dind] so it lands on
the Incus runners. A job that uses services: docker:dind without the tag may be
scheduled onto an unprivileged runner where the dind service cannot start.