Can’t connect to the server running in your container? Let’s see why, and how to fix it, starting with an example.
If you run a server on your machine listening on
127.0.0.1 , the “loopback” or “localhost” address:
You can then load it in your browser at http://127.0.0.1:8000.
But if you kill that and run it in a container:
If you then try to connect with your browser to http://127.0.0.1:8000 you’ll get connection refused or connection reset.
What’s going on?To understand how to solve this, you need to know a minimal amount about how Docker’s networking works.In particular, this article will cover:
Install Docker for Mac. Install docker using brew cask install docker or directly from the website here. Start XQuartz from command line using open -a XQuartz. In the XQuartz preferences, go to the “Security” tab and make sure you’ve got “Allow connections from network clients” ticked: Host Machine IP. If you expect that IP address might change you could go the extra mile and do something like docker container run -e 'DOCKERHOST=$(ip -4 addr show docker0 grep -Po 'inet K d.+')'., this way every time you run your container, it’ll have the IP address available inside the container set to the DOCKERHOST environment variable. The host has a changing IP address (or none if you have no network access). We recommend that you connect to the special DNS name host.docker.internal which resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Mac.
Networking without Docker
Let’s start with our first scenario: you run a server directly inside your operating system, and then connect to it.I’m going to assume the main OS is Linux, for simplicity of explanation. Docker runs on non-Linux OSes like macOS by running a Linux virtual machine, but the practical consequences are the same.
Your operating system has multiple network “interfaces”.For example, on my computer (with output shortened for clarity):
Docker Mac Ip Address
In this output we see three network interfaces:
Let’s go back to our starting, working example—you run a server listening on
127.0.0.1 , and then connect to it.We can visualize it like this:
Network namespaces
You’ll notice the image above talks about a “Default network namespace”.So what’s that?
Docker is a system for running containers: a way to isolate processes from each other.It builds on a number of Linux kernel features, one of which is network namespaces—a way for different processes to have different network devices, IPs, firewall rules, and so on.
By default, each container run by Docker has its own network namespace, with its own IPs:
So this container has two interfaces,
eth0 and lo , each with their own IP addresses.But because this is a different network namespace, these are different interfaces than the default namespace we saw above.
To make it clear what this means, let’s run the Flask server inside a Docker container, and then diagram the results:
The resulting network setup looks like this:
Now it’s clear why there’s a connection refused: the server is listening on
127.0.0.1 inside the container’s network namespace.The browser is connecting to 127.0.0.1 in the main, default network namespace.But those are different interfaces, so no connection is made.
How do we connect the two network namespaces? With Docker port-forwarding.
Docker run port-forwarding (is not enough)
If we run
docker run with -p 5000:5000 , it will forward from all interfaces where the Docker daemon is running (for our purposes, the main network namespace) to the external IP address of the containter.
To break it down explicitly:
-p 5000:5000 means redirecting traffic from port 5000 on all interfaces in the main network namespace to the container’s port 5000 on its external interface.-p 8080:80 would redirect traffic from port 8080 on all interfaces in the main network namespace to port 80 on the container’s external interface. And so on.
(We’re doing port 5000 specifically because that’s where our Docker image is listening, Flask’s default port.)
So let’s run a container, and then look at a diagram to visually see what that means:
And now we see the second problem: the server is listening on
127.0.0.1 inside the container network namespace, but the port forwarding is going to the external IP, 172.17.0.2 .
Thus, a connection reset or refused.
The solution: listen on all interfaces
Port forwarding can only connect to a single destination—but you can change where the server process is listening.You do this by listening on
0.0.0.0 , which means “listen on all interfaces”.
For example, you can do:
Note:
--bind 0.0.0.0 is specifically an option for http.server ; it’s not a Docker option.Other servers will have other ways of specifying this.For example, for a Flask application packaged with a Dockerfile , you can do:
Note: Outside the very specific topic under discussion, the Dockerfiles in this article are not examples of best practices, since the added complexity would obscure the main point of the article.
To ensure you’re writing secure, correct, fast Dockerfiles, consider my Quickstart guide, which includes a packaging process and 60+ best practices.
Now the network diagram looks like this:
Want to quickly get up to speed on Docker packaging? This article is an excerpt from my book, Just Enough DockerPackaging.
Takeaways
In this post, we’ll use Docker to create an image of a Spring Boot application and run it in a container. We’ll start with a single Dockerfile, then we’ll also cover how to scale our application using Docker Compose, and we’ll see how to build our Java code using Docker.
The Hola Spring Boot application
Updated: Code examples use Java 10 and Spring Boot 2. More info.
The application we’ll employ through this post is pretty simple: a Spring Boot Web application with a REST controller giving us a warm Spanish greeting. Apart from the ‘Hola’, we’ll show from which IP address the greeting is coming from. That will be useful for us to see how Docker works.
Docker Spring Boot - tpd.io
All the code in this post is available on GitHub: Hola Docker Spring Boot. If you find it useful, please give it a star!
Creating an example controller
I’ll guide you through this post as if you were creating the application from scratch, but you can also clone the existing repository and play with it.
First, let’s create the application skeleton using the Spring Boot Initializr: http://start.spring.io, including ‘Web’ as the only dependency and choosing Maven (to follow the same instructions as in this post). Then, we code this simple REST Controller:
Since there is no specified path, that text is returned when we perform a GET request to the root context. Let’s give it a try.
Running the application
Use your local Maven installation or the wrapper (
mvnw command) normally included by the Spring initializer. From the application’s root folder, execute:
The Spring Boot application should start and show a message similar to this one:
Download netflix app mac. If you navigate with your browser (or use command line tools like curl or httpie) to
localhost:8080 , you’ll see the response from our friendly app (the IP may vary of course):
As an alternative, you can also run the application by first packaging it in a jar file, and then run it with the java command. In that case, use Maven to create the package:
The resulting
.jar file will be placed in a new target folder. Now you can execute this command and you’ll get the application running as before:
Understanding the basics
So far there is nothing to do with Docker, but it’s important to highlight a couple of concepts to fully understand the rest of this post:
Dockerizing Spring Boot (or any executable .jar file)
Let’s start playing with Docker. If you haven’t done it yet, you need to install Docker in one of its versions (Windows, Mac, Linux). If you use an old Windows or Mac system, you’ll need to use the Docker Toolbox. In that case, please note that when I refer to
localhost , you should replace it with the IP of your VM (in which Docker runs).
To keep the learning path as smooth as possible, I’ll go through several steps that will show you different things you can do with Docker and a java application:
Minimal configuration: DockerfileDockerfile for Spring Boot
The minimal requirement we have to run our Spring Boot app in a container is to create a file named
Dockerfile with instructions to build the image. Then we’ll run a Docker container using that image.
As you see, I kept it really simple. The image is based on a slim Linux with JRE 10; on top of that we copy the JAR file, we change to the working directory in which that package is, and we execute the same command as we did before when running from our machine. The
EXPOSE instruction is telling Docker that the 8080 port can be exposed outside the container, but note that we’ll also need to make the port available when running it anyways. If you want further details about what every line does, I recommend you to have a look at the Dockerfile reference documentation.
If you use Java 8 instead, you can benefit from an even smaller Linux image (
alpine ). Unfortunately, the alpine distribution is not yet compatible with Java 10 at the time of writing this post.
Building the image
We build the image so it’ll be available in our local Docker registry. We give it a name with the
-t flag, and specify the current directory as the one in which the Dockerfile lives.
The command will output the status of every step while building the image. It will download the base image if it’s not available yet in your existing docker images, so be patient. If we now run the command
docker images to list the available images in our registry, we’ll see the new one:
Running the Spring Boot application with Docker
We’re almost there to have our application up and running on Docker. We just need to create a container using the new image:
By using the
-p flag we’re telling docker to expose the container’s port 8080 (on the right of the colon) on the host’s port 8000 (on the left, our machine). We can access from our machine to localhost:8000 (you can also use your browser) and see the greeting message again, this time coming from the Docker container:
Note that the IP is different from the previous one since now the application is deployed inside a Docker container. Each container will get a new assigned IP inside the Docker’s network.
Dockerfile: Recap
We did it! We have now the java Spring Boot application running in a Docker container. Note the difference with the previous case:
Running a Spring Boot application using docker-compose
Docker Compose is a tool to run multiple containers, define how they are connected, how many instances should be deployed, etc. It allows you to define how the image should be built as well. Let’s use it in our scenario to simplify the way we run the container.
Defining the docker-compose file
We create a file
docker-compose.yml with this content:
We create only one service:
hola . We define how it’s built: using the same folder as a context, since there is the Dockerfile and the different files located. We don’t need to specify the dockerfile parameter, but we’ll introduce it here since we plan to modify it later. We give a name to the image so, if it’s not present, the compose tool will build it and assign that name to the resulting image. If it’s there, it won’t build the image again unless we instruct it to do so (we’ll see how later).
Building and Running the application
Note that we’re using
ports to make the container port 8080 available from outside. In this case, we’re not specifying the host port, so Docker will pick a random one. Let’s execute docker-compose and see what happens:
This should be the output of the command:
As you can see, the first step in
docker-compose builds the image, since there is no holaweb image available. It will tag it with that name, and then run it according to the rest of the details in the docker-compose.yml file. It will expose the container’s port to the host in a port that we don’t know yet. Let’s find it out (you need to open a new terminal):
And here is the output of that command:
So now we know that the host port is 32768 (it may vary in your case). We navigate to
localhost:32768 to have our greeting, again coming from a container.
These are some remarks about this solution:
Docker-compose: Recap
Let’s summarize what we got so far with this
docker-compose solution:
Scaling up the Spring Boot app using docker-composeChanges in docker-compose
We can use
docker-compose to run multiple container instances of the same image. We prepared for that already, by leaving Docker to assign a random port in the host for our application, so we only need to add one line to our docker-compose.yml file to run multiple instances (in this case 3):
Just by adding that
scale: 3 line, we’ll get three containers up and running when we run:
Listing the multiple Docker instances
You’ll see in the output how the three containers with the three Spring Boot applications start in parallel. In a new terminal, let’s now execute:
So we can list what are the ports exposed from Docker. In my case, this is the output:
You can see how easy is to have multiple instances of our containers up and running. You can also override the number of instances from the command line, by running
docker-compose up --scale holaweb=[number_of_instances] .
Testing
If we do the GET requests to the three different ports, we’ll see how each container responds with a different IP in their greeting:
If you’re experienced with topics such as service discovery and routing, you must be visualizing now what are the possibilities that docker brings. We could include in this scenario a service registry and an API gateway and benefit from load balancing without too much effort. I’ll try to cover that in a different post.
My book can help you to grasp these patterns - Service Discovery and Routing
Building a Spring Boot app using DockerIntroduction![]()
This is a more advanced scenario in which we use a docker container to build the application and also to run it. Don’t do this unless you have a good reason; most of the modern Continuous Integration tools use Docker themselves to create dynamic containers to build the application packages, run the tests, etc.
Docker App Mac Ipad
Anyway, I wanted to cover this since it’s a good showcase of the possibilities of Docker and it can make sense in some specific situations:
However, note that for the scenario shown here it’s easier to build the code as we’ve been doing until now, just using java and maven on our machine while building the application and then docker to run it. Anyway, we’ll use it as a reference so you can get the idea and apply it (if needed) to other cases.
Dockerfile to build Java code and run itWindows Docker On Mac
Let’s create a different Dockerfile, which we’ll name
Dockerfile-build :
These are the most important remarks:
Combine it from docker-compose
We reuse our existing
docker-compose.yml file to build the image with this new approach, changing the line pointing to the Dockerfile to be
dockerfile: Dockerfile-build
Then we execute:
We’ll see how the build process is packaging the java application, downloading all required dependencies and running the tests. The subsequent steps to build the image are taking the resulting
.jar file and copying to a different folder.
As you might expect, we can now run the container with the same result as before, using
docker-compose up .
![]() What are the differences?
Let’s use the same structure as before (Recap sections) to show you what happened in this case:
Mouse settings mac app windows 10. That could be seen as an advantage but remember my previous comments: that scenario has just a few real-life applications.
Another noticeable difference is the docker image size. If we run
docker images with the image built from the first dockerfile, I get a result around 300 MB:
However, if we do that after building the image with
Dockerfile-build , the image size gets quite bigger:
It’s the expected result since now our image has more layers and also includes the source code in it.
Accessing the source code
The last difference I want to point out is that you can access the source code when running a container. To do that, you can attach a bash terminal to a running container, for instance, when running multiple instances, we can do this:
How can CI tools help here?
Even though this is a working experiment, there are only a few real-life scenarios in which building your Java application with Docker by yourself might be useful. If you don’t need a complex setup on your machine to build the application, you can rely on tools like Jenkins Pipelines, CircleCI, GitLab, etc.
These tools already build your apps in Docker containers out-of-the-box, so you just need to configure them properly and use their pipeline definition language to specify how to build your code - in this case, using maven.
Summary
In this post, we went through different steps to see how we can containerize a Java Spring Boot application using Docker.
I hope you found the guide useful. Use a comment or the Facebook page to give me some feedback!
Comments are closed.
|
AuthorWrite something about yourself. No need to be fancy, just an overview. ArchivesCategories |