How to Persist Docker Build Arguments as Env Vars

Christian Emmer
Christian Emmer
Aug 22, 2021 · 4 min read
How to Persist Docker Build Arguments as Env Vars

Values from the Docker ARG instruction aren't persisted in built images, so here's a quick tip on how to persist them!

Use cases

ARG instructions are great for setting or pinning values such as version numbers like this:

ARG NODE_VERSION=lts

FROM node:${NODE_VERSION}-alpine

RUN node --version
FROM node:lts-alpine

ARG NCU_VERSION=11.8.3

RUN npm install --global npm-check-updates@${NCU_VERSION} && \
    ncu --version

The problem

To prove that ARG values aren't persisted in built images, let's take the following Dockerfile and then build and run it:

FROM node:lts-alpine

ARG NODE_ENV=development

RUN echo NODE_ENV=${NODE_ENV}

CMD echo NODE_ENV=${NODE_ENV}

When we build it, we'll get output similar to:

$ docker build --tag arg .
[2/2] RUN echo NODE_ENV=development
sha256:d6cb91e153d8eae5f90b7584d24260c818276a3bc2b7969338851035d5e1f5aa
0.204 NODE_ENV=development
DONE 0.2s

Which has the RUN output we would expect, but if we run that image:

$ docker run arg
NODE_ENV=

We see the environment variable is unset.

The solution

The solution is pretty simple - ARG values aren't persisted, but ENV values are, so let's make an ENV out of an ARG:

FROM node:lts-alpine

ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

RUN echo NODE_ENV=${NODE_ENV}

CMD echo NODE_ENV=${NODE_ENV}

When we build this version, we'll still get the RUN output we would expect:

$ docker build --tag arg .
[2/2] RUN echo NODE_ENV=development
sha256:d6cb91e153d8eae5f90b7584d24260c818276a3bc2b7969338851035d5e1f5aa
0.225 NODE_ENV=development
DONE 0.2s

And now when we run the image we'll also get the CMD output we're hoping for:

$ docker run arg
NODE_ENV=development

And to prove it actually persists CLI build arguments, let's run:

$ docker build --build-arg NODE_ENV=production --tag arg .
[2/2] RUN echo NODE_ENV=production
sha256:0191c73e7ca9c7db7e8d83e4c4eecc5dc3c54a040eebb326bac1a7faa1c93a3d
0.213 NODE_ENV=production
DONE 0.2s

$ docker run arg
NODE_ENV=production

And we can see it's working!

Dangers of persistence

There are values you don't want to persist in Docker images, mainly secrets. You might need $NPM_TOKEN to install private npm packages, or a GitHub personal access token to clone private GitHub repositories. These are sensitive secrets and you don't want to accidentally publish them in your final image.

Real world example

I ran across a use case for this trick while I was developing a Flexget Docker image. Flexget is a Python project that's installed via pip, and I wanted the image to automatically update Flexget to the latest patch version on startup. Because I wanted to pin the major and minor version of Flexget, I needed to persist the version in the built image, and for that I needed ENV.

The full Dockerfile can be found here, but here's a slimmed-down, slightly modified version to demonstrate this use case:

FROM python:3-alpine

ARG FLEXGET_VERSION=3.1.135
ENV FLEXGET_VERSION=${FLEXGET_VERSION}

RUN apk add g++ gcc linux-headers musl-dev && \
    pip install flexget~=${FLEXGET_VERSION} && \
    flexget --version

CMD pip install flexget~=${FLEXGET_VERSION} && \
    flexget --version
$ docker build --tag flexget .
78.18 3.1.135
78.18 You are on the latest release.
DONE 78.5s

$ docker run flexget
Requirement already satisfied: flexget~=3.1.135 in /usr/local/lib/python3.9/site-packages (3.1.135)
3.1.135
You are on the latest release.