gradle-docker-plugin
gradle-docker-plugin copied to clipboard
How to pass docker environment variables into java application?
Hello! As part of migrating from Java 12 to Java 14 I'm updating gradle-docker-plugin
straight from 3.6.2
to 6.6.1
.
While overall everything seems OK, we can remove a lot of old quirks (like setting and pushing latest
tag) the only problem we currently have is that it is not clear how to pass docker environment variable into java application.
Old Dockerfile:
FROM openjdk:12
LABEL maintainer=vleushin
ADD myapp-node /myapp-node
ADD app-lib/myapp-node-2.18.14.jar /myapp-node/lib/myapp-node-2.18.14.jar
ENTRYPOINT ["/myapp-node/bin/myapp-node"]
EXPOSE 8080
New Dockerfile:
FROM openjdk:14
LABEL maintainer=vleushin
WORKDIR /app
COPY libs libs/
COPY resources resources/
COPY classes classes/
ENTRYPOINT ["java", "-cp", "/app/resources:/app/classes:/app/libs/*", "com.example.MyAppNode"]
EXPOSE 8080
As I understood, as part of optimisation and layering improvements -- there are new layers -- libs, resources, classes. That's all great. But the problem is ENTRYPOINT
.
In old Dockerfile java application startup script has following line at the end:
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $MYAPP_NODE_OPTS -classpath "\"$CLASSPATH\"" com.example.MyAppNode "$APP_ARGS"
So it is passing DEFAULT_JVM_OPTS
, JAVA_OPTS
and MYAPP_NODE_OPTS
into java application. Which is great way to customize docker image to run on specific environment or with specific features. We use it heavily afterwards in Kubernetes -- when we specify in deployment image, and using environment variable to pass different java flags, DEV/PROD environment, etc.
Quick example of Kubernetes deployment:
containers:
- name: myapp-node
image: myapp/myapp-node:2.18.20
env:
- name: MYAPP_ENV
valueFrom:
configMapKeyRef:
name: myapp-config
key: env
- name: JAVA_OPTS
value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=40.0 -Dsun.jnu.encoding=UTF-8 -Dfile.encoding=UTF-8 -Darchaius.deployment.environment=k8s-${MYAPP_ENV} -Des.set.netty.runtime.available.processors=false -Dmyapp.node.services.excluded=GeoService,TextService -Dio.netty.allocator.useCacheForAllThreads=false -Dlog4j.configurationFile=log4j2-k8s-${MYAPP_ENV}.xml -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.rmi.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false -Djava.rmi.server.hostname=127.0.0.1 -Djava.net.preferIPv4Stack=true -Dsun.net.inetaddr.ttl=5 -Dnetworkaddress.cache.ttl=5 -XX:UseAVX=2 -XX:+ExitOnOutOfMemoryError"
It is even one step ahead since MYAPP_ENV -- is config variable in Kubernetes cluster (we have one for DEV, and one for PROD), and it is propagated into deployment, and via JAVA_OPTS into java application, so we have same Kubernetes deployment for Dev and Prod.
Currently I do not see how to do it with new gradle-docker-plugin
. It is not possible to use environment variables in ENTRYPOINT
. I can only hardcode values (using jvmArgs
) it in the image, but then we have to make a lot of images and it is not customizable.
Please advise how to achieve this in new version, maybe I'm just missing something.
Did you solve this issue ?
you can use docker-remote-api
in gradle.build
...
apply plugin: 'com.bmuschko.docker-remote-api'
...
import com.bmuschko.gradle.docker.tasks.image.Dockerfile
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
jar {
archivesBaseName = 'my-app-name'
manifest { attributes "Main-Class": "com....MyAPPApplication" }
}
task createDockerfile(type: Dockerfile) {
from('openjdk:11-jre')
environmentVariable("APP_JAR", "${rootProject.name}-${version}.jar")
//you need build jar to copy
addFile("\"./build/libs/${rootProject.name}-${version}.jar\"", "/app.jar")
entryPoint('sh')
defaultCommand('-c', 'java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar')
exposePort(8080)
}
task buildDocker(type: DockerBuildImage) {
dependsOn createDockerfile
dockerFile = createDockerfile.destFile
inputDir = project.projectDir
images.add("$project.group/my-app-name:$project.version")
}
and run it with
./gradlew buildDocker
@vleushin
This is what I figured it out as a workaround
The idea is re-construct the ENTRYPOINT by sh -c "java $JAVA_OPTS -cp xxxx Main $JAVA_ARGS"
afterEvaluate {
// refine ENTRYPOINT for java
project.tasks.withType(com.bmuschko.gradle.docker.tasks.image.Dockerfile).forEach({
it.instructions = it.instructions.get()
.stream()
.map(inst -> {
if (inst.keyword != 'ENTRYPOINT') {
return inst
}
def javacmd = groovy.util.Eval.me(inst.text.replaceFirst('ENTRYPOINT ', "")).toArray()
javacmd[0] = 'java $JAVA_OPTS' // [0] == 'java'
return new Dockerfile.EntryPointInstruction("sh", "-c", Stream.of(javacmd).collect(Collectors.joining(" ")) + ' $JAVA_ARG')
})
.collect(Collectors.toList())
})
}
PR is created https://github.com/bmuschko/gradle-docker-plugin/pull/1021