Why Spring Boot?
I wanted to try Docker for the first time, and I already have some experience with Spring Boot apps, so I figured building a Containerized (is that the right term?) Spring Boot app would be a good way for me to get familiar with using Docker.
I started by doing this Getting Started tutorial on the Spring Framework site: https://spring.io/guides/gs/spring-boot-docker/
The tutorial instructions say that you’ll need “15 minutes”. Yeah right! May be true if you’re already experienced with Docker and already have it installed and working on your workstation, but that was not my situation. It took some effort to get Docker to work at all on my aging PC. Once I did, it was still necessary to deviate from the tutorial in order to get things working.
Let’s just get into the details…
Where is the code?
I followed the tutorial steps and checked in my working code on Github. I’m more comfortable with Maven, so I skipped the Gradle parts of the tutorial and followed the Maven examples instead.
In the root project directory, there is a file named HOWTO-build-and-deploy.txt. That’s where I kept notes as I was getting things working on my PC. Some parts of the tutorial just wouldn’t work for me, so I kept track of my workarounds, such as:
- The dockerfile-maven-plugin didn’t work as advertized for me, so I modified the plugin declaration in my Maven POM file. I’m only using some of the features of this plugin now, and I have to deploy my Docker images to Docker Hub manually via the Docker CLI (Command Line Interface).
- I had to enable the “Expose daemon on tcp://localhost:2375 without TLS” option in Docker for Windows, otherwise one of the other Maven plugins wouldn’t work.
Building the app
I used Windows PowerShell to build the application from the command line, using the command:
My project code from Github already has all the Docker configuration included, so the Maven build includes both the Spring Boot application build, and the local Docker image creation.
Running the Spring Boot app locally without Docker
Let’s confirm that the Spring Boot app is able to run locally before we add Docker into the mix. The build command above should have created the Spring Boot application JAR in the ‘target’ subdirectory of the project. We can run it directly:
java -jar target/gs-spring-boot-docker-0.1.0.jar
The Spring Boot app is now running locally. Open a browser and connect to the app.
The Spring Boot app will keep running as long as you leave the Windows PowerShell open. You can kill it by pressing CTRL-C inside PowerShell, or by just closing the PowerShell.
We have a working Spring Boot app, so now let’s check the Docker part of the project. Before we try to run the Docker image, let’s take a look at the “Dockerfile” in the project.
FROM openjdk:8-jdk-alpine # We added a VOLUME pointing to "/tmp" because that is where a Spring Boot application creates # working directories for Tomcat by default. The effect is to create a temporary file on your # host under "/var/lib/docker" and link it to the container under "/tmp". This step is optional # for the simple app that we wrote here, but can be necessary for other Spring Boot applications # if they need to actually write in the filesystem. VOLUME /tmp ADD target/gs-spring-boot-docker-0.1.0.jar app.jar ENV JAVA_OPTS="" # To reduce Tomcat startup time we added a system property pointing to "/dev/urandom" as a source of entropy. ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
I’m not pretending to know much about Docker at this point. I did a Docker “Hello World” tutorial some time ago, but I’ve already forgotten most of what I learned! 🙂
If I remember correctly, the FROM line at the start of the Dockerfile specifies the base image that we want to use as the container for our application. The other lines in the Dockerfile describe what we want to do with that base image. The image “openjdk:8-jdk-alpine” is an image that is available to the public for download from Docker. The first time you build the project, that image gets downloaded to your PC and put into a local repository for later use.
The VOLUME command already has very clear comments about its purpose. This Spring Boot app doesn’t write anything to local file storage, so this VOLUME command doesn’t serve any purpose, other than to demonstrate how this can be done.
The ADD command copies a source file from the build machine (your PC) into the container’s filesystem at the specified destination. In the tutorial, they use an “ARG” with the dockerfile-maven-plugin rather than explicitly specify the source file in the Dockerfile. Since the dockerfile-maven-plugin didn’t work for me, I removed the ARG from the Dockerfile and hardcoded the source file path/name in my Dockerfile.
The ENTRYPOINT command tells Docker what to run when a container is created for your image. It looks like the ENTRYPOINT from the tutorial is set up to feed the ‘java’ command to a shell, with some parameters, and of course the Spring Boot application JAR as the last argument.
Running the Docker image locally
The Maven build command above built the Spring Boot app JAR, and also built a Docker image by using the instructions from the Dockerfile. Now we want to run that image in a Docker container.
My process is different from the Getting Started tutorial because one of the Maven plugins they use didn’t work for me.
Executing this command in PowerShell will run my newly built image locally in a Docker container:
docker run -p 8080:8080 -t jtough/gs-spring-boot-docker
Take note of the first line of console output from the Spring Boot application
Starting Application v0.1.0 on c929883214d2 with PID 5 (/app.jar started by root in /)
Where we would normally see the hostname, we instead see “c929883214d2”. That’s the “Short UUID” of the Docker image. Each Docker image has its own unique id. When the Spring Boot app is running in a Docker container, it doesn’t know anything about the host that is running Docker, and in turn, running this Docker container.
Perhaps there IS a way for the app to get details about the real host, but I don’t know how to do that with my current limited knowledge of Docker. That will be a topic for another day. 🙂
Also take note of this:
(/app.jar started by root in /)
In the Dockerfile, we included this line:
ADD target/gs-spring-boot-docker-0.1.0.jar app.jar
So inside the image, the Spring Boot application is named “app.jar”. The ADD command did not specify a path, so it seems that the default path used is the Linux root directory: /.
It also appears that the Spring Boot application is run as the Linux root user.
This isn’t what I would expect to see done on a traditional Linux host server, however, Docker creates this virtual Linux host on demand for the singular purpose of running the Spring Boot app. I suppose there is no reason to complicate things, if the simple Spring Boot app can just be run from the / directory and run as root.
The containerized version of the app is now running, so let’s try to connect via a browser.
The Docker ‘run’ command
Reference pages for ‘docker run’ command:
The command that we used to run the image was:
docker run -p 8080:8080 -t jtough/gs-spring-boot-docker
The -p argument is used to “publish” (I’d probably call it “map”) port numbers from the container host to the real host. So in our case, we are mapping port 8080 in the Docker image to port 8080 on the server that is running Docker and hosting the container.
Why port 8080? Because that’s the port we set in the Spring Boot app.
The reference page says that the -t argument will cause Docker to “Allocate a pseudo-TTY”. I don’t know exactly what that does for us, and I don’t really need to know right now. I’ll use it because the Getting Started tutorial used it, and I’ll learn more later. The Docker reference page talks more about the pseudo-TTY feature.
The final argument was: “jtough/gs-spring-boot-docker“. That’s the image name. The image name is generated from two properties in the Maven POM file:
I chose ‘jtough‘ because that’s my Docker Hub account name. You’ll want to change that value to your own account name before you try to push any images to Docker Hub.
The Docker ‘ps’ command
Reference page for ‘docker ps’ command:
If you still have the PowerShell window open where you executed the ‘docker run’ command, then keep it. Open a second PowerShell window.
Run the following command:
docker ps -a
This will display all Docker containers on this machine, both running ones and ones that were created in the past and are no longer active. Note that without the -a argument, Docker will only display running containers.
On my PC, I see the container that is currently hosting the Spring Boot app at the top. Below that, there is a history of containers that were created when I was messing around with other tutorials months ago. Docker will automatically assign a goofy name to each container if you don’t explicitly specify a name. The container for the Spring Boot app is named “nervous_beaver”. Okey dokey…
The Docker ‘stop’ command
Reference page for ‘docker stop’ command:
A running Docker container will keep running until you explicitly ‘stop’ or ‘kill’ the container. The ‘stop’ command should be used if possible, as it will attempt to do a graceful shutdown of the running process in the container.
If you still have the PowerShell window open where you executed the ‘docker run’ command, then keep it open and watch what happens when the ‘docker stop’ command is executed. Open a second PowerShell window for the ‘docker stop’.
docker stop <container_id>
The only required argument to ‘docker stop’ is the container id that you want to stop. You can get the container id from the ‘docker ps’ listing.
After a few moments, the container will be stopped. If you now attempt to connect to the Spring Boot app via a browser at http://localhost:8080/, you will get a ‘connection refused’ error. The app is no longer running.
Typing ‘docker ps -a’ in PowerShell will confirm that “nervous_beaver” is no longer running.
Publishing the image to Docker Hub
The Getting Started tutorial that I followed (see above) relied on a Maven plugin to deploy the image to Docker Hub. That feature of the plugin didn’t work for me, so I used the Docker CLI to manually issue the commands to do that.
Docker Hub is a thing. You can find it here:
Why do we need Docker Hub? Good question. I don’t know the answer.
I understand that some people want a central place to publish their Docker images so the general public can access them. I don’t know how useful that will be to a regular Docker user like me. Regardless, its still good to know how to deploy to Docker Hub, just in case I do have a use for it in the future.
First, you’ll need to sign up for a free Docker Hub account. Here’s what the main Docker Hub page looks like when I’m logged in.
Publishing a Docker image to Docker Hub
Publishing a Docker image that you’ve built locally to Docker Hub is easy. We just need to issue two Docker CLI commands in PowerShell.
docker login -u <YOUR_USERNAME> -p <YOUR_PASSWORD>
This command will authenticate your local Docker with Docker Hub. You only need to do this once in PowerShell, and you will stay logged in as long as you keep the PowerShell window open.
docker push jtough/gs-spring-boot-docker:latest
This command will upload the specified image to my Docker Hub account. The keyword “latest” that comes after the colon means that I want to upload the newest build of my image. It is possible to tag images, and keep multiple versions, then refer to them by tag name. That’s beyond what I’m trying to accomplish with this very simple app, so just using “latest” is fine for now.
Remember to change the “docker.image.prefix” in the POM file from “jtough” to your own Docker Hub username. In the commands above, substitute your own username wherever “jtough” appears.
And now I see this in my Docker Hub account:
I just found this interesting and wanted to share. If you open the Hyper-V Manager in Windows 10 Pro, you can see a VM running that is used by Docker for Windows.