Skip to content

Commit 6d9b617

Browse files
authored
Merge pull request moby#3013 from jedevc/dev-docs
Enhanced developer documentation
2 parents 9114527 + 07357d6 commit 6d9b617

File tree

8 files changed

+1516
-580
lines changed

8 files changed

+1516
-580
lines changed

docs/dev/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# BuildKit Developer Docs
2+
3+
These are the BuildKit developer docs, designed to be read by technical users
4+
interested in contributing to or integrating with BuildKit.
5+
6+
> **Warning**
7+
>
8+
> While these docs attempt to keep up with the current state of our `master`
9+
> development branch, the code is constantly changing and updating, as bugs are
10+
> fixed, and features are added. Remember, the ultimate source of truth is
11+
> always the code base.
12+
13+
## Jargon
14+
15+
The following terms are often used throughout the codebase and the developer
16+
documentation to describe different components and processes in the image build
17+
process.
18+
19+
| Name | Description |
20+
| :--- | :---------- |
21+
| **LLB** | LLB stands for low-level build definition, which is a binary intermediate format used for defining the dependency graph for processes running part of your build. |
22+
| **Definition** | Definition is the LLB serialized using protocol buffers. This is the protobuf type that is transported over the gRPC interfaces. |
23+
| **Frontend** | Frontends are builders of LLB and may issue requests to Buildkit’s gRPC server like solving graphs. Currently there is only `dockerfile.v0` and `gateway.v0` implemented, but the gateway frontend allows running container images that function as frontends. |
24+
| **State** | State is a helper object to build LLBs from higher level concepts like images, shell executions, mounts, etc. Frontends use the state API in order to build LLBs and marshal them into the definition. |
25+
| **Solver** | Solver is an abstract interface to solve a graph of vertices and edges to find the final result. An LLB solver is a solver that understands that vertices are implemented by container-based operations, and that edges map to container-snapshot results. |
26+
| **Vertex** | Vertex is a node in a build graph. It defines an interface for a content addressable operation and its inputs. |
27+
| **Op** | Op defines how the solver can evaluate the properties of a vertex operation. An op is retrieved from a vertex and executed in the worker. For example, there are op implementations for image sources, git sources, exec processes, etc. |
28+
| **Edge** | Edge is a connection point between vertices. An edge references a specific output a vertex’s operation. Edges are used as inputs to other vertices. |
29+
| **Result** | Result is an abstract interface return value of a solve. In LLB, the result is a generic interface over a container snapshot. |
30+
| **Worker** | Worker is a backend that can run OCI images. Currently, Buildkit can run with workers using either runc or containerd. |
31+
32+
## Table of Contents
33+
34+
The developer documentation is split across various files.
35+
36+
For an overview of the process of building images:
37+
38+
- [Request lifecycle](./request-lifecycle.md) - observe how incoming requests
39+
are solved to produce a final artifact.
40+
- [Dockerfile to LLB](./dockerfile-llb.md) - understand how `Dockerfile`
41+
instructions are converted to the LLB format.
42+
- [Solver](./solver.md) - understand how LLB is evaluated by the solver to
43+
produce the solve graph.
44+
45+
We also have a number of more specific guides:
46+
47+
- [MergeOp and DiffOp](./merge-diff.md) - learn how MergeOp and DiffOp are
48+
implemented, and how to program with them in LLB.

docs/dev/dockerfile-llb.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Dockerfile conversion to LLB
2+
3+
If you want to understand how Buildkit translates Dockerfile instructions into
4+
LLB, or you want to write your own frontend, then seeing how Dockerfile maps to
5+
using the Buildkit LLB package will give you a jump start.
6+
7+
The `llb` package from Buildkit provides a chainable state object to help
8+
construct a LLB. Then you can marshal the state object into a definition using
9+
protocol buffers, and send it off in a solve request over gRPC.
10+
11+
In code, these transformations are performed by the [`Dockerfile2LLB()`](../../frontend/dockerfile/dockerfile2llb/convert.go)
12+
function, which takes a raw `Dockerfile`'s contents and converts it to an LLB
13+
state, and associated image config, which are then both assembled in the
14+
[`Build()`](../../frontend/dockerfile/builder/build.go) function.
15+
16+
## Basic examples
17+
18+
Here are a few Dockerfile instructions you should be familiar with:
19+
20+
- Base image
21+
22+
```dockerfile
23+
FROM golang:1.12
24+
```
25+
26+
```golang
27+
st := llb.Image("golang:1.12")
28+
```
29+
30+
- Scratch image
31+
32+
```dockerfile
33+
FROM scratch
34+
```
35+
36+
```golang
37+
st := llb.Scratch()
38+
```
39+
40+
- Environment variables
41+
42+
```dockerfile
43+
ENV DEBIAN_FRONTEND=noninteractive
44+
```
45+
46+
```golang
47+
st = st.AddEnv("DEBIAN_FRONTEND", "noninteractive")
48+
```
49+
50+
- Running programs
51+
52+
```dockerfile
53+
RUN echo hello
54+
```
55+
56+
```golang
57+
st = st.Run(
58+
llb.Shlex("echo hello"),
59+
).Root()
60+
```
61+
62+
- Working directory
63+
64+
```dockerfile
65+
WORKDIR /path
66+
```
67+
68+
```golang
69+
st = st.Dir("/path")
70+
```
71+
72+
## File operations
73+
74+
This is where LLB starts to deviate from Dockerfile in features. In
75+
Dockerfiles, the run command is completely opaque to the builder and just
76+
executes the command. But in LLB, there are file operations that have better
77+
caching semantics and understanding of the command:
78+
79+
- Copying files
80+
81+
```dockerfile
82+
COPY --from=builder /files/* /files
83+
```
84+
85+
```golang
86+
var CopyOptions = &llb.CopyInfo{
87+
FollowSymlinks: true,
88+
CopyDirContentsOnly: true,
89+
AttemptUnpack: false,
90+
CreateDestPath: true,
91+
AllowWildcard: true,
92+
AllowEmptyWildcard: true,
93+
}
94+
st = st.File(
95+
llb.Copy(builder, "/files/*", "/files", CopyOptions),
96+
)
97+
```
98+
99+
- Adding files
100+
101+
```dockerfile
102+
ADD --from=builder /files.tgz /files
103+
```
104+
105+
```golang
106+
var AddOptions = &llb.CopyInfo{
107+
FollowSymlinks: true,
108+
CopyDirContentsOnly: true,
109+
AttemptUnpack: true,
110+
CreateDestPath: true,
111+
AllowWildcard: true,
112+
AllowEmptyWildcard: true,
113+
}
114+
st = st.File(
115+
llb.Copy(builder, "/files.tgz", "files", AddOptions),
116+
)
117+
```
118+
119+
- Chaining file commands
120+
121+
```dockerfile
122+
# not possible without RUN in Dockerfile
123+
RUN mkdir -p /some && echo hello > /some/file
124+
```
125+
126+
```golang
127+
st = st.File(
128+
llb.Mkdir("/some", 0755),
129+
).File(
130+
llb.Mkfile("/some/file", 0644, "hello"),
131+
)
132+
```
133+
134+
## Bind mounts
135+
136+
Bind mounts allow unidirectional syncing of the host's local file system into
137+
the build environment.
138+
139+
Bind mounts in Buildkit should not be confused with bind mounts in the linux
140+
kernel - they do not sync bidirectionally. Bind mounts are only a snapshot of
141+
your local state, which is specified through the `llb.Local` state object:
142+
143+
- Using bind mounts
144+
145+
```dockerfile
146+
WORKDIR /builder
147+
RUN --mount=type=bind,target=/builder \
148+
PIP_INDEX_URL=https://my-proxy.com/pypi \
149+
pip install .
150+
```
151+
152+
```golang
153+
localState := llb.Local(
154+
"context",
155+
llb.SessionID(client.BuildOpts().SessionID),
156+
llb.WithCustomName("loading .")
157+
llb.FollowPaths([]string{"."}),
158+
)
159+
160+
execState = st.Dir("/builder").Run(
161+
llb.Shlex("pip install ."),
162+
llb.AddEnv(
163+
"PIP_INDEX_URL",
164+
"https://my-proxy.com/pypi",
165+
),
166+
)
167+
_ := execState.AddMount("/builder", localState)
168+
// the return value of AddMount captures the resulting state of the mount
169+
// after the exec operation has completed
170+
171+
st := execState.Root()
172+
```
173+
174+
## Cache mounts
175+
176+
Cache mounts allow for a shared file cache location between build invocations,
177+
which allow manually caching expensive operations, such as package downloads.
178+
Mounts have options to persist between builds with different sharing modes.
179+
180+
- Using cache mounts
181+
182+
```dockerfile
183+
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
184+
--mount=type=cache,target=/var/lib/apt \
185+
apt-get update
186+
```
187+
188+
```golang
189+
var VarCacheAptMount = llb.AddMount(
190+
"/var/cache/apt",
191+
llb.Scratch(),
192+
llb.AsPersistentCacheDir(
193+
"some-cache-id",
194+
llb.CacheMountLocked,
195+
),
196+
)
197+
198+
var VarLibAptMount = llb.AddMount(
199+
"/var/lib/apt",
200+
llb.Scratch(),
201+
llb.AsPersistentCacheDir(
202+
"another-cache-id",
203+
llb.CacheMountShared,
204+
),
205+
)
206+
207+
st := st.Run(
208+
llb.Shlex("apt-get update"),
209+
VarCacheAptMount,
210+
VarLibAptMount,
211+
).Root()
212+
```

0 commit comments

Comments
 (0)