Stalwart — a modern, all-in-one mail server (SMTP, IMAP, JMAP, POP3) with a web admin, spam filtering, encryption and full-text search, in a single Rust binary. Deployed as a single host-networked Nomad service.
Needs nomad-pack on PATH. The script only adds the nomploy registry and runs this pack.
Source ↗ Project ↗ ★ 14.9k ⚑ Report an issue
Save as values.hcl, edit, then run:
# The name of the Nomad job.
job_name = "stalwart"
# The Nomad namespace to deploy into.
namespace = "default"
# The datacenters to deploy to.
datacenters = ["*"]
# The Stalwart container image. Pin a tag in production.
image = "stalwartlabs/stalwart:latest"
# Host port for the web admin / setup UI (and HTTP/JMAP).
admin_port = 8080
# Recovery administrator username, pinned on first start.
admin_user = "admin"
# Recovery administrator password, pinned on first start. CHANGE THIS.
admin_password = "stalwart_change_me"
# Host port for SMTP (incoming mail / MX).
smtp_port = 25
# Host port for mail submission (STARTTLS).
submission_port = 587
# Host port for mail submission (implicit TLS).
submissions_port = 465
# Host port for IMAP (STARTTLS).
imap_port = 143
# Host port for IMAP (implicit TLS).
imaps_port = 993
# Host port for ManageSieve.
sieve_port = 4190
# Named volume for Stalwart config, data, queue and logs (/opt/stalwart).
data_volume = "stalwart_data"
# Placement constraints. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true". A mail server usually wants a dedicated public node.
constraints = []
# The task resources.
resources = {
cpu = 1000
memory = 1024
}
| Name | Type | Default | Description |
|---|---|---|---|
| job_name | string | "stalwart" | The name of the Nomad job. |
| namespace | string | "default" | The Nomad namespace to deploy into. |
| datacenters | list | ["*"] | The datacenters to deploy to. |
| image | string | "stalwartlabs/stalwart:latest" | The Stalwart container image. Pin a tag in production. |
| admin_port | number | 8080 | Host port for the web admin / setup UI (and HTTP/JMAP). |
| admin_user | string | "admin" | Recovery administrator username, pinned on first start. |
| admin_password set me | string | "stalwart_change_me" | Recovery administrator password, pinned on first start. CHANGE THIS. |
| smtp_port | number | 25 | Host port for SMTP (incoming mail / MX). |
| submission_port | number | 587 | Host port for mail submission (STARTTLS). |
| submissions_port | number | 465 | Host port for mail submission (implicit TLS). |
| imap_port | number | 143 | Host port for IMAP (STARTTLS). |
| imaps_port | number | 993 | Host port for IMAP (implicit TLS). |
| sieve_port | number | 4190 | Host port for ManageSieve. |
| data_volume | string | "stalwart_data" | Named volume for Stalwart config, data, queue and logs (/opt/stalwart). |
| constraints | list | [] | Placement constraints. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true". A mail server usually wants a dedicated public node. |
| resources | object | {
cpu = 1000
memory = 1024
} | The task resources. |
No variables match.
This pack stores data in one Docker named volume:
stalwart_data
restic
# Run on the node hosting this pack. Point restic at your repo first: # export RESTIC_REPOSITORY="s3:https://<account>.r2.cloudflarestorage.com/<bucket>" # export RESTIC_PASSWORD="<repo-password>" # export AWS_ACCESS_KEY_ID=<key> AWS_SECRET_ACCESS_KEY=<secret> restic backup \ /var/lib/docker/volumes/stalwart_data/_data
rclone (sync to S3/R2)
rclone sync /var/lib/docker/volumes/stalwart_data/_data backup:<bucket>/stalwart_data
Paths assume the default Docker volume location (/var/lib/docker/volumes). Restore by stopping the job, restoring files into the same volume, and re-running the pack.
Stalwart is a modern, all-in-one mail server written in Rust. One binary speaks SMTP, IMAP, JMAP and POP3, with a web admin, built-in spam/phishing filtering, DKIM/SPF/DMARC/ARC, encryption at rest, full-text search, sieve scripting and ACME TLS — a self-hosted alternative to heavyweight mail stacks like Mailcow or a Postfix/Dovecot combo.
This pack runs Stalwart as a single host-networked Nomad service.
nomad-pack run stalwart --registry=nomploy \
--var admin_password=$(openssl rand -hex 16)
Open http://<node-ip>:8080 and log in as admin. On first start Stalwart is in
bootstrap mode — add your domain, accounts and TLS/DNS from the admin UI.
| Variable | Default | Description |
|---|---|---|
image |
stalwartlabs/stalwart:latest |
Container image (pin a tag in production). |
admin_port |
8080 |
Web admin / setup / HTTP(JMAP) port. |
admin_user |
admin |
Recovery admin username. |
admin_password |
stalwart_change_me |
Recovery admin password — change this. |
smtp_port … |
25/587/465/143/993/4190 |
SMTP / submission / IMAP / Sieve ports. |
data_volume |
stalwart_data |
Volume for config, data, queue (/opt/stalwart). |
resources |
1000 MHz / 1024 MB | CPU and memory for the task. |
Running a mail server means public DNS (MX, SPF, DKIM, DMARC), a static IP with clean reverse DNS, and open ports 25/465/587/993. Pin this job to the node that owns your mail hostname's public IP (
constraints). All state persists indata_volume.