Merge pull request #15603 from jedevc/cache-docs

Add build cache introduction docs
This commit is contained in:
CrazyMax 2022-09-27 11:08:26 +02:00 committed by GitHub
commit 99370cd8dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 577 additions and 0 deletions

View File

@ -1387,6 +1387,8 @@ manuals:
section:
- path: /build/building/packaging/
title: Packaging your software
- path: /build/building/cache/
title: Optimizing builds with cache management
- sectiontitle: Choosing a build driver
section:
- path: /build/building/drivers/

View File

@ -180,6 +180,11 @@ body.night {
img.white-bg {
background-color: white;
}
/* apply to images that support being inverted */
img.invertible {
filter: invert(100%) hue-rotate(180deg);
}
/* accordion */
.panel {

294
build/building/cache.md Normal file
View File

@ -0,0 +1,294 @@
---
title: Optimizing builds with cache management
description: Improve your build speeds by taking advantage of the builtin cache
keywords: >
build, buildx, buildkit, dockerfile, image layers, build instructions, build
context
---
You will likely find yourself rebuilding the same Docker image over and over
again. Whether it's for the next release of your software, or locally during
development. Because building images is a common task, Docker provides several
tools that speed up builds.
The most important feature for improving build speeds is Docker's build cache.
## How does the build cache work?
Understanding Docker's build cache helps you write better Dockerfiles that
result in faster builds.
Have a look at the following example, which shows a simple Dockerfile for a
program written in C.
```dockerfile
FROM ubuntu:latest
RUN apt-get update && apt-get install -y build-essentials
COPY main.c /src/
WORKDIR /src/
RUN make build
```
Each instruction in this Dockerfile translates (roughly) to a layer in your
final image. You can think of image layers as a stack, with each layer adding
more content on top of the layers that came before it:
![Image layer diagram showing the above commands chained together one after the other](../images/cache-stack.svg){:.invertible}
Whenever a layer changes, that layer will need to be re-built. For example,
suppose you make a change to your program in the `main.c` file. After this
change, the `COPY` command will have to run again in order for those changes to
appear in the image. In other words, Docker will invalidate the cache for this
layer.
![Image layer diagram, but now with the link between COPY and WORKDIR marked as invalid](../images/cache-stack-invalidate-copy.svg){:.invertible}
If a layer changes, all other layers that come after it are also affected. When
the layer with the `COPY` command gets invalidated, all layers that follow will
need to run again, too:
![Image layer diagram, but now with all links after COPY marked as invalid](../images/cache-stack-invalidate-rest.svg){:.invertible}
And that's the Docker build cache in a nutshell. Once a layer changes, then all
downstream layers need to be rebuilt as well. Even if they wouldn't build
anything differently, they still need to re-run.
> **Note**
>
> Suppose you have a `RUN apt-get update && apt-get upgrade -y` step in your
> Dockerfile to upgrade all the software packages in your Debian-based image to
> the latest version.
>
> This doesn't mean that the images you build are always up to date. Rebuilding
> the image on the same host one week later will still get you the same packages
> as before. The only way to force a rebuild is by making sure that a layer
> before it has changed, or by clearing the build cache using
> [`docker builder prune`](/engine/reference/commandline/builder_build/).
## How can I use the cache efficiently?
Now that you understand how the cache works, you can begin to use the cache to
your advantage. While the cache will automatically work on any `docker build`
that you run, you can often refactor your Dockerfile to get even better
performance. These optimizations can save precious seconds (or even minutes) off
of your builds.
### Order your layers
Putting the commands in your Dockerfile into a logical order is a great place to
start. Because a change causes a rebuild for steps that follow, try to make
expensive steps appear near the beginning of the Dockerfile. Steps that change
often should appear near the end of the Dockerfile, to avoid triggering rebuilds
of layers that haven't changed.
Consider the following example. A Dockerfile snippet that runs a JavaScript
build from the source files in the current directory:
```dockerfile
FROM node
WORKDIR /app
COPY . . # Copy over all files in the current directory
RUN npm install # Install dependencies
RUN npm build # Run build
```
This Dockerfile is rather inefficient. Updating any file causes a reinstall of
all dependencies every time you build the Docker image &emdash; even if the
dependencies didn't change since last time!
Instead, the `COPY` command can be split in two. First, copy over the package
management files (in this case, `package.json` and `yarn.lock`). Then, install
the dependencies. Finally, copy over the project source code, which is subject
to frequent change.
```dockerfile
FROM node
WORKDIR /app
COPY package.json yarn.lock . # Copy package management files
RUN npm install # Install dependencies
COPY . . # Copy over project files
RUN npm build # Run build
```
By installing dependencies in earlier layers of the Dockerfile, there is no need
to rebuild those layers when a project file has changed.
### Keep layers small
One of the best things you can do to speed up image building is to just put less
stuff into your build. Fewer parts means the cache stay smaller, but also that
there should be fewer things that could be out-of-date and need rebuilding.
To get started, here are a few tips and tricks:
#### Don't include unnecessary files
Be considerate of what files you add to the image.
Running a command like `COPY . /src` will `COPY` your entire build context into
the image. If you've got logs, package manager artifacts, or even previous build
results in your current directory, those will also be copied over. This could
make your image larger than it needs to be, especially as those files are
usually not useful.
Avoid adding unnecessary files to your builds by explicitly stating the files or
directories you intend to copy over. For example, you might only want to add a
`Makefile` and your `src` directory to the image filesystem. In that case,
consider adding this to your Dockerfile:
```dockerfile
COPY ./src ./Makefile /src
```
As opposed to this:
```dockerfile
COPY . /src
```
You can also create a
[`.dockerignore` file](https://docs.docker.com/engine/reference/builder/#dockerignore-file),
and use that to specify which files and directories to exclude from the build
context.
#### Use your package manager wisely
Most Docker image builds involve using a package manager to help install
software into the image. Debian has `apt`, Alpine has `apk`, Python has `pip`,
NodeJS has `npm`, and so on.
When installing packages, be considerate. Make sure to only install the packages
that you need. If you're not going to use them, don't install them. Remember
that this might be a different list for your local development environment and
your production environment. You can use multi-stage builds to split these up
efficiently.
#### Use the dedicated `RUN` cache
The `RUN` command supports a specialized cache, which you can use when you need
a more fine-grained cache between runs. For example, when installing packages,
you don't always need to fetch all of your packages from the internet each time.
You only need the ones that have changed.
To solve this problem, you can use `RUN --mount type=cache`. For example, for
your Debian-based image you might use the following:
```dockerfile
RUN \
--mount=type=cache,target=/var/cache/apt \
apt-get update && apt-get install -y git
```
Using the explicit cache with the `--mount` flag keeps the contents of the
`target` directory preserved between builds. When this layer needs to be
rebuilt, then it'll use the `apt` cache in `/var/cache/apt`.
### Minimize the number of layers
Keeping your layers small is a good first step, and the logical next step is to
reduce the number of layers that you have. Fewer layers mean that you have less
to rebuild, when something in your Dockerfile changes, so your build will
complete faster.
The following sections outline some tips you can use to keep the number of
layers to a minimum.
#### Use an appropriate base image
Docker provides over 170 pre-built
[official images](https://hub.docker.com/search?q=&image_filter=official) for
almost every common development scenario. For example, if you're building a Java
web server, use a dedicated image such as
[`openjdk`](https://hub.docker.com/_/openjdk/). Even when there's not an
official image for what you might want, Docker provides images from
[verified publishers](https://hub.docker.com/search?q=&image_filter=store) and
[open source partners](https://hub.docker.com/search?q=&image_filter=open_source)
that can help you on your way. The Docker community often produces third-party
images to use as well.
Using official images saves you time and ensures you stay up to date and secure
by default.
#### Use multi-stage builds
<!-- x-link to multi-stage builds once we have some reworked content for that -->
Multi-stage builds let you split up your Dockerfile into multiple distinct
stages. Each stage completes a step in the build process, and you can bridge the
different stages to create your final image at the end. The Docker builder will
work out dependencies between the stages and run them using the most efficient
strategy. This even allows you to run multiple builds concurrently.
Multi-stage builds use two or more `FROM` commands. The following example
illustrates building a simple web server that serves HTML from your `docs`
directory in Git:
```dockerfile
# stage 1
FROM alpine as git
RUN apk add git
# stage 2
FROM git as fetch
WORKDIR /repo
RUN git clone https://github.com/your/repository.git .
# stage 3
FROM nginx as site
COPY --from=fetch /repo/docs/ /usr/share/nginx/html
```
This build has 3 stages: `git`, `fetch` and `site`. In this example, `git` is
the base for the `fetch` stage. It uses the `COPY --from` flag to copy the data
from the `docs/` directory into the Nginx server directory.
Each stage has only a few instructions, and when possible, Docker will run these
stages in parallel. Only the instructions in the `site` stage will end up as
layers in the final image. The entire `git` history doesn't get embedded into
the final result, which helps keep the image small and secure.
#### Combine commands together wherever possible.
Most Dockerfile commands, and `RUN` commands in particular, can often be joined
together. For example, instead of using `RUN` like this:
```dockerfile
RUN echo "the first command"
RUN echo "the second command"
```
It's possible to run both of these commands inside a single `RUN`, which means
that they will share the same cache! This can is achievable using the `&&` shell
operator to run one command after another:
```dockerfile
RUN echo "the first command" && echo "the second command"
# or to split to multiple lines
RUN echo "the first command" && \
echo "the second command"
```
Another shell feature that allows you to simplify and concatenate commands in a
neat way are [`heredocs`](https://en.wikipedia.org/wiki/Here_document){:
target="blank" rel="noopener" class="\_"}. It enables you to create multi-line
scripts with good readability:
```dockerfile
RUN <<EOF
set -e
echo "the first command"
echo "the second command"
EOF
```
(Note the `set -e` command to exit immediately after any command fails, instead
of continuing.)
## Other resources
For more information on using cache to do efficient builds:
<!-- x-link to dedicated cache exporter content once that's written -->
- [Export your build cache](https://github.com/moby/buildkit#export-cache)

View File

@ -0,0 +1,19 @@
// dot -Tsvg ./cache-invalidate-copy.dot > ./cache-invalidate-copy.svg
digraph {
rankdir="LR";
nodesep=0.3;
edge[minlen=0];
bgcolor="#00000000";
node [ shape=rect, width=5, height=0.4, fontname=monospace, fontsize=10 ];
from [ label = <<B>FROM </B>ubuntu:latest> ];
deps [ label = <<B>RUN </B>apt-get update &amp;&amp; \\<br/>apt-get install -y build-essentials> ];
copy [ label = <<B>COPY </B>main.c /src/>, color = "red" ];
workdir [ label = <<B>WORKDIR </B>/src/> ];
build [ label = <<B>RUN </B>make build> ];
from -> deps;
deps -> copy;
copy -> workdir [ color = "red", label = " ❌ " ];
workdir -> build;
}

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 6.0.1 (20220911.1526)
-->
<!-- Pages: 1 -->
<svg width="368pt" height="256pt"
viewBox="0.00 0.00 368.00 256.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 252)">
<polygon fill="none" stroke="none" points="-4,4 -4,-252 364,-252 364,4 -4,4"/>
<!-- from -->
<g id="node1" class="node">
<title>from</title>
<polygon fill="none" stroke="black" points="360,-248 0,-248 0,-219 360,-219 360,-248"/>
<text text-anchor="start" x="125" y="-232" font-family="monospace" font-weight="bold" font-size="10.00">FROM </text>
<text text-anchor="start" x="156" y="-232" font-family="monospace" font-size="10.00">ubuntu:latest</text>
</g>
<!-- deps -->
<g id="node2" class="node">
<title>deps</title>
<polygon fill="none" stroke="black" points="360,-197 0,-197 0,-168 360,-168 360,-197"/>
<text text-anchor="start" x="110" y="-185.5" font-family="monospace" font-weight="bold" font-size="10.00">RUN </text>
<text text-anchor="start" x="135" y="-185.5" font-family="monospace" font-size="10.00">apt&#45;get update &amp;&amp; \</text>
<text text-anchor="start" x="74.5" y="-175.5" font-family="monospace" font-size="10.00">apt&#45;get install &#45;y build&#45;essentials</text>
</g>
<!-- from&#45;&gt;deps -->
<g id="edge1" class="edge">
<title>from&#45;&gt;deps</title>
<path fill="none" stroke="black" d="M180,-218.77C180,-214.96 180,-211.15 180,-207.33"/>
<polygon fill="black" stroke="black" points="183.5,-207.24 180,-197.24 176.5,-207.24 183.5,-207.24"/>
</g>
<!-- copy -->
<g id="node3" class="node">
<title>copy</title>
<polygon fill="none" stroke="red" points="360,-146 0,-146 0,-117 360,-117 360,-146"/>
<text text-anchor="start" x="128" y="-130" font-family="monospace" font-weight="bold" font-size="10.00">COPY </text>
<text text-anchor="start" x="159" y="-130" font-family="monospace" font-size="10.00">main.c /src/</text>
</g>
<!-- deps&#45;&gt;copy -->
<g id="edge2" class="edge">
<title>deps&#45;&gt;copy</title>
<path fill="none" stroke="black" d="M180,-167.77C180,-163.96 180,-160.15 180,-156.33"/>
<polygon fill="black" stroke="black" points="183.5,-156.24 180,-146.24 176.5,-156.24 183.5,-156.24"/>
</g>
<!-- workdir -->
<g id="node4" class="node">
<title>workdir</title>
<polygon fill="none" stroke="black" points="360,-80 0,-80 0,-51 360,-51 360,-80"/>
<text text-anchor="start" x="140" y="-64" font-family="monospace" font-weight="bold" font-size="10.00">WORKDIR </text>
<text text-anchor="start" x="189" y="-64" font-family="monospace" font-size="10.00">/src/</text>
</g>
<!-- copy&#45;&gt;workdir -->
<g id="edge3" class="edge">
<title>copy&#45;&gt;workdir</title>
<path fill="none" stroke="red" d="M180,-116.89C180,-109.12 180,-99.36 180,-90.43"/>
<polygon fill="red" stroke="red" points="183.5,-90.15 180,-80.15 176.5,-90.15 183.5,-90.15"/>
<text text-anchor="middle" x="169.5" y="-94.8" font-family="Times,serif" font-size="14.00">&#160;</text>
</g>
<!-- build -->
<g id="node5" class="node">
<title>build</title>
<polygon fill="none" stroke="black" points="360,-29 0,-29 0,0 360,0 360,-29"/>
<text text-anchor="start" x="137" y="-13" font-family="monospace" font-weight="bold" font-size="10.00">RUN </text>
<text text-anchor="start" x="162" y="-13" font-family="monospace" font-size="10.00">make build</text>
</g>
<!-- workdir&#45;&gt;build -->
<g id="edge4" class="edge">
<title>workdir&#45;&gt;build</title>
<path fill="none" stroke="black" d="M180,-50.77C180,-46.96 180,-43.15 180,-39.33"/>
<polygon fill="black" stroke="black" points="183.5,-39.24 180,-29.24 176.5,-39.24 183.5,-39.24"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,19 @@
// dot -Tsvg ./cache-invalidate-rest.dot > ./cache-invalidate-rest.svg
digraph {
rankdir="LR";
nodesep=0.3;
edge[minlen=0];
bgcolor="#00000000";
node [ shape=rect, width=5, height=0.4, fontname=monospace, fontsize=10 ];
from [ label = <<B>FROM </B>ubuntu:latest> ];
deps [ label = <<B>RUN </B>apt-get update &amp;&amp; \\<br/>apt-get install -y build-essentials> ];
copy [ label = <<B>COPY </B>main.c /src/>, color = "red" ];
workdir [ label = <<B>WORKDIR </B>/src/>, color = "red" ];
build [ label = <<B>RUN </B>make build>, color = "red" ];
from -> deps;
deps -> copy;
copy -> workdir [ color = "red", label = " ❌ " ];
workdir -> build [ color = "red", label = " ❌ " ];
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 6.0.1 (20220911.1526)
-->
<!-- Pages: 1 -->
<svg width="368pt" height="271pt"
viewBox="0.00 0.00 368.00 271.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 267)">
<polygon fill="none" stroke="none" points="-4,4 -4,-267 364,-267 364,4 -4,4"/>
<!-- from -->
<g id="node1" class="node">
<title>from</title>
<polygon fill="none" stroke="black" points="360,-263 0,-263 0,-234 360,-234 360,-263"/>
<text text-anchor="start" x="125" y="-247" font-family="monospace" font-weight="bold" font-size="10.00">FROM </text>
<text text-anchor="start" x="156" y="-247" font-family="monospace" font-size="10.00">ubuntu:latest</text>
</g>
<!-- deps -->
<g id="node2" class="node">
<title>deps</title>
<polygon fill="none" stroke="black" points="360,-212 0,-212 0,-183 360,-183 360,-212"/>
<text text-anchor="start" x="110" y="-200.5" font-family="monospace" font-weight="bold" font-size="10.00">RUN </text>
<text text-anchor="start" x="135" y="-200.5" font-family="monospace" font-size="10.00">apt&#45;get update &amp;&amp; \</text>
<text text-anchor="start" x="74.5" y="-190.5" font-family="monospace" font-size="10.00">apt&#45;get install &#45;y build&#45;essentials</text>
</g>
<!-- from&#45;&gt;deps -->
<g id="edge1" class="edge">
<title>from&#45;&gt;deps</title>
<path fill="none" stroke="black" d="M180,-233.77C180,-229.96 180,-226.15 180,-222.33"/>
<polygon fill="black" stroke="black" points="183.5,-222.24 180,-212.24 176.5,-222.24 183.5,-222.24"/>
</g>
<!-- copy -->
<g id="node3" class="node">
<title>copy</title>
<polygon fill="none" stroke="red" points="360,-161 0,-161 0,-132 360,-132 360,-161"/>
<text text-anchor="start" x="128" y="-145" font-family="monospace" font-weight="bold" font-size="10.00">COPY </text>
<text text-anchor="start" x="159" y="-145" font-family="monospace" font-size="10.00">main.c /src/</text>
</g>
<!-- deps&#45;&gt;copy -->
<g id="edge2" class="edge">
<title>deps&#45;&gt;copy</title>
<path fill="none" stroke="black" d="M180,-182.77C180,-178.96 180,-175.15 180,-171.33"/>
<polygon fill="black" stroke="black" points="183.5,-171.24 180,-161.24 176.5,-171.24 183.5,-171.24"/>
</g>
<!-- workdir -->
<g id="node4" class="node">
<title>workdir</title>
<polygon fill="none" stroke="red" points="360,-95 0,-95 0,-66 360,-66 360,-95"/>
<text text-anchor="start" x="140" y="-79" font-family="monospace" font-weight="bold" font-size="10.00">WORKDIR </text>
<text text-anchor="start" x="189" y="-79" font-family="monospace" font-size="10.00">/src/</text>
</g>
<!-- copy&#45;&gt;workdir -->
<g id="edge3" class="edge">
<title>copy&#45;&gt;workdir</title>
<path fill="none" stroke="red" d="M180,-131.89C180,-124.12 180,-114.36 180,-105.43"/>
<polygon fill="red" stroke="red" points="183.5,-105.15 180,-95.15 176.5,-105.15 183.5,-105.15"/>
<text text-anchor="middle" x="169.5" y="-109.8" font-family="Times,serif" font-size="14.00">&#160;</text>
</g>
<!-- build -->
<g id="node5" class="node">
<title>build</title>
<polygon fill="none" stroke="red" points="360,-29 0,-29 0,0 360,0 360,-29"/>
<text text-anchor="start" x="137" y="-13" font-family="monospace" font-weight="bold" font-size="10.00">RUN </text>
<text text-anchor="start" x="162" y="-13" font-family="monospace" font-size="10.00">make build</text>
</g>
<!-- workdir&#45;&gt;build -->
<g id="edge4" class="edge">
<title>workdir&#45;&gt;build</title>
<path fill="none" stroke="red" d="M180,-65.89C180,-58.12 180,-48.36 180,-39.43"/>
<polygon fill="red" stroke="red" points="183.5,-39.15 180,-29.15 176.5,-39.15 183.5,-39.15"/>
<text text-anchor="middle" x="169.5" y="-43.8" font-family="Times,serif" font-size="14.00">&#160;</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,19 @@
// dot -Tsvg ./cache-stack.dot > ./cache-stack.svg
digraph {
rankdir="LR";
nodesep=0.3;
edge[minlen=0];
bgcolor="#00000000";
node [ shape=rect, width=5, height=0.4, fontname=monospace, fontsize=10 ];
from [ label = <<B>FROM </B>ubuntu:latest> ];
deps [ label = <<B>RUN </B>apt-get update &amp;&amp; \\<br/>apt-get install -y build-essentials> ];
copy [ label = <<B>COPY </B>main.c /src/> ];
workdir [ label = <<B>WORKDIR </B>/src/> ];
build [ label = <<B>RUN </B>make build> ];
from -> deps;
deps -> copy;
copy -> workdir;
workdir -> build;
}

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 6.0.1 (20220911.1526)
-->
<!-- Pages: 1 -->
<svg width="368pt" height="241pt"
viewBox="0.00 0.00 368.00 241.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 237)">
<polygon fill="none" stroke="none" points="-4,4 -4,-237 364,-237 364,4 -4,4"/>
<!-- from -->
<g id="node1" class="node">
<title>from</title>
<polygon fill="none" stroke="black" points="360,-233 0,-233 0,-204 360,-204 360,-233"/>
<text text-anchor="start" x="125" y="-217" font-family="monospace" font-weight="bold" font-size="10.00">FROM </text>
<text text-anchor="start" x="156" y="-217" font-family="monospace" font-size="10.00">ubuntu:latest</text>
</g>
<!-- deps -->
<g id="node2" class="node">
<title>deps</title>
<polygon fill="none" stroke="black" points="360,-182 0,-182 0,-153 360,-153 360,-182"/>
<text text-anchor="start" x="110" y="-170.5" font-family="monospace" font-weight="bold" font-size="10.00">RUN </text>
<text text-anchor="start" x="135" y="-170.5" font-family="monospace" font-size="10.00">apt&#45;get update &amp;&amp; \</text>
<text text-anchor="start" x="74.5" y="-160.5" font-family="monospace" font-size="10.00">apt&#45;get install &#45;y build&#45;essentials</text>
</g>
<!-- from&#45;&gt;deps -->
<g id="edge1" class="edge">
<title>from&#45;&gt;deps</title>
<path fill="none" stroke="black" d="M180,-203.77C180,-199.96 180,-196.15 180,-192.33"/>
<polygon fill="black" stroke="black" points="183.5,-192.24 180,-182.24 176.5,-192.24 183.5,-192.24"/>
</g>
<!-- copy -->
<g id="node3" class="node">
<title>copy</title>
<polygon fill="none" stroke="black" points="360,-131 0,-131 0,-102 360,-102 360,-131"/>
<text text-anchor="start" x="128" y="-115" font-family="monospace" font-weight="bold" font-size="10.00">COPY </text>
<text text-anchor="start" x="159" y="-115" font-family="monospace" font-size="10.00">main.c /src/</text>
</g>
<!-- deps&#45;&gt;copy -->
<g id="edge2" class="edge">
<title>deps&#45;&gt;copy</title>
<path fill="none" stroke="black" d="M180,-152.77C180,-148.96 180,-145.15 180,-141.33"/>
<polygon fill="black" stroke="black" points="183.5,-141.24 180,-131.24 176.5,-141.24 183.5,-141.24"/>
</g>
<!-- workdir -->
<g id="node4" class="node">
<title>workdir</title>
<polygon fill="none" stroke="black" points="360,-80 0,-80 0,-51 360,-51 360,-80"/>
<text text-anchor="start" x="140" y="-64" font-family="monospace" font-weight="bold" font-size="10.00">WORKDIR </text>
<text text-anchor="start" x="189" y="-64" font-family="monospace" font-size="10.00">/src/</text>
</g>
<!-- copy&#45;&gt;workdir -->
<g id="edge3" class="edge">
<title>copy&#45;&gt;workdir</title>
<path fill="none" stroke="black" d="M180,-101.77C180,-97.96 180,-94.15 180,-90.33"/>
<polygon fill="black" stroke="black" points="183.5,-90.24 180,-80.24 176.5,-90.24 183.5,-90.24"/>
</g>
<!-- build -->
<g id="node5" class="node">
<title>build</title>
<polygon fill="none" stroke="black" points="360,-29 0,-29 0,0 360,0 360,-29"/>
<text text-anchor="start" x="137" y="-13" font-family="monospace" font-weight="bold" font-size="10.00">RUN </text>
<text text-anchor="start" x="162" y="-13" font-family="monospace" font-size="10.00">make build</text>
</g>
<!-- workdir&#45;&gt;build -->
<g id="edge4" class="edge">
<title>workdir&#45;&gt;build</title>
<path fill="none" stroke="black" d="M180,-50.77C180,-46.96 180,-43.15 180,-39.33"/>
<polygon fill="black" stroke="black" points="183.5,-39.24 180,-29.24 176.5,-39.24 183.5,-39.24"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB