Kinoko's TIL Log

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 test

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_TAG

Why 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 builddocker 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 build

Setting 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.

#devops #til

← Back to Main Page