Here are answers to each of your four questions, grounded in the current documentation:
1. PostgreSQL: inside or outside the container?
The Docker image does not include PostgreSQL. It only bundles the Stalwart binary. PostgreSQL must run outside the container (on the host or another container/service). When configuring the DataStore in the setup wizard, you specify the host, port, database name, username, and secret. If PostgreSQL is running on the host and your Docker container uses the default bridge network, use host.docker.internal (on Docker Desktop) or the host’s Docker bridge IP (typically 172.17.0.1 on Linux) as the host value. The exact hostname depends on your Docker network setup.
Reference: PostgreSQL backend docs
2. Which ports should the new instance listen on?
Do not bind the new instance to the same ports as the running 0.15.x instance – that will cause a port conflict and the new container will fail to start. The standard approach is to map the new container to non-standard host ports (e.g. 10025, 10587, 10993, etc.) during setup and migration, and only remap to the standard ports (25, 587, 465, 143, 993…) once the old instance is stopped. The Docker docs show that you can prune the -p list freely:
“Publishing every port is not required. Internal deployments that only need, for example, submission (587) and IMAPS (993) can prune the list accordingly.”
During migration, run the new container on alternate host ports and only switch over at cutover time.
3. Apache reverse proxy and the migration
Apache is not covered with a specific example in the Stalwart docs (the covered proxies are HAProxy, NGINX, Caddy, and Traefik), but the general principles apply. Key points from the docs:
- For the initial setup, bypass the proxy entirely and reach the new container directly at
http://<host>:<mapped-port>/admin to complete the wizard. The docs explicitly recommend this:
“The smoothest path through this transition for deployments that already have a reverse proxy in front of Stalwart is to bypass the proxy for the duration of the wizard and the first sign-in, then put the proxy back in the path.”
- Set the
STALWART_PUBLIC_URL environment variable on the container to your public-facing URL (e.g. -e STALWART_PUBLIC_URL=https://mail.example.com) so discovery documents advertise the correct URLs.
- For HTTP proxying (Apache terminates TLS, forwards plain HTTP to Stalwart on port 8080), configure Apache to trust
X-Forwarded-For headers and mark the proxy’s IP as trusted in Stalwart’s listener settings so real client IPs are preserved for SPF/DMARC and rate limiting.
Since Apache does not natively support Proxy Protocol for TCP (SMTP/IMAP) streams, you will need to handle SMTP/IMAP traffic separately or ensure the correct client IP is passed through another mechanism.
Reference: Reverse proxy overview
4. Enterprise license and tenants
Yes, you should activate Enterprise on the new 0.16.x instance using your current license key. The license is entered on the Enterprise singleton object in the WebUI (Settings > Enterprise). After saving, a config reload via the CLI is required, and you need to log out and back in before Enterprise features become visible. Tenants are an Enterprise-only feature and are migrated as part of your data (if you use the database import/export utility to move data from the old instance). There is no indication in the docs that having a single tenant configured complicates the migration beyond the normal data import process.
Reference: Enterprise license docs | v0.16 upgrade notes (strongly recommended reading before proceeding)
One final note: the v0.16 upgrade documentation explicitly states this is a major upgrade with multiple breaking changes and strongly recommends reading the full upgrading guide before proceeding on production.
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.