s6, Java, and Gradle - blitterated/docker-dev-env-s6 GitHub Wiki

s6, Java, and Gradle install and setup

Create a directory for this experiment and change into it.

mkdir docker-java && cd $_

Download the s6 installer from here.

Ex.

curl -O https://github.com/just-containers/s6-overlay/releases/download/v2.2.0.3/s6-overlay-amd64-installer

Download the latest OpenJDK from here.

Ex.

curl -O https://download.java.net/java/GA/jdk17.0.1/2a2082e5a09d4267845be086888add4f/12/GPL/openjdk-17.0.1_linux-x64_bin.tar.gz

Extract the JDK into the directory. There should be a directory called something like jdk-17.0.1 when it's done.

tar -zvxf openjdk-17.0.1_linux-x64_bin.tar.gz

Download the latest Gradle from here. Not sure if this can be curled or not.

Unzip Gradle.

unzip gradle-7.3.3-bin.zip

Tried to create an /etc/environment file with paths and variables needed for the JDK and Gradle. But it none of the changes would take. Turns out it is only "read on login, when the PAM stack is activated – specifically pam_env.so, which reads the file." See the Best Answer here

Instead, here's a .bashrc to copy into the container.

cat <<"EOF" > bashrc
PATH="$PATH:/opt/jdk-17.0.1/bin:/opt/jdk-17.0.1/db/bin:/opt/jdk-17.0.1/jre/bin:/opt/gradle-7.3.3/bin"
J2SDKDIR="/opt/jdk-17.0.1"
J2REDIR="/opt/jdk-17.0.1"
JAVA_HOME="/opt/jdk-17.0.1"
DERBY_HOME="/opt/jdk-17.0.1/db"
EOF

Create a Dockerfile that copies and installs s6, copies the JDK over, and copies Gradle over as well.

cat <<"EOF" > Dockerfile
FROM ubuntu

RUN apt update && apt --yes upgrade

COPY jdk-17.0.1 /opt/jdk-17.0.1
COPY gradle-7.3.3 /opt/gradle-7.3.3

WORKDIR /root
COPY bashrc .bashrc
COPY s6-overlay-amd64-installer s6-overlay-amd64-installer

RUN chmod +x /root/s6-overlay-amd64-installer && \
    /root/s6-overlay-amd64-installer /

ENTRYPOINT ["/init"]
EOF

Build an image.

docker build -t s6_java .

Fire it up.

docker run -it --rm s6_java /bin/bash

Check the java version.

java --version
openjdk 17.0.1 2021-10-19
OpenJDK Runtime Environment (build 17.0.1+12-39)
OpenJDK 64-Bit Server VM (build 17.0.1+12-39, mixed mode, sharing)

Check JAVA_HOME.

echo $JAVA_HOME
/opt/jdk-17.0.1

Check the Java compiler version.

javac --version
javac 17.0.1

Check the Gradle version.

gradle --version
Welcome to Gradle 7.3.3!

Here are the highlights of this release:
 - Easily declare new test suites in Java projects
 - Support for Java 17
 - Support for Scala 3

For more details see https://docs.gradle.org/7.3.3/release-notes.html


------------------------------------------------------------
Gradle 7.3.3
------------------------------------------------------------

Build time:   2021-12-22 12:37:54 UTC
Revision:     6f556c80f945dc54b50e0be633da6c62dbe8dc71

Kotlin:       1.5.31
Groovy:       3.0.9
Ant:          Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM:          17.0.1 (Oracle Corporation 17.0.1+12-39)
OS:           Linux 5.10.76-linuxkit amd64

Exit the container.

exit

Configure Gradle Daemon to run as a service under s6

Quick observation of no daemon running.

Before we dig in to getting a Gradle Daemon running, take a look at what things look like when it's not running.

Start another container.

docker run -it --rm s6_java /bin/bash

Check on the status of the Gradle Daemon.

gradle --status

You may see a lot of text since Gradle thinks it's running for the first time. But you should see the following message in there:

No Gradle daemons are running.

Exit the container.

exit

Delete the image. We'll create a new one below.

docker rmi s6_java

Run the Gradle Daemon as an s6 supervised service

Create a run file for s6 that starts the Gradle Daemon. Note that --foreground needs to be used instead of --daemon because the latter will exit as soon as the daemon process is launched in the background. This causes s6 to think the service went down, and it will run the finish script and then try to restart with run. This results in an endless loop.

cat <<"EOF" > run
#!/usr/bin/with-contenv execlineb
gradle --foreground
EOF

Create a finish file for s6 that stops the Gradle Daemon.

cat <<"EOF" > finish
#!/usr/bin/with-contenv execlineb
gradle --stop
EOF

execlineb won't be able to see the changes to the system path made before with .bashrc since it's not in an interactive shell. The shell is also non-login, but that doesn't matter as much since .bash_profile calls .bashrc by default. Move the PATH and other variables into the Dockerfile with ENV.

Update the Dockerfile so it copies run and finish files into an s6 managed service directory, /etc/services.d/gradle/.

cat <<"EOF" > Dockerfile
FROM ubuntu

RUN apt update && apt --yes upgrade

ENV PATH=$PATH:/opt/jdk-17.0.1/bin:/opt/jdk-17.0.1/db/bin:/opt/jdk-17.0.1/jre/bin:/opt/gradle-7.3.3/bin \
    J2SDKDIR=/opt/jdk-17.0.1 \
    J2REDIR=/opt/jdk-17.0.1 \
    JAVA_HOME=/opt/jdk-17.0.1 \
    DERBY_HOME=/opt/jdk-17.0.1/db

COPY jdk-17.0.1 /opt/jdk-17.0.1
COPY gradle-7.3.3 /opt/gradle-7.3.3

WORKDIR /root
COPY s6-overlay-amd64-installer s6-overlay-amd64-installer

RUN chmod +x /root/s6-overlay-amd64-installer && \
    /root/s6-overlay-amd64-installer /

COPY run /etc/services.d/gradle/run
COPY finish /etc/services.d/gradle/finish

ENTRYPOINT ["/init"]
EOF

Build an image.

docker build -t s6_gradle .

Fire it up.

docker run -it --rm s6_gradle /bin/bash

Now check on the Gradle Daemon.

gradle --status

It lives!

   PID STATUS   INFO
   181 IDLE     7.3.3

If you manually stop it, s6 will restart it unless you tell it to do otherwise.

gradle --stop

Well, there's a little bit of a race condition with output to STDOUT, but it works.

Daemon is stopping immediately stop command received
Stopping Daemon(s)
1 Daemon stopped
root@cfd395e841be:~# No Gradle daemons are running.
Daemon server started.

One more status check.

gradle --status
   PID STATUS   INFO
   354 IDLE     7.3.3
   181 STOPPED  (stop command received)

NEXT: how to stop s6 from restarting the daemon

Failed approaches missing with-contenv

Each case here was mooted because the run file's hashbang was

#!/usr/bin/execlineb

instead of

#!/usr/bin/with-contenv execlineb

The .bashrc version

Update the Dockerfile so it copies the run and finish files into an s6 managed service directory, /etc/services.d/gradle/.

cat <<"EOF" > Dockerfile
FROM ubuntu

RUN apt update && apt --yes upgrade

COPY jdk-17.0.1 /opt/jdk-17.0.1
COPY gradle-7.3.3 /opt/gradle-7.3.3

COPY run /etc/services.d/gradle/run
COPY finish /etc/services.d/gradle/finish

WORKDIR /root
COPY bashrc .bashrc
COPY s6-overlay-amd64-installer s6-overlay-amd64-installer

RUN chmod +x /root/s6-overlay-amd64-installer && \
    /root/s6-overlay-amd64-installer /

ENTRYPOINT ["/init"]
EOF

Why can't execlineb see my system path?

run script:

#!/usr/bin/execlineb
gradle --daemon

s6 reports:

execlineb: fatal: unable to exec gradle: No such file or directory

run script with explicit path to gradle:

#!/usr/bin/execlineb
/opt/gradle-7.3.3/bin/gradle --daemon
ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation.

It's because the PATH is set in .bashrc

The /etc/profile.d version

execlineb won't be able to see the changes to the system path made before with .bashrc since it's not in an interactive shell. The shell is also non-login, but that doesn't matter as much since .bash_profile calls .bashrc by default. Move the PATH and other variables into file to be dropped in /etc/profile.d

cat <<"EOF" > java_paths_and_env_vars.sh
PATH="$PATH:/opt/jdk-17.0.1/bin:/opt/jdk-17.0.1/db/bin:/opt/jdk-17.0.1/jre/bin:/opt/gradle-7.3.3/bin"
J2SDKDIR="/opt/jdk-17.0.1"
J2REDIR="/opt/jdk-17.0.1"
JAVA_HOME="/opt/jdk-17.0.1"
DERBY_HOME="/opt/jdk-17.0.1/db"
EOF

Update the Dockerfile so it copies the java_paths_and_env_vars.sh into the /etc/profile.d/ directory. Also copy the run and finish files into an s6 managed service directory, /etc/services.d/gradle/.

cat <<"EOF" > Dockerfile
FROM ubuntu

RUN apt update && apt --yes upgrade

COPY jdk-17.0.1 /opt/jdk-17.0.1
COPY gradle-7.3.3 /opt/gradle-7.3.3

COPY java_paths_and_env_vars.sh /etc/profile.d/java_paths_and_env_vars.sh
COPY run /etc/services.d/gradle/run
COPY finish /etc/services.d/gradle/finish

WORKDIR /root
COPY s6-overlay-amd64-installer s6-overlay-amd64-installer

RUN chmod +x /root/s6-overlay-amd64-installer && \
    /root/s6-overlay-amd64-installer /

ENTRYPOINT ["/init"]
EOF

The Dockerfile ENV version

execlineb won't be able to see the changes to the system path made before with .bashrc since it's not in an interactive shell. The shell is also non-login, but that doesn't matter as much since .bash_profile calls .bashrc by default. Move the PATH and other variables into the Dockerfile with ENV.

Update the Dockerfile so it copies run and finish files into an s6 managed service directory, /etc/services.d/gradle/.

cat <<"EOF" > Dockerfile
FROM ubuntu

RUN apt update && apt --yes upgrade

ENV PATH=$PATH:/opt/jdk-17.0.1/bin:/opt/jdk-17.0.1/db/bin:/opt/jdk-17.0.1/jre/bin:/opt/gradle-7.3.3/bin \
    J2SDKDIR=/opt/jdk-17.0.1 \
    J2REDIR=/opt/jdk-17.0.1 \
    JAVA_HOME=/opt/jdk-17.0.1 \
    DERBY_HOME=/opt/jdk-17.0.1/db

COPY jdk-17.0.1 /opt/jdk-17.0.1
COPY gradle-7.3.3 /opt/gradle-7.3.3

COPY run /etc/services.d/gradle/run
COPY finish /etc/services.d/gradle/finish

WORKDIR /root
COPY s6-overlay-amd64-installer s6-overlay-amd64-installer

RUN chmod +x /root/s6-overlay-amd64-installer && \
    /root/s6-overlay-amd64-installer /

ENTRYPOINT ["/init"]
EOF
⚠️ **GitHub.com Fallback** ⚠️