Conditionally set flag in Dockerfile CMD instruction - docker

At the end of my dockerfile I have a CMD instruction:
CMD ['my_script.sh', '--debug']
But I want the debug flag to be optional, chosen at build time. My solution to this is to use a build arg, i.e.
ARG DEBUG=""
CMD ["my_script.sh", "$DEBUG"]
Then building with either
docker image build --build-arg='--debug' ...etc
or
docker image build ...etc
With the first option, the image builds successfully, but when running a container I get the following error:
Invalid option '$DEBUG'
The double-quotes must be causing the command to literally use the string $DEBUG. But changing things so that $DEBUG is in single quotes or in no quotes gets
/bin/sh: [my_script.sh,: No such file or directory
Finally, using
CMD ./my_script.sh $DEBUG
successfully runs the container but ignores $DEBUG
Am I doing anything wrong? Is there a better solution? Is there any solution at all?

The problem is that $DEBUG is only known at the build time. So if you want it to be resolved at the container runtime you can set ENV variable from your DEBUG ARG as a workaround :
ARG DEBUG=""
ENV DEBUG ${DEBUG}
CMD ["sh", "-c", "my_script.sh $DEBUG"]
This way you will have an environment variable called DEBUG which will have value from ARG DEBUG and this environment variable will be accessible from the cmd.
However I think that you could use environment variable only - there is no need for build arg for that. You can then pass environment variable value to the container using -e option. For example empty DEBUG env variable :
docker container run -e DEBUG image_name
or with value :
docker container run -e DEBUG=--debug image_name

Related

Why is argument empty in docker?

When I built docker I have argument that is empty.
ARG ENV=prod
RUN echo ${ENV} // this works very good it show right
CMD /my-project -env ${ENV} this ENV is empty it show empty
ARG is for building your docker image. ENV is what you want for a running container.
CMD is executed when your container is run.
https://vsupalov.com/docker-arg-vs-env/
ENV ENV prod
Might suggest changing your name to something that isn't a Dockerfile keyword though.

how to set an environment variable with pwd in a docker container

I would like to set the LD_LIBRARY_PATH variable based on my working directory using my Dockerfile. I have tried using ENV $PWD/some/subpath but when I inspect the container later using docker exec mycontainer bash -c "env" it shows up as /some/subpath rather than /my/working/dir/some/subpath however I also see that PWD is defined as /my/working/dir/ as I would expect it to be. so why is using $PWD in my Dockerfile not substituting the way I am expecting it to?
From this answer, $PWD is a special environment variable set when running a shell. Unlike RUN commands, ENV commands do not create a shell so PWD is never set.
To get the value of PWD at build time, you could instead use a build-arg and pass in $PWD in the build command.
You'd do this in your Dockerfile like this:
# dockerfile
ARG working_directory
ENV $working_directory/some/subpath
and build like this:
docker build --build-arg "working_directory=$PWD" .

How to set environment variables in Dockerfile and start parent image

I have a dockerfile that has a entrypoint.sh file which exports some Postgres variable.
Then I want to start the parent docker container which is referenced in "FROM pactfoundation/pact-broker" image. Looking at github for it's Dockerfile github pact broker it has CMD ["config.ru"] at the end. So I did similar to that in my Dockerfile:
FROM pactfoundation/pact-broker
COPY entrypoint.sh .
CMD ["config.ru"]
When I execute my docker run command:
docker run --rm -e POSTGRES_PORT=5433 -e POSTGRES_DBNAME=pactsd -e POSTGRES_URL=localhost -e POSTGRES_PASSWORD=1234 -e POSTGRES_USERNAME=postgres --name pact sonamsamdupkhangsar/pact:test -d
I see my entrypoint.sh echo statement and the container is dead.
setting pact broker database variables
How do I start the parent container after setting my envrionment variables in my entrypoint.sh file?
I also tried with the following:
FROM pactfoundation/pact-broker
ENV PACT_BROKER_DATABASE_NAME=${POSTGRES_DBNAME}
ENV PACT_BROKER_DATABASE_USERNAME=${POSTGRES_USERNAME}
ENV PACT_BROKER_DATABASE_PASSWORD=${POSTGRES_PASSWORD}
ENV PACT_BROKER_DATABASE_HOST=${POSTGRES_URL}
ENV PACT_BROKER_DATABASE_NAME=${POSTGRES_DBNAME}
ENV PACT_BROKER_DATABASE_PORT=$POSTGRES_PORT
RUN echo "PACT_BROKER_DATABASE_PORT: $PACT_BROKER_DATABASE_PORT"
Yet, when I run my built docker image I still don't see the variables being set. I tried both approaches for "${}" and "$" for env var setting.
You've to set your environment variables using the ENV in your docker file.
As each step executed at different containers which altogether builds the image if you set via shell scripts it won't work. Consider using the ENV command to set it
Ref: DOCKERFILE ENV
What is happening is that those environment variables that you are passing in at run-time with '-e' parameter are not yet defined at build-time as the ENV instructions are executed at build-time only.
E.g. at build-time this line you have:
ENV PACT_BROKER_DATABASE_NAME=${POSTGRES_DBNAME}
becomes this line:
ENV PACT_BROKER_DATABASE_NAME=
as '${POSTGRES_DBNAME}' evaluates to empty at build-time. Then, when run-time happens, you are defining all your POSTGRES_ environment variables as parameters so they will indeed exist in the container, BUT no further instructions will be executed to set the PACT_BROKER_ environment variables to any other values.
Proposed solution: I would recommend the simplest approach if you can make it work to just use the environment variables 'directly' however you define them as parameters. I.e. either change the names of your '-e' parameters to PACT_BROKER_'s or use the POSTGRES_ environment variables in your container. Either way you would remove the ENV lines from the Dockerfile.
If you really-really need to set the environment variables to other names at run-time, then you should be able to do this by writing to the appropriate 'startup' file in the Dockerfile (making sure to literally write the '$'s to the file so they could be dereferenced at run-time).

Pass ARGs in docker ENTRYPOINT

I'm running my app on Azure App Services, so I don't have access to docker run command. Now, I want to pass some variables into ENTRYPOINT for which I'm trying to use ARGs during build time itself. Here is how it looks,
docker build -t $IMAGE_NAME --build-arg env=dev --build-arg amplify_key=xxxxxxxxxxxxxx .
In my Dockerfile,
ARG env
ARG amplify_key
ENTRYPOINT ["/bin/bash", "-c", "init.sh $env $amplify_key"]
But this doesn't seem to be working. Please let me know the issue.
$variable references can be either expanded in the Dockerfile or by the shell when a command gets run. Only some Dockerfile commands perform variable expansions; for RUN, CMD, and ENTRYPOINT, it is only done by a shell. When a shell does the expansion it's not aware of Docker-specific ARGs, only environment variables, so you need to copy the argument to an ENV. There's an example of this in the Dockerfile documentation.
ARG env
ARG amplify_key
ENV env=$env amplify_key=$amplify_key
CMD init.sh $env $amplify_key
Assuming init.sh is your own script, though, once you have those values in environment variables, you can just access them directly, without passing them through positional parameters.
#!/bin/sh
echo "Running in $env environment"
curl -H "Authorization: Bearer $amplify_key" ...
ARG env
ARG amplify_key
ENV env=$env amplify_key=$amplify_key
CMD ["init.sh"]
This lets you do things like override the variable values at startup time more easily, and if you need to manually supply parameters to the command, the set of mandatory options is much smaller. If you're using the pattern of ENTRYPOINT doing some first-time setup and then running exec "$#" to run the CMD, this also works much better (ENTRYPOINT and sh -c have some tricky interactions).

Docker - pass env variable to replace Java max memory

I have a Dockerfile as follows.
ENV SPRING_ENV="local"
ENV APP_OPTS "-Xmx8144m"
RUN echo "/usr/lib/jvm/java-1.8-openjdk/bin/java ${APP_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /apps/demo/demo-fe.jar --spring.config.location=file:///apps/demo/conf/ump.properties -Dspring.profiles.active=${SPRING_ENV} &" > /apps/demo/entrypoint.sh
RUN chmod +x /apps/demo/entrypoint.sh
When I run the dockerfile, I see a file 'entrypoint.sh' with the java command that I specified in the Dockerfile.
But I want to change the java max memory depending on the environment. So I am running like this.
docker run -it <image_id> sh -e "APP_OPTS=-Xmx9144m" -e "SPRING_ENV=dev"
But when I run it, i check the entrypoint.sh, i don't see the environment variables replaced. Am I missing something?
Does it replace only on the fly when I actually run the container?
You need to escape the $ in ${APP_OPTS} (i.e., change it to \${APP_OPTS}) -- during docker build, the variable is getting replaced with the "current" environment variable, which would be whatever is in your env output (otherwise null). Calling docker run ... -e "APP_OPTS=-Xmx9144m" won't do anything at this point because ${APP_OPTS} has been replaced after the image was created.
Otherwise, you could try saving the entrypoint.sh file and put it in the same folder as your Dockerfile instead of having your Dockerfile create it (and use COPY instead to put it where you want it). That way, the ${APP_OPTS} environment variable won't get replaced during docker build
The Dockerfile (and the RUN command) are only executed when you build the image. SPRING_ENV and APP_UMPFE_OPTS are being evaluated only once and during the build.
When you run the image, the --env=KEY=VALUE are passed to the shell (!) running the process defined in the ENTRYPOINT or CMD (which you need but do not have).
You're missing a FROM ... statement near the top of the Dockerfile too.
You will need to define (recommend the shell-form of) ENTRYPOINT that invokes the java runtime, passes the environment variables and runs your code, perhaps (have not tried this):
FROM ???
ENV SPRING_ENV="local"
ENV APP_OPTS "-Xmx8144m"
ENTRYPOINT /usr/lib/jvm/java-1.8-openjdk/bin/java ${APP_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /apps/demo/demo-fe.jar --spring.config.location=file:///apps/demo/conf/ump.properties -Dspring.profiles.active=${SPRING_ENV}
Example:
FROM busybox
ENV DOG=Freddie
ENTRYPOINT echo ${DOG}
Then:
docker build --tag=58208029 --file=./Dockerfile .
docker run -it 58208029:latest
Freddie
docker run -it --env=DOG=Henry 58208029:latest
Henry
HTH!
The entrypoint.sh is being written when you build the image, so that RUN statement won't be executed again when you run the container. So the entrypoint.sh file itself will not be updated.
Another issue is that when you do the docker run, the -e options need to be before the image name and command:
docker run -it -e "APP_OPTS=-Xmx9144m" -e "SPRING_ENV=dev" <image_id> sh
Otherwise those are just being passed as arguments to the entrypoint/command
Also, in your Dockerfile, you probably want single quotes around your entrypoint script so that it doesn't interpolate the values at build time.
RUN echo '/usr/lib/jvm/java-1.8-openjdk/bin/java ${APP_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /apps/demo/demo-fe.jar --spring.config.location=file:///apps/demo/conf/ump.properties -Dspring.profiles.active=${SPRING_ENV} &' > /apps/demo/entrypoint.sh
Then when you run the container, the entrypoint script should read the variable values at run time from the environment.

Resources