the dependencies. Finally, copy over the project source code, which is subject
to frequent change.
```dockerfile
# syntax=docker/dockerfile:1
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 the context small
The easiest way to make sure your context doesn't include unnecessary files is
to create a `.dockerignore` file in the root of your build context. The
`.dockerignore` file works similarly to `.gitignore` files, and lets you
exclude files and directories from the build context.
Here's an example `.dockerignore` file that excludes the `node_modules`
directory, all files and directories that start with `tmp`:
```plaintext {title=".dockerignore"}
node_modules
tmp*
```
Ignore-rules specified in the `.dockerignore` file apply to the entire build
context, including subdirectories. This means it's a rather coarse-grained
mechanism, but it's a good way to exclude files and directories that you know
you don't need in the build context, such as temporary files, log files, and
build artifacts.
## Use bind mounts
You might be familiar with bind mounts for when you run containers with `docker
run` or Docker Compose. Bind mounts let you mount a file or directory from the
host machine into a container.
```bash
# bind mount using the -v flag
docker run -v $(pwd):/path/in/container image-name
# bind mount using the --mount flag
docker run --mount=type=bind,src=.,dst=/path/in/container image-name
```
To use bind mounts in a build, you can use the `--mount` flag with the `RUN`
instruction in your Dockerfile:
```dockerfile
FROM golang:latest
WORKDIR /app
RUN --mount=type=bind,target=. go build -o /app/hello
```
In this example, the current directory is mounted into the build container
before the `go build` command gets executed. The source code is available in
the build container for the duration of that `RUN` instruction. When the
instruction is done executing, the mounted files are not persisted in the final
image, or in the build cache. Only the output of the `go build` command
remains.
The `COPY` and `ADD` instructions in a Dockerfile lets you copy files from the
build context into the build container. Using bind mounts is beneficial for
build cache optimization because you're not adding unnecessary layers to the
cache. If you have build context that's on the larger side, and it's only used
to generate an artifact, you're better off using bind mounts to temporarily
mount the source code required to generate the artifact into the build. If you
use `COPY` to add the files to the build container, BuildKit will include all
of those files in the cache, even if the files aren't used in the final image.
There are a few things to be aware of when using bind mounts in a build:
- Bind mounts are read-only by default. If you need to write to the mounted
directory, you need to specify the `rw` option. However, even with the `rw`
option, the changes are not persisted in the final image or the build cache.
The file writes are sustained for the duration of the `RUN` instruction, and
are discarded after the instruction is done.
- Mounted files are not persisted in the final image. Only the output of the
`RUN` instruction is persisted in the final image. If you need to include
files from the build context in the final image, you need to use the `COPY`
or `ADD` instructions.
- If the target directory is not empty, the contents of the target directory
are hidden by the mounted files. The original contents are restored after the
`RUN` instruction is done.
{{< accordion title="Example" >}}
For example, given a build context with only a `Dockerfile` in it:
```plaintext
.
└── Dockerfile