Skip to content

Commit

Permalink
Extract examples to external files
Browse files Browse the repository at this point in the history
Create two stack examples to show setup with multiple instances and related configuration.
  • Loading branch information
brablc authored May 29, 2024
1 parent fbd6b06 commit 3cdb20c
Showing 1 changed file with 43 additions and 85 deletions.
128 changes: 43 additions & 85 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,64 @@
# swarm-certbot-traefik

[Traefik Proxy](https://doc.traefik.io/traefik/v2.11/) community edition does not really support Let's Encrypt in a serious way for **docker swarm**. If you have multiple instances of traefik with [letsencrypt](https://doc.traefik.io/traefik/https/acme/) support enabled, they would all start to generate same certificates, overwriting `acme.json` storage and exhausting the limits very quickly.
[Traefik Proxy](https://doc.traefik.io/traefik/v2.11/) community edition does not really support Let's Encrypt in a serious way for **docker swarm**. If you have multiple instances of traefik with [letsencrypt](https://doc.traefik.io/traefik/https/acme/) support enabled, they would all start to generate same certificates, overwriting `acme.json` storage and exhausting the limits very quickly if things go wrong.

## Functionality
This project handles certificates using separate service (here called `certbot`), which exports file with certificates in format expected by `traefik`. It uses auto-discovery by searching for `certbot.domain` labels. Please check following examples, which show both `traefik` and `certbot` labels:

```yml
vector:
image: timberio/vector:0.36.1-alpine
networks:
- web
deploy:
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
- "traefik.http.routers.myproject-vector.entrypoints=websecure"
- "traefik.http.routers.myproject-vector.rule=Host(`sink.example.com`)"
- "traefik.http.services.myproject-vector.loadbalancer.server.port=8687"
- "certbot.domain=sink.example.com"
```
The provided example of `manager-stack.yml` provides following functionality:
## Example stacks
- Traefik dashboard protected by basic authentication - replace `REPLACE-ME-USE` with your password generated by `htpasswd -nb`.
- Dashboard itself is covered by created certificate - replace `traefik.example.com` with your dashboard domain.
- Generic redirect from 80 to 433 with exception of ACME challenge.
This project shows two examples of docker swarm stacks:
- [manager_single_stack.yml](examples/manager_single_stack.yml) - for single traefik instance,
- [manager_multiple_stack.yml](examples/manager_multiple_stack.yml) - with two main traefik instances (to support rolling update) running on the same host, in this case there is an `edge` traefic instance added which acts as a TCP proxy, in this setup, it is expected, that there is only one public IP without load balancing.

## Functionality

- Traefik dashboard is protected by basic authentication.
- Generic redirect from 80 to 433 is provided (with exception of ACME challenge).
- Dynamic loading of generated certificates - Treafik actually requires TLS to be in a dynamically loaded file.
- Challenge webroot gets automatically routed by traefik, but only gets opened when needed.
- Automatic discovery of domains, that require cerificate - use `certbot.domain` label - you can separate multiple domains with commas.
- Challenge requests get automatically routed by traefik - the server serving webroot directory is only started when needed.
- Renewal is performed once in a day, when date change is detected. You can force from outside using:
```sh
SERVICE_NAME=manager_certbot; docker exec --tty $(docker ps --format json | jq -r 'select(.Names | startswith("'$SERVICE_NAME'")) | .ID') ./renew.sh
```

> [!IMPORTANT]
> - When certbot fails to generate certificate it would store log into `/etc/letsencrypt/failed/$DOMAIN` - you have to delete it to get another attempt.
> - The setup expects that multiple copies of traefik run on the same node (manager) to handle rolling update of traefik service.
## Configuration

Use `certbot.domain` label - you can separate multiple domains with commas. No attempt to reuse traefik labels was done, there might be scenarios where traefik uses wildcards, but certbot needs to know the names:

```yml
version: '3.8'

services:

traefik:
image: traefik:v3.0
tty: true
networks:
- web
ports:
- "80:80"
- "443:443"
deploy:
replicas: 2
placement:
constraints:
- node.role == manager
update_config:
delay: 10s
labels:
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
- "traefik.http.middlewares.auth.basicauth.users=admin:REPLACE-ME-USE"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.routers.http-catchall.rule=HostRegexp(`.*`)"
- "traefik.http.routers.http-catchall.priority=1000"
- "traefik.http.services.dashboard.loadbalancer.server.port=8080"
- "certbot.domain=traefik.example.com"
command:
- "--log.level=DEBUG"
- "--api.dashboard=true"
- "--providers.swarm.endpoint=unix:///var/run/docker.sock"
- "--providers.swarm.exposedByDefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
- "--accesslog=true"
- "--accesslog.format=json"
- "--providers.file.filename=/etc/letsencrypt/traefik.yml"
- "--providers.file.watch=true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/letsencrypt:/etc/letsencrypt
- "traefik.http.routers.myproject-nginx.entrypoints=websecure"
- "traefik.http.routers.myproject-nginx.rule=HostRegexp(`(.*).example.com`)"
- "traefik.http.services.myproject-nginx.loadbalancer.server.port=80"
- "certbot.domain=admin.example.com,www.example.com"
```
certbot:
image: brablc/swarm-certbot-traefik
tty: true
networks:
- web
deploy:
replicas: 1
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
- "traefik.http.routers.certbot.entrypoints=web"
- "traefik.http.routers.certbot.rule=PathPrefix(`/.well-known/acme-challenge`)"
- "traefik.http.routers.certbot.priority=1010"
- "traefik.http.services.certbot.loadbalancer.server.port=80"
environment:
LOOP_SLEEP: 60s
CERTBOT_EMAIL: [email protected]
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/letsencrypt:/etc/letsencrypt
When using provided stacks please change following:
networks:
web:
external: true
```
- Replace `REPLACE-PASSWORD` with your password generated by `htpasswd -nb`.
- Dashboard itself is covered by created certificate - replace `traefik.example.com` with your dashboard domain.
- Replace `[email protected]` environment variable with your email for Let's Encrypt registration.

> [!IMPORTANT]
> - When certbot fails to generate certificate it would store log into `/etc/letsencrypt/failed/$DOMAIN` - you have to delete it to get another attempt.
> - The setup expects that multiple copies of traefik run on the same node (manager) to handle rolling update of traefik service.

## Credits

Expand Down

0 comments on commit 3cdb20c

Please sign in to comment.