I have this Dockerfile:
FROM java:8
ARG HOST
...
...
ENTRYPOINT java -DHOST=${HOST} -jar ./app.jar
I build the docker image passing the necessary build-arg: docker build -t co/myprogram --build-arg=HOST=myhost.com .
However when I run the image: docker run co/myprogram I get an exception in my program because the placeholder didn't substitute the HOST value. The url looks like http:///action/get instead of http://myhost.com/action/get..
Is there a way to use placehoders in the ENTRYPOINT?
ARGs are not used for interpolating ENTRYPOINT.
To achieve what you want to do, you can add a line with ENV HOST $HOST.
The entrypoint will then use $HOST at runtime, rather the interpolated value at buildtime, but, the variable will be set with the default value which was set at buildtime, but is still overridable.
Here is an example:
$ cat Dockerfile
FROM busybox
ARG what
ENV what $what
RUN echo ${what} > /tmp/${what}
ENTRYPOINT cat /tmp/${what}
$ docker run -ti $(docker build -q --build-arg=what=test . )
test
Unfortunately it seems to be impossible by design: https://github.com/docker/docker/issues/18492
Related
I have a Spring boot application in a Docker container and when I run the command to execute tests I see that the app starts correctly but there is no test executed. Looks like the mvn test is completely ignored.
Below my docker commands:
docker build -t cygnetops/react-test -f Dockerfile.dev .
docker run cygnetops/react-test mvn test
Dockerfile.dev
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
ADD /target/demoCI-CD-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
EXPOSE 5000
Entrypoints and commands are working together in a Docker container, if you specify an entrypoint, your command will be passed as an argument to the entrypoint, and if that entrypoint does nothing with the arguments passed to it, then the behaviour you are observing it totally normal.
The chapter "Understand how CMD and ENTRYPOINT interact" of the documentation provides a nice table explaining in depth how they interact.
In order to run you tests from that image, you could override the entrypoint when running you container:
docker run --entrypoint "" cygnetops/react-test mvn test
Note:
you will also have to install Maven, as it is not part of your base image
as you pointed, you will also need the POM and files of the Java project in order to run the tests, so you need to copy those sources in the image
So, add, in your Dockerfile, the lines:
COPY . .
RUN apk add --no-cache maven
If you want both to work, on the other hand, you will have to write your own entrypoint and make something from the command passed as arguments.
Here is an example:
entrypoint.sh, should be located at the same level as your Dockerfile:
#!/usr/bin/env sh
exec "$#" # execute what is passed as argument
java -Djava.security.egd=file:/dev/./urandom -jar /app.jar
Then, for your Dockerfile
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
RUN apk add --no-cache maven
COPY . .
COPY /target/demoCI-CD-0.0.1-SNAPSHOT.jar app.jar
COPY entrypoint.sh /usr/local/bin/
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 5000
Maybe a better solution is to create a docker file that runs a script instead of plain java. for example create a runner.sh file as follow:
#!/bin/bash
CMD="java -jar app.jar"
$CMD &
SERVICE_PID=$!
mvn test
wait "$SERVICE_PID"
and this will be your dockerfile
FROM maven:3.9.0-eclipse-temurin-11-alpine
COPY . .
RUN mvn install
COPY runner.sh /scripts/runner.sh
RUN ["chmod", "+x", "/scripts/runner.sh"]
ENTRYPOINT ["/scripts/runner.sh"]
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.
You've got at least 3 things wrong here.
ARG values are scoped, and go out of scope when you start the next stage.
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.
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.
First, sorry if my question sounds too easy or silly. I'm new to docker.
I have created my docker image and passed several jar files which are to be run immediately when the container starts.
I want to run the script "serve.sh" immediately when the container starts
I succeeded in creating the images well, but when I run the container, it throws me this error:
C:\Program Files\Docker\Docker\resources\bin\docker.exe: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-it\": executable file not found in $PATH": unknown.
Here is the command I use to run the image I craeted:
docker run b24b37614e1a -it
Here is my docker file:
FROM openjdk:8-jdk-alpine
EXPOSE 8080:8080
COPY apigateway-0.0.1-SNAPSHOT.jar apigateway.jar
COPY authservice-0.0.1-SNAPSHOT.jar authservice.jar
COPY institutionsservice-0.0.1-SNAPSHOT.jar institutionsservice.jar
COPY messagesservice-0.0.1-SNAPSHOT.jar messagesservice.jar
COPY postsservice-0.0.1-SNAPSHOT.jar postsservice.jar
COPY userservice-0.0.1-SNAPSHOT.jar userservice.jar
COPY serve.sh serve.sh
CMD [ "bash" "./serve.sh" ]
Please what am I doing wrong ? I'm new to docker
There are a couple of things which you should correct, First one is the CMD format which should be
CMD instruction has three forms:
CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
CMD [ "/bin/bash" , "./serve.sh" ]
Another thing, When you do docker run, the instructions are
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
which means all the options has to be before IMAGE and in your case it is appearing after IMAGE.
The correct command should be
docker run -it b24b37614e1a
BTW, small question, why you want to run an interactive container of this application. Ideally, it should be something like
docker run -p $HOST_PORT:$APP_PORT b24b37614e1a
-p => Publish a container's port(s) to the host
and then you can access your application localhost:$HOST_PORT or machine_IP:$HOST_PORT
Keep in mind that docker args order matters:
you wrote docker run b24b37614e1a -it which is different from
docker run -it b24b37614e1a
Hope it solves your problem :)
tl;dr
docker run -it then args (c/o Luca Fabbian)
You don't have bash. Use sh. (c/o MC Emperor)
CMD exec form needs an array, so use commas: CMD ["sh", "./serve.sh"]
CMD has a shell form if you forget syntax easily: CMD ./serve.sh
Don't even exec the shell if you don't have to: CMD ["./serve.sh"]
Argument order matters
This assumes you already took Luca's advice:
Keep in mind that docker args order matters:
you wrote docker run b24b37614e1a -it which is different from docker run -it b24b37614e1a
You don't have bash.
I pulled the openjdk:8-jdk-alpine image to confirm, and it does not come with bash. Alpine images come with sh so the code provided will never work unless you correct the shell used or install bash.
"unknown operand"
From your response to nischay:
I get this error: "sh: ./serve.sh: unknown operand"
I updated My CMD instruction to: CMD [ "bin/bash" "./serve.sh" ]
CMD in the exec form takes an array of instructions. These must be separated by a comma.
Do this:
CMD [ "executable", "param1" ]
^
A note on using CMD
As nischay said, there are a few different ways to use CMD to do roughly the same thing, from the reference quoted.
You can in fact use the exec form of CMD to say things like:
CMD [ "sh", "./serve.sh" ]
Typing out /bin/sh or /bin/bash or whichever shell you want is not required unless desired. But if you don't need the shell, you can use exec form without the shell as well:
CMD [ "./serve.sh" ]
I have a simple REST application developed using SpringBoot and this application jar has been deployed in a docker container. The end goal is to test the latency of this application under different JVM flag value combinations. I need to know how I can specify a long list of JVM flag values that can be repeatedly changed?
I know that you can specify one or two flags like this:
Dockerfile
FROM openjdk:9
ADD target/java-container.jar /usr/src/myapp/
WORKDIR /usr/src/myapp
EXPOSE 8080
CMD java -XX:+PrintFlagsFinal $JAVA_OPTIONS -jar java-container.jar
and running the command:
$ docker run -d --name mycontainer8g -p 8080:8080 -m 800M -e JAVA_OPTIONS='-Xmx300m' rafabene/java-container:openjdk-env
using the JAVA_OPTIONS. But I have a very long list of JVM flags as shown below:
-XX:+UseSerialGC -XX:+ResizePLAB -XX:-ResizeOldPLAB -XX:-AlwaysPreTouch -XX:-ParallelRefProcEnabled -XX:+ParallelRefProcBalancingEnabled -XX:+UseTLAB -XX:-ResizeTLAB -XX:-ZeroTLAB -XX:-FastTLABRefill -XX:+NeverActAsServerClassMachine -XX:-AlwaysActAsServerClassMachine -XX:+UseAutoGCSelectPolicy -XX:+UseAdaptiveSizePolicy -XX:+UsePSAdaptiveSurvivorSizePolicy -XX:-UseAdaptiveGenerationSizePolicyAtMinorCollection -XX:+UseAdaptiveGenerationSizePolicyAtMajorCollection -XX:+UseAdaptiveSizePolicyWithSystemGC -XX:+UseAdaptiveGCBoundary -XX:+UseAdaptiveSizePolicyFootprintGoal -XX:-UseAdaptiveSizeDecayMajorGCCost -XX:+UseGCOverheadLimit -XX:+DisableExplicitGC -XX:-CollectGen0First -XX:+BindGCTaskThreadsToCPUs -XX:+UseGCTaskAffinity -XX:YoungPLABSize=3397 -XX:OldPLABSize=1123 -XX:GCTaskTimeStampEntries=240 -XX:TargetPLABWastePct=6 -XX:PLABWeight=75 -XX:OldPLABWeight=46 -XX:MarkStackSize=4617021 -XX:MarkStackSizeMax=713160576 -XX:RefDiscoveryPolicy=0 -XX:InitiatingHeapOccupancyPercent=48 -XX:MaxRAM=139765086242 -XX:ErgoHeapSizeLimit=0 -XX:MaxRAMFraction=4 -XX:DefaultMaxRAMFraction=4 -XX:MinRAMFraction=2 -XX:InitialRAMFraction=61 -XX:AutoGCSelectPauseMillis=5557 -XX:AdaptiveSizeThroughPutPolicy=0 -XX:AdaptiveSizePausePolicy=0 -XX:AdaptiveSizePolicyInitializingSteps=28 -XX:AdaptiveSizePolicyOutputInterval=0 -XX:AdaptiveSizePolicyWeight=12 -XX:AdaptiveTimeWeight=19 -XX:PausePadding=0 -XX:PromotedPadding=3 -XX:SurvivorPadding=3 -XX:ThresholdTolerance=10 -XX:AdaptiveSizePolicyCollectionCostMargin=49 -XX:YoungGenerationSizeIncrement=16 -XX:YoungGenerationSizeSupplement=104 -XX:YoungGenerationSizeSupplementDecay=9 -XX:TenuredGenerationSizeIncrement=22 -XX:TenuredGenerationSizeSupplement=117 -XX:TenuredGenerationSizeSupplementDecay=2 -XX:MaxGCPauseMillis=13557897735059052544 -XX:GCPauseIntervalMillis=0 -XX:MaxGCMinorPauseMillis=16119267456708329472 -XX:GCTimeRatio=73 -XX:AdaptiveSizeDecrementScaleFactor=4 -XX:AdaptiveSizeMajorGCDecayTimeScale=11 -XX:MinSurvivorRatio=1 -XX:InitialSurvivorRatio=6 -XX:BaseFootPrintEstimate=272901592 -XX:GCHeapFreeLimit=2 -XX:PrefetchCopyIntervalInBytes=654 -XX:PrefetchScanIntervalInBytes=748 -XX:PrefetchFieldsAhead=1 -XX:ProcessDistributionStride=3
How can this be achieved?
As A. Wolf said, I'd put all jvm options in a file and run
docker run --rm -d --name mycontainer8g -p 8080:8080 -m 800M -e JAVA_OPTIONS="$(cat myjavaoptions)" rafabene/java-container:openjdk-env
While passing values using .sh is valid, you have so many params so I think that the clearer way is using docker-compose.
Just install it from here then you can specify in your docker-compose.yml something like this:
environment:
- JAVA_OPTS=-Xmx128m -Xms128m
You will be able to change your configuration and then restart the container just typing:
docker-compose down
docker-compose up -d
You can use --env-file parameter for passing multiple env variables.
docker run --env-file ./env.list image-name
Here env.list file will be list of env values you want to set inside docker container.
I have a main.java function that uses command like arguments to run processes specific to the input. I am now running the java project through a docker container. I would need the command be similar to
docker exec -it container_id -argument1
How would I specify in dockerfile what "-argument1" is and to send it to the main.java for execution?
One way would be to pass your arguments as environment variables. Ex:
docker exec -it <container_id> -e "FOO=BAR" <command>
Then use System.getenv("FOO"); to get the value.