Moby: Dockerfile CMD doesn't understand ENV variables

Created on 30 Apr 2014  路  21Comments  路  Source: moby/moby

Similar to #1136.

I want to be able to say CMD [ "$CATALINA_HOME/bin/catalina.sh", "run"]

arebuilder aredocs

Most helpful comment

Confirmed that the form CMD ["sh", "-c", "echo ${MY_HOME}"] works as expected. I'm assuming the other form will as well.

Might be nice to add a note about this to the documentation.

All 21 comments

Have you tried this way?

CMD [ "${CATALINA_HOME}/bin/catalina.sh", "run"]

Yes. It doesn't work: docker run -P -i -t tomcat gives me:

2014/04/30 17:52:12 exec: "${CATALINA_HOME}/bin/catalina.sh": stat ${CATALINA_HOME}/bin/catalina.sh: no such file or directory

I can confirm this. Using a Dockerfile with this

ENV MY_HOME /opt
CMD ["echo", "$MY_HOME"]

And it doesn't work on the build of the image. However, it work when you enter through /bin/bash (docker run [image] /bin/bash) and type:

echo $MY_HOME

Will print:

/opt

I have also tried:

CMD ["echo", "${MY_HOME}"]
CMD ["echo", "MY_HOME"]
CMD ["echo $MY_HOME"]
CMD ["echo ${MY_HOME}"]

With no luck.

I'll keep trying to work around this issue

Try CMD echo ${MY_HOME} or CMD ["sh", "-c", "echo ${MY_HOME}"] and you should have more luck.

The explanation is that the shell is responsible for expanding environment variables, not Docker. When you use the JSON syntax, you're explicitly requesting that your command bypass the shell and be execed directly.

Confirmed that the form CMD ["sh", "-c", "echo ${MY_HOME}"] works as expected. I'm assuming the other form will as well.

Might be nice to add a note about this to the documentation.

And based on @sfitts logic, as CMD is passed to the ENTRYPOINT as an argument, the following is possible:

Dockerfile:

FROM ubuntu:16.04

env MY_HOME /opt
ADD start.sh /start.sh
ENTRYPOINT ["/start.sh"]
CMD ["echo", "${MY_HOME}"]

start.sh

#!/bin/bash

/bin/bash -l -c "$*"

I'd like this issue to be reopened.

The "answer" as provided above is merely a workaround, punting the expansion of ENV variables to the shell. I can immediately think of several scenarios in which this workaround is insufficient.

EDIT: Please see issue #34772, which I just created as a correlation for ARG directives. The use cases I mention there have possible overlap with the issues with ENV.

+1 to @dejayc

I think this is a good example where shell expansion doesn't work. The container is built from scratch, so it has a single binary and no shell.

In case this may be useful for anyone, I wrote a small Go script to achieve this. Wrapping with shell script works but does not forward OS signals to the process.

https://github.com/abiosoft/parent

+1 many of us are using the ENTRYPOINT ["docker-entrypoint.sh"] CMD ["something", "${VARIABLE"] pattern where this fails. The last line of docker-entrypoint.sh is typically exec $@ which will not expand variables. The current proposed work-around does not handle OS signal forwarding gracefully.

@akravetz I recall on DockerConf it was said multiple times avoid using docker-entrypoint.sh, they grow massively.

@holms thank you, this is good info. Is there a place where best practices like that are documented for those of us who don't attend DockerConf? It's a bit confusing considering numerous high profile projects on docker hub (memcached, postgres, redis, etc) use the docker-entrypoint.sh pattern.

@akravetz same for me actually :) Docs has some best practises: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

Also there's case studies: https://www.docker.com/products/resources/case-studies
Also success stories: https://success.docker.com
Also I'm reading a lot of Docker blog: https://blog.docker.com

Regarding entrypoint.sh I might be wrong but I clearly remember that some features appeared because of long entrypoint.sh, personally trying not to use them at all, because everything possible to do better most of the time, by using docker provided features.

Another workaround is to move the command into the script. And in there you'll get variable expansion.

ENV var doesn't work using scratch image, any idea how to fix it?

I've tried

FROM scratch
ENV CONFIG=config/dev.json
COPY --from=builder /config /config
COPY --from=builder /rush-rbac /rush-rbac
ENTRYPOINT [ "/rush-rbac","--config=${CONFIG}" ] # or
# CMD [ "/rush-rbac --config=${CONFIG}"]

I think this is a good example where shell expansion doesn't work. The container is built from scratch, so it has a single binary and no shell.

@ryanjaeb
Wouldn't the env vars be available within the app, and so it would not need variable expansion during call time? Assuming it's your own binary.

@andho Yes. I tried it a couple weeks ago and the env vars are always available within the app. I've been trying to remember what I was getting stuck on and can't. It for sure would have been a 3rd party binary I was trying to build into a container, so maybe it was just doing something odd and I didn't notice.

I'm curious how the app ends up with access to the expanded env vars if the shell is responsible for expanding them. Is that a Go specific thing? When I tested it I noticed that if I use exec.Command(...).Run() to run another Go binary it also gets access to the env vars.

@ryanjaeb The apps will have access to the environment variables in the environment it's run within. The shell is also an app, which is used to call other apps. So the apps called from within the shell also has access to env vars. But most apps take input/config through arguments instead of env vars, and that's where variable expansion is needed. Hope the explanation is clear.

Write the command without use the args array: ie CMD gunicorn --bind 0.0.0.0:$PORT wsgi:app

Just write this in your docker file.
This example runs a node js app.

ARG ENV

node server.js $ENV
Was this page helpful?
0 / 5 - 0 ratings