I am running a dropwizard Java application in a Docker container using the image java:7u79
based on debian/jessie
.
My Java application handles the SIGTERM
signal to shutdown gracefully. The SIGTERM
handling works perfect when I run the application without Docker.
When I run it in a Docker container the SIGTERM
does not reach the Java application when I issue a docker stop
command. It kills the process abruptly after 10 seconds.
My Dockerfile
:
FROM java:7u79
COPY dropwizard-example-1.0.0.jar /opt/dropwizard/
COPY example.keystore /opt/dropwizard/
COPY example.yml /opt/dropwizard/
WORKDIR /opt/dropwizard
RUN java -jar dropwizard-example-1.0.0.jar db migrate /opt/dropwizard/example.yml
CMD java -jar dropwizard-example-1.0.0.jar server /opt/dropwizard/example.yml
EXPOSE 8080 8081
What is wrong with this Dockerfile
? Is there any other way to tackle this problem?
The docker stop command attempts to stop a running container first by sending a SIGTERM signal to the root process (PID 1) in the container. If the process hasn't exited within the timeout period a SIGKILL signal will be sent.
You can use the Java 11 image from hub.docker.com by typing the command :docker pull openjdk:tag on your machines terminal, where the tag is version of your intended java version. Or you can simply specify the image on your Dockerfile where FROM attribute must be the version of java.
The base image is the foundation of the new image you are about the build for your Java application.
Assuming you launch a Java service by defining the following in your Dockerfile
:
CMD java -jar ...
When you now enter the container and list the processes e.g. by docker exec -it <containerName> ps AHf
(I did not try that with the java
but with the ubuntu
image) you see that your Java process is not the root process (not the process with PID 1) but a child process of a /bin/sh
process:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:27 ? 00:00:00 /bin/sh -c java -jar ...
root 8 1 0 18:27 ? 00:00:00 java -jar ...
So basically you have a Linux shell that is the main process with PID 1 which has a child process (Java) with PID 8.
To get signal handling working properly you should avoid those shell parent process. That can be done by using the builtin shell command exec
. That will make the child process taking over the parent process. So at the end the former parent process does not exist any more. And the child process becomes the process with the PID 1. Try the following in your Dockerfile
:
CMD exec java -jar ...
The process listing then should show something like:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 18:30 ? 00:00:00 java -jar ...
Now you only have that one process with PID 1. Generally a good practice is to have docker containers only contain one process - the one with PID 1 (or if you really need more processes then you should use e.g. supervisord
as PID 1 which itself takes care of signal handling for its child processes).
With that setup the SIGTERM
will be treated directly by the Java process. There is no shell process any more in between which could break signal handling.
EDIT:
The same exec
effect could be achieved by using a different CMD
syntax that does it implicitly (thanks to Andy for his comment):
CMD ["java", "-jar", "..."]
@h3nrik answer is right but sometimes you really need to use a script for setup the launch. Just use the exec command to do the trick in most of the cases:
#!/bin/sh
#--- Preparations
exec java -jar ...
See this wonderful blog post
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With