Migration from 0.15.x to 0.16.9

Your question

I am preparing to use the zero-downtime migration to migrate from Stalwart 0.15.x to 0.16.9.

Currently I am running Stalwart 0.15.x behind an Apache2 reverse proxy as I am running other websites from that server.

I will use a docker instance for the new 0.16.9 deployment and I plan on using PostgreSQL. I have some questions, when setting up 0.16.9 in docker:

  1. Does the docker image have a PostgreSQL instance running, or is that set up “outside” the container on the host server?
    If on the host server, how should be database host be specified in the setup?
  2. When setting up the new deployment, what ports should its listeners listen to? The old deployment listens to the default SMTP/IMAP ports, should the new instance listen to these same ports? I am worried this might cause problems, as the new instance will run on the same server as the old instance.
  3. As noted I use a Apache reverse-proxy to accept outside HTTPS connections, how should the migration proxy configuration be modified to take this into account?
  4. I am running an Enterprise version of Stalwart with one tenant configured. Will this complicate the migration to 0.16.9? Should I activate Enterprise on 0.16.9 with my current key?

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.

How can I get Stalwart to listen for http on port 10080 in Docker?

Login as admin, go to settings → network → listeners and create a new http listener on ur desired port.

Thank you for your reply, but I need to be able to access the WebUI in my browser to do that, which I can’t unless I get Stalwart to listen for http on 10080.

Ok i dont know if its possible to set this via cli commands. What u can do: Map the port different via docker eg. -p 10080:80, setup up everything and restart.

I am trying to do the set up with the default ports, and I am in the set-up wizzard, but I cannot get Stalwart to connect to the PostgreSQL server running on the host from docker, getting an error

Failed to initialize data store: PostgreSQL error (store.postgresql-error): reason = Error occurred while creating a new object: error connecting to server

which isn’t a super helpful error message.

what address u used for the postgres server?

u should check ur docker networks (docker network ls) and then inspect the bridge (docker network inspect <networkid_of_the_bridge>). There u should see ur postgress server and ur stalwart server (and perhaps ur old stalwart server if its still running) and their intern ip addresses.

It turned out to be PICNIC :smiley:

When following the guide, it says to issue to the new Stalwart instance:

stalwart-cli update SystemSettings singleton \
  --field 'proxyTrustedNetworks={"203.0.113.10/32":true}'

and replace the IP with the proxy’s own IP. if the new stalwart instance is running in a docker container on the host that is running the proxy, what IP should I specify? The public IP of the server?

After starting the proxy, I have successfully testes IMAP over tls. But when I try to set smtp submission, I get these errors:


0093098ABB7F0000:error:80000071:system library:BIO_connect:No route to host:crypto/bio/bio_sock2.c:183:calling connect()
0093098ABB7F0000:error:10000067:BIO routines:BIO_connect:connect error:crypto/bio/bio_sock2.c:185:
0093098ABB7F0000:error:80000065:system library:BIO_connect:Network is unreachable:crypto/bio/bio_sock2.c:183:calling connect()
0093098ABB7F0000:error:10000067:BIO routines:BIO_connect:connect error:crypto/bio/bio_sock2.c:185:
connect:errno=101

what am I doing wrong?

There’s no information about what port you’re trying to use, but based on the earlier bot response I’m guessing you’re pointing Docker at 587, and there is no listener on 587 by default in Stalwart, only 465 for implicit TLS on SMTP.

why on earth does the docs ask us to test it works!