Enterprise Java

Deploying a Quarkus or any Java based microservice behind an Nginx reverse proxy with SSL using docker

It has been a while but as per a friend requested I am going to show you how to deploy a Quarkus microservice behind an Nginx reverse proxy using docker.

What are we going to do…

I am going to install docker and docker-compose on a centos 8 host and I am going to deploy a docker container that will expose Nginx on ports 80 and 443 and a microservice using Quarkus. The same technique can be used with ANY java microservices framework like microprofile, Springboot etc because in the end what you will do is run a simple jar file (java is magic right?).

Let’s start…

I am going to skip the installation details for docker and docker-compose. In case you haven’t heard of docker-compose have look here https://gabrieltanner.org/blog/docker-compose and you’ll love it. It automates your container deployments and it just rocks!

Prerequisites

First of all make sure you have the ports required open

1
2
3
4
5
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --zone=public --add-port=22/tcp
sudo firewall-cmd --zone=public --add-port=80/tcp
sudo firewall-cmd --zone=public --add-port=443/tcp
sudo firewall-cmd --reload

Now install docker as per documentation

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#remove previous versions if any
sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
#install
sudo yum install -y yum-utils
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
#Verify that Docker Engine is installed correctly by running the hello-world image.
sudo docker run hello-world

Last but not least install docker-compose

1
2
3
4
5
6
7
8
#curl is required
dnf install curl
#Download the latest version of Docker Compose. Currenlty I am using version 1.25.4
curl -L https://github.com/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# Test the installation.
docker-compose --version

Now to the fun stuff…

Check out a sample application that I have developed using Quarkus that calculates a runners pace by executing git clone https://github.com/diakogiannis/pacecalculatorapi.git

In the case you have forgotten to install GIT (I won’t tell anyone if you execute sudo yum install git)

Now let’s build it INSIDE the Docker image (yes you don’t even have to have java installed)…

1
docker run --name=pacecalculator -d -p 9090:8080 diakogiannis/pacecalculator:latest

En voila! the application is ready to run!

We actually told docker to run the container giving him the name pacecalculator, with ‘-d’ we told him to be in ‘detached’ mode so it will run in the background and with ‘-p 9090:8080’ we told him to expose the 8080 port internally to the 9090 port in the running system.

Let’s test if it works, and since I am a bad long-distance runner, I will try to calculate the running pace for 5km for just under 30 minutes (1.700s) try inputing

that will result in  {"pace":"5.67"}

Let’s examine the docker file

01
02
03
04
05
06
07
08
09
10
11
12
13
14
# Stage 1 : build with maven builder image
FROM maven:3.6.0-jdk-11-slim AS BUILD
MAINTAINER Alexius Diakogiannis
COPY . /usr/app/
RUN mvn -f /usr/app/ clean package
# Stage 2 : copy from the previous container the jar file, put it in a java one and run it
FROM adoptopenjdk:11-jdk-openj9
WORKDIR /app
COPY --from=BUILD /usr/app/target/PaceCalculatorApp-runner.jar /app/
ENTRYPOINT ["java", "-jar", "/app/PaceCalculatorApp-runner.jar"]
  1. First of all, we use a maven container with JDK-11 and we use the COPY command to copy ALL the project inside.
  2. After that, we build it in the same way as we would in our normal development environment by mvn clean package pointing out the location of the pom.xml file. Afterwards, we use another container (because after all, we might need a different environment to run the application) and in this case JDK-11 but with OpenJ9 JVM (that rocks and has it’s origins to IBM’s Java SDK/IBM J9 with great memory management)
  3. Then we copy the jar file created from the previous container to the new one
  4. Last we tell docker to execute java -jar /app/PaceCalculatorApp-runner.jarwhen the container starts. Be very careful and notice that when using ENTRYPOINT each parameter must be on a separate section.

Now let’s stop and remove the container docker stop pacecalculator && docker rm pacecalculator

Preparing the filesystem

For NGinX SSL to work we need to store the certificates somewhere. Also, a folder for the NGinX logs is needed. It is a very best practice not to generate IO inside a Docker image so in production would have externalize also the console log of the  java application but this is just a PoC.

For my sinstallation I am usually using the pattern /volumes/{docker image name}/{feature} and I don’t let docker decide where to store my volumes. So in this case, I created

  • /volumes/reverse/config
  • /volumes/reverse/certs
  • /volumes/reverse/logs

reverse will be the name of the docker container that NGinX will run

I have issued a certificate under a free authority and placed  its two files (pacecalculator.pem and pacecalculator.key) in /volumes/reverse/certs directory

I create the file /volumes/reverse/config/nginx.conf with the contents

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid       /var/run/nginx.pid;
events {
  worker_connections 1024;
}
http {
  default_type  application/octet-stream;
  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log /var/log/nginx/access.log  main;
  sendfile    on;
  tcp_nopush  on;
  keepalive_timeout 65;
  gzip on;
  gzip_http_version 1.0;
  gzip_proxied any;
  gzip_min_length 500;
  gzip_disable "MSIE [1-6]\.";
  gzip_types
        text/plain
        text/html
        text/xml
        text/css
        text/comma-separated-values
        text/javascript
        application/x-javascript
        application/javascript
        application/atom+xml
        application/vnd.ms-fontobject
        image/svg+xml;
  proxy_send_timeout 120;
  proxy_read_timeout 300;
  proxy_buffering    off;
  tcp_nodelay        on;
 server {
    listen   *:80;
    server_name  jee.gr;
    # allow large uploads of files
    client_max_body_size 80M;
    # optimize downloading files larger than 1G
    #proxy_max_temp_file_size 2G;
    location / {
      # Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
      proxy_pass http://pacecalculator:80;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
  }
server {
    listen   443 ssl;
    server_name  nomisma.com.cy www.nomisma.com.cy app.nomisma.com.cy;
    ssl_certificate     /etc/ssl/private/pacecalculator.pem;
    ssl_certificate_key /etc/ssl/private/pacecalculator.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    # allow large uploads of files
    client_max_body_size 80M;
    # optimize downloading files larger than 1G
    #proxy_max_temp_file_size 2G;
    location / {
      # Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
      proxy_pass http://pacecalculator:80;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
  }
}

I will not go in much detail with the configuration but in general, it will gzip the communication between the client and the reverse proxy and it will listen for the hostname jee.gr. Both 80 and 443 ports will reverse proxy on port 80 of the microservice, this means that the internal docker communication is NOT encrypted (but do we need to encrypt it?). We can, of course, encrypt it but this falls out of the scope of this tutorial. Please note that we use for the internal hostname the docker name “pacecalculator”.

Lets create the orchestrator aka docker-compose.yml file that will orchestrate the deployment of both microservices with the correct order.

1
nano docker-compose.yml

and inside paste

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: '3'
services:
  reverse:
    depends_on:
      - pacecalculator
    container_name: reverse
    hostname: reverse
    image: nginx
    ports:
      - 80:80
      - 443:443
    restart: always
    volumes:
      - /volumes/reverse/config/:/etc/nginx/
      - /volumes/reverse/logs/:/var/log/nginx/
      - /volumes/reverse/certs/:/etc/ssl/private/
  pacecalculator:
    container_name: reverse
    hostname: reverse
    image: diakogiannis/pacecalculator:latest
    restart: always
networks:
    default:
        external:
            name: proxy-net

So what we did here is that we started our pacecalculator service and the the reverse service telling it to expose both ports 80 and 443 BUT also to wait (depends_on) until pacecalculator is started successfully. Also we are using an internal dedicated network for the communications that we named it proxy-net

Time to fire it up!

We start the containers by issuing

1
docker-compose -f /{path to}/docker-compose.yml up --remove-orphans -d

this will clean up leftover containers and start again in detached mode (aka background)

If we want to stop it we issue

1
docker-compose -f /{path to}/docker-compose.yml down

As the French say ç’est très difficile? No ç’est très facile!

Published on Java Code Geeks with permission by Alexius Diakogiannis, partner at our JCG program. See the original article here: Deploying a Quarkus or any java based microservice behind an Nginx reverse proxy with ssl using docker

Opinions expressed by Java Code Geeks contributors are their own.

Alexius Diakogiannis

Author of JEE.gr, JEE Architect, Scrum Master, Enthusiastic Entrepreneur, Passionate Archer, Linux Lover and JAVA Geek!
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Uroš Majerič
Uroš Majerič
3 years ago

Hi Alexius,

Looking at “docker-compose.yml” -> “pacecalculator”->”container_name” should probably be something like “pacecalculator” (or something similar) instead of “reverse”, right?

Best,
Uroš

Back to top button