"Ever wondered how your secrets might be exposed in your docker image? You're not alone 😉".
In this blog post, we'll dive into the world of Docker builds to learn two powerful ways to handle variables: build arguments and mount secrets.
Imagine you're building a container that needs both configuration value, like API_URL
and API_TOKEN
.
API_URL
is generally fine to be exposed, as it's not sensitive information.
However, the API_TOKEN
must be kept secret to prevent unauthorized access and potential security breaches.
Which method should you use? When should you use each? By the end of this post, you'll have a clear understanding of how to handle both scenarios with confidence.
First we'll build an image with build arguments, expose why it's risky to do so. Then, level up our security by using mount secrets. Let's dive in 🚀 !
Here's a typical Dockerfile that uses build arguments:
FROM alpine
ARG API_URL
ARG API_TOKEN
RUN echo "API_URL=${API_URL}" > /config.txt
RUN echo "API_TOKEN=${API_TOKEN}" > /token.txt
Let's build the image:
$ docker build \
-f app.dockerfile \
--build-arg API_URL=https://api.example.com \
--build-arg API_TOKEN=super_secret_12345 \
-t app .
Now, here's the issue - let's see what's stored in our image:
$ docker history app
# Output
IMAGE CREATED CREATED BY SIZE COMMENT
660d66666fc9 18 minutes ago RUN |2 API_URL=https://api.example.com API_T… 8.19kB buildkit.dockerfile.v0
<missing> 18 minutes ago RUN |2 API_URL=https://api.example.com API_T… 8.19kB buildkit.dockerfile.v0
<missing> 18 minutes ago ARG API_TOKEN=super_secret_12345 0B buildkit.dockerfile.v0
<missing> 18 minutes ago ARG API_URL=https://api.example.com 0B buildkit.dockerfile.v0
<missing> 2 weeks ago CMD ["/bin/sh"] 0B buildkit.dockerfile.v0
<missing> 2 weeks ago ADD alpine-minirootfs-3.21.2-x86_64.tar.gz /… 8.5MB buildkit.dockerfile.v0
"Look at that - our secret, API token is right there in plain sight!"
No Bueno, right? This is exactly why we need a better approach. Let's level up our security game."
To use this feature, Docker 23.0.0 or later and the buildx plugin are required.
When you mount a secret, Docker creates a file at /run/secrets/<secret_id>
.
The <secret_id>
is a temporary name you give to access your secret only during build time.
Here's the pattern:
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret
Let's see it in action. Here's our improved Dockerfile:
FROM alpine
ARG API_URL
RUN echo "API_URL=${API_URL}" > /config.txt
RUN --mount=type=secret,id=api_token \
cat /run/secrets/api_token > /token.txt
Build it the secure way:
$ API_TOKEN=super_secret_12345
$ docker build \
-f secure-app.dockerfile \
--build-arg API_URL=https://api.example.com \
--secret id=api_token,env=API_TOKEN \
-t secure-app .
Now, let's verify the secret is not stored:
$ docker history secure-app
# Output
IMAGE CREATED CREATED BY SIZE COMMENT
88f88888c950 3 minutes ago RUN |1 API_URL=https://api.example.com /bin/… 4.1kB buildkit.dockerfile.v0
<missing> 3 minutes ago RUN |1 API_URL=https://api.example.com /bin/… 8.19kB buildkit.dockerfile.v0
<missing> 3 minutes ago ARG API_URL=https://api.example.com 0B buildkit.dockerfile.v0
<missing> 2 weeks ago CMD ["/bin/sh"] 0B buildkit.dockerfile.v0
<missing> 2 weeks ago ADD alpine-minirootfs-3.21.2-x86_64.tar.gz /… 8.5MB buildkit.dockerfile.v0
# Check secret is empty
$ docker run --rm secure-app cat /token.txt
The output shows that:
API_TOKEN
is not stored in the image history.token.txt
file exists only during build time and it's empty or inaccessible after build./run/secrets/<secret_id>
RUN
steps🛠 build args for non-sensitive variables, such as:
🔒 mount secrets for sensitive variables, such as:
Ready to secure your docker builds? Start using mount secrets today! 🔒 and keep build args for public configurations.