How to define a variable in a Dockerfile?
Asked Answered
U

7

345

In my Dockerfile, I would like to define variables that I can use later in the Dockerfile.

I am aware of the ENV instruction, but I do no want these variables to be environment variables.

Is there a way to declare variables at Dockerfile scope?

Uthrop answered 26/11, 2015 at 10:10 Comment(1)
ARG doesn’t define a var that can be used within the dockerfile. You would have to mate that with the ENV command to get what you’re looking for; ARG foo; ENV FOO=$foo; COPY file $foo (sorry for the bad formatting, I guess it’s not possible to do code blocks and multi line from a phone)Lumpen
C
246

You can use ARG - see https://docs.docker.com/engine/reference/builder/#arg

The ARG instruction defines a variable that users can pass at build-time to the builder with the docker build command using the --build-arg <varname>=<value> flag. If a user specifies a build argument that was not defined in the Dockerfile, the build outputs an error.

Can be useful with COPY during build time (e.g. copying tag specific content like specific folders) For example:

ARG MODEL_TO_COPY
COPY application ./application
COPY $MODEL_TO_COPY ./application/$MODEL_TO_COPY

While building the container:

docker build --build-arg MODEL_TO_COPY=model_name -t <container>:<model_name specific tag> .
Carpic answered 5/6, 2016 at 8:33 Comment(1)
It doesn't seem to work for the COPY command. docs.docker.com/engine/reference/builder/#argSaxon
I
200

To answer your question:

In my Dockerfile, I would like to define variables that I can use later in the Dockerfile.

You can define a variable with:

ARG myvalue=3

Note that spaces around the equal character are not allowed.

And use it later with:

RUN echo "$myvalue" > /test
Idiosyncrasy answered 9/8, 2018 at 14:46 Comment(2)
I get told by IntelliJ to write RUN echo "$myvalue" > /test Is this correct?Hoax
Yes, IntelliJ is probably triggering the warning shellcheck.net/wiki/SC2086. In the above example, it makes no differences. I've updated my answer by adding double quotes in case the variable contains spaces, line feeds or glob characters.Idiosyncrasy
L
95

To my knowledge, only ENV allows that, as mentioned in "Environment replacement"

Environment variables (declared with the ENV statement) can also be used in certain instructions as variables to be interpreted by the Dockerfile.

They have to be environment variables in order to be redeclared in each new containers created for each line of the Dockerfile by docker build.

In other words, those variables aren't interpreted directly in a Dockerfile, but in a container created for a Dockerfile line, hence the use of environment variable.


This day, I use both ARG (docker 1.10+, and docker build --build-arg var=value) and ENV.
Using ARG alone means your variable is visible at build time, not at runtime.

My Dockerfile usually has:

ARG var
ENV var=${var}

In your case, ARG is enough: I use it typically for setting http_proxy variable, that docker build needs for accessing internet at build time.


Christopher King adds in the comments:

Watch out!
The ARG variable is only in scope for the "stage that it is used" and needs to be redeclared for each stage.

He points out to Dockerfile / scope

An ARG variable definition comes into effect from the line on which it is defined in the Dockerfile not from the argument’s use on the command-line or elsewhere.

For example, consider this Dockerfile:

FROM busybox
USER ${user:-some_user}
ARG user
USER $user
# ...

A user builds this file by calling:

docker build --build-arg user=what_user .

The USER at line 2 evaluates to some_user as the user variable is defined on the subsequent line 3.
The USER at line 4 evaluates to what_user as user is defined and the what_user value was passed on the command line.
Prior to its definition by an ARG instruction, any use of a variable results in an empty string.

An ARG instruction goes out of scope at the end of the build stage where it was defined.
To use an arg in multiple stages, each stage must include the ARG instruction.

Leavelle answered 26/11, 2015 at 10:20 Comment(2)
Watch out! The ARG variable is only in scope for the "stage that it is used" and needs to be redeclared for each stagate! docs.docker.com/engine/reference/builder/#scopeDezhnev
@ChristopherKing THank you for this feedback, good point. I have included your comment and reference in the answer for more visibility.Leavelle
K
19

If the variable is re-used within the same RUN instruction, one could simply set a shell variable. I really like how they approached this with the official Ruby Dockerfile.

Kutenai answered 29/11, 2015 at 3:21 Comment(1)
Great answer. While you should use ARG if possible, sometimes you need to assign and re-use a dynamic variable. Example: RUN foo=$(date) && echo $fooHarmsworth
K
11

You can use ARG variable defaultValue and during the run command you can even update this value using --build-arg variable=value. To use these variables in the docker file you can refer them as $variable in run command.

Note: These variables would be available for Linux commands like RUN echo $variable and they wouldn't persist in the image.

Kharif answered 18/9, 2019 at 9:14 Comment(2)
This is the better answer since the default value is key to the intentAnet
Syntax needs to be updated to ARG variable=defaultvalue instead of ARG variable defaultvalueThirst
N
3

Late to the party, but if you don't want to expose environment variables, I guess it's easier to do something like this:

RUN echo 1 > /tmp/__var_1
RUN echo `cat /tmp/__var_1`
RUN rm -f /tmp/__var_1

I ended up doing it because we host private npm packages in aws codeartifact:

RUN aws codeartifact get-authorization-token --output text > /tmp/codeartifact.token
RUN npm config set //company-123456.d.codeartifact.us-east-2.amazonaws.com/npm/internal/:_authToken=`cat /tmp/codeartifact.token`
RUN rm -f /tmp/codeartifact.token

And here ARG cannot work and i don't want to use ENV because i don't want to expose this token to anything else

Northeast answered 20/11, 2020 at 0:7 Comment(1)
3 lines of RUNs would have 3 layers. If we inspect the earlier layers, can we see your secrets?Friesen
I
1

Adding my own answer as I had to do a lot more research to understand and use https://mcmap.net/q/93111/-how-to-define-a-variable-in-a-dockerfile @Evgeny's answer above.

For my situation, I needed the Dockerfile itself to set the value of an argument. I wanted to run Node.js to run a script, but unfortunately, the environment that is going to run the Dockerfile (docker build, docker run) is a Jenkins box which doesn't have Node.js installed. The Dockerfile I'm using, on the other hand, is responsible for installing Node.js. (This situation may also apply to you if you are coding in Python, Go, Ruby (as in Evegeny's answer), etc. and the Dockerfile installs those, but they're not available in the outside environment.)

I then wanted to take the output of my Node.js script and use that to "set a variable". It seems like all the other answers here are about doing docker run -e BLAH=something (which will let docker know that an environmental variable or ARG is a certain value, but that assumes that the exterior environment knows the value is something (or can figure it out) and pass it in to the Dockerfile. To me this is essentially hard coding it, not setting it dynamically, as the question asks. SO it wouldn't work for me

Anyway, let's say you have a script which requires that docker install something to be able execute:

  1. you'll want to have that executable output something to stdout (aka console.log aka print etc.)
  2. Take the output of that executable script and set it as a shell variable using the docker RUN command.
  3. The RUN command lets you execute shell and use the language that you just installed using docker aka (node my-pre-build-script.js or python script.py).
  4. Now you just have to set that to a temporary variable: RUN prebuildout=$(node pre-build-script.js).
  5. Unfortunately that variable will be lost if you try to use another RUN command within docker, so instead you have to execute it and use it all within one long command.
RUN prebuildout=$(node pre-build-script.js) && \
echo "$(prebuildout)" && \
MY_VARIABLE="$(prebuildout)" npm start && \
...etc

NOTE:

  1. that you can use \ to break the line even though you didn't finish that RUN command.
  2. you have to use " quotes to use the shell temporary variable within the command you are running like: RUN python myCommand "$(metaShellVariable)".

For my particular use case this didn't end up working, maybe my syntax was off, but I got it to work easily since I only needed the output of the command once using something like npm run "$(node my-pre-build-script.js)". In my actual use case I couldn't use npm run to do the script otherwise I could just use something like (prebuild: node my-pre-build-script.js or something), in my case this is before the package.json is there but after node is installed.

Again my-pre-build-script.js has only one console.log statement in it and looks for the sake of this example like this:

(async () => {
  const res = await fetch('/blah');
  const json = await res.json();
  const commandToRun = extractCommand(json);
  console.log(commandToRun || 'build');
})()

In other words have your script's output printed to console (System.out.println, printf(), etc.) and you can use it within the docker shell command RUN.

This is powerful, because your script might involve making several secure API calls that only this docker container is configured to be able to access and you might not want to use curl and try to parse the output out of that.

Inarch answered 19/1 at 18:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.