A CircleCI job that uses the docker executor runs docker build and fails because no Docker daemon is reachable. Why does that happen, and what are your two options?
answer
- a container is not a machine
- no daemon socket inside the executor
- exposing the host socket would break isolation
- one extra step attaches a separate environment
- setup_remote_docker, or switch to machine
basics
~20 sThe docker executor runs steps inside a container that has no Docker daemon of its own. Add the setup_remote_docker step to attach an isolated remote Docker environment, or switch the job to the machine executor, a VM with a local daemon.
solid answer
~40 sCircleCI makes you choose the execution environment per job rather than inferring it from a label, and the `docker` executor puts your steps inside a container on shared infrastructure — deliberately, there is no daemon socket in there to build against. Option one is to add the `setup_remote_docker` step, which provisions a separate, isolated Docker environment and points the job's `docker` CLI at it; that keeps the fast container start, but the remote daemon does not share a filesystem with your job, so bind-mounting a path from the working directory into a container does not work and you copy files in instead. Option two is to declare `machine:` with a CircleCI-provided image, giving the job a full VM with its own local daemon and normal volume behaviour, at the cost of a slower start.
code
yaml · 26 linesversion: 2.1
jobs:
build-image:
docker:
- image: cimg/base:current
steps:
- checkout
- setup_remote_docker
- run:
name: Build the image
command: docker build -t myapp:$CIRCLE_SHA1 .
integration-tests:
machine:
image: ubuntu-2204:current
steps:
- checkout
- run: docker compose up -d
- run: ./scripts/integration-tests.sh
workflows:
ci:
jobs:
- build-image
- integration-testsgo deeper
Know that a job declares its environment explicitly and that the docker executor's image is where your steps run, not the image you build. Recognize the daemon-not-reachable error as an executor problem.
Explain that the executor container has no daemon by design, name both fixes, and state the filesystem boundary that makes bind mounts fail under setup_remote_docker. Mention layer caching as the speed lever.
Choose per job with reasons: remote Docker for build-and-push, machine for compose-based integration tests or anything privileged. Talk about start-up latency versus debugging cost, and about why exposing a host socket to shared build infrastructure is unacceptable.
Own the standard: which executor families are approved, what the default is for new repositories, how the choice shows up in cost per pipeline minute, and whether image builds should live in dedicated jobs so their environment never leaks into general test jobs.
## Executors are chosen, not inferred A CircleCI job names its execution environment explicitly. There are four families: `docker` (your steps run inside a container image you specify), `machine` (a full Linux VM), `macos` (a macOS VM, selected by Xcode version), and Windows, which is provided through an orb-supplied executor rather than as a bare key. This is the design contrast the leaf exists for: instead of picking a runner label and inheriting whatever that pool happens to provide, you state the environment in the job definition. That explicitness is exactly why the failure in the question surprises people. The `docker` executor is the default choice because it starts fastest, but a container is not a machine, and the platform does not quietly hand you a daemon. ## Why the docker executor has no daemon Under the `docker` executor your steps execute inside a container scheduled on shared infrastructure. Exposing the host's Docker socket into that container would hand every build effective control of the host and of its neighbours — that is a privilege boundary, not an oversight. So `docker build`, `docker run`, `docker compose` and anything else that needs to talk to a daemon fail with a connection error. ## Option one: `setup_remote_docker` Adding the `setup_remote_docker` step provisions a **separate, isolated Docker environment** for the job and configures the job's Docker CLI to talk to it. The job keeps the fast-starting container, and image builds work. ```yaml version: 2.1 jobs: build-image: docker: - image: cimg/base:current steps: - checkout - setup_remote_docker - run: docker build -t myapp:$CIRCLE_SHA1 . ``` The constraint to remember — and the one interviewers probe — is that **the remote environment does not share a filesystem with your job's container**. The build context is sent over, so `docker build .` works, but bind-mounting a path from your working directory into a container you start (`docker run -v $(pwd):/src ...`) does not behave as it would locally, because the remote daemon cannot see that path. The workaround is to copy files into the container explicitly with `docker cp`, or to bake what you need into the image. Ports published by containers started remotely are likewise not reachable at `localhost` from the job. On CircleCI's paid plans, `setup_remote_docker` also accepts `docker_layer_caching: true`, which preserves layers between runs so unchanged Dockerfile stages are not rebuilt. ## Option two: the machine executor Declaring `machine:` with a CircleCI-provided image gives the job a dedicated VM: ```yaml jobs: build-image: machine: image: ubuntu-2204:current steps: - checkout - run: docker build -t myapp:$CIRCLE_SHA1 . ``` Here the daemon is local, so volumes, published ports and multi-container setups behave the way they do on a laptop — which matters for integration tests that start dependencies with `docker compose` and mount fixtures into them. The price is start-up time: a VM boots more slowly than a container, and the resource classes cost more. There is no daemon-reachability puzzle to explain to teammates, though, which has its own value. ## Choosing between them - Building and pushing an image with no volume mounting: `docker` executor plus `setup_remote_docker`. Fastest, and the common case. - Integration tests that mount host paths, publish ports, or run `docker compose` with local networking: `machine`. Fighting the remote environment here costs more than the slower boot. - Anything needing kernel-level access, privileged containers, or nested virtualization: `machine`. - Building and testing an application without touching Docker at all: `docker` executor with a language image, no remote Docker. ## The related trap The image listed under a `docker:` executor is the environment your **steps run in**, not the image you are building. A candidate who tries to fix the daemon error by changing that image to something Docker-flavoured has misread the failure: the missing thing is a daemon to connect to, not a client binary. Say that explicitly and the diagnosis reads as lived experience rather than recall.
- Why does mounting a working-directory path into a container fail under setup_remote_docker?Because the remote Docker environment is a separate host from the container your steps run in. The daemon resolves a bind-mount path against its own filesystem, where your checkout does not exist, so you get an empty or missing mount. Copy the files in with `docker cp`, or build them into the image instead.
- How would you speed up image builds that use setup_remote_docker?Enable `docker_layer_caching: true` on the step, available on paid plans, so unchanged layers survive between runs. Beyond that, order the Dockerfile so rarely changing layers come first, and keep the build context small — the context is transferred to the remote environment, so a bloated one costs real time.
- When is the machine executor the wrong answer despite being simpler?When start-up latency dominates. A VM boots more slowly and costs more per minute than a container, so for a fan-out of many short jobs the aggregate penalty is significant. If the job only builds and pushes an image and never mounts a host path, the docker executor with remote Docker is both faster and cheaper.
saying these in an interview costs you the question
- Changing the executor image to fix a missing daemon
- Expecting the host Docker socket inside the docker executor
- Assuming volume mounts work with remote Docker
- Believing setup_remote_docker runs on the same filesystem
- Defaulting every job to machine to avoid thinking