No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-06-04 12:25:26 +00:00
.gitea/workflows [skip ci] feat: boilerplate code 2026-05-31 22:18:37 +02:00
defaults feat: added domain alias setting 2026-06-04 00:34:57 +02:00
handlers feat: initial release 2026-06-01 00:50:44 +02:00
meta [skip ci] feat: boilerplate code 2026-05-31 22:18:37 +02:00
tasks fix: fixed minor permission problem 2026-06-04 14:24:24 +02:00
templates fix: fixed dns hetzner issues 2026-06-02 11:38:52 +02:00
.gitignore [skip ci] fix: added claude.md to gitignore 2026-06-03 17:25:10 +02:00
CHANGELOG.md fix: fixed minor permission problem 2026-06-04 14:24:24 +02:00
LICENSE [skip ci] feat: boilerplate code 2026-05-31 22:18:37 +02:00
README.md Prettified Code! 2026-06-04 12:25:26 +00:00

mailcow

Deploys a mailcow-dockerized instance — a full email server stack — via Docker Compose. The role clones the mailcow repository, renders configuration from your inventory variables, and manages the Compose project lifecycle.

Requirements

  • Docker >= 24
  • community.docker Ansible collection >= 5.2.0
  • Target host: Debian/Ubuntu

Variables

Variable Default Description
mailcow_instance mailcowdockerized Unique instance name. Used as Compose project name and container name prefix.
mailcow_docker_path /docker/mailcow Host path for the cloned mailcow-dockerized repository and compose files.
mailcow_data_path /data/mailcow Host path for persistent data (mail, database, Redis, etc.).
mailcow_version 2026-05c mailcow-dockerized git tag to deploy. Pin to a specific release tag in production.
mailcow_hostname ~ Public hostname mailcow runs on (FQDN, no protocol prefix). Required.
mailcow_tz Europe/Berlin Timezone for the mailcow stack. Use a TZ identifier from the tz database.
mailcow_db_name mailcow Database name. Changing after initial deploy requires manual migration.
mailcow_db_user mailcow Database user. Changing after initial deploy requires manual migration.
mailcow_db_pass ~ Database user password. Required. See Secret Generation.
mailcow_db_root_pass ~ Database root password. Required. See Secret Generation.
mailcow_redis_pass ~ Redis password. Required. See Secret Generation.
mailcow_sogo_url_encryption_key ~ SOGo URL encryption key. Required. Exactly 16 alphanumeric characters (A-Za-z0-9). See Secret Generation.
mailcow_http_port 80 HTTP host port exposed by nginx.
mailcow_https_port 443 HTTPS host port exposed by nginx.
mailcow_http_redirect true When true, redirects plain HTTP requests to HTTPS.
mailcow_nginx_use_proxy_protocol false Enable PROXY protocol on the nginx listener (for upstream load balancers).
mailcow_trusted_proxies "" Comma-separated list of trusted proxy IPs when PROXY protocol is enabled.
mailcow_additional_server_names [] Additional hostnames the mailcow UI responds to (also added to the TLS certificate SAN).
mailcow_enable_ipv6 ~ Enable IPv6 for the mailcow stack. Defaults to auto-detecting host IPv6 connectivity when unset.
mailcow_skip_letsencrypt false When true, skips Let's Encrypt certificate issuance.
mailcow_acme_account_email ~ Email address for the ACME CA account. Required when mailcow_skip_letsencrypt is false.
mailcow_acme_dns_challenge false When true, uses DNS-01 challenge via Hetzner DNS API instead of HTTP-01.
mailcow_hetzner_dns_api_token ~ Hetzner DNS API token. Required when mailcow_acme_dns_challenge is true.
mailcow_traefik false When true, attaches nginx to the traefik_web network and adds Traefik routing labels. See Traefik.
mailcow_traefik_cert_path /docker/traefik/cert Path to the Traefik TLS certificates directory (mounted read-only by certdumper).
mailcow_traefik_certresolver letsencrypt Traefik certificate resolver name used for mailcow routes.
mailcow_traefik_acme_file acme.json ACME JSON filename inside mailcow_traefik_cert_path.
mailcow_uninstall false Stops and removes all containers and the compose directory.
mailcow_purge false Also removes mailcow_data_path. Requires mailcow_uninstall: true. Irreversible.
mailcow_update false When true, runs mailcow's update.sh instead of a fresh deploy.
mailcow_update_skip_ping_check false When true, passes --skip-ping-check to update.sh (useful when outbound ICMP is blocked).

Secret Generation

All four secrets must be set before the first deploy:

LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28  # → mailcow_db_pass
LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28  # → mailcow_db_root_pass
LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 28  # → mailcow_redis_pass
LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 | head -c 16  # → mailcow_sogo_url_encryption_key

Copy the output values into your inventory:

mailcow_db_pass: "..."
mailcow_db_root_pass: "..."
mailcow_redis_pass: "..."
mailcow_sogo_url_encryption_key: "..." # exactly 16 characters

Usage

Minimal setup:

mailcow_hostname: "mail.example.com"
mailcow_acme_account_email: "admin@example.com"
mailcow_db_pass: "{{ vault_mailcow_db_pass }}"
mailcow_db_root_pass: "{{ vault_mailcow_db_root_pass }}"
mailcow_redis_pass: "{{ vault_mailcow_redis_pass }}"
mailcow_sogo_url_encryption_key: "{{ vault_mailcow_sogo_key }}"

With additional UI hostnames:

mailcow_hostname: "mail.example.com"
mailcow_acme_account_email: "admin@example.com"
mailcow_additional_server_names:
  - "webmail.example.com"
  - "autodiscover.example.com"
mailcow_db_pass: "{{ vault_mailcow_db_pass }}"
mailcow_db_root_pass: "{{ vault_mailcow_db_root_pass }}"
mailcow_redis_pass: "{{ vault_mailcow_redis_pass }}"
mailcow_sogo_url_encryption_key: "{{ vault_mailcow_sogo_key }}"

Note: Running multiple mailcow instances on the same host is not supported. mailcow binds host ports for SMTP (25), IMAP (143/993), POP3 (110/995), and other mail protocols that cannot be shared between instances.

Traefik

When mailcow_traefik: true, the role:

  • Attaches the nginx container to the external traefik_web Docker network.
  • Adds Traefik HTTP router labels for mailcow_hostname and all mailcow_additional_server_names, plus autodiscover.* and autoconfig.* subdomains of each registered mail domain.
  • Deploys a certdumper sidecar (kereis/traefik-certs-dumper) that watches mailcow_traefik_cert_path and syncs TLS certs into mailcow's SSL directory, then signals Postfix and Dovecot to reload.
mailcow_traefik: true
mailcow_traefik_cert_path: /docker/traefik/cert # directory containing acme.json
mailcow_traefik_certresolver: letsencrypt
mailcow_traefik_acme_file: acme.json
mailcow_skip_letsencrypt: true # Traefik handles TLS; tell mailcow not to run acme-mailcow

Update

To update the mailcow stack to the latest upstream release:

mailcow_update: true

This runs mailcow's own update.sh --force, which pulls the latest Docker images and restarts the stack. The role skips the initial git clone in this mode since update.sh manages the repository. Set mailcow_update back to false after the update to return to normal deploy behaviour.

If outbound ICMP is blocked on the host:

mailcow_update: true
mailcow_update_skip_ping_check: true

Uninstall

To stop and remove all containers and the compose directory:

mailcow_uninstall: true

To also wipe all persistent data including mail, the database, and Redis (irreversible):

mailcow_uninstall: true
mailcow_purge: true

Notes

  • All secret variables (mailcow_db_pass, mailcow_db_root_pass, mailcow_redis_pass, mailcow_sogo_url_encryption_key, mailcow_hetzner_dns_api_token) should be stored in Ansible Vault.
  • mailcow_sogo_url_encryption_key must be exactly 16 characters and contain only A-Za-z0-9. The role asserts this before deploying.
  • Data directories are pre-created with the ownership expected by each container (e.g. vmail 5000:5000, MariaDB 999:999, Redis 999:1000). This avoids permission errors on first start.
  • The role forces iptables mode on the netfilter-mailcow container via compose.override.yml. This is required on Ubuntu hosts where Docker's nftables integration causes mail rejection.
  • IPv6 is auto-detected by probing 2001:4860:4860::8888. When connectivity is confirmed, the Docker daemon is configured for IPv6 (daemon.json) before the stack starts. Override with mailcow_enable_ipv6: true/false to skip detection.
  • A snake-oil TLS certificate is generated on first deploy and copied into mailcow's SSL directory. It is never overwritten once a real certificate exists.

TODO

  • check traefik installation
  • check update