Just because of so many technical details, this article might be hard to read at times. I was trying to make it as self explanatory as possible. I really hope that everyone, no matter how experienced in self hosting, TLS encryption, proxy implementation and Docker, all of you will be able to follow along, get all working and enjoy secure way of exposing multiple services on a single Docker host to the Internet. This is especially useful if you want to host multiple services on a single IP efficiently.
This instruction can be easily adopted to any Docker-compose instance. I will be deploying Traefik container using LMDS but you can copy/paste Traefik definition from below to your own compose file and continue following steps in this article.
Ok, there is no such thing as free domain name of your choice, but you can always get a free subdomain instead. Free subdomain will allow you to better understand how all the pieces work together before committing any money to a proper domain registration. Subdomains will be needed if you want to host multiple services from a single IP on the Internet, as this is what we are trying to achieve in this article.
You can already start thinking about your proper root domain name - finding good, easy to remember and available name for yourself on the Internet will take some time, start thinking about it. On the side note, I bought my greenfrognest.com domain for about 10 euro and each year it will cost me another 10 euro just to keep it. I am pointing that out just so you know what to expect. Of course some domains are more expensive than others, but you won't go wrong with .COM or .ORG
Below I will show you one example on how to get a free subdomain, so you can start somewhere and follow the article with no money spend while still trying things.
Go visit FreeDNS and create a free account there - I know this page look quite outdated but it works.
After you are logged in using your newly created account:
Done, your new subdomain is active and should point to your home IP address now. Test what you registered with nslookup
C:\Users\GreenFrog>nslookup gf.cloudwatch.net
Server: UnKnown
Address: 192.168.100.1
Non-authoritative answer:
Name: gf.cloudwatch.net
Address: 100.255.170.237
Great, we have a domain name pointing at the home IP - now what?
Well, now we will deploy "traefik" and "whoami" containers on Docker, secure them with TLS and make "whoami" container available under gf.cloudwatch.net
domain from the Internet. This will be a simple example so I can explain some basics. Later on we will expose another container, obtain a proper TLS certs for all of them etc, but first lets start simple.
Before we deploy anything, there are two very IMPORTANT things you have to do first.
Copy and paste below into docker-compose.yaml
file somewhere, then save it and run docker-compose up -d
Later on, I will explain what all of these lines mean in details, but for now we need to test if you can open your site from outside of your network. This is all what matters for now before going any further.
Adjust certificatesresolvers.letsencrypt.acme.email
and traefik.http.routers.whoami.rule
accordingly to reflect your details.
traefik:
image: traefik:latest
container_name: traefik
ports:
- 80:80
- 443:443
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.letsencrypt.acme.email=your.email@email.com # replace with your email
- --certificatesresolvers.letsencrypt.acme.storage=acme/acme.json
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.web.http.redirections.entrypoint.permanent=true
- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./volumes/traefik/acme:/acme
- ./volumes/traefik/logs:/logs
whoami:
image: "traefik/whoami"
container_name: "simple-service"
labels:
- traefik.enable=true
- traefik.http.routers.whoami.rule=Host(`gf.cloudwatch.net` || `gf`) # replace gf.cloudwatch.net with you domain name, leave /gf unchanged for now
- traefik.http.routers.whoami.entrypoints=websecure
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
What to expect now?
At this point you should be able to access your test site from the Internet (use cellular network on your phone to test it, disconnect it from home WiFi). Unfortunately your phone will suggest not to open this URL as certificate we received from Let's Encrypt is from a staging server and browsers will report it as unsafe - this is to be expected:
Tap on Advanced
Tap on warning (address bar)
Tap on Details
Tap Certificate information
As you see, I was able to access my site gf.cloudwatch.net
, but TLS certificate is not valid due to staging server being used to generate that certificate.
YES, since we know that our config works, website is reachable by domain name, we received a test certificate from Let's Encrypt, now we can swap Let's Encrypt staging to production, all by simply commenting out or deleting below line from the current config:
--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
`~/LMDS/volumes/traefik/acme/
folder. If staging cert is there new one wont be created for that subdomain.
Remember also that, if you ever change anything in docker-compose.yaml
file, you have to run docker-compose up -d --remove-orphans
in order for these changes to be effective.
New certificate from Let's Encrypt production server should look different - no warnings.
Well, the idea is quite simple. There are many so called CA - Certificate Authorities, Let's Encrypt is one of them. There are root level CAs and so called intermediate ones. Root level CA's work with browser developers like Chrome or Firefox making sure that each browser know who they are and if any certificate is signed by them, browsers know how to verify authenticity of that signature using so called chain of trust.
As a domain owners we can verify each subdomain with CA. This verification process can be done in three different ways - in our case it is fully automated and done by Traffick. If the verification process is success, CA will issue a certificate that we will use on your server, this certificate loosely say - "We - Certificate Authority confirm that this domain exist and we issued this certificate" (this is of course more sophisticated than that), but now any browser presented with this certificate can check its signature and verify if it came from commonly known CA. This way you wont be able to get a certificate for google.com as you do not own this domain and even if you would generate so-called self-signed certificate for google.com, browsers will know it did not came from a proper CA. This way we have a third party certification companies that browsers trust and they wont give you cert for someones else domain record.
Self signed certificate or received from staging server will always cause a warning. Your communication with the web server is still encrypted and secure, but browser is not able to verify if your website is what it claims to be as none of the CAs backing this up with their valid certificate. Anyone can generate a certificate and encrypt connection, what is commonly used for private communication between you and a server over SSH but for WEB and public access you need a proper cert issued by one of the CAs.
Above is more or less how it all works, if you would like to know more, I strongly recommend watching videos created by Computerphile. This channel will present you with more scientific approach on TLS and many other things behind all of that. These scientists operate at Bletchley Park in UK, this is the place where Enigma, was cracked during World War II, they know what they are doing and you should also.
We will modify our earlier example by adding few things:
http://192.168.100.120:8080
https://sr.cloudwatch.net
https://gf.cloudwatch.net
DEBUG
on Traefik - might help in case of something not working, logs to be found ~/LMDS/volumes/traefik/logs
For below to work, you have to adjust host IP address and use your own subdomain names. I went and registered another subdomain sr.cloudwatch.net
with FreeDNS the same way as before. In order to follow instruction below, go ahead and register another subdomain for yourself. I think you can register upto 4 of them there.
version: '3.9'
services:
traefik:
image: traefik:latest
container_name: traefik
ports:
- 80:80
- 443:443
- 8080:8080
command:
- --api=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --entrypoints.console.address=:8080
- --certificatesresolvers.letsencrypt.acme.email=greenfrog@email.com # replace with your email
- --certificatesresolvers.letsencrypt.acme.storage=acme/acme.json
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
- --entryPoints.web.http.redirections.entryPoint.to=websecure
- --entryPoints.web.http.redirections.entryPoint.scheme=https
- --entryPoints.web.http.redirections.entrypoint.permanent=true
# - --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --log.level=DEBUG
- --log.filePath=/logs/traefik.log
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./volumes/traefik/acme:/acme
- ./volumes/traefik/logs:/logs
labels:
- traefik.enable=true
- traefik.http.routers.thisproxylocal.rule=Host(`192.168.100.120`) # Change 192.168.100.120 to your Docker server IP.
- traefik.http.routers.thisproxylocal.entryPoints=console,websecure
- traefik.http.routers.thisproxylocal.service=api@internal
restart: unless-stopped
sonarr:
image: linuxserver/sonarr
container_name: sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=UTC
- UMASK_SET=022 #optional
volumes:
- ./volumes/sonarr/data:/config
- ./media/tvshows:/tv
- ./downloads:/downloads
labels:
- traefik.enable=true
- traefik.http.routers.sonarr.rule=Host(`sr.cloudwatch.net`) || Path(`/sonarr`) # replace sr.cloudwatch.net with your domain name, leave /sonarr unchanged for now
- traefik.http.routers.sonarr.entrypoints=websecure
- traefik.http.routers.sonarr.tls.certresolver=letsencrypt
restart: unless-stopped
whoami:
image: "traefik/whoami"
container_name: "simple-service"
labels:
- traefik.enable=true
- traefik.http.routers.whoami.rule=Host(`gf.cloudwatch.net`) || Path(`/gf`) # replace gf.cloudwatch.net with your domain name, leave /gf unchanged for now
- traefik.http.routers.whoami.entrypoints=websecure
- traefik.http.routers.whoami.tls.certresolver=letsencrypt
restart: unless-stopped
After having all adjusted to your needs, save the docker-compose.yml
file and run docker-compose up -d
. If all went well, you should be able to access both subdomains from your phone now.
This is the simplest way of using Traefik and having multiple containers reachable on the Internet by URL.
As you might noticed, none of the containers behind proxy have their ports exposed. Just because container internal IPs might change Traefik dynamically adopt to these changes and is also able to detect which port on the container is open and use it for communication. This way you do not have to worry about container internal configuration changes - if the labels are set correctly Traefik will pass the traffick to that container on the fly anyway.
Traefik initially might be harder to understand and configure than HAProxy or Nginx but it pays benefits with certain automation that is not available on other products of such kind.
You might not need a paid domain name to achieve simple tasks of passing traffick to few services behind proxy. For that you can use a free subdomain from FreeDNS and call it a day.
At some point you might want to redirect multiple services from Docker through Traefik to the Internet, therefore you will need multiple subdomains and it is always better to have them under a single root name. This might be a reason to consider buying a domain name for yourself. Also, you might want to register domain with Cloudflare in order to hide home IP address behind SSL socket. Cloudflare registration is only possible with root level domain. Even while Cloudflare services are free for home users, proper domain name has to be paid for in order to use Cloudflare services. Domain can be cheap and entire purchasing process is a simple task. Proper domain name will allow you to do much more than any free subdomain and having one might be good fun to try new things. Setting up anything public on the Internet will always require a domain name. You buy one root domain and then you can create hundreds of subdomains underneath if you like, you will only pay for a root domain of course.
It is not as important where you buy a domain name, all the offers out there are going to be similar in prices. You might consider Domain Provider that has a good reputation and offers other services like hosting or integration services so you have all in on place if needed.
Cloudflare is a company that provides number of services in the cloud. We are going to use only few of them but if you get comfortable with their offering, you could expand on that. When you are registering with Cloudflare you will be given so called NS (Name Server) records that would have to be provided to your current domain registrar (this is not mandatory if your current TLDR is supported by Traefik API that allows domain verification through DNS record). The main reason why we would use CloudFlare is to proxy our home IP through them so our real home IP is hidden. By default Cloudflare will give you some domain statistics and advanced security protection for your server. Cloudflare will also cache your server content in a form of CDN (Content Delivery Network) this way even slow Internet at home wont be having massive impact on your site performance.
Things to do if you already have a root level domain name (not a subdomain)
From now on, if you decide to go further with the instructions you will need a proper paid domain name, instruction below will not work with subdomains.
Creating an account with Cloudflare and registering a site with them is simple. What you might be confused with is all that Name Servers records etc. Let me explain.
When you purchased your domain name, your TLDR (Top Level Domain Registrar) created a record with your domain name in their database for "everyone" to see. Later on you will associate an IP address with your domain name and maybe create a few subdomains "A records" with different IPs pointing to a different servers etc. All these IPs will be also stored in TLDR database for every DNS in the world to check.
We want to manage our domain records with Cloudflare, they offer superior functionality comparing to regular TLDR, so we have to give current domain provider some way of being able to check what we do with this domain while we have it still with them and managing it using Cloudflare. We can migrate domains between TLDRs, and move it even to Cloudflare directly but we wont be doing that as it takes time and effort. We will only tell current TLDR to check Cloudflare NS records in case any request is received for anything ending up with *.yourdomain.com by your TLDR where you bought the domain initially. This is why we have to put Cloudflare NS "Name Servers" records in current domain provider database so TLDR knows where to look for details about our domain and IPs you will configure in Cloudflare. Each domain provider will have its own way of setting up custom NS records, but all of them will have this option somewhere in the menu.
In my case, while I was using FreeNom TLDR, this option was under [Management Tools] -> [Nameservers], while going there, I was able to choose [custom nameservers] and paste a Cloudflare NS records in these dedicated fields. Bear in mind that this change can take some time to propagate, so when you set that once - just wait, do not revert or change anything when applied, be patient, leave it all for next day. Also your NS from Cloudflare might be different than mine.
When you [Add Site] in Cloudflare you will be given Name Servers specific to your "Site", each Site added might have differed NS that needs to be given to TLDR that you purchased the domain name from. You can add multiple sites to Cloudflare from as many TLDRs as you want.
After you have your domain/site added to Cloudflare and NS record updated at your TLDR, it is time to create an "A Record" and point it to the IP address - IP where the Docker server is. I assume it will be your external home IP, the WAN IP given to you by your ISP.
Do not enable Proxy at this point, use "DNS only" as set by default while creating "A Record". Cloudflare will show you warning that IP address of your host will be exposed - this is ok. Test all without proxy, when all works then enable Proxied traffick on the tested record.
Now let's find values of CF_API_EMAIL and CF_API_KEY. Value of CF_API_EMAIL you know, it is an email address that you used to register with Cloudflare, you can also find it under your profile - if you are not sure. CF_API_KEY can be found under your [Profile] / [API Tokens] / [Global API Key]
Having these two values and an "A Record" created, you are ready to configure Traefik using dnsChallenge
for the domain verification.
Below is what you would see if you deploy Traefik container using LMDS deployment script - quick and easy option. You can also copy/paste entire Traefik declaration to any docker-compose file you develop and continue with the article.
My goal was to include as many and as little in this Traefik container declaration. Obviously you will have to adjust certain values to reflect your particular situation.
There are certain thing to pay attention to. You will see different symbol in comments section in each line inside the container declaration below, each symbol mean something different. Some lines require you to change the value in order to adopt container to your particular situation, where others are static. Pay attention to these symbols as they will tell you what to change and what can stay as is.
! = Important - something to watch up for.
^ = Mandatory to the config - not always mandatory according to traefik documentation.
* = Specific to your config - you have to change it and adopt to your situation.
? = Optional - us if you like.
All what you see below is just to configure Traefik container itself. None of the containers other then Traefik are going to be exposed by default and nothing else will be public on the Internet yet. I was trying to put a meaningful comment on each and every line to help you understand what all of this mean.
traefik:
container_name: traefik # ^ Container name
image: traefik:latest # ^ Pull latest image
ports: # ! Remember to open below ports on your router and forward them to the server where Traefik is running - otherwise requests from the Internet will be blocked by your router/firewall and nothing will reach Traefik.
- 80:80 # ^ Port exposed by Traefik container - related to aliases defined below under "command:" section
- 443:443 # ^ Port exposed by Traefik container - related to aliases defined below under "command:" section
- 8080:8080 # ^ Port exposed by Traefik container - related to aliases defined below under "command:" section
volumes:
- /var/run/docker.sock:/var/run/docker.sock # ^ Docker volume allowing Traefik to access Docker engine - this way Traefik knows what containers are running and what are their network settings.
- /root/DockerConfig/traefik/acme:/acme # ^ Docker volume for Traefik to store TLS certificate
- /root/DockerConfig/traefik/logs:/logs # ? Docker volume for Traefik to store logs
# - ./traefik.yml:/traefik.yml:ro # ? Traefik file provider definitions
# - ./config.yml:/config.yml:ro # ? External Traefik config file
environment: # ^ Environment variables below are specific to CloudFlare
# as they are my DNS provider which is also defined few lines below
# (--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare")
# Just because I use Cloudflare, variables below are what they are
# and I took them from here: (https://doc.traefik.io/traefik/https/acme/#providers).
# Find your DNS provider there and check what variables you should use instead.
- [email protected] # *
- CF_API_KEY=482551228411726548CB1F8B0A1443C9 # *
command:
# Globals
- --api=true # ^ Enabling Traefik API - In production API, Access should be secured
# by authentication and authorizations if exposed outside the network
# (https://doc.traefik.io/traefik/operations/api/#configuration)
- --global.checkNewVersion=true # ? Periodically check for update - (Default: true)
- --log.level=DEBUG # ? Log level - (Default: ERROR) other logging levels are DEBUG, PANIC, FATAL, ERROR, WARN, and INFO.
- --log.filePath=/logs/traefik.log # ? Log path - optional - related to volume /logs defined above
- --accessLog.filePath=/logs/access.log # ? Log path - optional - related to volume /logs defined above
- --accessLog.bufferingSize=100 # ? Log size - optional
# Docker
- --providers.docker=true # ^ Enable Docker provider - other providers (https://doc.traefik.io/traefik/providers/overview)
- --providers.docker.exposedbydefault=false # ^ Expose only containers that have labels setup (https://doc.traefik.io/traefik/providers/docker/#exposedbydefault)
- --providers.docker.endpoint=unix:///var/run/docker.sock # ^ Traefik requires access to the Docker socket in order to get its dynamic configuration from there - related to volume defined above
# Entrypoints
- --entryPoints.console.address=:8080 # ^ Defining port 8080 as "alias" called "console" - this port will be reachable from outside of Traefik container
- --entryPoints.web.address=:80 # ^ Defining port 80 as "alias" called "web" - this port will be reachable from outside of Traefik container
- --entrypoints.websecure.address=:443 # ^ Defining port 443 as "alias" called "websecure" - this port will be reachable from outside of Traefik container
# Redirection to SSL
- --entryPoints.web.http.redirections.entryPoint.to=websecure # ^ If trying to access service using port 80 redirect to 443
- --entryPoints.web.http.redirections.entryPoint.scheme=https # ^ If trying to access service using http redirect to https
- --entryPoints.web.http.redirections.entrypoint.permanent=true # ^ Apply a permanent redirection.
# LetsEncrypt (https://doc.traefik.io/traefik/user-guides/docker-compose/acme-tls/)
- --certificatesResolvers.letsencrypt.acme.email=your.email@gmail.com # * Put your email address instead - the same as above in "CF_API_EMAIL"
- --certificatesResolvers.letsencrypt.acme.storage=acme/acme.json # ^ Storage location where ACME certificates are going to be saved, this work with conjunction to volume definer above.
- --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory # ! Let's Encrypt Staging Server, comment out after testing - (https://doc.traefik.io/traefik/https/acme/#caserver) - highly recommend that you config works ok in staging before using Let's Encrypt live servers. In case of failures in this config you might be banned by Let's Encrypt for a while for abusing their live servers with faulty configuration requests.
- --certificatesResolvers.letsencrypt.acme.dnsChallenge=true # * DNS challenge, there are other ways of proving that you owned domain name defined below (https://doc.traefik.io/traefik/https/acme/#dnschallenge)
- --certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare # * Find your provider (https://doc.traefik.io/traefik/https/acme/#providers) and replace cloudflare with the one you use. This corresponds to "environment:" variables defined earlier.
#- --providers.file=true # ? Enable file provider if you need it.
labels: # Labels - finally section where you define if container should be exposed, under what domain name, IP, path etc. You will be creating labels for each container that you want Traefik to route packets to from the Internet.
- traefik.enable=true # ^ Enabling Traefik container to be exposed by itself
- traefik.http.middlewares.admin.basicauth.users=admin:{SHA}/jIOs1SoLMVGd6FMOlt5mF6Ega0= # * Generate SHA1 to protect access to the Web UI here: https://hostingcanada.org/htpasswd-generator - on this page I used: user/password = admin/greenfrog and got htpasswd: admin:{SHA}/jIOs1SoLMVGd6FMOlt5mF6Ega0= (https://doc.traefik.io/traefik/middlewares/basicauth/). You can reuse this line multiple times under different containers to protect access to them.
# Define route/router called "thisproxylocal"
- traefik.http.routers.thisproxylocal.rule=Host(`xxx.xxx.xxx.xxx`) # * Change xxx.xxx.xxx.xxx to your Docker server IP
- traefik.http.routers.thisproxylocal.entryPoints=console # ^ Traefik WebUI is by default exposed on port 8080 so we have to redirect all requests to that port by creating entryPoint equal to "console" - alias that we defined several lines above.
- traefik.http.routers.thisproxylocal.service=api@internal # ^ Enable WebUI service on this specific router.
- traefik.http.routers.thisproxylocal.middlewares=admin # ^ Enabling authentication on this specific router.
# Define route/router called "thisproxytls"
- traefik.http.services.thisproxytls.loadbalancer.server.port=8080 # ^ Define loadBalancer port for WebUI
- traefik.http.routers.thisproxytls.rule=Host(`your.domain.com`) # * Define URL that will be redirected to this container on port 8080 from https
- traefik.http.routers.thisproxytls.entrypoints=websecure # ^ Just because we defined redirection where any request from the Internet received on port 80 - http will be redirected to port 443 https we open websecure entrypoint as this is from where we will be receiving all the traffick anyway.
- traefik.http.routers.thisproxytls.service=api@internal # ^ Enable WebUI service on this specific router.
- traefik.http.routers.thisproxytls.middlewares=admin # ^ Enabling authentication on this specific router.
- traefik.http.routers.thisproxytls.tls.certresolver=letsencrypt # ^ Use Let's Encrypt resolver for TLS certification
restart: always
At this point you should be able to access Traefik dashboard using below links, browser will prompt you for user/password = admin/greenfrog:
http://Your-IP:8080 (from your LAN)
https://your.domain.com:8080 (from the Internet - use your phone)
So far we only configured Traefik container and nothing else, even if you have other containers declaration in your docker-compose file, none of them are going to be exposed by Traefik therefore access to them is not possible through Traefik proxy yet. Let's fix that.
In order for containers to be seen by Traefik and for them to be accessible from the Internet and locally we need to create for each container so called "labels" and an "A Record" in Cloudflare.
First let's create labels for Traefik to become aware about the other containers. Each container will have its own set of labels. This is not going to be any different from the example shown above in basic setup for tlschallenge
.
Lets take as an example Sonarr container:
sonarr:
image: linuxserver/sonarr
container_name: sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=UTC
- UMASK_SET=022 #optional
volumes:
- ./volumes/sonarr/data:/config
- ./media/tvshows:/tv
- ./downloads:/downloads
ports:
- 8989:8989
restart: unless-stopped
We will change above to what you see below, this will allow Traefik to proxy this container. Container declaration should exist in the same docker-compose file where Traefik declaration was placed.
sonarr:
image: linuxserver/sonarr
container_name: sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=UTC
- UMASK_SET=022 #optional
volumes:
- ./volumes/sonarr/data:/config
- ./media/tvshows:/tv
- ./downloads:/downloads
labels:
- traefik.enable=true
- traefik.http.routers.sonarr.rule=Host(`your.domain.com`) || Path(`/sonarr`) # replace your.domain.com
- traefik.http.routers.sonarr.entrypoints=websecure
- traefik.http.routers.sonarr.tls.certresolver=letsencrypt
restart: unless-stopped
After saving above changes run docker-compose up -d --remove-orphans
now your container should be accessible from the Internet and from local network:
https://your.domain.com
LAN-IP/sonarr
if you want to tunnel another container through Traefik just copy/paste labels:
section and replace XXX with something different that resemble your situation.
labels:
- traefik.enable=true
- traefik.http.routers.XXX.rule=Host(`XXX.XXX.com`) || Path(`/XXX) # replace your.domain.com
- traefik.http.routers.XXX.entrypoints=websecure
- traefik.http.routers.XXX.tls.certresolver=letsencrypt
restart: unless-stopped
I really hope you are successful in deploying Traefik on LMDS and poxing other containers through it.
Please consider supporting LMDS by using affiliate links below or donate to the project using link on the right at the top of the page.
With your support anything is possible