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:
12345678project/├── config/│ └── nginx.conf└── site/├── html/│ └── index.html└── docker/└── Dockerfile
Build from project/ so both config/ and site/ fall inside the context:
1docker build -f site/docker/Dockerfile -t my-site .
Source paths in the Dockerfile are now relative to project/:
1234# syntax=docker/dockerfile:1FROM nginx:1.27-alpineCOPY site/html /usr/share/nginx/htmlCOPY config/nginx.conf /etc/nginx/nginx.conf
Add a .dockerignore at project/ so the wider context stays small:
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:
123# syntax=docker/dockerfile:1FROM nginx:1.27-alpineCOPY /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:
1COPY /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:
1234docker 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:
1234# syntax=docker/dockerfile:1FROM nginx:1.27-alpineCOPY html /usr/share/nginx/htmlCOPY 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:
1234docker 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:
12docker build https://github.com/myorg/my-service.git#maindocker 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.