244

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...

david
  • 6,303
  • 16
  • 54
  • 91
  • 1
    How and when are you defining `$PROJECTNAME`? – Piotr Wittchen Nov 06 '16 at 21:12
  • 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). – david Nov 06 '16 at 21:14
  • What type of variable do you mean: dockerfile variable or environmental variable (as in your system runtime)? – Mike Doe Nov 06 '16 at 21:21
  • 1
    `CMD ["sh", "-c", "your command with ${any ENV} here"]` – vanduc1102 Jan 02 '22 at 04:50

6 Answers6

338

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
larsks
  • 277,717
  • 41
  • 399
  • 399
  • 5
    Some more reading in Docker's issue tracker: https://github.com/docker/docker/issues/5509 – jannis Nov 06 '16 at 21:36
  • 4
    If I use one of these tricks then I can't stop my container with CTRL+C anymore. Anyone found a solution for that? – mr.bjerre Feb 08 '22 at 13:37
  • 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. – larsks Feb 08 '22 at 13:46
  • 5
    @mr.bjerre 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"]`. – user3132194 Feb 10 '22 at 12:37
  • @user3132194 exactly yeah! – mr.bjerre Feb 11 '22 at 09:35
  • 1
    please see @Asimandia answer below https://stackoverflow.com/a/73334944/1034782 for a simple way to support signals forwarding – Donatello Aug 19 '22 at 09:00
  • 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. – h3r3 Sep 17 '22 at 00:06
56

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"
Mr. S
  • 1,469
  • 2
  • 15
  • 27
rex roy
  • 1,019
  • 12
  • 5
22

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
Asimandia
  • 248
  • 2
  • 5
  • 3
    nobody saw your answer (that I wanted to add), but this is by far the best solution ;) – Donatello Aug 19 '22 at 08:55
  • 3
    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: https://github.com/hadolint/hadolint/wiki/DL3025 So I would change the example to use that syntax. – h3r3 Sep 17 '22 at 00:08
  • 1
    @h3r3 so it should look like this `CMD ["exec django-admin startproject $PROJECTNAME"]`? – ahaertig Sep 21 '22 at 19:23
  • Indeed this is the correct answer. Can confirm that sigterm is respected. On the use of the json array @h3r3 don't we have the same issue where you cannot use variables in the command? – Bronumski Feb 17 '23 at 00:38
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 "$@")
Flavio
  • 1,645
  • 2
  • 11
  • 9
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}
Zana Simsek
  • 89
  • 1
  • 1
  • 7
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 && \ 
       .....
csomakk
  • 5,369
  • 1
  • 29
  • 34