Let's Encrypt + Spring Boot + Docker

Is there any Docker image to add the Let’s Encrypt certificate on the server using Docker Compose? I have a project in Spring Boot 2. I upload a .jar file to the server so I don’t have a Tomcat service in the .yaml file. I use Tomcat Embedded. I want Docker to automatically update the certificate every 90 days.
Thank you

I don’t know how embedded Tomcat works but you can use a reverse proxy create certificates for that.

1 Like

I can’t manage Tomcat because Spring Boot uses a version of Tomcat Embedded.
Tomcat is managed by Java. The Tomcat .jar file is inside the .jar file of my application created in Spring Boot.
I found this code but what should I write inside the .json file?

entryPoints:
  web:
    address: ":80"

  websecure:
    address: ":443"

certificatesResolvers:
  myresolver:
    acme:
      email: your-email@example.com
      storage: acme.json
      httpChallenge:
        # used during the challenge
        entryPoint: web

I can’t make up the code. I can also produce a .war file in place of the .jar file and therefore not use Tomcat Enbedded but simply Tomcat. Clearly it is another approach to the problem. I need to add the Tomcat service to Docker Comose.
I’ve been working on this project for several months and I can’t finish it.
Isn’t there a user on this forum who uses Spring Boot 2 and can you show me a solution he uses to create a productive web server?
I don’t think there are better Docker experts outside of this forum. If I don’t find a solution here I have to leave Docker. It may also be that programmers using Spring Boot do not use Docker.
Do all subscribers to this forum use PHP?

Why do we still talk about standalone Tomcat servers? If you can make a service to listen on a port, then you can make it available from outside a container so you can sue a proxy server to access that port. If you don’t know how to make the service to listen on a port even without Docker, then it is not a Docker issue. I am sure many people on this forum are very capable of deploying almost any application if they have time and colleagues who can tell them about the programming language / environment they would like to use. And this is the key. If you can tell us how you would do it without Docker, we can help you do it with Docker. If you need help with Java or PHP or .NET, etc… then it is not impossible to find someone on this forum who could help you, but you can have more luck on a forum where people talk about that programming language specifically.

I am sure you tried to search for acme.json can you show me what you have found? Just to make sure I don’t recommend that you have already found.

2 Likes

It’s not as simple as you think. I try to tell you what I need.
I need a .yaml file that makes the following services available on my server:
Tomcat
Java
pgAdmin
PostgreSQL
Let’s Encrypt
Or a server with the following services:
Java (work completed 30% - when I change the .jar file or update the certificate I have to restart the container)
pgAdmin (work completed 50% - I can’t use https with java and pgadmin at the same time. At this moment pgadmin works 100% only with http.)
PostgreSQL (work completed 100%)
Let’s Encrypt (work completed 0%)
In the first case I have to produce a .war file with Spring Boot 2, in the second case a .jar file. With Spring Boot 2 I can’t use PEM but PKCS12.
I can produce .jar or .war with my IDE, this is not a problem.
I found this page:

I just don’t understand what I have to do. Maybe I have to register on the traefik site to get the .json file but it looks like a paid site.
The only thing I know is that I have to write my domain name somewhere.
Is Traefik a free service?
Does Traefik work thanks to a physical server other than mine or is it independent from info.traefik.io?
I would prefer a service of this type inside a container:

(beautiful this guide but without Docker and does not solve the problem of restarting the .jar file)

I would prefer an approach like that of Emad Heydari Beni (read link above) but inside a container and find a solution to avoid restarting the application manually every 90 days or every time I change the .jar file.
Doing some research I often find nginx.
Should I do it this way or am I off track?

version: '3.9'

services:
  postgresql-postgis:
    ...

  pgadmin:
    ...

  java:
    ...

volumes:
  data-postgresql:

networks:
  eb:

entryPoints:
  web:
    address: ":80"

  websecure:
    address: ":443"

certificatesResolvers:
  myresolver:
    acme:
      email: your-email@example.com
      storage: acme.json
      httpChallenge:
        entryPoint: web

@rimelek, you said you don’t know Spring Boot. Do you know Certbot?

Can you help me add Certbot on Docker?
I could create a shared volume with the java service and insert the volume link in the Spring Boot .properties file. If you read the guide I reported and this message from me I am sure that you can help me even if you do not know Spring Boot.
If you decide to help me you will find me here.

Which certificate? Are you talking about the certificate generated by Let’s Encrypt? (If you are, solution (can be): revere proxy)

Why? Is it because you need to use the same port? (if it is, solution: reverse proxy)

I quote from the issue:

There is no need to write it, this file should be generated automatically by traefik, cf. https://docs.traefik.io/toml/#acme-lets-encrypt-configuration.

Then I quote the second part of the same post from the issue you linked as a beginning to help you how to search:

You just need to set up the appropriate configuration, either manually listing your domains under acme.domains , or using onHostRule so that they’re grabbed directly from your frontend rules.

Despite this issue is from 2016 and some configuration has changed, it contains some keywords.

  • You search for “acme.domains” in the traefik documentation and you won’t find anything.

  • Then you search for “acme.domain” on Google, and this is the first result:
    Let's Encrypt | Traefik | v1.7

  • Now you know it worked this way in an older version, so you check how the current configuration looks like.

  • You can find [acme] at the beginning of the old config file and you have “acme” in your yaml.

  • There are common parameters like “storage” and “email”.

  • You can find “acme.domains” in the old docs, but there is no “domains” at all in the new docs.

  • You notice that the syntax is different oin the old docs only, because the new docs has multiple suppoted syntax. TOML is just one of them and YAML is the default.

This is the point where you can start to be confused. So where is the domain definition?
Let’s go back to the documentation: Traefik Let's Encrypt Documentation - Traefik

You notice the navigation bar on the right side. The second item is “Domain Definition”, but you don’t just read that section, because you want to understand the whole concept.

The first section tells you that you can use “static configuration” and “dynamic configuration”.
Start with the first link, the static configuration, which tells you, you have three ways to configure traefik.

Now you can search for “domain”. (Good practice not to use plural forms so you can find “domain” and “domains” too)

Arguments:

--entrypoints.<name>.http.tls.certresolver:
Default certificate resolver for the routers linked to the entry point.

--entrypoints.<name>.http.tls.domains:
Default TLS domains for the routers linked to the entry point.

--entrypoints.<name>.http.tls.domains[n].main:
Default subject name.

--entrypoints.<name>.http.tls.domains[n].sans:
Subject alternative names.

Variables:

TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_CERTRESOLVER:
Default certificate resolver for the routers linked to the entry point.

TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_DOMAINS:
Default TLS domains for the routers linked to the entry point.

TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_DOMAINS_n_MAIN:
Default subject name.

TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_TLS_DOMAINS_n_SANS:
Subject alternative names.

Let’s see dynamic configuration. I quote:

Traefik gets its dynamic configuration from providers: whether an orchestrator, a service registry, or a plain old configuration file.

Since this configuration is specific to your infrastructure choices, we invite you to refer to the dedicated section of this documentation.

Now you have new links to check containing very useful information, but mostly for a little more advanced users, so you probably don’t understand them and read the next note after the previously quoted paragraphs.

In the Quick Start example, the dynamic configuration comes from docker in the form of labels attached to your containers.

Finally you have the Docker Compose example you need (I quote them).

Start the Traefik reverse proxy:

version: '3'

services:
  reverse-proxy:
    # The official v2 Traefik docker image
    image: traefik:v2.7
    # Enables the web UI and tells Traefik to listen to docker
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "80:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock

Start a service which defines its domain name:

# ...
  whoami:
    # A container that exposes an API to show its IP address
    image: traefik/whoami
    labels:
      - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"

But wait, we are not done yet… This domain in the example is not even a public domain. How could Let’s Encrypt give you a certificate for that? now you remember you had an other section, you have not read yet in Let’s Encrypt-related part of the docs: “Domain definition

I quote again:

  • If the router has a tls.domains option set, then the certificate resolver uses the main (and optionally sans) option of tls.domains to know the domain names for this router.
  • If no tls.domains option is set, then the certificate resolver uses the router’s rule, by checking the Host() matchers. Please note that multiple Host() matchers can be used) for specifying multiple domain names for this router.

So that Host() is indeed what you need, even though the example did not contain a valid, public domain because that was not about certificates. But what about tls.domains ? The only thing you need to do is to click on the link, which is red here, but it was a more noticable link in the official docs.

Now you also find something familiar. “domain”

## Dynamic configuration
http:
  routers:
    routerbar:
      rule: "Host(`snitest.com`) && Path(`/bar`)"
      tls:
        certResolver: "bar"
        domains:
          - main: "snitest.com"
            sans:
              - "*.snitest.com"

One thing is still not clear. Where should we save these configurations? It was probably mentioned somewhere, but we missed it for some reason or just did not understand. This is when you use the “magic word”, which is “reference” in the official docs or “traefik configuration reference” on Google:

Now you can recognize “entryPoint” this is probably the same “traefik.yml” which you read about earlier.

Please, use the navigation bar on the left side to check the other references as well.

If you found the info link, you can clikc on the “pricing” which is always one of the first link you should check when you don’t know if something is free, a servvice, or a downloadable app. Here you can find the open source (free) version.

Still don’t see the relation between the jar and the TLS provided by Let’s Encrypt and Traefik (reverse proxy).

I can’t imagine where you got this idea from. But as I explained above, no… I understand, because you told us multiple times, that you are not an engineer. Maybe I also mentioned somewhere that you mentioned it… It doesn’t matter. Read and interpret the documentation or find a colleauge who does it for you, or you will not get less answer here, since nobody wants to read the documentation for you, when you. When you read the documentation, do not read just one page, spend hours or even days on interpreting, understanding the docs, until you find, where you need to put a parameter.

Since I don’t use traefik (although I have been planning it for a long time, but I still use nginx proxy with a self-written automation), you just wittnessed step by step, how I learn. I did it, because it was relevant for me, and I decided I wanted to do it now instead of doing what I actually planned to, but I can’t do it every time, so you may notice that you don’t always get answer or you get it days later.

Just listen to my guide. That one you linked does not say anything about reverse proxies. If you want to create certificates directly for a spring boot aplication, you may need some “hack” to convert certificates, I was writing abut reverse proxies from the beginning. In that case, you don’t need to configure Spring Boot, only the reverse proxy which accepts HTTPS requests forwards the requests over HTTP. This is TLS termination.

Now there can be some situation when the application itself needs to communicate over HTTPS and if it gets a request on an HTTP port, it rejects it or some links can work wrong, becuse it generates HTTP links for internal pages. If this is the case, then you can start to work on that specific issue, but I think I could use Google to find a solution for that too by searching for “spring boot behind SSL termination proxy”:

I quote Enrico Morelli’s accepted answer:

solve the problem. The first step was to add

server.tomcat.use-relative-redirects=true

in the application.properties. With this directive, the proxy works fine.

In the end, I configure the apache/application to use AJP.

If it is not enough for you, you can search more as I did.

3 Likes

Thank you for your informative message.
All the guides I tried did not work because Oracle’s Cloud required a specific server configuration which I did not know about but now I have solved.
I think I need a reverse proxy but before going into this problem I have to verify that I am able to manipulate the certificate in Spring Boot and Ubuntu and above all to do it in the right way.
I have updated this guide to 2022 with the new commands.

Now, on Ubuntu 22.04 LTS I have a working letsencrypt certificate that updates itself every 90 days thanks to certbot.
I have the file at this address on Ubuntu:
/etc/letsencrypt/live/sitoweb.tk/keystore.p12
I copied the file to this location with Linux:
/home/ubuntu/project folder/volumes/data-java/keystore.p12
With Docker I created this volume:

    volumes:
      - type: bind
        source: ./volumes/data-java
        target: / appfolder

In Spring Boot I write this:

server.port = 8443
security.require-ssl = true
server.ssl.key-store: /appfolder/keystore.p12
server.ssl.key-store-password = *********
server.ssl.keyStoreType = PKCS12
server.ssl.keyAlias ​​= letsencrypt

It is not much but the site works, is reachable and the browser recognizes the certificate.
Obviously to do this I have to copy the certificate by hand every time and this is very inconvenient.
Also I can’t connect to pgAdmin with https.

If you know nginx or apache I can use these tools. Why do you tell me about traefik?
If you share your automation I can adapt it to my project.
It’s easier for you to help me because you can show me a tool you know well and it’s easier for me because I already have the script ready.
In your automation with nginx, so in your Docker Compose file, you also have the Certbot image?
Did you also fix the issue of updating certificates every 90 days?
Did you use the following image together with nginx?

https://hub.docker.com/r/certbot/certbot

I can do the same by simply adding this code to Dockerfile:

RUN openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out keystore.p12 -name letsencrypt -CAfile chain.pem -caname root
RUN sudo cp /etc/letsencrypt/live/website.tk/keystore.p12 /home/ubuntu/prodir/volumes/data-java

I found this page:

Now it’s very late but tomorrow I’ll try it.

I’ve read the guide but haven’t tested it yet. In my opinion something could be improved by setting a Dockerfile for nginx and certbot. I would just like to ask you a general question about the problem. If I manage to implement this code on my project the Let’s Encrypt certificate is used by nginx and not by Tomcat. In this scenario on my Spring Boot application what should I do? On Spring Boot can I upload the self-signed certificate or do I have to upload the same certificate present on the nginx proxy?

PS: I also found this image but without a practical example I won’t try it.
https://hub.docker.com/r/jonasal/nginx-certbot

I tried the guide but it doesn’t work. I stopped at this command:
docker compose run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ --dry-run -d example.org
In practice, the certifying body cannot read the link that certbot prepares.

Simulating a certificate request for **************.tk

Certbot failed to authenticate some domains (authenticator: webroot). The Certificate Authority reported these problems:
  Domain: **************.tk
  Type:   connection
  Detail: ###.###.###.###: Fetching http://**************.tk/.well-known/acme-challenge/M...w: Connection refused

Hint: The Certificate Authority failed to download the temporary challenge files created by Certbot. Ensure that the listed domains serve their content from the provided --webroot-path/-w and that files created there can be downloaded from the internet.

Some challenges have failed.
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.

Can you give me any advice on how to proceed to solve the problem?

root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione# dig +short ***.tk
root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione# dig +short www.***.tk
root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione#

From the commands above I should get the IP.

root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione# iptables -S
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -o br-9980cd171b11 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o br-9980cd171b11 -j DOCKER
-A FORWARD -i br-9980cd171b11 ! -o br-9980cd171b11 -j ACCEPT
-A FORWARD -i br-9980cd171b11 -o br-9980cd171b11 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -i br-9980cd171b11 ! -o br-9980cd171b11 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o br-9980cd171b11 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione#

I also typed this and retried:

In iptables, port 80 is open. In the cloud, the firewall is set up correctly.
root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione# iptables-translate -A INPUT -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
nft add rule ip filter INPUT tcp dport 80 ct state new counter accept
root@webserver:/home/ubuntu-22-04-lts/Nginx-Certbot-Produzione# sudo ufw allow 80
Skipping adding existing rule
Skipping adding existing rule (v6)

I also tried disabling the firewall and restarting the server.

sudo ufw disable

I was able to download the certificates but I didn’t understand how I did it.
In another guide I read that nginx needs a certificate to start and that validating a certificate requires nginx.
After many tests I have valid certificates on the server.
Now I would like to try to see an html page with Let’s Encrypt.
This is the code that’s not working:

version: '3'

services:
  webserver:
    image: nginx:latest
    ports:
      - 80:80
      - 443:443
    restart: always
    volumes:
      - ./volumes/data-nginx/conf/:/etc/nginx/conf.d/:ro
      - ./volumes/data-certbot/www:/var/www/certbot/:ro
      - ./volumes/data-certbot/conf/:/etc/nginx/ssl/:ro
      - ./volumes/data-certbot/html/:/usr/share/nginx/html:ro
  certbot:
    image: certbot/certbot:latest
    volumes:
      - ./volumes/data-certbot/www/:/var/www/certbot/:rw
      - ./volumes/data-certbot/conf/:/etc/letsencrypt/:rw
server {
    listen 443 default_server ssl http2;
    listen [::]:443 ssl http2;

    server_name *****.tk;

    ssl_certificate /etc/nginx/ssl/live/*****.tk/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/*********.tk/privkey.pem;
    
    root /usr/share/nginx/html;
	index index.html;
	location / {
        try_files $uri $uri/ =404;
	}
}

You said you know nginx so maybe you can help me.
Can you share your configuration?

I succeeded and everything works but not as I did, in fact I can’t replicate. I think the problem is the one exposed above that I had read in another guide.

How did you get around the problem that nginx requires a certificate to start?

This is an approach that does not convince me:

https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh

Thanks if you would like to share your script.

Here is a second approach:

https://www.poisel.info/posts/2021-09-10-letsencrypt-docker-compose/

This is very interesting but is maintained by one person, not a team.

Is there the same problem with Apache and Traefik?

First of all, I am sorry for disappearing, but I didn’t have much time recently and I still don’t have, so I try to give you some quick response.

I can’t help you with Spring Boot. I recommended Traefik instead of Nginx, because I want to switch to Traefik as well. This is why I was willing to go through the documentation for you. I used “nginx-proxy” started by Jason Wilder, but I created my own container to update the certificates. Then I relized it worked only because I already had a certificate, so I tried to fix it, but still not perfect, so I need to manually run it occasionally and once my cerrificates even expired because I forgot to update them. I didn’t care because I didn’t have so big audience and certainly didn’t have more time, so I want to switch to an other tool since then. Traefik is what I will probably use, but I have to finish other things before that. The benefit of a proxy server like that is that you don’t need to convert certificates.

Since you have asked about my configuration, this is it:

But as I said, this is what I want to replace with something else. Until that I update my certificates by running this command:

docker-compose run --rm certbot

Actually I have just run it, because it was about to expire again. So I don’t think that I could give you much more help regarding Docker and LetsEncrypt until I give my solution a higher priority. Since my images are old, I usually not advertise them. I need more time to work on these again.

1 Like

I have tried the following code. It works fine even without Let’s Encrypt.

version: "3.3"

services:

  traefik:
    image: "traefik:v2.7"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.myresolver.acme.email=postmaster@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "443:443"
      - "8080:8080"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    image: "traefik/whoami"
    container_name: "simple-service"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=myresolver"

I am not able to add certbot and my containers (java, pgadmin, postgresql) to the code.
If you want to answer this discussion, I will read it with great interest.

I am not able to answer, if you don’t share what your problem was when you tried to add your containers. whoami.example.com referrs to the IP address of the client. You need to have your own domains to add more containers or try to replace whoami with your springboot container before trying it with your own domain.

Keep in mind that letsencrypt has its limits, so don’t try it to many times, otherwise you will be blocked for a couple of hours, I don’t remember the exact duration.

Don’t even try it. You don’t need an other certbot when you have traefik with a built-in certbot client. If you want automatic renewal, the documentation of this is here:

1 Like