NikSp
NikSp

Reputation: 1539

Executing java command on ENTRYPOINT of Dockerfile doesn't recognize given ARG values

I have created a dockerfile to build and upload on DockerHub an image that will connect to a database and will create a table. Dockerfile

FROM openjdk:11 as builder
EXPOSE 8080
WORKDIR application
ARG JAR_FILE=toDoAppWithLogin.jar
COPY $JAR_FILE application.jar

ARG SQL_PASSWORD
ARG SQL_USERNAME
ARG SQL_PORT
ARG SQL_SERVER

RUN java -Djarmode=layertools -jar application.jar extract

FROM openjdk:11
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher", "--my_sql.host=$SQL_SERVER", "--my_sql.port=$SQL_PORT", "--my_sql.username=$SQL_USERNAME", "--my_sql.password=$SQL_PASSWORD"]

I build the image by running the following command (it's build it successfully):

docker build -t nikspanos/cicd-pipeline:tag1 --build-arg SQL_USERNAME=user --build-arg SQL_PASSWORD=pass --build-arg SQL_PORT=0000 --build-arg SQL_SERVER=server .

Then I run the docker image to test it before I upload it on Docker registry

docker run nikspanos/cicd-pipeline:tag1

The first error I get:

java.sql.SQLNonTransientConnectionException: Cannot load connection class because of underlying exception: com.mysql.cj.exceptions.WrongArgumentException: Failed to parse the host:port pair '$SQL_SERVER:$SQL_PORT'.

I guess I don't pass the arguments correctly so they are not recognized. I have searched about it and many other answers include the ENV option but I would like to use the ARG only.

Aprreciate any suggestions on this matter.

Upvotes: 0

Views: 1275

Answers (1)

BMitch
BMitch

Reputation: 263469

You've got at least 3 things wrong here.

  1. ARG values are scoped, and go out of scope when you start the next stage.
  2. ARG values are for build time (building the image), for runtime (when you start the container from the image) you need to set an ENV.
  3. Docker doesn't expand variables in RUN, CMD, or ENTRYPOINT. Instead you get the value injected as an environment variable. To expand the $var syntax to the value of the variable, you need a shell like /bin/sh. The json/exec syntax explicitly bypasses running your command with a shell.

The result looks like:

FROM openjdk:11 as builder
EXPOSE 8080
WORKDIR application
ARG JAR_FILE=toDoAppWithLogin.jar
COPY $JAR_FILE application.jar

RUN java -Djarmode=layertools -jar application.jar extract

FROM openjdk:11
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./

ARG SQL_PASSWORD
ARG SQL_USERNAME
ARG SQL_PORT
ARG SQL_SERVER
ENV SQL_PASSWORD=$SQL_PASSWORD
ENV SQL_USERNAME=$SQL_USERNAME
ENV SQL_PORT=$SQL_PORT
ENV SQL_SERVER=$SQL_SERVER

ENTRYPOINT java org.springframework.boot.loader.JarLauncher "--my_sql.host=$SQL_SERVER" "--my_sql.port=$SQL_PORT" "--my_sql.username=$SQL_USERNAME" "--my_sql.password=$SQL_PASSWORD"

I'd personally switch to running the entrypoint as a shell script so you can get back to the json syntax. That would allow other cli options to be passed in the CMD value. And that script could be

#!/bin/sh
exec java org.springframework.boot.loader.JarLauncher "--my_sql.host=$SQL_SERVER" "--my_sql.port=$SQL_PORT" "--my_sql.username=$SQL_USERNAME" "--my_sql.password=$SQL_PASSWORD" "$@"

Where the exec avoids leaving the /bin/sh as pid 1 which can mess with signals.

You then run into the next issue, you shouldn't be baking configuration settings like db hostnames, and especially passwords, into the image. Instead that becomes a runtime configuration when you run the container:

FROM openjdk:11 as builder
EXPOSE 8080
WORKDIR application
ARG JAR_FILE=toDoAppWithLogin.jar
COPY $JAR_FILE application.jar

RUN java -Djarmode=layertools -jar application.jar extract

FROM openjdk:11
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
COPY entrypoint.sh /entrypoint.sh    

ENTRYPOINT [ "/entrypoint.sh" ]

Then you'd build it without any args and run it with the settings:

docker build -t nikspanos/cicd-pipeline:tag1 .
docker run -e SQL_USERNAME=user -e SQL_PASSWORD=pass -e SQL_PORT=0000 -e SQL_SERVER=server nikspanos/cicd-pipeline:tag1

I'd also recommend looking into secrets solutions for passing credentials since environment variables are easily exposed. Those would mount the credentials as a file or make them available from an external secrets server.

Upvotes: 3

Related Questions