Against a remote Docker engine, where do `-v` bind mounts and `docker build`'s context resolve?
answer
- The API carries JSON, not files
- Paths are strings until someone opens them
- Ask which process opens the path
- Missing bind sources get created empty
- The build directory is uploaded first
basics
~20 sBoth resolve on the daemon's host, not yours. A -v path is interpreted there, so a missing directory is created empty instead of mounting your files. The build context directory is read locally but uploaded to the daemon first.
solid answer
~50 sThe docker CLI sends API calls; it does not ship your filesystem along with them. So `docker run -v $(pwd)/models:/models ...` expands `$(pwd)` on your laptop and then hands that **string** to the remote daemon, which resolves it against its own root filesystem - usually finding nothing, and with `-v` silently creating an empty root-owned directory there. `--mount type=bind` at least errors instead of inventing the path. Published ports bind the daemon host's interfaces, so `curl localhost:8080` from your laptop hits nothing. The build context is the exception that proves the rule: because the daemon cannot read your disk, the CLI packs that directory and **uploads** it, so a 418 MB context is 418 MB over the network before the first instruction runs. Commands that stream over the API - `docker cp`, `docker logs`, `docker exec` - keep working normally.
code
bash · 3 linesdocker context use build-host
docker run --rm --mount type=bind,source="$(pwd)/models",target=/models reranker:2.4
# docker: Error response from daemon: invalid mount config: bind source path does not existgo deeper
Remember the one rule: the docker CLI sends API calls, not files. Paths in -v are interpreted by the daemon, so against a remote engine they point at that machine's disk.
Explain the mechanics precisely: -v creates a missing source directory while --mount type=bind refuses, published ports land on the daemon host, and the build context is uploaded before the first instruction.
Diagnose it quickly. Confirm which engine served the command, read the source path back out of docker inspect, and check that path on the daemon host rather than trusting the laptop.
Decide the team pattern: whether remote engines are for builds only, how data reaches them without bind mounts, and whether the network cost of shipping a build context is worth the shared warm cache.
## One rule explains all of it The docker CLI and `dockerd` are separate processes that talk over an HTTP API, and the API carries **JSON, not your filesystem**. Every path you type is transmitted as a string and interpreted by whichever process ultimately opens it. Once the CLI is pointed at a remote engine, that process is on another machine. Everything below follows from that single fact. ## Bind mounts resolve on the daemon host ``` docker run -v $(pwd)/models:/models reranker:2.4 ``` Your shell expands `$(pwd)` locally, producing something like `/Users/dana/reranker/models`. The API request carries that string. The remote daemon looks for `/Users/dana/reranker/models` on **its** root filesystem, does not find it, and - this is the sharp edge - with the `-v` form it *creates* the missing directory, owned by root, and mounts it. The container starts, the mount is present, and the directory is empty. A recommendation re-ranker started this way comes up with no model files and fails in a way that looks like an application bug rather than a plumbing one. Two defences. First, prefer `--mount type=bind,source=...,target=...`, which refuses to start when the source does not exist rather than fabricating it. Second, stop bind-mounting altogether against a remote engine: put the data in the image, in a named volume that lives on the daemon host anyway, or copy it across with `docker cp` (which does stream over the API and therefore does work from your machine). Named volumes, incidentally, were never local: `docker volume create` allocates storage under the daemon's data root on the daemon's host, so a volume you "created" from your laptop is a directory on the remote server. ## Published ports bind the daemon's interfaces `-p 8080:8080` tells the remote daemon to publish on the remote host. `curl http://localhost:8080` from your laptop connects to your laptop and fails. You need the remote host's address and an open firewall, or a port forward you set up yourself over your existing connection to that host. This surprises people precisely because the command that created the mapping was typed locally. ## The build context is uploaded `docker build .` is the one case where data really does cross the wire, and for the same reason: the daemon cannot read your disk, so the CLI must send it the files. The classic builder tars the whole directory and streams it to the daemon before executing the first instruction - you see it as `Sending build context to Docker daemon`. BuildKit reports it as `transferring context` and is smarter about not resending unchanged files to the same builder, but the first transfer is still a full one. That is invisible over a unix socket and very visible over a network. A Kotlin repository whose working tree carries a 418 MB `build/` directory and a local Gradle cache will spend a minute pushing bytes that no instruction ever reads, on every build, before anything compiles. The fix is to keep the context small - which files are excluded, and how, is its own subject - and to *notice* that the cost exists at all, because on `default` it never showed up. Note the asymmetry once the build starts: `COPY` reads from the uploaded context, but `RUN` executes on the daemon host, and any `RUN --mount=type=cache` cache directory lives there too. That is exactly why remote build hosts are worth it - a shared, already-warm dependency cache reaching a 71% hit rate on a multi-stage Gradle build is a property of the *daemon's* disk, and it survives between your builds and your colleagues'. ## What still behaves as expected Anything that moves data through the API rather than through the filesystem: `docker cp` in both directions, `docker logs`, `docker exec -it`, `docker stats`, `docker events`. Interactive terminals work because the API proxies the stream. So the mental split is clean: **API traffic travels, file paths do not**. ## Diagnosing it When a container behaves as if its data is missing, first establish which engine ran it (`docker context ls` plus the environment), then check the host side of the mount *on that host*: `docker inspect -f '{{json .Mounts}}' <container>` shows the source path the daemon actually used, and a shell on the daemon host shows whether that path holds your files or an empty directory Docker created a minute ago. The moment someone says "but the folder is right there on my machine", the diagnosis is finished.
- You ran a container on a remote engine with -p 8080:8080 and localhost:8080 on your laptop refuses the connection. Why?Publishing happens on the daemon's host. The remote engine bound port 8080 on the remote machine's interfaces, so your laptop's loopback has nothing listening. Reach it at the remote host's address, if the firewall permits, or forward the port over the connection you already have to that host. Nothing about the mapping is wrong.
- How do you get a local data directory onto a remote engine when bind mounts cannot see it?Three options. Bake the data into the image if it is small and versioned. Create a named volume, which lives on the daemon host anyway, and populate it with `docker cp` into a container that mounts it - `docker cp` streams over the API, so it works from your machine. Or fetch the data on the daemon host directly from wherever it is stored.
- Why does a build against a remote engine feel slow before any instruction runs?The context directory has to reach the daemon before the build starts. Over a unix socket that copy is nearly free; over a network you are paying real bandwidth for every file in the directory, including build output the Dockerfile never reads. BuildKit avoids resending unchanged files to the same builder, but the first transfer is still the whole tree.
Telling a remote daemon to mount /Users/dana/models is like emailing a colleague the sentence 'open the folder on my desktop' - they will look on their own desktop, and find nothing.
saying these in an interview costs you the question
- Assumes -v mounts the laptop's directory into a remote container
- Cannot explain why the mounted directory came up empty
- Thinks -p publishes on the machine that typed the command
- Believes the daemon can read the client's filesystem directly
- Says named volumes live on the client machine
- Unaware the build directory is uploaded to the daemon