<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 https://www.facebook.com/tr?id=1063935717132479&amp;ev=PageView&amp;noscript=1 "> Secrets, Build Args, and Env Vars

Secrets, Build Args, and Env Vars

A secret is any credential your app needs but no one else should see: database passwords, API keys, tokens. The rule is simple. Don't bake them into the image. An image is shared and its layers can be inspected, so a secret built into one is a secret leaked to everyone who can pull it.

Two mechanisms feed values into a build or container, and only one is reasonable for secrets.

Build args vs. env vars

  • Build arg ( ARG ): a value available only while the image is being built, passed with --build-arg. Good for build-time choices like a version number. Not for secrets: the value is visible in the image's build history.
  • Env var ( ENV / -e): a value available to the app at runtime. This is where configuration belongs, and secrets are injected here at run time, never written into the Dockerfile.

What not to do

# DON'T: hardcode a real secret as an ENV in the image.
# It's baked into a layer and ships with every copy of the image.
ENV STRIPE_API_KEY="sk_live_4eC39HqLyjWDarjtT1zdp7dc"

# DON'T: pass a secret as a build arg either.
# Build args are recorded in the image history; `docker history` reveals them.
ARG STRIPE_API_KEY
RUN echo "$STRIPE_API_KEY" > /app/.key

Anyone who pulls the image can run docker history or inspect its layers and read these back, even if a later instruction appears to delete the value.

How to use it

# Build-time only: a non-secret choice
ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV
# DO: inject the real secret at run time, not at build time
docker run -p 3000:3000 -e STRIPE_API_KEY="sk_live_..." my-api

# DO: load a whole file of vars (keep this file out of git)
docker run -p 3000:3000 --env-file .env my-api

In Compose, the same idea uses environment for non-secret config and env_file for values you keep out of source control.

Two caveats

Env vars are a baseline, not a vault. Injecting secrets at runtime keeps them out of the image, which is the main goal. But the values aren't invisible: anyone with access to the Docker host can run docker inspect on a running container and read its environment. For a solo project that's usually fine. When more people share the infrastructure, production platforms (Kubernetes, AWS) provide dedicated secret stores that encrypt values and control who can read them.

Sometimes a secret is needed during the build itself. Say npm install has to pull a package from a private registry that requires a token. You can't use a runtime env var, and you now know not to use a build arg. Docker has a purpose-built escape hatch: a BuildKit secret mount . You pass the secret with docker build --secret, and a RUN --mount=type=secret,... instruction makes it available to that single step only. The value is never written into a layer or the build history. Just remember that "secret needed at build time" has a dedicated tool, and reach for it when you hit that wall.

Why it matters

  • Image layers and build history are inspectable. A RUN or ARG that touched a secret can be read back out, even if a later layer "removes" it.
  • Images get pushed to registries and shared. A baked-in secret travels with every copy.
  • Runtime env vars stay out of the image, so the same image is safe to ship to every environment, each supplying its own credentials.

Check your understanding

Question 1 of 3

Build arg isn't safe for secrets

You pass an API key to your image build with `--build-arg STRIPE_KEY=sk_live_...`. The key doesn't appear in any `ENV` instruction. Why is this still unsafe?