2026-08-15 06:22:02 +01:00
|
|
|
# Docker deployment
|
|
|
|
|
|
|
|
|
|
Two independent images, both built from the same source tree:
|
|
|
|
|
|
|
|
|
|
- **`Dockerfile`** — mailgoserver only.
|
|
|
|
|
- **`Dockerfile.rspamd`** — mailgoserver + the latest [rspamd](https://rspamd.com) in the
|
|
|
|
|
*same* container, already wired together (see below). Use this one if you want
|
|
|
|
|
stronger spam filtering than the built-in heuristic score alone.
|
|
|
|
|
|
|
|
|
|
`docker-compose.yml` defines both as Compose **profiles** so a plain `docker compose up`
|
|
|
|
|
can't accidentally start both at once:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd docker-deploy
|
|
|
|
|
|
|
|
|
|
# mailserver only
|
|
|
|
|
docker compose --profile standalone up -d --build
|
|
|
|
|
|
|
|
|
|
# mailserver + rspamd, bundled
|
|
|
|
|
docker compose --profile with-rspamd up -d --build
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Either way, the app itself now binds the real standard mail ports by default — 25
|
|
|
|
|
(SMTP), 465 (direct-TLS SMTP), 143 (IMAP), 993 (direct-TLS IMAP) — and the admin/webmail
|
|
|
|
|
UI on its usual non-privileged 5000/5001 (HTTP/HTTPS); put your own reverse proxy or a
|
2026-08-15 12:35:44 +01:00
|
|
|
`80:5000`/`443:5001` port mapping in front if you want those on 80/443 too (note port 80
|
|
|
|
|
is already published here for Let's Encrypt HTTP-01, see below — pick a different host
|
|
|
|
|
port for the web UI's 80 mapping if you use both). Binding the low mail ports needs no
|
|
|
|
|
extra capability here since the container runs as root. Copy `.env.example` to `.env` in
|
|
|
|
|
this folder to change any host-side port — useful if something else on the host already
|
|
|
|
|
owns 25/143/etc., or if you want to run both profiles side by side.
|
2026-08-15 06:22:02 +01:00
|
|
|
|
|
|
|
|
## What happens on first boot
|
|
|
|
|
|
|
|
|
|
There's no baked-in config. On first start, the binary generates a fresh
|
|
|
|
|
`settings.ini` with defaults (mirroring `internal/config/config.go`), a self-signed TLS
|
|
|
|
|
certificate, DKIM/mailstore master keys, and an empty SQLite database — all inside the
|
|
|
|
|
`/app/data` volume, so it survives container restarts/rebuilds. The web UI seeds one
|
|
|
|
|
admin account: username `admin`, password `Password123!`, and forces an immediate
|
|
|
|
|
username + password change on first login — see the main [README](../README.md) for
|
|
|
|
|
the full first-login walkthrough.
|
|
|
|
|
|
|
|
|
|
**Before using this for real mail**, exec into the container (or edit the volume from
|
|
|
|
|
the host) and update `settings.ini`:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
docker exec -it mailgoserver sh -c 'vi /app/data/settings.ini'
|
|
|
|
|
docker restart mailgoserver
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
At minimum, set `[Server] HOSTNAME` / `helo_hostname` to your real mail domain, and if
|
|
|
|
|
you'll use passkeys, `[Auth] rp_id` / `rp_origin` to match the exact domain the admin
|
|
|
|
|
dashboard is reached at (`rp_id` can't be `localhost` once you're on a real domain —
|
|
|
|
|
see the main README's WebAuthn note). There's no environment-variable override
|
|
|
|
|
mechanism — `settings.ini` in the volume is the one source of config truth.
|
|
|
|
|
|
|
|
|
|
## Enabling rspamd (the `with-rspamd` profile)
|
|
|
|
|
|
|
|
|
|
The bundled rspamd's default config already listens on `127.0.0.1:11333` for scanning
|
|
|
|
|
requests — exactly what `[Rspamd] url` defaults to in `settings.ini`, and since both
|
|
|
|
|
processes share the container's network namespace, no networking setup is needed. All
|
|
|
|
|
that's left is turning it on:
|
|
|
|
|
|
|
|
|
|
```ini
|
|
|
|
|
[Rspamd]
|
|
|
|
|
enabled = true
|
|
|
|
|
url = http://127.0.0.1:11333
|
|
|
|
|
reject_score = 15
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
...then restart the container. The built-in heuristic spam score
|
|
|
|
|
(`internal/mailstore/spam.go`) always runs regardless of this setting — rspamd is
|
|
|
|
|
additive, not a replacement, and if it's ever unreachable, mail still flows on the
|
|
|
|
|
heuristic score alone (rspamd errors are logged, never fatal to delivery).
|
|
|
|
|
|
|
|
|
|
This bundle intentionally skips Redis — rspamd runs fine without it for SPF/DKIM/RBL/
|
|
|
|
|
regexp-based scoring, but Bayes learning and greylisting need it. Add a `redis` service
|
|
|
|
|
to `docker-compose.yml` and point rspamd's `redis.conf` at it if you need those.
|
|
|
|
|
|
2026-08-15 12:35:44 +01:00
|
|
|
## Let's Encrypt HTTP-01 (no DNS provider needed)
|
|
|
|
|
|
|
|
|
|
If this domain's DNS isn't hosted anywhere the app can automate, enable HTTP-01 on the
|
|
|
|
|
admin dashboard's Let's Encrypt page and restart the container — it runs independently
|
|
|
|
|
alongside (or instead of) the DNS-01 flow above, obtaining its own separate certificate.
|
|
|
|
|
Port 80 (already published by `docker-compose.yml`) stays bound for the container's
|
|
|
|
|
whole lifetime once enabled, not just during an obtain — `curl` it and you should get a
|
|
|
|
|
plain `200 ok`, the quickest way to confirm your port-forwarding/reverse-proxy setup
|
|
|
|
|
actually reaches this container. Optionally also request the certificate for this
|
|
|
|
|
container's public IP address (autodetected, or a manual override) so clients connecting
|
|
|
|
|
by bare IP get a trusted cert too — note this uses Let's Encrypt's `shortlived` profile,
|
|
|
|
|
so those certificates renew roughly every few days instead of every couple months
|
|
|
|
|
(handled automatically).
|
|
|
|
|
|
|
|
|
|
Which listener actually uses which certificate — the DNS-01 cert, the HTTP-01 cert, or
|
|
|
|
|
the custom/self-signed one — is chosen independently per listener (SMTP-TLS, IMAP-TLS,
|
|
|
|
|
web UI) on the admin dashboard's Settings page. A common setup: HTTP-01 for
|
|
|
|
|
SMTP/IMAP, DNS-01 (or a real custom cert) for the web UI.
|
|
|
|
|
|
2026-08-15 06:22:02 +01:00
|
|
|
## Persistence
|
|
|
|
|
|
|
|
|
|
| Volume | What's in it |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `mailserver-data` / `mailserver-rspamd-data` | `settings.ini`, the SQLite DB, encrypted mailbox storage, DKIM/mailstore master keys, TLS certs, the CSRF app secret — everything mailgoserver itself owns. |
|
|
|
|
|
| `rspamd-data` (rspamd profile only) | rspamd's own Bayes/fuzzy-hash storage, so spam-learning state survives image rebuilds. |
|
|
|
|
|
|
|
|
|
|
Back up the `*-data` volume like you would the equivalent bare-metal `server_data/`
|
|
|
|
|
directory — losing the mailstore master key makes all stored mail unrecoverable, same
|
|
|
|
|
as a non-Docker install.
|
|
|
|
|
|
|
|
|
|
## Logs / health
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
docker compose --profile standalone logs -f # or --profile with-rspamd
|
|
|
|
|
docker inspect --format '{{.State.Health.Status}}' mailgoserver
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Both images expose `GET /health` (used by the container `HEALTHCHECK`), matching the
|
|
|
|
|
JSON the admin dashboard's own health check reads.
|