Skip to content
Joey Wang
Menu

Search

DevOps and Reliability

ARG, ENV, and BuildKit secrets: how each behaves in Docker

ARG, ENV, exported shell variables, and BuildKit secret mounts each persist differently in a Docker image, and only one of them is actually safe for tokens.

· 2 min read

devopssecurity #docker#security#devops

Audio summary

The same Dockerfile can declare a value four different ways, ARG, ENV, an exported shell variable, or a BuildKit secret mount, and each one persists differently. Only one of them is actually safe to put a token in.

FROM alpine
ARG arg_key
ENV env_key=45678
RUN export EXPORT_KEY=123455 ls
RUN INLINE_KEY=123456 ls
RUN echo $arg_key
RUN --mount=type=secret,id=github_key,required=true \
    export GITHUB_KEY="$(cat /run/secrets/github_key)" && echo 'this is safe'

What each one actually does

ARG exists only at build time. It’s accessible while the image is being built, and gone from the runtime environment, but it still shows up in docker history and the build cache, so it’s not safe for anything sensitive.

ENV persists into the final image and stays there for the container’s whole lifecycle. Anyone who can run the image, or pull its layers, can read it. Fine for configuration, wrong for secrets.

export EXPORT_KEY=... ls and INLINE_KEY=... ls are both scoped to that one RUN command. Neither survives into the next layer. Useful for one-off shell logic, not a substitute for real secret handling.

BuildKit’s --mount=type=secret is the one built for this. The value is mounted into the step at build time and never written to a layer:

RUN --mount=type=secret,id=github_key,required=true \
    export GITHUB_KEY="$(cat /run/secrets/github_key)" && echo 'this is safe'

required=true fails the build outright if the secret isn’t provided, which is better than a build that silently proceeds with an empty token.

TypePersistenceSafe for secrets
ARGBuild-time only, but visible in historyNo
ENVFull container runtimeNo
exported / inline varSingle RUN commandNo, but fine for non-sensitive one-off logic
BuildKit secret mountTemporary, never in a layerYes

Where this shows up in practice

Cloning a private repository during a build is the case that comes up most:

RUN --mount=type=secret,id=github_token \
    git clone https://token:$(cat /run/secrets/github_token)@github.com/org/repo.git

Baking the token into an ARG or ENV instead would leave it sitting in the image’s layer history indefinitely, readable by anyone who can pull the image or inspect its layers, long after the token itself has been rotated.