1, 2, 3: SpringBoot, Docker, Nginx, DigitalOcean
This is another step-by-step walkthrough which explains how to dockerize and deploy a SpringBoot microservice behind Nginx.
This article aims at helping Java developers who want to start using popular tools (Docker, Nginx, DockerHub, Certbot) to create a production-ready application. It will also show how to obtain a (free!) domain, setup a (free!) SSL certificate and deploy on DigitalOcean. Some Devops for developers.
Why 1–2–3? Focus on simple essential aspects, clear code samples and relevant references.
DigitalOcean
DigitalOcean is one of the most popular cloud platforms for deploying and managing applications and web sites.
The cheapest plan starts from 5$ per month (there is a Free Trial to start with) which is absolutely worth paying: it gives you access to a Droplet (1GB memory, 25GB SSD) with several flavors available and plenty of documentation to read.
Architecture
Our architecture will be something like this:
Few interesting remarks: the core components run on Docker while certbot is provided by the Ubuntu VM as well the storage of the SSL certificates (and any other file and static resource).
Steps
We will go through the following steps to make it happen:
- create and configure the Droplet
- run Nginx on Docker
- build and deploy the SpringBoot application
- obtain a domain
- obtain a SSL certificate (our application will run on HTTPS)
Create a Droplet
Signup for DigitalOcean (via your Github account is also possible and very convenient) and go straight into the dashboard to create a basic VM from a “Docker on Ubuntu” image.
The setup is pretty much straight-forward, but you can also have a look at the How to Create a Droplet to understand all settings and options.
Once created you will see the IP Address and can log in into the Droplet.
Run Nginx
Nginx will run on Docker using the latest image: I don’t bother to fix a version as I have never seen regressions. Maybe it is something to consider when start serving production requests.
When deploying the Nginx base image we only need to provide our own configuration file.
# nginx.confhttp {
server {
listen 443 ssl;
server_name myapp.com;
charset utf-8;
access_log off;location / {
proxy_pass http://myapp:8080;
}
}}
events { worker_connections 1024; }
The file defines the ssl access and a domain (see further), and the proxy_pass, the mechanism to delegate the incoming requests to a downstream application.
SpringBoot Application
Create a SpringBoot app using initializr: in this demo we only need a simple endpoint (ie /test) to make sure the service is reachable.
@RestController
@RequestMapping
public class AppController { @GetMapping(value = "/test")
ResponseEntity<?> test(HttpServletRequest request) throws URISyntaxException {
log.info("/web/test");
return ResponseEntity.ok().body("Ok");
}
You obviously need a little more than this (the SpringApplication file, unit testing, etc..), check out the source code on Github to get all you need.
Once the application is unit tested and run locally we can proceed with the most important step: dockerize it.
# base image
FROM adoptopenjdk/openjdk11:alpine-jre# home folder in the Docker image
RUN mkdir -p /software# copy config files (application.properties)
ADD config /software/config
# copy jar file
ADD target/myapp.jar /software/myapp.jar# run the app
WORKDIR /software
CMD java -Dserver.port=$PORT $JAVA_OPTS -jar myapp.jar
The application is ready to be built:
docker build -t mydomain/myapp:latest .
Run the application on the local Docker to verify it is working:
docker run -it — rm — name myapp -p 8080:8080 mydomain/myapp:latest
Hit it now at http://localhost:8080/test
DockerHub
Once built the image is available on the local environment, to confirm run:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydomain/myapp latest 20de1e1496d5 5 minutes ago 95MB
Nice, but we need to make the same image available on DigitalOcean. We then need to introduce another tool: DockerHub.
DockerHub is a container registry where we can store (for free) public images. Go ahead, signup and login with the Docker cli
docker login
Push the image
docker push mydomain/myapp:latest
Congrats! You have developed, tested and pushed your Dockerized application to the DockerHub container registry.
Docker Compose
The last step in this section is to create a docker-compose file. We are going to run multiple containers so one of the options is to define a docker-compose file to keep all the configuration together.
As you application ecosystem grows (for example adding a logging container i.e. logspout) you can just add it in the compose file.
version: "3"services:
myapp:
image: mydomain/myapp:v0.0.1-a
container_name: myapp
ports:
- "8080:8080"
networks:
- my_networknginx:
image: nginx:latest
container_name: nginx
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- 80:80
- 443:443
networks:
- my_networknetworks:
my_network:
Be aware the above is a working in progress version of the final docker-compose: we are going to add the ‘letsencrypt’ configuration.
Free Domain
The application will need its own domain: head to Freenom where you can get one for free, typically with a .tk or .ml domain.
You can then create a domain in your DigitalOcean dashboard and link it with with the Droplet (configuring the relevant nameservers).
Free SSL Certificate
Let’s Encrypt is non-profit certificate authority which provides free 90 days SSL certificates, which they can be always renewed within (and after) the validity window.
We will another tool: certbot.
Certbot allows to create and renew SSL certificates from Let’s Encrypt
# install certbot
$ sudo apt install certbot# create (or renew) cert
$ certbot certonly --force-renewal -d myapp.com
There are 2 options to proceed
How would you like to authenticate with the ACME CA?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Spin up a temporary webserver (standalone)
2: Place files in webroot directory (webroot)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
Choose #1 to let Certbot handle the challenge using its own built-in web server: it is a lot easier and it does not need any extra plugin (ie Apache for Nginx). The certificate can be found at ‘/etc/letsencrypt/live/myapp.com’
It is very important however that no process is already running on port 80. This is not the case at this stage but it will be during a future renewal: make sure to stop (docker-compose down) the Nginx container before renewing the certificate.
# renew letsencrypt ssl certificate$ docker-compose down
$ certbot certonly --force-renewal -d myapp.com
$ docker-compose up -d
Start it up
All components are in place and there is a domain with its own SSL certificate. A final change is required in ‘nginx.conf’ to add the SSL certificate path before starting it all up.
nginx.confhttp {
server {
listen 443 ssl;
server_name myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem; charset utf-8;
access_log off;location / {
proxy_pass http://myapp:8080;
}
}}
events { worker_connections 1024; }
Copy the docker.compose.yml and nginx.conf onto the Droplet using Filezilla or a scp command.
scp docker-compose.yml root@{droplet_ip}:/myapp/docker-compose.yml
scp nginx.conf root@{droplet_ip}:/myapp/nginx.conf
Log on into the Droplet and run the containers
$ cd /myapp
$ docker-compose up -d# check containers are up
$ docker ps
# view app logs
$ docker logs myapp
# view nginx logs
$ docker logs nginx
The service should now respond at https://mydomain/test
Conclusion
Check out the source code on GitHub
Another useful reference: how to add a SSH key to your Droplet
Reach me on Twitter for questions/suggestions and more 1–2–3 tutorials