Dockerfile of console application that accept arguments in Commons CLI style - java

I have a java console application that I package as jar and run it as
java -jar target/myProject-1.0-SNAPSHOT.jar -arg1 145 -arg2 345 -arg3 99
I want to run the same command inside a container and pass these arguments (arg1, arg2, arg3) to docker run command. My docker file look like:
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/myProject-1.0-SNAPSHOT.jar myProject-1.0-SNAPSHOT.jar
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /myProject-1.0-SNAPSHOT.jar" ]
then I try to run the image as follows:
docker run myProject:0.3 -e -arg1 145 -arg2 345 -arg3 99
but my program don't get the arguments. what I'm missing ?

You have to add the ENV command in the DOCKERFILE so that you can receive the arguments that you are passing in and then pass that onto the ENTRYPOINT script
Dockerfile will look something like this
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ENV arg1
ENV arg2
ENV arg3
ADD target/myProject-1.0-SNAPSHOT.jar myProject-1.0-SNAPSHOT.jar
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /myProject-1.0-SNAPSHOT.jar ${arg1} ${arg2} ${arg3}" ]
Let me know if you have any questions

The arguments you pass to docker run are the command it's running, which it appends to the end of the entry point. So what you're doing is equivalent to running:
sh -c "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /myProject-1.0-SNAPSHOT.jar" -arg1 145 -arg2 345 -arg3 99
Presented like this, you can see that the arguments are going to sh and not to java. If you want to combine a set of options that you define when you build the image with a set of options that you can append at runtime, you'll need to use a wrapper script or something similar.
Create file wrapper.sh and make it executable:
#!/bin/sh
exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /myProject-1.0-SNAPSHOT.jar "$#"
Add it to your container with ADD wrapper.sh /bin in the Dockerfile
Change your entrypoint to ["/bin/wrapper.sh"]
Now, when you run the image, it will append the arguments to the java command line

Related

Executing java command on ENTRYPOINT of Dockerfile doesn't recognize given ARG values

I have created a dockerfile to build and upload on DockerHub an image that will connect to a database and will create a table.
Dockerfile
FROM openjdk:11 as builder
EXPOSE 8080
WORKDIR application
ARG JAR_FILE=toDoAppWithLogin.jar
COPY $JAR_FILE application.jar
ARG SQL_PASSWORD
ARG SQL_USERNAME
ARG SQL_PORT
ARG SQL_SERVER
RUN java -Djarmode=layertools -jar application.jar extract
FROM openjdk:11
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher", "--my_sql.host=$SQL_SERVER", "--my_sql.port=$SQL_PORT", "--my_sql.username=$SQL_USERNAME", "--my_sql.password=$SQL_PASSWORD"]
I build the image by running the following command (it's build it successfully):
docker build -t nikspanos/cicd-pipeline:tag1 --build-arg SQL_USERNAME=user --build-arg SQL_PASSWORD=pass --build-arg SQL_PORT=0000 --build-arg SQL_SERVER=server .
Then I run the docker image to test it before I upload it on Docker registry
docker run nikspanos/cicd-pipeline:tag1
The first error I get:
java.sql.SQLNonTransientConnectionException: Cannot load connection class because of underlying exception: com.mysql.cj.exceptions.WrongArgumentException: Failed to parse the host:port pair '$SQL_SERVER:$SQL_PORT'.
I guess I don't pass the arguments correctly so they are not recognized. I have searched about it and many other answers include the ENV option but I would like to use the ARG only.
Aprreciate any suggestions on this matter.
You've got at least 3 things wrong here.
ARG values are scoped, and go out of scope when you start the next stage.
ARG values are for build time (building the image), for runtime (when you start the container from the image) you need to set an ENV.
Docker doesn't expand variables in RUN, CMD, or ENTRYPOINT. Instead you get the value injected as an environment variable. To expand the $var syntax to the value of the variable, you need a shell like /bin/sh. The json/exec syntax explicitly bypasses running your command with a shell.
The result looks like:
FROM openjdk:11 as builder
EXPOSE 8080
WORKDIR application
ARG JAR_FILE=toDoAppWithLogin.jar
COPY $JAR_FILE application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM openjdk:11
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
ARG SQL_PASSWORD
ARG SQL_USERNAME
ARG SQL_PORT
ARG SQL_SERVER
ENV SQL_PASSWORD=$SQL_PASSWORD
ENV SQL_USERNAME=$SQL_USERNAME
ENV SQL_PORT=$SQL_PORT
ENV SQL_SERVER=$SQL_SERVER
ENTRYPOINT java org.springframework.boot.loader.JarLauncher "--my_sql.host=$SQL_SERVER" "--my_sql.port=$SQL_PORT" "--my_sql.username=$SQL_USERNAME" "--my_sql.password=$SQL_PASSWORD"
I'd personally switch to running the entrypoint as a shell script so you can get back to the json syntax. That would allow other cli options to be passed in the CMD value. And that script could be
#!/bin/sh
exec java org.springframework.boot.loader.JarLauncher "--my_sql.host=$SQL_SERVER" "--my_sql.port=$SQL_PORT" "--my_sql.username=$SQL_USERNAME" "--my_sql.password=$SQL_PASSWORD" "$#"
Where the exec avoids leaving the /bin/sh as pid 1 which can mess with signals.
You then run into the next issue, you shouldn't be baking configuration settings like db hostnames, and especially passwords, into the image. Instead that becomes a runtime configuration when you run the container:
FROM openjdk:11 as builder
EXPOSE 8080
WORKDIR application
ARG JAR_FILE=toDoAppWithLogin.jar
COPY $JAR_FILE application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM openjdk:11
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
Then you'd build it without any args and run it with the settings:
docker build -t nikspanos/cicd-pipeline:tag1 .
docker run -e SQL_USERNAME=user -e SQL_PASSWORD=pass -e SQL_PORT=0000 -e SQL_SERVER=server nikspanos/cicd-pipeline:tag1
I'd also recommend looking into secrets solutions for passing credentials since environment variables are easily exposed. Those would mount the credentials as a file or make them available from an external secrets server.

Can't run docker image

First, sorry if my question sounds too easy or silly. I'm new to docker.
I have created my docker image and passed several jar files which are to be run immediately when the container starts.
I want to run the script "serve.sh" immediately when the container starts
I succeeded in creating the images well, but when I run the container, it throws me this error:
C:\Program Files\Docker\Docker\resources\bin\docker.exe: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-it\": executable file not found in $PATH": unknown.
Here is the command I use to run the image I craeted:
docker run b24b37614e1a -it
Here is my docker file:
FROM openjdk:8-jdk-alpine
EXPOSE 8080:8080
COPY apigateway-0.0.1-SNAPSHOT.jar apigateway.jar
COPY authservice-0.0.1-SNAPSHOT.jar authservice.jar
COPY institutionsservice-0.0.1-SNAPSHOT.jar institutionsservice.jar
COPY messagesservice-0.0.1-SNAPSHOT.jar messagesservice.jar
COPY postsservice-0.0.1-SNAPSHOT.jar postsservice.jar
COPY userservice-0.0.1-SNAPSHOT.jar userservice.jar
COPY serve.sh serve.sh
CMD [ "bash" "./serve.sh" ]
Please what am I doing wrong ? I'm new to docker
There are a couple of things which you should correct, First one is the CMD format which should be
CMD instruction has three forms:
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
CMD [ "/bin/bash" , "./serve.sh" ]
Another thing, When you do docker run, the instructions are
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
which means all the options has to be before IMAGE and in your case it is appearing after IMAGE.
The correct command should be
docker run -it b24b37614e1a
BTW, small question, why you want to run an interactive container of this application. Ideally, it should be something like
docker run -p $HOST_PORT:$APP_PORT b24b37614e1a
-p => Publish a container's port(s) to the host
and then you can access your application localhost:$HOST_PORT or machine_IP:$HOST_PORT
Keep in mind that docker args order matters:
you wrote docker run b24b37614e1a -it which is different from
docker run -it b24b37614e1a
Hope it solves your problem :)
tl;dr
docker run -it then args (c/o Luca Fabbian)
You don't have bash. Use sh. (c/o MC Emperor)
CMD exec form needs an array, so use commas: CMD ["sh", "./serve.sh"]
CMD has a shell form if you forget syntax easily: CMD ./serve.sh
Don't even exec the shell if you don't have to: CMD ["./serve.sh"]
Argument order matters
This assumes you already took Luca's advice:
Keep in mind that docker args order matters:
you wrote docker run b24b37614e1a -it which is different from docker run -it b24b37614e1a
You don't have bash.
I pulled the openjdk:8-jdk-alpine image to confirm, and it does not come with bash. Alpine images come with sh so the code provided will never work unless you correct the shell used or install bash.
"unknown operand"
From your response to nischay:
I get this error: "sh: ./serve.sh: unknown operand"
I updated My CMD instruction to: CMD [ "bin/bash" "./serve.sh" ]
CMD in the exec form takes an array of instructions. These must be separated by a comma.
Do this:
CMD [ "executable", "param1" ]
^
A note on using CMD
As nischay said, there are a few different ways to use CMD to do roughly the same thing, from the reference quoted.
You can in fact use the exec form of CMD to say things like:
CMD [ "sh", "./serve.sh" ]
Typing out /bin/sh or /bin/bash or whichever shell you want is not required unless desired. But if you don't need the shell, you can use exec form without the shell as well:
CMD [ "./serve.sh" ]

Dockerfile Spring Boot property from variable not working

I can't seem to get Spring boot properties to work via variable in my Dockerfile. This is what I am doing:
ENTRYPOINT exec java -Dapp-version=$app_version -jar /app.jar
If I do RUN echo "App Version: $app_version" inside of my Dockerfile then I get then I get the correct output like App Version: 1.70.0.
If I manually put the version like this: ENTRYPOINT exec java -Dapp-version=1.70.0 -jar /app.jar then the value is injected correctly.
In fact, if I do RUN echo "ENTRYPOINT exec java -Dapp-version=$app_version -jar /app.jar" then I get output like
Step 9/10 : RUN echo "ENTRYPOINT exec java -D******ion=$app_version -jar /app.jar"
---> Running in b6c3cd9bb69a
ENTRYPOINT exec java -D******ion=1.70.0 -jar /app.jar
The value inside of Spring is being set as an empty string when I use the Dockerfile variable. When I hard code it to 1.70.0 then it is being set correctly. What am I missing?
I have tried many different things including using {}, quotes, etc.
Edit: Added Dockerfile
FROM java:8
ARG app_version
RUN echo -------------------
RUN echo "App Version: $app_version"
RUN echo -------------------
VOLUME /tmp
COPY ./build/libs/mango-sticky-rice-1.0.0-SNAPSHOT.jar /app.jar
RUN bash -c 'touch /app.jar'
ENTRYPOINT exec java -Dapp-version=$app_version -jar /app.jar
This answer worked: https://stackoverflow.com/a/49889134/3088642. This is what my Dockerfile looks like:
FROM java:8
ARG app_version
RUN echo -------------------
RUN echo "App Version: ${app_version}"
RUN echo -------------------
VOLUME /tmp
COPY ./build/libs/mango-sticky-rice-1.0.0-SNAPSHOT.jar /app.jar
RUN bash -c 'touch /app.jar'
RUN echo "#!/bin/bash \n java -Dapp-version=${app_version} -jar /app.jar" > ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
RUN cat ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

java vm options and AWS::ECS::TaskDefinition

I would like to run my docker container as following:
docker run java \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseCGroupMemoryLimitForHeap
Where should I pass vm arguments when writing AWS::ECS::TaskDefinition in cloudformation stack?
I have done the same thing at work, you could pass the flags directly in Dockerfile as per below.
ENV JAVA_OPTS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]

How to set environment variables via env-file

I have a Dockerfile based on the spring guide about docker. My application consumes some private data, so I want to pass these parameters through environment variables. When I run a docker container:
docker run -p 8080:8080 -t myname/myapplication --env-file=~/env.list
it appears that the variables are not set and the application can't see them, what do I do wrong? How to pass these parameters?
env.list:
ACCOUNT_ID=my_account_id
ACCOUNT_PASSWORD=my_secret_password
My ENTRYPOINT:
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar $APPLICATION_NAME
I think
docker run
takes all parameters before the image and the command. If I do
docker run -t --env-file=env.list ubuntu sh -c "while true; do echo world; sleep 100 ;done"
and then
docker exec -it container_id env
I get
HOSTNAME=195f18677a91
TERM=xterm
ACCOUNT_ID=my_account_id
ACCOUNT_PASSWORD=my_secret_password
HOME=/root
Try
docker run -p 8080:8080 --env-file=~/env.list -t myname/myapplication
This works very well:
cat <<EOF > test.env
MYVAR=test
EOF
docker run -it --env-file test.env busybox env | grep MYVAR
That will print as expected:
MYVAR=test
In your case in your Java application you can access the environment variables via System.getenv().

Categories