Skip to content

Repository files navigation

Stalwart

Build Status Last Commit

Stalwart Mail Server is an open-source mail server solution with JMAP, IMAP4, POP3, and SMTP support and a wide range of modern features. It is written in Rust and designed to be secure, fast, robust and scalable.

Port 25
Registry ghcr.io/daemonless/stalwart
Source https://stalw.art/
Website https://stalw.art/

Version Tags

Tag Description Best For
latest Upstream Binary. Built from official release. Most users. Matches Linux Docker behavior.
pkg FreeBSD Quarterly. Uses stable, tested packages. Production stability.
pkg-latest FreeBSD Latest. Rolling package updates. Newest FreeBSD packages.

Prerequisites

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.

Deployment

Podman Compose

services:
  stalwart:
    image: "ghcr.io/daemonless/stalwart:latest"
    container_name: stalwart
    environment:
      - TZ=UTC  # Timezone for the container
      - ADMIN_SECRET=changeme  # Password for the fallback 'admin' web-admin account
      - ENABLE_V4PROXY=true  # Enable internal socat IPv4->IPv6 proxy on port 8080 for initial setup UI and healthchecks on FreeBSD (default true). Set false to disable.
      - SKIP_CHOWN=true  # Skip the startup recursive chown of /config once ownership is recorded in /config/.chown_done (default true). Set false to force a chown on every start. The marker lives in /config, so /config must be a persistent volume for the skip to take effect across restarts.
    volumes:
      - "/path/to/containers/stalwart:/config"
    ports:
      - "25:25"
      - "465:465"
      - "587:587"
      - "143:143"
      - "993:993"
      - "110:110"
      - "995:995"
      - "4190:4190"
      - "443:443"
      - "8080:8080"
    restart: unless-stopped

AppJail Director

.env:

# .env

DIRECTOR_PROJECT=stalwart
TZ=UTC
ADMIN_SECRET=changeme
ENABLE_V4PROXY=true
SKIP_CHOWN=true

appjail-director.yml:

# appjail-director.yml

options:
  - virtualnet: ':<random> default'
  - nat:
services:
  stalwart:
    name: stalwart
    options:
      - container: 'boot args:--pull'
      - expose: '25:25 proto:tcp' \
      - expose: '465:465 proto:tcp' \
      - expose: '587:587 proto:tcp' \
      - expose: '143:143 proto:tcp' \
      - expose: '993:993 proto:tcp' \
      - expose: '110:110 proto:tcp' \
      - expose: '995:995 proto:tcp' \
      - expose: '4190:4190 proto:tcp' \
      - expose: '443:443 proto:tcp' \
      - expose: '8080:8080 proto:tcp' \
    oci:
      user: root
      environment:
        - TZ: !ENV '${TZ}'
        - ADMIN_SECRET: !ENV '${ADMIN_SECRET}'
        - ENABLE_V4PROXY: !ENV '${ENABLE_V4PROXY}'
        - SKIP_CHOWN: !ENV '${SKIP_CHOWN}'
    volumes:
      - stalwart: /config
volumes:
  stalwart:
    device: '/path/to/containers/stalwart'

Makejail:

# Makejail

ARG tag=latest

OPTION overwrite=force
OPTION from=ghcr.io/daemonless/stalwart:${tag}

Note: Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the IPv4 address assigned by the virtual network.

Podman CLI

podman run -d --name stalwart \
  -p 25:25 \
  -p 465:465 \
  -p 587:587 \
  -p 143:143 \
  -p 993:993 \
  -p 110:110 \
  -p 995:995 \
  -p 4190:4190 \
  -p 443:443 \
  -p 8080:8080 \
  -e TZ=UTC \
  -e ADMIN_SECRET=changeme \
  -e ENABLE_V4PROXY=true \
  -e SKIP_CHOWN=true \
  -v /path/to/containers/stalwart:/config \
  ghcr.io/daemonless/stalwart:latest

AppJail

appjail oci run -Pd \
  -o overwrite=force \
  -o container="args:--pull" \
  -o virtualnet=":<random> default" \
  -o nat \
  -o expose="25:25 proto:tcp" \
  -o expose="465:465 proto:tcp" \
  -o expose="587:587 proto:tcp" \
  -o expose="143:143 proto:tcp" \
  -o expose="993:993 proto:tcp" \
  -o expose="110:110 proto:tcp" \
  -o expose="995:995 proto:tcp" \
  -o expose="4190:4190 proto:tcp" \
  -o expose="443:443 proto:tcp" \
  -o expose="8080:8080 proto:tcp" \
  -e TZ=UTC \
  -e ADMIN_SECRET=changeme \
  -e ENABLE_V4PROXY=true \
  -e SKIP_CHOWN=true \
  -o fstab="/path/to/containers/stalwart /config <pseudofs>" \
  ghcr.io/daemonless/stalwart:latest stalwart

Note: Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the IPv4 address assigned by the virtual network.

Ansible

- name: Deploy stalwart
  containers.podman.podman_container:
    name: stalwart
    image: "ghcr.io/daemonless/stalwart:latest"
    state: started
    restart_policy: always
    env:
      TZ: "UTC"
      ADMIN_SECRET: "changeme"
      ENABLE_V4PROXY: "true"
      SKIP_CHOWN: "true"
    ports:
      - "25:25"
      - "465:465"
      - "587:587"
      - "143:143"
      - "993:993"
      - "110:110"
      - "995:995"
      - "4190:4190"
      - "443:443"
      - "8080:8080"
    volumes:
      - "/path/to/containers/stalwart:/config"

Access at: http://localhost:25

Parameters

Environment Variables

Variable Default Description
TZ UTC Timezone for the container
ADMIN_SECRET changeme Password for the fallback 'admin' web-admin account
ENABLE_V4PROXY true Enable internal socat IPv4->IPv6 proxy on port 8080 for initial setup UI and healthchecks on FreeBSD (default true). Set false to disable.
SKIP_CHOWN true Skip the startup recursive chown of /config once ownership is recorded in /config/.chown_done (default true). Set false to force a chown on every start. The marker lives in /config, so /config must be a persistent volume for the skip to take effect across restarts.

Volumes

Path Description
/config Config (config.toml) + rocksdb data store

Ports

Port Protocol Description
25 TCP SMTP
465 TCP SMTP submission (implicit TLS)
587 TCP SMTP submission (STARTTLS)
143 TCP IMAP4
993 TCP IMAP4 (implicit TLS)
110 TCP POP3
995 TCP POP3 (implicit TLS)
4190 TCP ManageSieve
443 TCP HTTPS / JMAP / web admin
8080 TCP HTTP / JMAP / web admin

IPv4 / Dual-Stack & Internal Proxy Notes

  • Internal IPv4 Proxy (ENABLE_V4PROXY): Stalwart v0.16.x bootstrap mode binds to [::]:8080 (IPv6). On FreeBSD (where net.inet6.ip6.v6only=1 by default), accessing http://127.0.0.1:8080 via IPv4 is refused. The container includes an internal socat helper (stalwart-v4proxy) enabled by default (ENABLE_V4PROXY=true) that bridges IPv4 port 8080 to IPv6 [::1]:8080. You can disable the internal proxy by setting ENABLE_V4PROXY=false, or enable IPv4-mapping host-wide on FreeBSD by setting sysctl net.inet6.ip6.v6only=0.
  • External IPv4 Reverse Proxy / PROXY Protocol: If placing Stalwart behind an external IPv4 reverse proxy (Nginx, Traefik, HAProxy) or using PROXY protocol headers, add your proxy's IPv4 address/subnet (e.g. 127.0.0.1/32) to proxyTrustedNetworks under Settings → Network → Services in Stalwart to prevent header parsing failures and proxy auto-bans.

Using PostgreSQL or SQLite instead of RocksDB

By default config.toml stores everything (data, full-text search, blobs, and lookups) in RocksDB under /config/data. To swap in an external PostgreSQL database instead, replace the [store."rocksdb"] section and point [storage] at the new store:

[storage]
data = "postgresql"
fts = "postgresql"
blob = "postgresql"
lookup = "postgresql"
directory = "internal"

[store."postgresql"]
type = "postgresql"
host = "postgres-host"
port = 5432
database = "stalwart"
user = "stalwart"
password = "changeme"
pool.max-connections = 10

[directory."internal"]
type = "internal"
store = "postgresql"

For SQLite (a single-file database, good for small single-node setups):

[storage]
data = "sqlite"
fts = "sqlite"
blob = "sqlite"
lookup = "sqlite"
directory = "internal"

[store."sqlite"]
type = "sqlite"
path = "/config/stalwart.db"
pool.max-connections = 10

[directory."internal"]
type = "internal"
store = "sqlite"

Edit /config/config.toml after the first run seeds it, then restart the container. Blob storage can also be split onto a separate store (e.g. keep blob = "rocksdb" while data/fts/lookup move to PostgreSQL) — see stalw.art/docs/storage for the full per-store breakdown.

Architectures: amd64 User: stalwart (UID/GID via PUID/PGID, defaults to 1000:1000) Base: FreeBSD 15.1


Need help? Join our Discord community.

About

Stalwart Mail Server is an open-source mail server solution with JMAP, IMAP4, POP3, and SMTP support and a wide range of modern features. It is written in Rust and designed to be secure, fast, robust and scalable.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages