In this article we’re going to walk through installing Docker on Ubuntu, starting a few Docker containers, and then running those containers behind a reverse proxy. When we’re done, you’ll have a load balance terminating SSL (TLS) connections with multiple Docker containers running the workload.
This is how it will look:
- Your workstation connects (with a web browser) to the NginX server.
- The NginX server terminates the HTTPS session.
- NginX establishes connections (in a load balanced scheme) to two back-end Docker web servers – also running Nginx.
- The Docker containers running NginX serve up content which is sent back to your workstation’s web browser.
- If one of the Docker containers fails, NginX (the reverse proxy) will remove it from the pool to prevent errors. If/when the Docker container returns, it will become available again automatically.
Install docker. We’re using Apt in this case. You’ll notice Docker is running and enabled once installed.
apt update apt install docker* systemctl status docker
List containers and images. The “-a” shows the stopped containers as well as the started containers. Without the “-a”, you’ll only see the started containers.
docker container ls (-a) docker image ls
Run (create and start) a new container. We’ll start two containers using the same “nginx” image. If you haven’t started a container from a given image before, Docker will go and download it for us, and then create the container from that image. You should take note of the “81:80” and “82:80” below. It instructs Docker to start the container named “NginXDemo81” on port “81”. Nginx in the container is listening on the default port of “80”. Hence the mapping of 81 to 80. Obviously we can’t run two things on a single host on the same port. Therefore we increment them. If you were to browse to the Docker server and specify the port :81, you’d see your NginX default page.
Eg “http://docker.local:81“. And “http://docker.local:82“.
Throughout this tutorial, you can test your progress by browsing to the containers using the method above.
docker run --name NginXDemo81 -p 81:80 -d nginx docker run --name NginXDemo82 -p 82:80 -d nginx
Stop a running container. The above commands created and started the containers. In the steps below, we’ll stop and then list the containers. You’re going to see what it looks like when a container is in a started and stopped state.
docker stop NginXDemo81 docker container ls -a docker start NginXDemo81 docker container ls -a
Customise a started container. Now we’ll change the default NginX page. We’ll display the test “This is my test index…” when someone browses to our Docker server on ports 81 and 82. You can see that once we’ve populated the “index.html” file on the Docker host, we then need to copy it into the NginXDemo81 and NginXDemo82 container.
For the record, there are other ways to deal with customising a container. But we’re keeping it simple. The more professional method is the “Dockerfile” method.
echo "This is my test index..." > index.html docker cp index.html NginXDemo81:/usr/share/nginx/html docker cp index.html NginXDemo82:/usr/share/nginx/html
Stop and delete a container. Now let’s stop and delete our containers. Don’t worry, we’ll create them again soon. We jsut need to know how it’s done. We first stop the container, and then we can delete it.
docker container stop NginXDemo80 docker container stop NginXDemo81 docker container rm NginXDemo80 docker container rm NginxDemo81
Install and configure a reverse proxy to load balance docker containers. As outlined earlier, in order to browse to the Docker container NginX websites, we needed to specify the port (81 and 82 respectively). That’s a huge limitation, and one we’re about to solve. We want to load balance between the two containers (or even more containers if you like). That way, if one of the Docker containers failed, our website (which exists on both containers) would still continue to be accessible to visitors.
To achieve this, we’re going to install NginX on the Docker host. NginX makes a very nice and simple reverse proxy.
apt install nginx systemctl enable nginx
Replace the contents in file “/etc/nginx/sites-available/default” with the following. The following is a very basic (minimal) NginX server. It will listen on port 80 only (not HTTPS on port 443 yet, but we will fix that shortly) and load-balance across the two Docker containers. In this example, the Docker containers are on the same host as the NginX load-balancer. But they could actually live on different servers. In-fact, it’s a wise idea to have the Docker containers running on different Docker hosts to ensure strong availability. The interesting parts are in bold.
upstream docker { server localhost:81; server localhost:82; } server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { proxy_pass http://docker; } }
Restart NginX. We do this to active the above configuration change.
systemctl restart nginx
Start two docker containers on ports 81 and 82 and modify their index’s to distinguish between them. This is what we did earlier. However, we’re changing our “index.html” so that each Docker container has a different message. This will illustrate the way the load-balancer works. Ie, the first visit will be served by the first Docker container, and the second visit will be served by the second Docker container, and then it starts the loop again.
docker run --name NginXDemo81 -p 81:80 -d nginx echo "Docker container 1" > index.html docker cp index.html NginXDemo81:/usr/share/nginx/html docker run --name NginXDemo82 -p 82:80 -d nginx echo "Docker container 2" > index.html docker cp index.html NginXDemo82:/usr/share/nginx/html
Test it out. You can now browse to your Docker host and see the content served by your Docker containers. Refresh the page and you will see that the back-end servers are taking turns in serving the content.
Eg: “http://docker.local“.
But remember, right now everything is HTTP only. We want to secure the connection to our website using HTTPS. That’s come up next.
Create the self-signed certificates. We really should used signed certificates. But in this demonstration, we’ll keep it simple and use a self-signed certificate.
mkdir /etc/nginx/ssl cd /etc/nginx/ssl openssl genrsa -des3 -out server.key 2048 openssl req -new -key server.key -out server.csr openssl rsa -in server.key -out server.key.nopass openssl x509 -req -days 365 -in server.csr -signkey server.key.nopass -out server.crt
Replace (again) the contents in file “/etc/nginx/sites-available/default” with the following. The change here is that we’re going to add the HTTPS configuration. We’re going to terminate the HTTPS connection between the web browser and the Docker hosts, and then re-establish the connection insecurely to the back-end Docker containers. The interesting parts are in bold.
upstream docker { server localhost:81; server localhost:82; } server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name _; location / { proxy_pass http://docker; } } server { listen 443 ssl http2; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key.nopass; ssl_session_timeout 1d; ssl_session_cache shared:SharedNixCraftSSL:10m; ssl_session_tickets off; ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; add_header Strict-Transport-Security "max-age=63072000" always; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8; location / { proxy_pass http://docker; } }
Restart NginX. We do this to active the above configuration change.
systemctl restart nginx
Right now you can browse to your Docker host and expect to see the contents served up by the Docker containers. Remember that each container is serving a different message, so you can easily see that it’s properly load-balancing between the two containers. Refresh the page a few times to see that it’s properly rotating between the back-end containers.
Eg: “https://docker.local“.
Reboot or restart docker service.
systemctl stop docker systemctl start docker docker container ls -a docker container start NginXDemo81
Test NginX reverse proxy pool status (visit the page). You will only see the contents served by the first Docker container (the only one running). Let’s start the second Docker container.
docker container start NginXDemo82
Test NginX reverse proxy pool status (visit the page). You may need to give it a few seconds for NginX (on the Docker host) to see that the second Docker container is up – at which point it will be included in the load-balanced pool.
You’ll notice that Docker doesn’t restart containers on start. You’ll also notice that Docker containers continue how they left off. In-fact, we can log into a running container, make some changes (create a file for example), “stop” the container, “start” the container, and the changes will still be there.