Docker
Dispatch ships as a multi-arch container image (linux/amd64 + linux/arm64), so the same tag runs
natively on x86 servers and Apple Silicon. There are two common paths: a one-command local stack with
Compose, or running the published image directly from GHCR.
Try it locally with Compose
Section titled “Try it locally with Compose”From a clone of the repository, bring up Dispatch with its bundled SQLite database - no separate database service, since SQLite lives in a Docker volume:
docker compose up -d --buildThe dashboard comes up at https://localhost:8420 with a self-signed certificate (accept the browser
warning). The default login password is in docker-compose.yml - change it after your first login.
To run against PostgreSQL instead (mainly for exercising that backend locally), layer the override file,
which starts a postgres:17 container and points Dispatch at it:
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up -d --buildThe same shape works for MariaDB/MySQL and SQL Server by pointing ConnectionStrings__DispatchLog at a
server you run - Dispatch does not install or manage a database server itself.
Run from GHCR
Section titled “Run from GHCR”The plain image has no installer wrapping it, so ConnectionStrings__DispatchLog must always be set -
either to a SQLite file path (mount a volume for it) or to a database server you already run:
docker run -d --name dispatch \ -p 8420:8420 -p 8025:8025 -p 25:25 -p 587:587 \ -e ConnectionStrings__DispatchLog="Data Source=/app/.dispatch-data/dispatch.db" \ -e AdminPassword="<DashboardPassword!>" \ -v dispatch-data:/app/.dispatch-data \ -v dispatch-spool:/app/.dispatch-spool \ ghcr.io/cinderhillsdev/dispatch-platform:latest
# ...or against a database server you already run:docker run -d --name dispatch \ -p 8420:8420 -p 8025:8025 -p 25:25 -p 587:587 \ -e ConnectionStrings__DispatchLog="Host=<host>;Port=5432;Database=DispatchLog;Username=<user>;Password=<pw>" \ -e AdminPassword="<DashboardPassword!>" \ -v dispatch-spool:/app/.dispatch-spool \ ghcr.io/cinderhillsdev/dispatch-platform:latestOnly two settings are passed in: the database connection string and the first-run admin password. Everything else is seeded into the database’s config table and managed from the dashboard - see Configuration. The schema is created and migrated automatically on first start.
| Port | Purpose |
|---|---|
| 8420 | Dashboard (HTTPS) |
| 8025 | HTTP API |
| 25, 587 | SMTP listener (the container runs as root, so it binds the standard ports; if host 25 is busy, remap e.g. -p 2525:25) |
Volumes
Section titled “Volumes”| Volume | Purpose |
|---|---|
dispatch-spool (/app/.dispatch-spool) |
Durable message queue - persists in-flight and captured mail across restarts |
Allow-lists
Section titled “Allow-lists”The default source-IP allow-lists are container-aware: the dashboard and API allow all connections and are gated by the password and API keys, while the SMTP listener accepts only loopback and private (RFC1918) ranges so it isn’t an open relay. Tighten these in Settings - see Security.