Skip to content

Commit 1349992

Browse files
authored
docs: clarify context rules and separation of concerns. (#77)
1 parent e525f33 commit 1349992

6 files changed

Lines changed: 96 additions & 16 deletions

File tree

README.md

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,74 @@
3131
3232
flowchart LR
3333
classDef soft fill:#EFF6FF,stroke:#2563EB,stroke-width:2px,rx:8,ry:8,color:#111827;
34-
client["container build ..."]
34+
client["container build ...\n(macOS host)"]
3535
subgraph boundary["Builder Container"]
3636
direction LR
3737
shim["container‑builder-shim"]
3838
buildkit["buildkitd"]
39-
shim -- "Buildkit API (gRPC)" --> buildkit
39+
shim -- "BuildKit API (gRPC)" --> buildkit
4040
end
4141
42-
client -- BuilderAPI --> shim
42+
client -- "Builder API" --> shim
4343
4444
```
4545

46-
1. Buildkit initiates a session via gRPC.
46+
1. BuildKit initiates a session via gRPC.
4747
2. container-builder-shim intercepts session requests (file sync, image resolution, etc.).
48-
3. Requests are translated to containerization's Build API format.
49-
4. containerization processes the build.
48+
3. Requests are translated to containerization's Build API format and forwarded to the macOS host over a bidirectional gRPC stream.
49+
4. The macOS host processes the request (serving context files, resolving images, etc.) and streams the response back.
5050
5. Build output and metadata flow back through container-builder-shim to BuildKit.
5151

52+
## Build Context Transfer
53+
54+
Build context files flow from the macOS host to BuildKit through a three-tier pipeline. Each tier has distinct responsibilities; understanding the split is important when working on file-transfer or security-related code.
55+
56+
### Responsibilities
57+
58+
| Tier | Responsibility |
59+
|---|---|
60+
| **macOS host** (`container` / `BuildFSSync`) | Owns the context directory. Enforces the context boundary: rejects any request that resolves outside the root. Packs requested files into a tar archive. Does **not** apply `.dockerignore`. |
61+
| **container-builder-shim** (`pkg/fssync`) | Bridges the host's wire format and BuildKit's `filesync` gRPC interface. Receives the tar, unpacks it to a content-addressed local cache, applies `.dockerignore` exclusions, and presents the result to BuildKit via `DiffCopy`. |
62+
| **BuildKit** | Owns all Dockerfile copy semantics: when to dereference symlinks, how to recurse directories, and how `.dockerignore` patterns are interpreted. Drives the transfer by sending `Walk` requests with `followpaths` and `exclude-patterns`. |
63+
64+
### Primary data flow
65+
66+
```
67+
BuildKit ──► shim Walk (followpaths, exclude-patterns)
68+
──► host: resolve globs, pack matching paths into tar
69+
◄── tar stream
70+
shim unpacks tar to content-addressed cache, applies .dockerignore
71+
BuildKit ◄── DiffCopy PACKET_STAT (one entry per file/dir/symlink)
72+
BuildKit ──► PACKET_REQ (for each regular file it needs)
73+
BuildKit ◄── PACKET_DATA (shim reads from local cache)
74+
```
75+
76+
There is also a fallback path (`Info``Read`) used by `FS.Open` when the local cache is unpopulated — a narrow race window at the start of a build. The host enforces the same boundary rules on this path.
77+
78+
### Context boundary rules
79+
80+
These rules govern how files are selected, transferred, and presented. They reflect standard Docker build semantics and are enforced jointly by the host and the shim.
81+
82+
1. **Context boundary.** All files served to BuildKit must physically reside within the context root on the macOS host. Any request that resolves outside the root — whether by direct path or symlink chain — is rejected by the host.
83+
84+
2. **Out-of-context symlinks.** A symlink whose target lies outside the context root is present in the tar as a symlink entry, but its target is not included. BuildKit will fail to dereference it during `COPY`/`ADD` because the target is absent from the unpacked context. Symlinks in a build context should refer only to paths within the context root.
85+
86+
3. **In-context file symlink.** The host includes both the symlink entry and its target file in the tar. BuildKit dereferences the symlink during `COPY`/`ADD`; the result in the image is a regular file containing the target's content, not a symlink.
87+
88+
4. **In-context directory symlink.** The host follows the symlink and includes the directory's contents in the tar, applying rule 1 to every entry discovered through it. BuildKit recurses the directory during `COPY`/`ADD` as if it were a plain directory.
89+
90+
5. **Dangling symlink.** A symlink that is lexically within the context root but whose target cannot be resolved causes the target to be absent from the tar; BuildKit fails at `COPY`/`ADD` time.
91+
92+
### `.dockerignore`
93+
94+
`.dockerignore` filtering is the shim's responsibility, not the host's. After unpacking the tar, the shim walks the cache directory and applies the `exclude-patterns` received from BuildKit before emitting `PACKET_STAT` entries. The host has no knowledge of `.dockerignore`.
95+
96+
### `followpaths`
97+
98+
`followpaths` is a comma-separated list of glob patterns identifying which context paths BuildKit needs for the current build step. The shim forwards it to the host, which resolves the patterns against the context root to determine which files to pack into the tar.
99+
100+
If BuildKit does not supply `followpaths`, the shim falls back to `addedGlobs`: source paths pre-computed by scanning the Dockerfile AST for `COPY`, `ADD`, and `RUN --mount=type=bind` instructions (see `pkg/build/buildopts.go`).
101+
52102
## Contributing
53103

54104
Contributions to Containerization are welcomed and encouraged. Please see our [main contributing guide](https://github.com/apple/containerization/blob/main/CONTRIBUTING.md) for more information.

pkg/build/buildopts.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,10 @@ func NewBuildOpts(ctx context.Context, basePath string, contextMap map[string][]
317317
return nil, err
318318
}
319319

320-
// do add .dockerignore support
321-
// to ExcludePatterns(dockerignore) patterns
320+
// addedGlobs is the fallback value for followpaths when BuildKit does not
321+
// supply it. Pre-compute it by scanning the Dockerfile AST for COPY, ADD,
322+
// and RUN --mount=type=bind source paths so the host packs only the files
323+
// those instructions need rather than the entire context.
322324
addedGlobs := []string{}
323325
for _, node := range dockerfile.AST.Children {
324326
if strings.EqualFold(node.Value, "COPY") || strings.EqualFold(node.Value, "ADD") {

pkg/fileutils/tarxfer.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,16 @@ import (
3131

3232
const DockerfileStaging = ".com.apple.container"
3333

34-
// Receiver streams a remote tar archive, caches it under cacheBase and calls fn
35-
// for every entry that is a regular file, directory or symlink.
34+
// Receiver streams a tar archive from the macOS host, stores it in a
35+
// content-addressed cache under cacheBase, unpacks it, and walks the result.
36+
//
37+
// Symlinks are unpacked as real OS symlinks (os.Symlink). filepath.Walk does
38+
// not follow them, so they appear in the walked tree with their Linkname
39+
// intact. BuildKit decides at COPY/ADD time whether to dereference them based
40+
// on its own copy semantics.
41+
//
42+
// If the cache directory for the tar's content hash already exists the
43+
// download is skipped and the cached tree is used directly.
3644
type Receiver struct {
3745
demux *stream.Demultiplexer
3846
cacheBase string

pkg/fssync/diffcopy.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@ import (
3232
"golang.org/x/sync/errgroup"
3333
)
3434

35-
// DiffCopy from fsutil pkg uses a smaller buffer size. This implementation
36-
// is essentially the same except for larger buffers
35+
// DiffCopy handles BuildKit's primary file-transfer path.
36+
//
37+
// It calls Walk to request a tar of the build context from the host, then
38+
// concurrently serves PACKET_REQ requests from BuildKit by reading files from
39+
// the local unpacked cache. The buffer size is larger than fsutil's default
40+
// to reduce syscall overhead for large files.
3741
func (f *FSSyncProxy) DiffCopy(ss filesync.FileSync_DiffCopyServer) error {
3842
ctx := ss.Context()
3943
fs := NewFS(ctx, f, f.contextDir, f.basePath)

pkg/fssync/fssync.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@ var (
3939
_ filesync.FileSyncServer = &FSSyncProxy{}
4040
)
4141

42-
// A fsSync that proxies requests over the bidirectional grpc stream
43-
// It is used by buildkit to retrieve context directory and other build artifacts
42+
// FSSyncProxy implements BuildKit's filesync.FileSyncServer by proxying
43+
// file requests over the bidirectional gRPC stream to the macOS host.
44+
//
45+
// BuildKit drives the transfer by calling DiffCopy, which delegates to Walk
46+
// and FS.Open. Walk is the primary path: it sends a followpaths request to
47+
// the host, receives a tar archive, unpacks it to a content-addressed local
48+
// cache, and presents the result to BuildKit. FS.Open falls back to direct
49+
// Info/Read host calls only when the cache is unpopulated.
4450
type FSSyncProxy struct {
4551
stream.UnimplementedBaseStage
4652
filesync.UnimplementedFileSyncServer

pkg/fssync/walk.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,19 @@ import (
3232
)
3333

3434
/*
35-
Walk is proxied over the gRPC stream to the caller.
35+
Walk requests build-context files from the macOS host and presents them to BuildKit.
3636
37-
In JSON mode, the server sends file info as JSON; we walk those files directly.
37+
The host is asked for a tar archive containing the paths identified by
38+
followpaths (glob patterns BuildKit sends in the request metadata). The shim
39+
unpacks the tar to a content-addressed local cache and then walks the unpacked
40+
tree, filtering each entry through the exclude-patterns (from .dockerignore)
41+
before passing it to fn.
42+
43+
Only TAR mode is supported. The JSON mode wire format is defined in
44+
RawFileInfo below but is not exercised by the current shim.
45+
46+
If BuildKit does not supply followpaths, the shim falls back to addedGlobs —
47+
source paths pre-computed from the Dockerfile AST (see pkg/build/buildopts.go).
3848
3949
Request Format:
4050

0 commit comments

Comments
 (0)