Where Docker Fits in a Repository
Summary
- Docker in a repository is a build concern: it turns source into an OCI image, and where you place its inputs decides what is reproducible and what leaks.
- Insight: the
Dockerfileis code, but the build context is data - the directory tree the daemon can see. Getting the context boundary right matters more than theDockerfileitself. - Key Concepts:
build context,Dockerfile,.dockerignore,image boundary(one image per deployable), andBuildKitas the default backend in Docker Engine 29. - When to Use: anytime a service, job, or CLI needs to ship as a container. Docker owns build and local dev; on the cluster, containerd runs the resulting images.
- Limitations/Trade-offs: a wide context slows builds and expands attack surface; a narrow context breaks shared-code imports in monorepos.
- Related Topics: repo layout, monorepo build contexts, internal base-image registries.
Foundations
Docker's job in a repository is narrow and well-defined: take a build context plus a Dockerfile and produce an image.
Everything else - orchestration, scaling, networking - happens later, on the cluster, where containerd via CRI runs the image. Docker Engine is not the runtime inside Kubernetes.
The mental model is a boundary, not a folder.
Each deployable unit (a service, a batch job, a CLI) maps to exactly one image, and one image is defined by one Dockerfile.
The build context is the second half of that boundary. It is the directory you point docker build at, and BuildKit sends its contents (minus .dockerignore entries) to the build.
If a file is not in the context, COPY cannot reach it. If a secret is in the context, it can end up in a layer.
So "where Docker fits" is really two questions: where the Dockerfile lives, and what the context is allowed to see.
Mechanics & Interactions
A build invocation names both boundaries at once.
docker build -f services/api/Dockerfile -t api:dev services/apiHere -f is the Dockerfile path and the trailing services/api is the context root.
BuildKit computes a checksum of the context, transfers it, and executes each instruction as a cacheable step. Instructions that do not change reuse cached layers.
The .dockerignore file, read from the context root, prunes the transfer before it happens.
That means .git, node_modules, local .env files, and build artifacts should be ignored - both to shrink the context and to keep secrets out of images.
COPY and ADD are the only ways files enter an image, and their source paths are resolved relative to the context root, never relative to the Dockerfile.
This single rule explains most monorepo confusion: a Dockerfile at services/api/Dockerfile built with context . can COPY packages/shared, but the same Dockerfile built with context services/api cannot.
BuildKit adds capabilities that change how the context is used.
Multi-stage builds let a single Dockerfile produce a fat build stage and a slim runtime stage, copying only artifacts forward with COPY --from.
Build secrets (RUN --mount=type=secret) and cache mounts (RUN --mount=type=cache) let you use credentials and package caches without baking them into layers.
The daemon boundary matters too.
docker build ships the context to a daemon (local or remote). A large context is slow even when most of it is ignored at instruction time, because ignore rules run at transfer, not before path enumeration on huge trees.
Advanced Considerations & Applications
In a monorepo, the tension is real: a root context sees shared code but is enormous, while a per-service context is small but blind to shared packages.
The common resolution is a root context with an aggressive .dockerignore and a per-service Dockerfile, so COPY can select exactly the shared packages a service needs.
Reproducibility depends on pinning what the context cannot control.
Pin base images by digest (FROM registry.internal/base@sha256:...), pin package versions, and prefer an internal base-image registry so the golden FROM line is governed centrally.
Security lives at this boundary as well.
Because anything in the context can reach a layer, treat the context as untrusted output: ignore secrets, run image scans (Trivy or Grype) in CI, and sign images with cosign so the cluster only admits provenance you produced.
CI is where this pays off.
The same docker build a developer runs locally is what CI runs, so build determinism (pinned bases, locked dependencies, deterministic .dockerignore) is what makes "works on my machine" stop being a phrase.
Local development often does not use production images at all.
Compose v2 (docker compose) wires services, databases, and volumes for the inner loop, frequently with bind mounts and dev-only stages, while the production image stays lean.
Common Misconceptions
"The Dockerfile location defines COPY paths." It does not. COPY resolves against the context root passed to docker build, regardless of where the Dockerfile sits.
"Docker runs my pods." No. Docker builds images; containerd (through the CRI) runs them on Kubernetes nodes. Dockershim was removed, so the in-cluster runtime is not Docker Engine.
".dockerignore is just an optimization." It is also a security control. It keeps .git, credentials, and local env files out of the transferred context and therefore out of image layers.
"One repo means one image." Repositories and images are independent. A monorepo can produce many images; a single service can still live in its own repo. The unit is the deployable, not the repo.
"A bigger context is fine because ignored files are dropped." Ignore rules reduce what is sent, but very large trees still cost enumeration and hashing time. Keep contexts scoped.
FAQs
Where should a Dockerfile live? Co-located with the service it builds (services/api/Dockerfile), so ownership and the image boundary are obvious in the tree.
Should the context be the repo root or the service folder? Service folder for standalone services; repo root (with .dockerignore) when a service imports shared packages from a monorepo.
Does BuildKit change any of this? BuildKit is the default backend in Docker Engine 29 and adds cache mounts, build secrets, and better parallelism, but the context and boundary rules are unchanged.
How do I keep secrets out of images? Never COPY them. Use .dockerignore for local secrets and RUN --mount=type=secret for build-time credentials so they never persist in a layer.
Is Compose part of the image boundary? No. Compose orchestrates local containers for development; it does not define the production image, which the Dockerfile alone owns.
Related
- Repo Layout Basics
- Monorepo Build Contexts
- Base Image Internal Registry
- Project Setup Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).