Docker
Symlink
Containerization
File System
DevOps

Docker follow symlink outside context

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This question usually appears when a Docker build or bind mount seems to "lose" files behind a symbolic link. The short answer is that Docker treats build-time context and runtime mounts differently, and a symlink that points outside the allowed context is usually blocked for security reasons.

Build Context Is the Real Boundary

When you run docker build ., Docker sends a build context to the builder. Only files inside that context are available to COPY and ADD. A symlink inside the context does not magically grant access to files outside the context.

For example, imagine this host layout:

text
1project/
2  Dockerfile
3  app/
4  shared -> ../shared
5../shared/
6  config.json

A Dockerfile like this is misleading:

dockerfile
FROM alpine:3.20
WORKDIR /app
COPY shared/config.json ./config.json

Even though project/shared exists as a symlink, the target directory lives outside the build context. Docker does not treat that as permission to copy arbitrary host files.

The fix is structural, not magical. Move the needed files under the build context, choose a larger context root, or use an additional named context if your build setup supports it.

A Safe Project Layout

One common solution is to move the Dockerfile one level higher so both directories are within the context:

text
1workspace/
2  Dockerfile
3  project/
4  shared/

Then the build becomes explicit:

dockerfile
1FROM alpine:3.20
2WORKDIR /app
3COPY project/ ./project/
4COPY shared/config.json ./project/config.json

And you build from workspace:

bash
docker build -t myapp /path/to/workspace

This keeps the dependency visible and reproducible. Hidden filesystem reach-through is exactly what Docker tries to prevent.

Runtime Mounts Behave Differently

At container runtime, bind mounts expose a real host path into the container. Symlink behavior now depends on the mounted directory and the host filesystem.

Example:

bash
1docker run --rm \
2  -v "$PWD/data:/data" \
3  alpine:3.20 \
4  ls -l /data

If /data/link.txt is a symlink, the container can see that symlink. Whether opening it succeeds depends on where the target points and whether that target is also reachable from inside the container.

So there are two separate questions:

  1. Can the container see the symlink entry
  2. Can the target path be resolved inside the container

A symlink that points to /Users/name/secrets.txt on the host usually fails inside the container unless that host path is also mounted.

Diagnosing the Problem Quickly

If you are unsure whether the failure is at build time or runtime, reduce it to a tiny repro:

bash
ls -l project
readlink project/shared
docker build -t symlink-test project

If the build fails on COPY, the issue is almost certainly the build context boundary. If the image builds but the running container cannot open the file, inspect the resolved path from inside the container:

bash
docker run --rm -it -v "$PWD/project:/app" alpine:3.20 sh
ls -l /app
cat /app/shared/config.json

Common Pitfalls

The biggest mistake is assuming Docker follows symlinks like a normal local shell command. It does not, because image builds must be reproducible and must not read arbitrary files from the host.

Another mistake is solving the issue by building from the filesystem root. That works technically, but it sends an enormous context, slows builds, and increases the chance of copying secrets into the image.

A third mistake is forgetting .dockerignore. Even when you choose a larger safe context, you should still exclude directories such as .git, node_modules, logs, and credentials to keep the build smaller and safer.

Summary

  • Docker build instructions can only access files inside the build context.
  • A symlink inside the context does not grant access to targets outside it.
  • Runtime bind mounts are different and depend on what host paths are actually mounted.
  • The safest fix is usually to change the project layout or build context root.
  • Use .dockerignore so a broader context does not become a slower or riskier build.

Course illustration
Course illustration

All Rights Reserved.