GitLab CI & Docker-in-Docker
The Point
Running docker build inside GitLab CI does not work out of the box – the job itself already runs in a container with no Docker daemon. You need Docker-in-Docker (DinD) as a sidecar service to provide the daemon.
Explanation
Overall structure
1stages:
2 - test # runs first
3 - build # runs after (only if all test jobs pass)Jobs in the same stage run in parallel; stages run sequentially.
run_tests job – typical job structure
1run_tests:
2 stage: test
3 image: python:3.11-slim # set up container based on this image where job runs
4 before_script:
5 - apt-get update && apt-get install -y make gcc python3-dev
6 script:
7 - make testimage: which Docker image to use as the execution environment for this jobbefore_script: runs before everyscript, good for dependency installationscript: the main commands to execute
build_image job – Docker-specific details
1build_image:
2 stage: build
3 image: docker:20.10.16 # Docker CLI
4 services:
5 - docker:20.10.16-dind # Docker daemon (sidecar)
6 variables:
7 DOCKER_TLS_CERTDIR: "/certs" # enable TLS for secure communication
8 before_script:
9 - docker login -u $REGISTRY_USER -p $REGISTRY_PASS
10 script:
11 - docker build -t $IMAGE_NAME:$IMAGE_TAG .
12 - docker push $IMAGE_NAME:$IMAGE_TAGWhy do you need services: docker:dind?
Each GitLab CI job runs inside a container. That container has no Docker daemon by default, so docker build fails.
services is GitLab CI’s sidecar mechanism – it starts an extra container alongside the job container, both on the same network. docker:dind is an image with a built-in Docker daemon, designed for exactly this:
job container (docker:20.10.16, has CLI)
↕ TLS-encrypted communication
sidecar (docker:20.10.16-dind, has daemon)What is DOCKER_TLS_CERTDIR: "/certs"?
Here “CLI” means the side that runs docker build – docker build itself just sends a request to the daemon via the Docker API, and the daemon does the actual build. TLS protects this API communication:
job container: docker build ...
↕ Docker API over TLS
sidecar: daemon actually runs the buildSetting DOCKER_TLS_CERTDIR: "/certs" makes DinD auto-generate TLS certificates. The CLI side reads them too, and the two complete a handshake before communicating. Setting it to an empty string "" disables TLS, but that is insecure and not recommended.
How are credentials passed in?
$REGISTRY_USER and $REGISTRY_PASS are CI/CD Variables configured in the GitLab project settings. They never appear in the YAML file, avoiding hardcoded secrets.
Knowledge Sugar
Top-level variables vs job-level variables
1variables: # available to all jobs
2 IMAGE_NAME: alienmushroom/demo-app
3
4build_image:
5 variables: # only this job; can override top-level
6 DOCKER_TLS_CERTDIR: "/certs"Does before_script behave differently from script on failure?
No – if any command returns a non-zero exit code, the job is marked as failed and subsequent stages do not run.
Keep versions aligned
image: docker:20.10.16 and services: docker:20.10.16-dind must use the same version number. A mismatch between CLI and daemon versions can cause unexpected issues.