--- title: Mailcow Dockerized Install and Config description: published: true date: 2026-02-25T21:05:48.256Z tags: editor: markdown dateCreated: 2026-02-25T21:05:38.864Z --- # MailCow — Installation & Configuration **Host:** docker4 (192.168.5.16) **Hostname:** hermes.netgrimoire.com **Admin URL:** https://mail.netgrimoire.com **Version:** 2025-10a (update 2026-01 available as of documentation date) **Installed:** /opt/mailcow-dockerized **Timezone:** America/Chicago **Architecture:** x86_64 **CPU:** 16 cores **RAM:** 30.63 GB **Disk:** /dev/nvme0n1p2 — 442G / 502G used (93% — monitor this) --- ## Overview Mailcow runs as a Docker stack on docker4, attached to the `netgrimoire` overlay network. All containers use `restart: unless-stopped` via a compose override. Outbound mail routes through MXRoute via sender-dependent transports. Inbound mail arrives from MXRoute which acts as the public-facing inbound gateway (solving residential AT&T IP filtering issues with banks). See [MXRoute Master Configuration](./mxroute-master) for full inbound/outbound/DNS detail per domain. --- ## Installation Paths | Path | Purpose | |------|---------| | `/opt/mailcow-dockerized/` | Mailcow root | | `/opt/mailcow-dockerized/mailcow.conf` | Primary configuration file | | `/opt/mailcow-dockerized/docker-compose.yml` | Base compose (do not edit) | | `/opt/mailcow-dockerized/docker-compose.override.yml` | Local overrides — network and restart policy | | `/opt/mailcow-dockerized/data/conf/postfix/extra.cf` | Persistent Postfix overrides | | `/opt/mailcow-dockerized/data/conf/postfix/main.cf` | Postfix base config (managed by Mailcow) | | `/opt/mailcow-dockerized/data/conf/rspamd/` | Rspamd configuration | | `/opt/mailcow-dockerized/data/assets/ssl/` | TLS certificates | --- ## mailcow.conf — Key Settings ```ini MAILCOW_HOSTNAME=hermes.netgrimoire.com MAILCOW_PASS_SCHEME=BLF-CRYPT # Database DBNAME=mailcow DBUSER=mailcow DBPASS=mg7Z8W9UsPlOh0S6vF7TmmPb6n1s DBROOT=JdymsZFFACHkDcOdziQ53QruCTG2 # Redis REDISPASS=6AduWQsmBYGMKfOi1CNEGQfTE3RH # Ports — HTTPS runs on 3443, proxied through Caddy HTTP_PORT=80 HTTP_BIND= HTTPS_PORT=3443 HTTPS_BIND= HTTP_REDIRECT=n # Mail ports (standard) SMTP_PORT=25 SMTPS_PORT=465 SUBMISSION_PORT=587 IMAP_PORT=143 IMAPS_PORT=993 POP_PORT=110 POPS_PORT=995 SIEVE_PORT=4190 # Internal ports (localhost only) DOVEADM_PORT=127.0.0.1:19991 SQL_PORT=127.0.0.1:13306 REDIS_PORT=127.0.0.1:7654 # TLS cert coverage ADDITIONAL_SAN=smtp.*,imap.* AUTODISCOVER_SAN=y # ACME / Let's Encrypt SKIP_LETS_ENCRYPT=n SKIP_IP_CHECK=y SKIP_HTTP_VERIFICATION=y # Services — all enabled SKIP_CLAMD=n SKIP_OLEFY=n SKIP_SOGO=n SKIP_FTS=n # FTS (Flatcurve/Xapian) FTS_HEAP=128 FTS_PROCS=1 # Watchdog USE_WATCHDOG=y WATCHDOG_NOTIFY_START=y WATCHDOG_NOTIFY_BAN=n WATCHDOG_EXTERNAL_CHECKS=n # Networking IPV4_NETWORK=172.22.1 IPV6_NETWORK=fd4d:6169:6c63:6f77::/64 ENABLE_IPV6=false # Misc MAILDIR_GC_TIME=7200 MAILDIR_SUB=Maildir SOGO_EXPIRE_SESSION=480 SOGO_URL_ENCRYPTION_KEY=ojmPfhnM4MYMsA2f ACL_ANYONE=disallow ALLOW_ADMIN_EMAIL_LOGIN=n DOCKER_COMPOSE_VERSION=native COMPOSE_PROJECT_NAME=mailcow LOG_LINES=9999 ``` --- ## docker-compose.override.yml All services are attached to the external `netgrimoire` overlay network and set to `restart: unless-stopped`. The override does not change any image versions or environment variables — it only adds network membership and restart policy. ```yaml services: unbound-mailcow: networks: netgrimoire: restart: unless-stopped mysql-mailcow: networks: - netgrimoire restart: unless-stopped redis-mailcow: networks: - netgrimoire restart: unless-stopped clamd-mailcow: networks: - netgrimoire restart: unless-stopped rspamd-mailcow: networks: - netgrimoire restart: unless-stopped php-fpm-mailcow: networks: - netgrimoire restart: unless-stopped sogo-mailcow: networks: - netgrimoire restart: unless-stopped dovecot-mailcow: networks: - netgrimoire restart: unless-stopped postfix-mailcow: networks: - netgrimoire restart: unless-stopped postfix-tlspol-mailcow: networks: - netgrimoire restart: unless-stopped memcached-mailcow: restart: unless-stopped nginx-mailcow: networks: - netgrimoire restart: unless-stopped acme-mailcow: networks: - netgrimoire restart: unless-stopped watchdog-mailcow: networks: - netgrimoire restart: unless-stopped dockerapi-mailcow: networks: - netgrimoire restart: unless-stopped olefy-mailcow: networks: - netgrimoire restart: unless-stopped ofelia-mailcow: networks: - netgrimoire restart: unless-stopped networks: netgrimoire: external: true driver: overlay ``` --- ## Container Image Versions From `docker-compose.yml` (base file — version 2025-10a): | Service | Image | |---------|-------| | unbound-mailcow | ghcr.io/mailcow/unbound:1.24 | | mysql-mailcow | mariadb:10.11 | | redis-mailcow | redis:7.4.6-alpine | | clamd-mailcow | ghcr.io/mailcow/clamd:1.71 | | rspamd-mailcow | ghcr.io/mailcow/rspamd:2.4 | | php-fpm-mailcow | ghcr.io/mailcow/phpfpm:1.94 | | sogo-mailcow | ghcr.io/mailcow/sogo:1.136 | | dovecot-mailcow | ghcr.io/mailcow/dovecot:2.35 | | postfix-mailcow | ghcr.io/mailcow/postfix:1.81 | | postfix-tlspol-mailcow | ghcr.io/mailcow/postfix-tlspol:1.0 | | memcached-mailcow | memcached:alpine | | nginx-mailcow | ghcr.io/mailcow/nginx:1.05 | | acme-mailcow | ghcr.io/mailcow/acme:1.94 | | netfilter-mailcow | ghcr.io/mailcow/netfilter:1.63 | | watchdog-mailcow | ghcr.io/mailcow/watchdog:2.09 | | dockerapi-mailcow | ghcr.io/mailcow/dockerapi:2.11 | | olefy-mailcow | ghcr.io/mailcow/olefy:1.15 | | ofelia-mailcow | mcuadros/ofelia:latest | --- ## Postfix Configuration ### extra.cf ``` myhostname = hermes.netgrimoire.com ``` > The MXRoute trusted network entries should also be here. Current extra.cf only contains myhostname — confirm mynetworks is set correctly or add the MXRoute IP ranges if not already present via the UI. ### Key Postfix Settings (from running config) ``` mynetworks = 127.0.0.0/8 172.22.1.0/24 10.0.1.0/24 [::1]/128 [fd4d:6169:6c63:6f77::]/64 [fe80::]/64 message_size_limit = 104857600 # 100MB mailbox_size_limit = 0 # unlimited bounce_queue_lifetime = 1d maximal_queue_lifetime = 5d delay_warning_time = 4h postscreen_dnsbl_threshold = 6 postscreen_dnsbl_action = enforce postscreen_greet_action = enforce smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination disable_vrfy_command = yes broken_sasl_auth_clients = yes ``` --- ## Domains 10 domains configured. All active. | Domain | Mailboxes | Sender-Dependent Transport | Created | |--------|-----------|---------------------------|---------| | bamalady.com | 0 / 10 | *(not confirmed)* | — | | bill740.com | 1 / 10 | *(not confirmed)* | — | | florosafd.org | 4 / 10 | ID 4: heracles.mxrouting.net:587 (relay@florosafd.org) | 2025-11-21 | | gnarlypandaproductions.com | 2 / 10 | ID 5: heracles.mxrouting.net:587 (relay@gnarlypandaproductions.com) | 2025-11-21 | | netgrimoire.com | 2 / 10 | ID 2: heracles.mxrouting.net:587 (relay@netgrimoire.com) | 2025-11-21 | | nucking-futz.net | 0 / 10 | *(not confirmed)* | — | | pncfishandmore.com | 4 / 10 | ID 6: heracles.mxrouting.net:587 (relay@pncfishandmore.com) | — | | pncharris.com | 4 / 10 | ID 3: heracles.mxrouting.net:587 (passer@pncharris.com) | 2025-11-21 | | pncharrisenterprises.com | 2 / 10 | *(not confirmed from screenshots)* | — | | wasted-bandwidth.net | 1 / 10 | ID 1: heracles.mxrouting.net:587 (relay@wasted-bandwidth.net) | — | > MXRoute relay hostname is `heracles.mxrouting.net:587` — note this differs from the generic `smtp.mxroute.com` placeholder used in setup docs. Always use `heracles.mxrouting.net:587` for this account. --- ## Mailboxes 19 active mailboxes across all domains: | Mailbox | Messages | Domain | |---------|----------|--------| | bill@bill740.com | 1 | bill740.com | | chieflee@florosafd.org | 2124 | florosafd.org | | cindy@pncfishandmore.com | 1109 | pncfishandmore.com | | cindy@pncharris.com | 33797 | pncharris.com | | cindy@pncharrisenterprises.com | 819 | pncharrisenterprises.com | | dads_attic@pncharris.com | 0 | pncharris.com | | jim.harris@florosafd.org | 8 | florosafd.org | | kyle@gnarlypandaproductions.com | 486 | gnarlypandaproductions.com | | kyle@pncfishandmore.com | 110 | pncfishandmore.com | | kyle@pncharris.com | 31182 | pncharris.com | | phil@florosafd.org | 5 | florosafd.org | | phil@gnarlypandaproductions.com | 5 | gnarlypandaproductions.com | | phil@netgrimoire.com | 1 | netgrimoire.com | | phil@pncfishandmore.com | 10 | pncfishandmore.com | | phil@pncharris.com | 3210 | pncharris.com | | phil@pncharrisenterprises.com | 1 | pncharrisenterprises.com | | times@florosafd.org | 191 | florosafd.org | | traveler@netgrimoire.com | 3 | netgrimoire.com | | traveler@wasted-bandwidth.net | 138 | wasted-bandwidth.net | --- ## Aliases | ID | Alias | Target Domain | Internal | |----|-------|---------------|---------| | 7 | cindy@bamalady.com | bamalady.com | No | --- ## Sender-Dependent Transports All outbound relay routes through `heracles.mxrouting.net:587`. This is your MXRoute server hostname — use this exact value when adding new transports. | ID | Host | Username | Password | |----|------|----------|----------| | 1 | heracles.mxrouting.net:587 | relay@wasted-bandwidth.net | dZ4yLYznVvgSJtqWZJFA | | 2 | heracles.mxrouting.net:587 | relay@netgrimoire.com | TVGCnJp9SxRbWU8EhkMw | | 3 | heracles.mxrouting.net:587 | passer@pncharris.com | bBJtPhrGkHvvhxhukkae | | 4 | heracles.mxrouting.net:587 | relay@florosafd.org | 2Fe8XMyaeh6Z5dvdHYdq | | 5 | heracles.mxrouting.net:587 | relay@gnarlypandaproductions.com | vG5ZsUQhRWD2UyzLPsqA | | 6 | heracles.mxrouting.net:587 | relay@pncfishandmore.com | *(confirm from MXRoute panel)* | --- ## DKIM Keys Two DKIM selectors are configured per domain — one for Mailcow (selector: `dkim`) and one added separately for MXRoute outbound signing. The Mailcow-managed keys use selector `dkim._domainkey`. ### pncharris.com ``` v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqhgQV7r+KKQwJceWenZ3FNq8AsllgW6cIm/0jpsLT62vF1yy0nh2MdhjYgQAX2MK9HHYzNZcCB3+OPpqBbXeNbSDckxB/dC+z/vboMHrJmYonfaSYshZjSR80V/a2Yoq+hiXQ9eBcuOggENtMm4XvEsl/vOWLBMfasqe+X11gzQBeRv1tTaXJB0C4i7tAcfi0O/AxH8QFTr2099+k2iepn8J15ukk1zu4zemBJj4Z3uFTNnBP8YpgKbYoUDyMVIKIxGjANVBBypcrMKavpQ4F1JLhgGFhWAsAuFRwZsnOaftZyMuzAZxM37DTd/bF2WanmK3Xe75SN5uOnEXjuzW/wIDAQAB ``` ### netgrimoire.com ``` v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoJ9YKqV9+6gOcVKI+UJ0TRcMmergxU8HLO+mwTMfqOhblsEcDPO60c8ya24iIXg51AA2k5Xcbb0bLScaaIi0P/TRzP/bonAZkPS1Y8Fx1se9dikTsA9Lazho u6DvoFkkV/IPH1ZNg68Cd9teAD5tvoY18OSneJJsocXwFo57c+XccUaTxjpV7eReuT4da7iNHMmUmZNfKenxVMKD740zrDJAeAsXtEb/71CochHYSm+qAvuG9/WPixJbMsJLF/iVhV3Byp0LCrB+CwGTwnsiUcd7QpuD6rRs/7zzdGBtoN22m/j390GimFstYvB61I20h8sHWGAG66dLko6Sgvs47wIDAQAB ``` ### gnarlypandaproductions.com ``` v=DKIM1;k=rsa;t=s;s=email;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... ``` *(scroll cut off in screenshot — retrieve full key from Mailcow UI → Edit domain → bottom of page)* > All other domain DKIM keys should be retrieved from the Mailcow domain edit page and recorded here for disaster recovery completeness. --- ## Network Configuration Mailcow containers join the `netgrimoire` external overlay network, allowing communication with other Docker Swarm services (Caddy reverse proxy, etc.) without exposing ports directly to the host network. **Internal Docker network:** `172.22.1.0/24` Key container IPs within the mailcow-network: - unbound: 172.22.1.254 - redis: 172.22.1.249 - sogo: 172.22.1.248 - dovecot: 172.22.1.250 - postfix: 172.22.1.253 **IPv6:** disabled (`ENABLE_IPV6=false`) --- ## Caddy Reverse Proxy Mailcow's nginx listens on HTTPS port 3443 internally. Caddy proxies external requests to it. Mailcow handles its own TLS for direct mail client connections (IMAP 993, SMTP 465/587). The admin UI at `mail.netgrimoire.com` is proxied through Caddy on the `netgrimoire` overlay network. --- ## Updating Mailcow ```bash cd /opt/mailcow-dockerized # Pull latest git fetch origin git checkout origin/master # Update containers docker compose pull ./update.sh ``` > As of documentation date, version 2026-01 is available. Current running version is 2025-10a. Update when convenient — check the [MailCow changelog](https://github.com/mailcow/mailcow-dockerized/releases) for breaking changes first. Monthly update check is recommended. MailCow had multiple security vulnerabilities in 2025 — staying current is important. --- ## Common Operations ### Restart all containers ```bash cd /opt/mailcow-dockerized docker compose restart ``` ### Restart single container (e.g. after extra.cf change) ```bash docker compose restart postfix-mailcow ``` ### View logs ```bash # Postfix docker compose logs postfix-mailcow -f # Dovecot docker compose logs dovecot-mailcow -f # All containers docker compose logs -f ``` ### Check queue ```bash docker exec mailcow-postfix-mailcow-1 postqueue -p ``` ### Flush queue ```bash docker exec mailcow-postfix-mailcow-1 postqueue -f ``` ### Check container health ```bash docker compose ps ``` --- ## Known Gotchas **Disk usage is at 93%.** The nvme0n1p2 volume has 442G used of 502G. This needs attention — vmail storage grows over time and garbage collection runs hourly but only removes items older than 7200 minutes (5 days). Monitor this and consider quota enforcement per mailbox if growth continues. **extra.cf is minimal.** The MXRoute trusted network IPs should be confirmed in the running Postfix config. The `mynetworks` value from `postconf` shows `10.0.1.0/24` is already trusted — confirm whether MXRoute IP ranges `69.167.160.0/19` and `198.54.120.0/22` are included. If not, add them to extra.cf and restart postfix. **MXRoute relay hostname.** The actual relay hostname for this account is `heracles.mxrouting.net:587` — not the generic `smtp.mxroute.com` placeholder. All 6 transports use `heracles.mxrouting.net:587`. Use this exact hostname for any new transport entries. **pncharris.com uses passer@ not relay@.** Transport ID 3 for pncharris.com authenticates as `passer@pncharris.com`, not `relay@pncharris.com`. This is intentional — the relay@ account exists but passer@ is the current active relay credential. **HTTPS on port 3443.** Mailcow's web UI is not on the standard 443 — it binds to 3443 and Caddy handles the public-facing 443 proxy. Direct access to the UI requires going through Caddy or using the internal port. **nucking-futz.net vs nucking-futz.com.** The domains list shows `nucking-futz.net` but the intended new domain is `nucking-futz.com`. Verify which is actually configured and correct if needed. **bamalady.com and bill740.com** have no transport assigned in the screenshots. Confirm whether these domains need MXRoute relay configured. --- ## Related Documentation - [MXRoute Master Configuration](./mxroute-master) — per-domain DNS, inbound forwarding, outbound relay credentials - [Mail Setup — nucking-futz.com](./mail-setup-nucking-futz) — new domain setup guide - [MailCow Security Hardening](./mailcow-security-hardening) - [Caddy Reverse Proxy](./caddy-reverse-proxy) — proxies mail.netgrimoire.com to port 3443 - [OPNsense Firewall](./opnsense-firewall) — ATT_Mail static IP, port forwarding rules