Dash0 Raises $110M Series B at $1B Valuation

  • 9 min read

How to Include Files outside the Docker Build Context

You tried to COPY a file from a sibling or parent directory and Docker stopped you with COPY failed: forbidden path outside the build context. This happens because the build context is fixed the moment you run the build: the Docker CLI collects the files at the path you pass and hands only those to the builder, so anything reached through ../ is off-limits by design. There are four reliable ways around it, and which one fits depends on whether you can change the project layout or the build setup.

Why the restriction exists

When you run docker build ., the trailing . is the build context. BuildKit reads the files it needs from that root and nothing above it. The COPY and ADD instructions resolve their source paths relative to that root, which is why COPY ../config/app.conf . fails: the builder has no way to reach a path outside the context, and Docker keeps it that way so builds stay reproducible regardless of where on the host they run.

This is also where .dockerignore comes in, and it's easy to get backwards. It doesn't let you include outside files; it does the opposite, trimming what gets sent into the context. It matters here because the simplest fix below works by widening the context, and a wide context with no .dockerignore will sweep in your .git directory, node_modules, and stray build artifacts on every run.

Widen the context and point "-f" at the Dockerfile

The most common fix is to move the context root up to a directory that contains everything you need, then tell Docker where the Dockerfile lives with -f. The Dockerfile path and the context path are separate arguments, so the Dockerfile can sit anywhere while the context covers a broader tree.

Take a project where the nginx config lives outside the site directory:

text
12345678
project/
├── config/
│ └── nginx.conf
└── site/
├── html/
│ └── index.html
└── docker/
└── Dockerfile

Build from project/ so both config/ and site/ fall inside the context:

bash
1
docker build -f site/docker/Dockerfile -t my-site .

Source paths in the Dockerfile are now relative to project/:

dockerfile
1234
# syntax=docker/dockerfile:1
FROM nginx:1.27-alpine
COPY site/html /usr/share/nginx/html
COPY config/nginx.conf /etc/nginx/nginx.conf

Add a .dockerignore at project/ so the wider context stays small:

text
123
.git
**/node_modules
**/*.log

This approach works everywhere and needs no special builder. The cost is that every COPY path now carries the full prefix from the new root, and a careless context can balloon in size.

Pull files straight from an image with "COPY --from"

If the file you need already lives in a published image, you don't need it in your context at all. COPY --from=<image> copies directly out of that image:

dockerfile
123
# syntax=docker/dockerfile:1
FROM nginx:1.27-alpine
COPY --from=nginx:1.27-alpine /etc/nginx/nginx.conf /tmp/reference-nginx.conf

The same pattern lifts a single static binary out of a tool image without installing the tool or its dependencies:

dockerfile
1
COPY --from=busybox:1.37 /bin/busybox /usr/local/bin/busybox

This is the multi-stage COPY --from syntax applied to an external image reference instead of a named build stage. It keeps the file out of your context and out of your layers except for exactly what you copy.

Use BuildKit named contexts

BuildKit lets you attach extra named contexts at build time with --build-context name=source, then reference them with COPY --from=name. You get outside files without restructuring the project or widening the root:

bash
1234
docker buildx build \
--build-context config=../shared/config \
-t my-site \
.

The Dockerfile pulls from that context the same way it would pull from a build stage:

dockerfile
1234
# syntax=docker/dockerfile:1
FROM nginx:1.27-alpine
COPY html /usr/share/nginx/html
COPY --from=config nginx.conf /etc/nginx/nginx.conf

Here the main context stays ., html comes from it, and nginx.conf comes from the config context pointing at a directory outside the project. A named context also overrides a FROM <name> stage of the same name, which is convenient for swapping a base image at build time. Named contexts need a BuildKit builder (any recent Docker with buildx); the legacy builder ignores the flag. Pin the frontend with # syntax=docker/dockerfile:1 at the top of the Dockerfile to guarantee the feature resolves: current Docker already ships a frontend new enough to support it, but the directive keeps the build portable across older installs.

Remote contexts

A named context source doesn't have to be a local directory. BuildKit resolves Git URLs, HTTP URLs, and image references, so you can stitch in files that live in another repository or registry:

bash
1234
docker buildx build \
--build-context shared=https://github.com/myorg/shared-assets.git \
--build-context certs=docker-image://myorg/certs:2026.1 \
-t my-site .

The main context can be remote too. Point the build at a Git repository or a tarball URL and BuildKit fetches it before building:

bash
12
docker build https://github.com/myorg/my-service.git#main
docker build https://example.com/context.tar.gz

This is what makes continuous integration builds that never touch a local checkout work, and it's the cleanest way to feed a single source of shared config into many service builds.

Common pitfalls

A few things trip people up here. Symlinks don't escape the context: a symlink inside the context that points to a file above it resolves to nothing during the build, because the builder follows the link and finds no target. Reach for a named context or a wider root instead.

If --build-context appears to do nothing, you're probably on the legacy builder. Named contexts need BuildKit, which is the default in current Docker; pin the frontend with # syntax=docker/dockerfile:1 at the top of the file so the feature resolves no matter which frontend version ships with the daemon. A related gotcha: when a named context points at a locally built image with docker-image://, the default docker-container builder driver can't read it from your local image store because it runs in isolation. Load the image into the builder first, or run that build with the docker driver.

Finally, watch the size of a widened context. Moving the root up to a monorepo top level without a .dockerignore ships everything below it to the builder on every run, which turns a fast build slow with no obvious cause. Scope the .dockerignore whenever you expand the root.

Final thoughts

Once your images build cleanly, the next question is what they do at runtime. A container that builds fine can still leak memory or crash because of a file you only half-copied, and none of that surfaces until it's running.

Dash0's infrastructure monitoring tracks container resource usage next to real-time logs and distributed traces, all built on OpenTelemetry, so you can see what every container is doing from one place. Start a free trial to monitor your containers, pods, and clusters in one view. No credit card required.