I have this CMD in my Dockerfile:
CMD ["sh", "-c", "java $JAVA_OPTS -jar xxx.jar"]
And I want to add parameter below to $JAVA_OPTS
-Dproducer.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username=xxx password=pass;
Because it contains space I couldn't suceeded (application is not started). How can I pass jvm parameter which contains space to use it in CMD in DockerFile?
Either remove ENTRYPOINT and use CMD as David suggested, or provide a shell script wrapper that better handles your $JAVAOPTS variable content.
Related
SO I am trying to change the JVM heap memory percentage
I have added these two lines in my Dockerfile
ENV JAVA_OPTS="--XX:MinRAMPercentage=50.0 --XX:MaxRAMPercentage=80.0"
CMD ["--spring.profiles.active=${profile}","${JAVA_OPTS}"]
But when I check this command:(java -XX:+PrintFlagsFinal -version | grep -E "UseContainerSupport | InitialRAMPercentage | MaxRAMPercentage | MinRAMPercentage") on my docker container, it shows default values of the above parameter:
No environment variable expansion or other processing is done on exec form CMD (or ENTRYPOINT or RUN). Your application is getting invoked with literally the strings --spring.profiles.active=${profile} and ${JAVA_OPTS}, and you will see these including the ${...} markers in your Java program's main() function.
Docker's normal assumption here is that any expansion like this will be done in a shell, and shell form CMD will wrap its string in /bin/sh -c '...' so this happens. However, this setup isn't compatible with splitting ENTRYPOINT and CMD: due to the mechanics of how the two parts are combined neither part can be wrapped in a shell if you're using the "container as command" pattern where the CMD is just an argument list.
The easiest way to handle this is to ignore ENTRYPOINT entirely; put all of the command and arguments into CMD; and use the shell form rather than the exec form.
ENV JAVA_OPTS="--XX:MinRAMPercentage=50.0 --XX:MaxRAMPercentage=80.0"
# no ENTRYPOINT; shell form of CMD
CMD java ${JAVA_OPTS} -jar /app/app.jar --spring.profiles.default=${profile}
If you docker run a temporary debugging container on this image, it will see the environment variable but the command you provide will replace the image's CMD.
docker run --rm your-image \
/bin/sh -c 'echo $JAVA_OPTS'
# prints the environment setting
Correspondingly if you run a separate java, it won't see the options that are only provided in the image's CMD. (Similarly try running java -XX:MaxRamPercentage=80.0 in one terminal window, and java -XX:+PrintFlagsFinal with no other options in another window, and the second invocation won't see the JVM options from the first JVM; the same mechanics apply to Docker containers.)
Let's say we have the following Dockerfile for the purpose of creating a java image and compiling two scripts.
FROM openjdk:latest
COPY src JavaDocker
WORKDIR JavaDocker
RUN mkdir -p bin
RUN javac -d bin ./com/myapp/HelloWorld1.java
RUN javac -d bin ./com/myapp/HelloWorld2.java
WORKDIR bin
ENTRYPOINT java
How can I run any of these two scripts that have been compiled?
I'm using the command: docker run myapp-image "com.myapp.Server"
And I get:
Usage: java [options] <mainclass> [args...]
(to execute a class)
or java [options] -jar <jarfile> [args...]
(to execute a jar file)
or java [options] -m <module>[/<mainclass>] [args...]
java [options] --module <module>[/<mainclass>] [args...]
(to execute the main class in a module)
or java [options] <sourcefile> [args]
(to execute a single source-file program)
Arguments following the main class, source file, -jar <jarfile>,
-m or --module <module>/<mainclass> are passed as the arguments to
main class.
I'd suggest building a separate image per application; that can help clarify what the image is supposed to do. I also generally recommend using CMD over ENTRYPOINT.
So a Dockerfile that runs only the first application could look like:
FROM openjdk:latest
# Prefer an absolute path for clarity.
WORKDIR /JavaDocker
# Set up the Java class path.
RUN mkdir bin
ENV CLASSPATH=/JavaDocker/bin
# Use a relative path as the target, to avoid repeating it.
# (If you change the source code, repeating `docker build` will
# skip everything before here.)
COPY src .
# Compile the application.
RUN javac -d bin ./com/myapp/HelloWorld1.java
# Set the main container command.
CMD ["java", "com.myapp.HelloWorld1"]
What if you do have an image that contains multiple applications? If you use CMD here, it's very easy to provide an alternate command when you run the image:
docker run myapp-image \
java com.myapp.HelloWorld2
# Wait, what's actually in this image?
docker run --rm myapp-image \
ls -l bin/com/myapp
I generally recommend reserving ENTRYPOINT for a wrapper script that does some first-time setup, then runs exec "$#" to run a normal CMD. There's an alternate pattern of giving a complete command in ENTRYPOINT, and using CMD to provide its arguments. In both of these cases ENTRYPOINT needs to be JSON-array syntax, not shell syntax.
ENTRYPOINT ["java", "com.myapp.HelloWorld1"] # <-- JSON-array syntax
CMD ["-argument", "to-program-1"]
docker run myapp-image \
-argument=different -options
but it's harder to make that image do something else
docker run \
--entrypoint ls \ # <-- first word of the command is before the image name
myapp-image \
-l bin/com/myapp # <-- and the rest after
docker run \
--entrypoint java \
myapp-image \
com.myapp.HelloWorld2
Your original Dockerfile will probably work if you change the ENTRYPOINT line from shell to JSON-array syntax; using shell syntax will cause the CMD part to be ignored (including a command passed after the docker run image-name). You might find it easier to make one complete application invocation be the default and include the java command if you need to run the other.
There is Dockerfile
FROM openjdk:11.0.12-jre-slim
COPY target/app.jar /app.jar
COPY configs configs
ENTRYPOINT ["java","-jar","/app.jar"]
In folder configs contains json configs for java application.
The build docker command is:
docker build --build-arg -f ~/IdeaProjects/app --no-cache -t app:latest
And the run command is:
docker run --entrypoint="cp configs var/opt/configs/ && java -jar app.jar" app:latest
Let's omit the ability to copy configs in the Dockerfile via COPY command. Unfortunately, this must be done using --entrypoint.
An error occurs when the docker run command was executed:
docker: Error response from daemon: OCI runtime create failed: container_linux.go:370: starting container process caused: exec: "cp configs var/opt/configs/ && java -jar app.jar": stat cp configs var/opt/configs/ && java -jar app.jar: no such file or directory: unknown.
Could you explain why the error occurred in this case?
I would do this with an entrypoint wrapper script. A Dockerfile can have both an ENTRYPOINT and a CMD; if you do, the CMD gets passed as arguments to the ENTRYPOINT. This means you can make the ENTRYPOINT a shell script that does first-time setup, then ends with exec "$#" to replace itself with the CMD.
#!/bin/sh
# docker-entrypoint.sh
# copy the configuration to the right place
cp configs var/opt/configs/
# run the main container command
exec "$#"
In the Dockerfile, make sure to COPY the script in (it should be checked in to source control as executable) and set it as the ENTRYPOINT.
...
COPY docker-entrypoint.sh .
ENTRYPOINT ["./docker-entrypoint.sh"] # must be JSON-array syntax
CMD ["java", "-jar", "/app.jar"] # what was previously ENTRYPOINT
When you run the container it's straightforward to replace the CMD, so you can double-check that this is doing the right thing by running an interactive shell in place of the java application.
docker run -v "$PWD/alt-configs:/configs" --rm -it my-image sh
If you do need to override the command like this at docker run time, the command you show uses && to run two commands consecutively. This needs to run a shell to be understood correctly, and in this context you need to manually provide a /bin/sh -c wrapper.
I would still recommend changing ENTRYPOINT to CMD in your Dockerfile; then you could run a relatively straightforward
docker run \
... \
-v "$PWD/alt-configs:/configs" \
my-image \
/bin/sh -c 'cp configs var/opt/configs && java -jar /app.jar'
If you use --entrypoint, it only takes the first word out of this command, and it is a Docker options so it needs to come before the image name. I'd recommend designing your image to avoid needing this awkward construct.
docker run \
... \
-v "$PWD/alt-configs:/configs" \
--entrypoint /bin/sh \
my-image \
-c 'cp configs var/opt/configs && java -jar /app.jar'
Your proposed command is having problems because it's trying to pass the entire command, including the embedded spaces and shell operators, as a single word, but that causes the OS-level process handling to try to look for an executable file with spaces and ampersands in the filename, hence the "no such file or directory" error.
I have Dockerfile
FROM java:8
ADD my_app.jar /srv/app/my_app.jar
WORKDIR /srv/app
ENTRYPOINT ["java", "-jar", "my_app.jar", "--spring.config.location=classpath:/srv/app/configs/application.properties"]
How I can do dynamic paramethers for java without ./run.sh in entrypoint? ( as -Dversion=$version or others )
I want pass this parameters when start container.
--entrypoint something doesn't work on Docker 1.11 ;(
You can append your dynamic parameters at the end of the docker run .... You haven't specified any CMD instruction, so it'll work.
What is actually run without specifying any command at the end, when running the docker run ..., is this:
ENTRYPOINT CMD (it's concatenated and there is a space in between)
So you can also use something like
...
ENTRYPOINT ["java", "-jar", "my_app.jar"]
CMD ["--spring.config.location=classpath:/srv/app/configs/application.properties"]
which means, when using
docker run mycontainer the
java -jar my_app.jar --spring.config.location=classpath:/srv/app/configs/application.properties
will be invoked (the default case), but when running
docker run mycontainer --spring.config.location=classpath:/srv/app/configs/some_other_application.properties -Dversion=$version
it'll be run w/ different property file and with the system property called version (overriding the default case)
So I know you can pass Environment variables to a docker container using -e like:
docker run -it -e "var=var1" myDockerImage
But I need to pass a System Property to a docker container, because this is how I run my JAR:
java -Denvironment=dev -jar myjar.jar
So how can I pass a -D System property in Docker? Like:
docker run -it {INSERT Denvironment here} myDockerImage
Use the variable you passed into the container on the java command:
docker run -it -e "ENV=dev" myDockerImage
java -Denvironment=$ENV -jar myjar.jar
One more way to do it, if running under Tomcat, is setting your system variables in your Dockerfile using ENV JAVA_OPTS like this:
ENV JAVA_OPTS="-Djavax.net.ssl.trustStore=C:/tomcatDev.jks -D_WS_URL=http://some/url/"
Hope it helps!
One can also use the following start.sh ENTRYPOINT for the Docker container, make sure to use the array syntax, e.g.:
Dockerfile:
...
ENTRYPOINT ["/start.sh"]
The actual start.sh script:
#!/bin/bash
export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64"
exec $JAVA_HOME/bin/java -jar myjar.jar $#
Then you can just pass the Java system properties directly to your application as docker run container arguments:
docker run myDockerImage "-Dvar=var1"
Have a start.sh file, e.g.:
#!/usr/bin/env sh
exec java -Djava.security.egd=file:/dev/./urandom $* -jar /app.jar
In your Dockerfile:
...
COPY start.sh /start.sh
RUN chmod a+rx /start.sh
ENTRYPOINT ["/start.sh"]