Docker-in-Docker
glci has comprehensive support for CI pipelines that use Docker-in-Docker services (docker:dind).
Automatic configuration#
When a job uses DinD services, glci automatically:
- Mounts the Docker socket for DinD service containers
- Injects
buildkitd.tomlmarking the mock registry as insecure (HTTP) so buildx builders trust it - Injects
daemon.jsonwithinsecure-registriesso DinD’sdockerdtrusts the mock registry - Handles TLS setup — waits for DinD to generate TLS client certs, configures
DOCKER_CERT_PATH,DOCKER_TLS_VERIFY, andDOCKER_HOSTwith the correct TLS port - Runs a daemon readiness gate — polls
docker infountil DinD is fully accepting connections, eliminating races where cert files exist but the daemon has not finished TLS initialization - Fixes architecture mismatches — e.g. CI downloads amd64 buildx binary but container runs arm64
- Registers QEMU binfmt handlers for cross-platform builds (one-time, idempotent)
- Configures pull-through mirroring — each pipeline’s DinD acts as a pull-through mirror of the embedded registry, so images pulled in one pipeline are cached for the next
- Wraps
docker buildx createto inject--config(registry trust) and--driver-opt network=(pipeline DNS) so custom builders can reach the embedded registry - Clears stale buildx state — removes
~/.docker/buildx/inside the job container to prevent the docker image’s default builder config from conflicting with DinD TLS
Isolation model#
Each pipeline gets its own ephemeral /var/lib/docker — no shared mount, no mutex, no serialization. Concurrent DinD pipelines run in parallel without interference.
Per-job DinD networks#
Each DinD job within a pipeline gets its own Docker network (glci-net-{pipelineID}-job-{jobID}). This prevents a critical issue: when multiple DinD jobs run concurrently, they would all register a docker service alias. Docker’s embedded DNS would round-robin between the DinD daemons, causing TLS cert verification failures and unpredictable behavior. Per-job networks ensure each job’s docker hostname resolves to exactly one DinD instance.
The mock server container is attached to each per-job network so the DinD daemon can reach the embedded registry.
Each job’s docker hostname resolves to its own DinD instance — no cross-talk, no DNS round-robin. The single glci-mock container is attached to all per-job networks simultaneously so every DinD daemon can pull from and push to the embedded registry.
TLS and DinD#
glci handles the full DinD TLS lifecycle automatically. Here is what happens when a job with DOCKER_TLS_CERTDIR: "/certs" starts:
- DinD service container starts generating TLS certificates at
/certs - glci’s pre-build script polls for
ca.pem,cert.pem, andkey.pemto appear (up to 30 seconds) - Once certs are found, the script sets
DOCKER_CERT_PATH,DOCKER_TLS_VERIFY=1, and fixesDOCKER_HOSTto use port 2376 (TLS) - A readiness gate polls
docker infountil the daemon accepts TLS connections - Job scripts can then use
dockercommands normally
This matches the standard GitLab.com DinD setup where DOCKER_TLS_CERTDIR=/certs.
Buildx and BuildKit#
glci wraps docker buildx create to transparently inject configuration that makes buildx work with the mock registry and pipeline network.
What glci injects#
| Configuration | Value | Purpose |
|---|---|---|
--config | /glci/buildkitd.toml | Marks the mock registry as HTTP + insecure |
--driver-opt network= | Pipeline network name | Gives the builder DNS access to glci-mock |
| Fallback for DinD | --driver-opt network=host | Inside DinD, the per-job network does not exist on the inner Docker daemon, so the builder shares the DinD container’s network namespace |
The wrapper is injected via pre_build_script in the runner config. It intercepts docker buildx create commands (and docker build --builder) and adds the required flags, leaving all user-specified flags intact.
Stale buildx state#
The docker:* images ship with ~/.docker/buildx/current pointing to unix:///var/run/docker.sock. When DOCKER_HOST is tcp://docker:2376 (DinD with TLS), the mismatch causes buildx to auto-create a builder with the docker-container driver, which spins up a separate BuildKit container that cannot verify the DinD TLS certificates. glci’s pre-build script removes ~/.docker/buildx/ to let buildx initialize fresh from DOCKER_HOST.
QEMU and cross-platform builds#
glci automatically registers QEMU binfmt handlers the first time a DinD job runs. This enables buildx to build images for architectures other than the host (e.g. amd64 on Apple Silicon, arm64 on amd64):
# What glci runs automatically (idempotent, cached after first success):
docker run --rm --privileged tonistiigi/binfmt --install all
If QEMU registration fails, the daemon logs a warning and cross-arch builds may fail. You can run the command manually to fix it.
Build layer cache#
glci does not inject cache flags automatically. To enable BuildKit layer caching across pipelines, use --cache-from / --cache-to with the embedded registry as the cache backend. The registry volume persists across daemon restarts, so cached layers survive between pipelines. No additional trust configuration is needed – the registry is already trusted by buildx via the injected buildkitd.toml.
docker-build:
image: docker:27
services: [docker:27-dind]
variables:
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker buildx create --use
- docker buildx build
--cache-from "type=registry,ref=$CI_REGISTRY_IMAGE/cache:latest"
--cache-to "type=registry,ref=$CI_REGISTRY_IMAGE/cache:latest,mode=max"
-t "$CI_REGISTRY_IMAGE:latest"
--push .
Standard DinD pipelines (basic builds, multi-platform builds, no-TLS builds) work the same as on GitLab CI with no glci-specific changes needed. QEMU binfmt handlers are registered automatically for cross-platform builds.
Troubleshooting DinD#
docker: command not found#
The job container needs the Docker CLI installed. Use a docker:* image or install the CLI in your Dockerfile.
Cannot connect to the Docker daemon#
| Cause | Fix |
|---|---|
| DinD service not started yet | glci’s pre-build script includes a readiness gate. If it still fails, increase the timeout or add a manual wait. |
Wrong DOCKER_HOST | Check if DOCKER_TLS_CERTDIR is set. With TLS, DOCKER_HOST should be tcp://docker:2376. Without TLS, tcp://docker:2375. glci’s pre-build script handles this automatically. |
| DinD container crashed | Check glci daemon logs for DinD container errors. |
x509: certificate signed by unknown authority#
The DinD daemon or buildx does not trust the mock registry.
| Context | What glci does | What to check |
|---|---|---|
docker push/pull | daemon.json with insecure-registries | Check glci daemon logs for “could not generate daemon.json” |
docker buildx build | buildkitd.toml with http = true | Check glci daemon logs for “could not generate buildkitd config” |
Restarting the daemon usually fixes this: glci daemon stop --force && glci daemon start.
exec format error#
The image architecture does not match the host. QEMU binfmt handlers may not be registered:
# Register manually
docker run --privileged --rm tonistiigi/binfmt --install all
On Apple Silicon with Colima, use Rosetta for better amd64 emulation:
colima stop
colima delete
colima start --vm-type=vz --vz-rosetta --cpu 12 --memory 16
Concurrent DinD jobs interfere with each other#
This should not happen with glci’s per-job network isolation. If it does, check that the per-job networks are being created:
docker network ls --filter name=glci-net-
Each DinD job should have its own glci-net-{pipelineID}-job-{jobID} network.
How it differs from production GitLab CI#
| Aspect | glci | Production GitLab |
|---|---|---|
DinD /var/lib/docker | Ephemeral per pipeline | Ephemeral per job |
| Registry trust | daemon.json + buildkitd.toml injected | Registry uses the same CA as the runner |
| QEMU binfmt | Registered on host Docker daemon | Pre-configured on runner hosts |
| Buildx wrapper | Injected via pre_build_script | Not needed (standard Docker setup) |
| Network isolation | Per-job Docker network for DinD | Runner manages networking |
| TLS setup | Pre-build script with cert wait + readiness gate | DinD service entrypoint handles it |