moby: Dockerfile CMD doesn't understand ARG variables

Similar to #5509.

I want to be able to say the following, without relying upon environment variables:

ARG PATH=/var/run/nowhere
CMD cat ${PATH}

I would like the following consequences of CMD to occur:

  • docker inspect container | grep -A5 Cmd would reveal:
    "Cmd": [
        "/bin/bash",
        "-c",
        "cat /var/run/nowhere"
    ]
    
  • No matter what environment variable PATH is present at runtime, it won’t interfere with my intended CMD.
  • I can craft indirect variable interpolation:
    ARG COMMAND="echo 'Hello, ${USER}'"
    CMD ${COMMAND}
    

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 18 (5 by maintainers)

Commits related to this issue

Most upvoted comments

@saites what is the answer?

Please check back in a few years. Docker should be developer-friendly by then.

I agree with https://github.com/moby/moby/issues/34772#issuecomment-330384294.

I don’t really understand why

ARG MY_URI=http://127.0.0.1:80
RUN curl ${MY_URI}

and

ARG MY_URI=http://127.0.0.1:80
CMD ["curl", "${MY_URI}"]

should have wildly different interpolation rules when they are both processed within the same engine at build-time. It seems like the CMD can be interpolated using exactly the same rules as are applied to RUN commands, except that the result would be baked into the image’s CMD or ENTRYPOINT instead of being evaluated immediately. If the value isn’t supposed to be interpolated immediately, it can be escaped in the same way that RUN commands are escaped to deal with this very issue.

I completely understand that this might simply be something the moby team does not want to do because it is difficult or would break backwards compatibility, but it seems like arguing against a preprocessor that clearly already exists and is used with a consistent set of semantics for directives other than CMD and ENTRYPOINT is a bit of a red herring?

@thaJeztah I greatly appreciate your patience in explaining your point of view. I still feel like either I don’t understand how ARG works, or you’re not understanding my proposal.

Let’s use HTTP_PROXY as an example.

Scenario 1: Dockerfile author wants to set HTTP_PROXY during build time, but does not want that value to be baked into the layer.

ARG HTTP_PROXY=http://127.0.0.1:80
CMD curl https://www.docker.com/

gets translated into the following at build time:

export HTTP_PROXY=http://127.0.0.1:80
curl https://www.docker.com/

and gets translated into the following at run time:

curl https://www.docker.com/

Scenario 2: Dockerfile author wants to set HTTP_PROXY during build time, and wants HTTP_PROXY to also be hard-coded into the layer for run-time:

ARG HTTP_PROXY=http://127.0.0.1:80
CMD HTTP_PROXY="${HTTP_PROXY}" curl https://www.docker.com/

gets translated into the following at build time:

export HTTP_PROXY=http://127.0.0.1:80
HTTP_PROXY="http://127.0.0.1:80" curl https://www.docker.com/

and gets translated into the following at run time:

HTTP_PROXY="http://127.0.0.1:80" curl https://www.docker.com/

Scenario 3: Dockerfile author wants to set HTTP_PROXY during build time, and wants HTTP_PROXY to be overridden by the user during run-time:

ARG HTTP_PROXY=http://127.0.0.1:80
ENV HTTP_PROXY=${HTTP_PROXY}
CMD curl https://www.docker.com/

gets translated into the following at build time:

export HTTP_PROXY=http://127.0.0.1:80
curl https://www.docker.com/

and gets translated into the following at run time:

export HTTP_PROXY=http://127.0.0.1:80
curl https://www.docker.com/

Am I either completely misunderstanding how ARG and ENV work? Am I missing some dramatic edge case here? This seems like a reasonable approach towards removing the need for templating engines.

As an aside: One of my pet peeves with almost every “DevOps” tool out there is that they all claim to make managing some specific digital artifact much easier, while simultaneously needing some other solution to make managing the tool easy. So we now have Docker to make managing a container easy, but need another tool to make managing Docker easier. I feel like it’s a never-ending cycle of implementing one solution to manage another solution, under the guise of saving time and effort.

Disappointed that this issue has been seemingly abandoned.

So what mechanism is performing the interpolation in the non-shell Dockerfile directives?

The world will never know.

Thanks for the response. I put together a more concrete example to understand your point:

FROM alpine
ARG TESTVAR=foo

# If a command is interpolated inside the shell, then it should get a value
# of "bar".  If it is being interpolated outside the shell, it will have a value
# of "foo" because the interpolation has replaced the variable already.
RUN export TESTVAR=bar && echo "RUN testvar=${TESTVAR}"
CMD export TESTVAR=bar && echo "CMD testvar=${TESTVAR}"
RUN testvar=bar
CMD testvar=bar

So I think I understand that now, the interpolation is actually being performed implicitly by setting the ARG values into the shell environment during each specific RUN command.

However, the same interpolation happens on the WORKDIR command, which has no shell AFAIK:

FROM alpine
ARG TESTVAR=foo

WORKDIR ${TESTVAR}
RUN echo "WORKDIR=`pwd`"
WORKDIR=/foo

So what mechanism is performing the interpolation in the non-shell Dockerfile directives?

Using exec in the CMD instruction, as suggested from this stackexchange answer, of the Dockerfile without brackets, I am able to benefit from the environment variable and the signals interception:

CMD exec node index.js --myoption ${MY_ENV_VARIABLE}