How can I use a variable inside a Dockerfile CMD?
Asked Answered
C

6

262

Inside my Dockerfile:

ENV PROJECTNAME mytestwebsite
CMD ["django-admin", "startproject", "$PROJECTNAME"]

Error:

CommandError: '$PROJECTNAME' is not a valid project name

What is the quickest workaround here? Does Docker have any plan to "fix" or introduce this functionality in later versions of Docker?

NOTE: If I remove the CMD line from the Docker file and then run the Docker container, I am able to manually run django-admin startproject $PROJECTNAME from inside the container and it will create the project...  

Cannibalize answered 6/11, 2016 at 20:59 Comment(4)
How and when are you defining $PROJECTNAME?Adelaadelaida
At the beginning of my Dockerfile using ENV. Also I forgot to mention that if I remove the CMD line from the Dockerfile and then run the container, from inside the container I can run this command and it will create the project (meaning the ENV variable is valid).Cannibalize
What type of variable do you mean: dockerfile variable or environmental variable (as in your system runtime)?Pipes
CMD ["sh", "-c", "your command with ${any ENV} here"]Landlocked
S
365

When you use an execution list, as in...

CMD ["django-admin", "startproject", "$PROJECTNAME"]

...then Docker will execute the given command directly, without involving a shell. Since there is no shell involved, that means:

  • No variable expansion
  • No wildcard expansion
  • No i/o redirection with >, <, |, etc
  • No multiple commands via command1; command2
  • And so forth.

If you want your CMD to expand variables, you need to arrange for a shell. You can do that like this:

CMD ["sh", "-c", "django-admin startproject $PROJECTNAME"]

Or you can use a simple string instead of an execution list, which gets you a result largely identical to the previous example:

CMD django-admin startproject $PROJECTNAME
Smiga answered 6/11, 2016 at 21:30 Comment(7)
Some more reading in Docker's issue tracker: github.com/docker/docker/issues/5509Malta
If I use one of these tricks then I can't stop my container with CTRL+C anymore. Anyone found a solution for that?Splanchnic
No tricks here! Nothing in this answer should generally impact your ability to CTRL+C a container; if something isn't behaving as you expect, you might want to open a new question with details and we'll see if we can help you out.Smiga
@Splanchnic I guess, one should use exec for new process replace bash. It could be related to recieving signals and CTRL+C. Something like CMD ["sh", "-c", "exec django-admin startproject $PROJECTNAME"].Saltpeter
@Saltpeter exactly yeah!Splanchnic
please see @Asimandia answer below https://mcmap.net/q/109077/-how-can-i-use-a-variable-inside-a-dockerfile-cmd for a simple way to support signals forwardingMelisenda
This is not the correct answer since it introduces the subtle issue of OS signals not propagated, which is very likely to cause problems that are often hard to debug. @Asimandia answer below is correct.Saltandpepper
L
59

If you want to use the value at runtime, set the ENV value in the Dockerfile. If you want to use it at build-time, then you should use ARG.

Example :

ARG value
ENV envValue=$value
CMD ["sh", "-c", "java -jar ${envValue}.jar"]

Pass the value in the build command:

docker build -t tagName --build-arg value="jarName"
Leopard answered 3/2, 2019 at 15:16 Comment(0)
T
35

You also can use exec This is the only known way to handle signals and use env vars simultaneously. It can be helpful while trying to implement something like graceful shutdown according to Docker github

Example:

ENV PROJECTNAME mytestwebsite 
CMD exec django-admin startproject $PROJECTNAME
Trachyte answered 12/8, 2022 at 13:28 Comment(5)
nobody saw your answer (that I wanted to add), but this is by far the best solution ;)Melisenda
This is the correct answer, not the one currently accepted, which does not handle OS signals correctly! Propagating OS signals to the executable is important. For example in Kubernetes signals are used for graceful shutdown. I would only add that the ["json array"] syntax is usually preferred for CMD, for example some linters enforce it: github.com/hadolint/hadolint/wiki/DL3025 So I would change the example to use that syntax.Saltandpepper
@Saltandpepper so it should look like this CMD ["exec django-admin startproject $PROJECTNAME"]?Crawl
Indeed this is the correct answer. Can confirm that sigterm is respected. On the use of the json array @Saltandpepper don't we have the same issue where you cannot use variables in the command?Annecorinne
Very good point about exec, we need it, but using exec in the CMD doesn't work if the image already has an entrypoint. You need to override your entrypoint (ENTRYPOINT []), or create your own.Waxman
S
13

Lets say you want to start a java process inside a container:

Example Dockerfile excerpt:

ENV JAVA_OPTS -XX +UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XshowSettings:vm 
... 
ENTRYPOINT ["/sbin/tini", "--", "entrypoint.sh"] 
CMD ["java", "${JAVA_OPTS}", "-myargument=true"]

Example entrypoint.sh excerpt:

#!/bin/sh 
... 
echo "*** Startup $0 suceeded now starting service using eval to expand CMD variables ***"
exec su-exec mytechuser $(eval echo "$@")
Seismic answered 8/9, 2017 at 18:6 Comment(0)
L
2

For the Java developers, following my solution below gonna work:

if you tried to run your container with a Dockerfile like below

ENTRYPOINT ["/docker-entrypoint.sh"]
# does not matter your parameter $JAVA_OPTS wrapped as ${JAVA_OPTS}
CMD ["java", "$JAVA_OPTS", "-javaagent:/opt/newrelic/newrelic.jar", "-server", "-jar", "app.jar"]

with an ENTRYPOINT shell script below:

#!/bin/bash
set -e
source /work-dir/env.sh
exec "$@"

it will build the image correctly but print the error below during the run of container:

Error: Could not find or load main class $JAVA_OPTS
Caused by: java.lang.ClassNotFoundException: $JAVA_OPTS

instead, Java can read the command line parameters either through the command line or by _JAVA_OPTIONS environment variable. so, it means we can pass the desired command line parameters through _JAVA_OPTIONS without changing anything on Dockerfile as well as to allow it to be able to start as parent process of container for the valid docker signalization via exec "$@".

The below one is my final version of the Dockerfile and docker-entrypoint.sh files:

...
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["java", "-server", "-jar", "app.jar"]
#!/bin/bash
set -e
source /work-dir/env.sh
export _JAVA_OPTIONS="-XX:+PrintFlagsFinal"
exec "$@"

and after you build your docker image and tried to run it, you will see the logs below that means it worked well:

Picked up _JAVA_OPTIONS: -XX:+PrintFlagsFinal
[Global flags]
      int ActiveProcessorCount                     = -1                                        {product} {default}
Lacework answered 8/9, 2021 at 10:3 Comment(0)
B
0

Inspired on above, I did this:

#snapshot by default. 1 is release.
ENV isTagAndRelease=0

CMD     echo is_tag: ${isTagAndRelease} && \
        if [ ${isTagAndRelease} -eq 1 ]; then echo "release build"; mvn -B release:clean release:prepare release:perform; fi && \
        if [ ${isTagAndRelease} -ne 1 ]; then echo "snapshot build"; mvn clean install; fi && \ 
       .....
Beesley answered 12/2, 2020 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.