Docker image which automatically renews mailserver/docker-mailserver certificates using traefik.
Set a label on mailserver container and define SSL configuration:
docker run -d --name mailserver --label mailserver-traefik.renew.domain=mail.localhost.com -e SSL_TYPE=manual -e SSL_KEY_PATH=/var/mail-state/manual-ssl/key -e SSL_CERT_PATH=/var/mail-state/manual-ssl/cert mailserver/docker-mailserver
Then start the traefik certificate renewer:
docker run -d --name cert-renewer-traefik -e DOMAINS=mail.localhost.com -v /var/run/docker.sock:/var/run/docker.sock -v "$PWD/acme.json:/tmp/traefik/acme.json:ro" registry.gitlab.com/youtous/docker-mailserver-traefik
services:
cert-renewer-traefik:
image: registry.gitlab.com/youtous/docker-mailserver-traefik:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme.json:/tmp/traefik/acme.json:ro # link traefik acme.json file (read-only)
environment:
- TRAEFIK_VERSION=2
- CERTS_SOURCE=file
- DOMAINS=mail.localhost.com
mailserver:
image: mailserver/docker-mailserver:latest
command: > # Since v10.3.0, certificates must be present at startup: https://github.com/docker-mailserver/docker-mailserver/blob/a4095a7d48082fe0dbfd2146cf9be4ed743736d1/target/scripts/startup/setup-stack.sh#L989
sh -c '
mkdir -p $$(dirname "$$SSL_KEY_PATH") &&
touch -a "$$SSL_KEY_PATH" &&
touch -a "$$SSL_CERT_PATH" &&
supervisord -c /etc/supervisor/supervisord.conf
'
hostname: mail
domainname: localhost.com
labels:
- "mailserver-traefik.renew.domain=mail.localhost.com" # tag the service
# traefik configuration using labels, not required
- "traefik.enable=true" # use traefik v2 for certificate generation
- "traefik.port=443" # dummy port, required generating certs with traefik
- "traefik.http.routers.mail.rule=Host(`mail.localhost.com`)"
- "traefik.http.routers.mail.entrypoints=websecure"
- "traefik.http.routers.mail.middlewares=redirect-webmail@docker" # redirect to webmail
- "traefik.http.middlewares.redirect-webmail.redirectregex.regex=.*"
- "traefik.http.middlewares.redirect-webmail.redirectregex.replacement=https://webmail.localhost.com/"
environment:
- SSL_TYPE=manual # enable SSL on the mailserver
- SSL_CERT_PATH=/var/mail-state/manual-ssl/cert
- SSL_KEY_PATH=/var/mail-state/manual-ssl/key
On the mailserver container : define the label and set SSL environment:
mailserver:
image: mailserver/docker-mailserver:latest
command: > # Since v10.3.0, certificates must be present at startup: https://github.com/docker-mailserver/docker-mailserver/blob/a4095a7d48082fe0dbfd2146cf9be4ed743736d1/target/scripts/startup/setup-stack.sh#L989
sh -c '
mkdir -p $$(dirname "$$SSL_KEY_PATH") &&
touch -a "$$SSL_KEY_PATH" &&
touch -a "$$SSL_CERT_PATH" &&
supervisord -c /etc/supervisor/supervisord.conf
'
labels:
- "mailserver-traefik.renew.domain=mail.localhost.com" # required label for hooking up the mailserver service
environment:
- SSL_TYPE=manual # required env values, enable SSL on the mailserver
- SSL_CERT_PATH=/var/mail-state/manual-ssl/cert
- SSL_KEY_PATH=/var/mail-state/manual-ssl/key
On the cert-renewer-traefik container, configure the following environment variables and map docker socket:
cert-renewer-traefik:
image: registry.gitlab.com/youtous/docker-mailserver-traefik:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock # required
- ./acme.json:/tmp/traefik/acme.json:ro # (only if you use file storage acme.json)
environment:
- CERTS_SOURCE=file
- DOMAINS=mail.localhost.com
Variable | Description | Type | Default value | Values |
---|---|---|---|---|
DOMAINS | domains to watch, separate domains using a comma | required | any tld separated by a coma. e.g.: mail.server.com,mail.localhost.com | |
CERTS_SOURCE | source used to retrieve certificates | optional | file | file, consul, etc, zookeeper, boltdb |
PUSH_PERIOD | by default, certificates will be pushed when a change is detected and every PUSH_PERIOD, allowing new containers to get existing certificates | optional | 15m | 0 = disabled (certificates are pushed only when updated) |
Other environment variables depends on the CERTS_SOURCE selected.
Mount acme.json
on /tmp/traefik/acme.json
read-only: -v "$PWD/acme.json:/tmp/traefik/acme.json:ro"
Specific environment variables:
Variable | Description | Type | Default value | Values |
---|---|---|---|---|
TRAEFIK_VERSION | traefik version | optional | 2 | 1 or 2 |
By default, traefik v2 is selected, change it depending of your traefik version.
Variable | Description | Type | Default value | Values |
---|---|---|---|---|
KV_ENDPOINTS | endpoints to connect | required | address:port , e.g.:consul:8500 etcd:2139 198.168.2.36:2139 |
|
KV_PREFIX | prefix used in KV store | optional | traefik | string |
KV_SUFFIX | suffix used in KV store | optional | /acme/account/object | string |
KV_USERNAME | KV store username | optional | string | |
KV_PASSWORD | KV store password | optional | string |
When using wildcard certificates, top domain is used for DOMAINS
and for the mailserver-traefik.renew.domain
label.
For instance, *.localhost.com
certificate used by the mailserver mail.localhost.com
will be configured as follows:
services:
cert-renewer-traefik:
image: registry.gitlab.com/youtous/docker-mailserver-traefik:latest
<...>
environment:
<...>
- DOMAINS=localhost.com
mailserver:
image: mailserver/docker-mailserver:latest
command: >
sh -c '
mkdir -p $$(dirname "$$SSL_KEY_PATH") &&
touch -a "$$SSL_KEY_PATH" &&
touch -a "$$SSL_CERT_PATH" &&
supervisord -c /etc/supervisor/supervisord.conf
'
labels:
- "mailserver-traefik.renew.domain=localhost.com" # use the top domain NOT mail.localhost.com
See swarm cluster.
docker-compose.yml
cert-renewer-traefik:
image: registry.gitlab.com/youtous/docker-mailserver-traefik:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock # required
environment:
- CERTS_SOURCE=consul
- KV_ENDPOINTS=consul-leader:8500
- DOMAINS=mail.localhost.com,mailserver2.localhost.com # using multi domains
mailserver:
image: mailserver/docker-mailserver:latest
command: >
sh -c '
mkdir -p $$(dirname "$$SSL_KEY_PATH") &&
touch -a "$$SSL_KEY_PATH" &&
touch -a "$$SSL_CERT_PATH" &&
supervisord -c /etc/supervisor/supervisord.conf
'
hostname: mail
domainname: localhost.com
labels:
- "mailserver-traefik.renew.domain=mail.localhost.com" # required, tag this service
# traefik v1 using labels
- "traefik.frontend.rule=Host:mail.localhost.com" # traefik ACME will handle creation of certificates for this domain
- "traefik.frontend.redirect.replacement=https://webmail.localhost.com/" # redirect access to smtp/imap domain to and other domain (e.g. webmail or autoconfig)
- "traefik.frontend.redirect.regex=.*"
- "traefik.enable=true"
- "traefik.port=443" # dummy port, not used
environment:
- SSL_TYPE=manual # required, do not change SSL_TYPE,SSL_CERT_PATH,SSL_KEY_PATH values
- SSL_CERT_PATH=/var/mail-state/manual-ssl/cert
- SSL_KEY_PATH=/var/mail-state/manual-ssl/key
When a new certificate is issued, cert-renewer-traefik will push it into the mailserver then restart dovecot and postfix services. The mailserver certificates will always be up to date :)
You can attach a traefik rule directly on the mailserver service in order to get certificates automatically requested by traefik or use traefik static configuration.
cert-renewer-traefik service does not require to be running in the mailserver stack, it can handles many mailserver and many domains. See: See also.
When ONE_DIR=1
is enabled on mailserver, state of the container will be consolidated across runs using a docker volume.
The cert-renewer-traefik detects when the mailserver has ONE_DIR
enabled and will copy the certificates.
That's why it's important not to change SSL_CERT_PATH=/var/mail-state/manual-ssl/cert
and SSL_KEY_PATH=/var/mail-state/manual-ssl/key
.
When ONE_DIR
is disabled, certificates will be lost at the end of the container's lifetime. Even if ONE_DIR
is disabled, you must set
SSL_CERT_PATH
and SSL_KEY_PATH
with the indicated values.
make tests
to run tests (docker-compose is required, swarm will be activated then disabled).MIT