Search code examples
javabashdockerdockerfile

Run java with shell script inside Dockerfile to receive OS signals


I want to run a shell script that will import Amazon RDS certificates when the container runs and then start my Java application.

I have the following ENTRYPOINT:

ENTRYPOINT ./import-amazon-rds-certs.sh && java -jar /app.jar

The problem is that ENTRYPOINT command does not receive OS signals - as explained https://docs.docker.com/reference/build-checks/json-args-recommended/

I wonder how to run this script to make my Java application receive OS signals.


Solution

  • You can write a script that does the first-time setup, and then uses the special shell exec command to replace the shell script with the main container process. Then your Java application will be pid 1 inside the container and it will directly receive docker stop and similar signals.

    I generally recommend splitting this into two parts, "do the setup" and "the main container command". If the "setup" part is run as the image's ENTRYPOINT, then it receives the CMD as parameters (possibly as overridden at container start time). So a typical script that does the setup could just be

    #!/bin/sh
    
    # run the setup script
    if ! ./import-amazon-rds-certs.sh; then
      echo 'failed to import certificates' >&2
      exit 1
    fi
    
    # switch to the main container command
    exec "$@"
    

    In your Dockerfile, you need to split the setup script and the main command into two separate parts, like

    COPY entrypoint.sh ./  # should be checked into source control as executable
    ENTRYPOINT ["/app/entrypoint.sh"]
    CMD ["java", "-jar", "/app/app.jar"]
    

    There is one more subtlety here. ENTRYPOINT and CMD (and also RUN) have two syntaxes. If you just write out a command string, Docker automatically wraps it in /bin/sh -c. This has a couple of undesirable side effects. For your case, the shell wrapper receives the signals and not your main command. For ENTRYPOINT specifically, sh -c will also "swallow" the command-line arguments, so you can't actually run the CMD. I've made both ENTRYPOINT and CMD be "exec form" JSON arrays, which avoids introducing the shell wrapper.


    There is an occasional pattern of trying to make the CMD list just be arguments to the application. I tend to avoid this pattern (if you docker run --rm -it ... sh your image with the entrypoint wrapper above, for example, you can verify that the RDS credentials have appeared). If you're trying to use this pattern, the shell construct "$@" expands to all of the current positional arguments, and Docker passes the CMD that way.

    In that case the entrypoint wrapper script could end with

    exec java -jar /app/app.jar "$@"
    

    and you could

    docker run ... your-image --options-to-java-app