/docker-mailserver-traefik

🐳 ⚙️ Automatically renew docker-mailserver/docker-mailserver certificates using traefik.

Primary LanguageShellMIT LicenseMIT

youtous/docker-mailserver-traefik

pipeline status Docker image GitHub Repo stars Gitlab Repo Licence


Docker image which automatically renews mailserver/docker-mailserver certificates using traefik.

Features

  • Automatically push certificates to mailserver containers on container creation or on cert renewal
  • Tested on docker compose and docker swarm
  • Supports traefik v1 and v2
  • Handles all ACME storage strategies
  • Restarts dovecot and postfix after certificate update
  • Wiring using a single label on the mailserver!
  • Lightweight docker image

Installation

Using docker cli

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

Using docker-compose

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

Usage

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)
m/s/h - see man timeout )

Other environment variables depends on the CERTS_SOURCE selected.

Using file storage acme.json

  • 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.

Using a KV Store

  • KV Stores (consul, etcd, boltdb, zookeeper) share lot of common options, main configuration resides in:
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

Wildcard certificates

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 

Examples

Using file

See Using docker-compose

Usage in a swarm cluster

See swarm cluster.

Using a KV Store

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.

Using ONE_DIR

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.

See also

  • See test-stack for more examples. This testing environment simulates a complete stack : traefik + acme server acting like Let's Encrypt, have a look!
  • Configuration for multidomains
  • Configuration for multiservers
  • Use make tests to run tests (docker-compose is required, swarm will be activated then disabled).
  • tests - Tests directory contains useful resources listing different usages (multidomains, multiservers, etc).
  • traefik-certs-dumper - Used in this image for watching certificates

Licence

MIT