troubleshooting warning ci-cd · ·

Fix GitLab CI "dial tcp: lookup docker" no such host error

The GitLab CI error "dial tcp lookup docker no such host" means the docker:dind service alias never resolved. Here is the fast fix and the real causes.

GitLab CI dial tcp: lookup docker: no such host
Fix GitLab CI "dial tcp: lookup docker" no such host error

The fast fix

If your GitLab CI job dies with error during connect: ... dial tcp: lookup docker on 127.0.0.11:53: no such host, your docker client resolved DOCKER_HOST fine but the DNS name docker does not exist on the job’s network. That name is the alias of the docker:dind service, and it only registers when the service container actually starts. The usual fix is to define the service with the -dind image tag and an explicit alias, and point DOCKER_HOST at TLS port 2376:

build:
  image: docker:28.3
  services:
    - name: docker:28.3-dind
      alias: docker
  variables:
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_CERTDIR: "/certs"
    DOCKER_CERT_PATH: "/certs/client"
    DOCKER_TLS_VERIFY: "1"
  script:
    - docker info
    - docker build -t my-app .

That covers the common case on the docker executor. If you are on the Kubernetes executor, or the service still refuses to resolve, keep reading. The name resolution is the whole game here, and there are three distinct reasons it fails.

Why this error is not the daemon-connection error

The address in the message tells you exactly how far the client got. This DNS variant is different from the two connection variants:

error during connect: Post "https://docker:2376/v1.44/info":
dial tcp: lookup docker on 127.0.0.11:53: no such host

The client tried to resolve the hostname docker through the container DNS resolver (127.0.0.11 on a Docker bridge network) and got back nothing. That is a name-resolution failure, not a refused connection. Compare it to the tcp://docker:2375 form, where the name resolves but the daemon is unreachable, covered in the Docker daemon connection error write-up, and the unix:///var/run/docker.sock form, where DOCKER_HOST was never set at all, covered in the socket-variant fix. If your message says no such host, the client never reached any daemon because the name pointed at nothing.

The three real causes

1. The dind service never started, so its alias never registered

This is the most common cause and the least obvious. GitLab registers the docker alias on the build network only when the docker:dind service container comes up. If that container fails to start, the alias is missing and every lookup returns no such host. Three things stop it from starting:

  • You used the plain image, not the -dind tag. docker:28.3 ships only the client. You need docker:28.3-dind, which bundles dockerd. A plain docker service starts, exits immediately (no daemon to run), and takes its alias down with it.
  • The runner is not in privileged mode. docker:dind runs its own daemon and needs privileged = true in the runner config.toml. Without it the container cannot start the daemon and dies during boot.
  • The image failed to pull. A registry rate limit or a typo in the tag means the service container never exists. Check the job log’s Preparing the "docker" ... service lines near the top.

Pin the runner config and confirm privileged mode:

$ grep -A3 '\[runners.docker\]' /etc/gitlab-runner/config.toml

You want to see privileged = true. If it says false or is absent, that is your problem.

2. A renamed service image broke the derived alias

GitLab derives a service’s default alias from its image name. docker:28.3-dind becomes the alias docker. But if you pull the image through a mirror or a private registry, the derived alias changes and docker stops resolving:

services:
  # WRONG: alias becomes "my-mirror.example.com__docker", not "docker"
  - name: my-mirror.example.com/library/docker:28.3-dind

The daemon is running, but under a name your DOCKER_HOST never asks for. Always set the alias explicitly when the image path is anything other than the bare docker name:

services:
  - name: my-mirror.example.com/library/docker:28.3-dind
    alias: docker

3. The Kubernetes executor puts services on localhost

On the docker executor, the service is a linked container with its own alias, so tcp://docker:2376 is correct. The Kubernetes executor is different: every service runs as a container in the same Pod as the build, sharing one network namespace. They all reach each other on localhost, and the docker alias may not resolve at all. If you copied a working docker-executor config onto a Kubernetes runner, this is why it broke.

Set the host to localhost for the Kubernetes executor:

variables:
  DOCKER_HOST: tcp://localhost:2376
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_CERT_PATH: "/certs/client"
  DOCKER_TLS_VERIFY: "1"

Behavior here has shifted across runner versions, so verify against your own version rather than trusting a blog snippet. If tcp://localhost:2376 gives you connection refused instead of no such host, the name resolved and you are back to a daemon-startup problem (see cause 1). The two messages tell you which side of the wall you are on.

A checklist that isolates the cause in under a minute

Work through these in order the next time the job goes red:

  1. Read the address in the error. no such host is DNS. connection refused is a live-but-unreachable daemon. Do not fix the wrong one.
  2. Confirm the -dind tag. Grep your .gitlab-ci.yml for the service image. No -dind, no daemon, no alias.
  3. Check privileged mode in the runner config.toml as shown above.
  4. Match DOCKER_HOST to your executor. tcp://docker:2376 for the docker executor, tcp://localhost:2376 for Kubernetes.
  5. Match the port to TLS. With DOCKER_TLS_CERTDIR set, the daemon listens on 2376. Blank it out and it listens on the plain 2375. A mismatch here surfaces as a connection error once the name resolves.
  6. Scan the top of the job log for the service Preparing/Waiting lines. A service that logs an exit code never registered its alias.

Confirm the daemon is reachable before you build

Once the service is up and named correctly, prove the client can talk to it before your real build steps run. A one-line docker info at the start of script fails fast and loud instead of letting a ten-minute build collapse at the push step:

  script:
    - docker info
    - docker build -t my-app .
    - docker push my-app

The daemon’s TCP listener and its 2375-versus-2376 split are documented in the Docker daemon reference; the TLS port only exists because DOCKER_TLS_CERTDIR generated certificates on boot. Keep the port and the TLS variables in sync and the no such host error stays gone. Once your images build cleanly, tightening them is the next job, and multi-stage builds are where that starts.

Get the next article in your inbox

Practical DevOps tips, tutorials, and guides. No spam, unsubscribe anytime.