Multi-stage builds
On June 25th, 2018 I published a blog post on using Docker multi-stage builds to securely download private npm packages. At the time this was the most secure method for using .npmrc
files in Docker builds.
Instead of rewriting that old post I’m publishing a follow-up post on Docker build secrets. This blog post assumes you’ve read my previous post on multi-stage builds.
Docker build secrets
In November 2018 Docker 18.09 introduced a new --secret
flag for docker build
. This allows us to pass secrets from a file to our Docker builds. These secrets aren’t saved in the final Docker image, any intermediate images, or the image commit history. With build secrets, you can now securely build Docker images with private npm packages without build arguments and multi-stage builds.
Enabling BuildKit support
To use build secrets you’ll first need to enable support for Moby BuildKit. If you don’t enable BuildKit you’ll get the error message Error response from daemon: Dockerfile parse error line 7: Unknown flag: mount
when trying to use build secrets.
To enable BuildKit, run export DOCKER_BUILDKIT=1
. If you don’t want to set this environment variable you can instead prepend docker build
commands with DOCKER_BUILDKIT=1
.
Docker 18.09 is the first version to ship with optional BuildKit support. Eventually, BuildKit will be the default Docker build engine.
Using Docker build secrets with npm tokens
Here’s a Dockerfile called Dockerfile-secure-secrets
that uses build secrets to install a Node.js app using private npm packages. The full source code and instructions are available at https://github.com/alulsh/docker-npmrc-security.
# syntax = docker/dockerfile:1.0-experimental
FROM node:8.11.3-alpine
WORKDIR /private-app
COPY . /private-app
RUN --mount=type=secret,id=npm,target=/root/.npmrc npm install
EXPOSE 3000
CMD ["node","index.js"]
To build this Docker image locally, run docker build . -f Dockerfile-secure-secrets -t secure-app-secrets --secret id=npm,src=$HOME/.npmrc
.
This docker build command passes your local .npmrc
file at $HOME/.npmrc
to the RUN
command. Then RUN --mount=type=secret,id=npm,target=/root/.npmrc
adds your .npmrc
file to /root/.npmrc
on the Docker image. The npm install
command then uses the /root/.npmrc
file to access and install private npm packages.
You can check out the full syntax for --mount=type=secret
in the Moby BuildKit documentation.
Verifying no secrets
To verify Docker build secrets didn’t leak our .npmrc
file or npm tokens, run docker history secure-app-secrets
.
Verifying that build secrets did not leak our npmrc file or npm tokens
Docker build secrets do leave a 0-byte blank file in our final Docker image though. It leaves this file at the target destination for our secrets, in this case /root/.npmrc
.
Empty file bug with Docker build secrets
This blank file is empty and so far there’s no evidence it can be exploited. It’s also a known issue from the Docker CLI pull request that added this feature in August 2018. I verified this bug still exists with Docker version 18.09.2, build 6247962
.
Experimental support?
Though the --secret
flag appears in a production version of Docker, there are a few hints that support is still somewhat experimental.
For example, the first line of a Dockerfile using build secrets must be # syntax = docker/dockerfile:1.0-experimental
. Without this line you’ll get the error failed to create LLB definition: Dockerfile parse error line 6: Unknown flag: mount
. This line enables the Docker CLI to use the “experimental Dockerfile frontend” for Moby BuildKit.
The build secrets launch blog post acknowledges that “secrets are currently not enabled in the stable channel of external Dockerfiles, so you need to use one of the releases in the experimental channel.” The Docker CLI pull request mentions this feature doesn’t use the stable default front end.
Even if the secrets CLI feature itself is stable, the external dockerfile it relies on is experimental. The syntax directive at the top of the Dockerfile feels like a hack or workaround that makes me feel uneasy about production usage.
Conclusion
Docker build secrets finally provide a secure way to use secrets in Docker images. Now multi-stage builds can be used for smaller Docker images instead of removing secrets.
Support for Docker build secrets still relies on an experimental Dockerfile frontend. As a result, Docker build secrets may or may not be ready for production use depending on your tolerance for risk.
I’ll update this blog post when Docker build secrets use a stable external Dockerfile frontend. We likely won’t have to wait long!