Your question
Attempting to create a three node cluster from scratch, but unsure on how to bootstrap the nodes into being cluster members rather than isolated servers.
Ref: Overview | Stalwart
Ref: Steps for enabling clustering: · stalwartlabs/stalwart · Discussion #1275 · GitHub
I have three fresh EC2 nodes that I have run the install.sh I have bootstrap mode up on all 3, so we’re on version 0.16.5 with binary.
I run through the entire configuration on Node 1 without issue - assigning a psql main store, a S3 blob store, search → main store, and Redis/Valkey to in-memory store.
I get to the main GUI and I proceed to setup → cluster and I enable cluster coordination using the in-memory store (redis) option.
I then attempt to finish setup on the 2nd node, but it tells me
The selected data store has already been initialized.
It thinks this is a brand new isolated setup, and isn’t recognizing that a cluster coordinator is present in the Redis setup.
What is the magic step to clue in this node that we are joining a cluster? Do you have to push through defaults (aka RocksDB x4), and then once in the main GUI, activate the cluster coordinator and it will work it out from there? Or (more likely), what did I overlook in the documentation? Maybe there’s an ENV to set, or .. spin around touching my nose .. or 
Looks great so far, just can’t get over this one hurdle.
Cheers!
I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.
on
The key insight is clear. The bootstrap wizard on nodes 2 and 3 is creating a brand-new isolated setup because it sees a fresh (or already-initialized from node 1’s perspective) data store. Here is the correct approach:
Nodes 2 and 3 should NOT go through the bootstrap wizard independently. The bootstrap wizard is only for the very first node. Additional nodes need to be pointed at the same config.json/data store that node 1 already set up, effectively bypassing the wizard entirely.
Here is the correct procedure for nodes 2 and 3:
-
Write a config.json manually on each additional node pointing at the same shared data store (your PostgreSQL) that node 1 configured. The config.json only needs to contain the DataStore settings. Because config.json exists, Stalwart will not enter bootstrap mode at all on startup.
-
Start Stalwart in recovery mode on those nodes (set the STALWART_RECOVERY_MODE=1 env variable). This keeps mail services down and exposes only the management API, letting you finish configuration via the WebUI or CLI without going through the wizard.
-
From there, configure the coordinator (Redis), node IDs, and roles on each node through the WebUI/CLI just as you did on node 1.
The declarative deployments page describes this exact pattern: write a minimal config.json (just the DataStore stanza), start in recovery mode, then use stalwart-cli apply or the WebUI to push the rest of the config from node 1’s snapshot. Since all nodes share the same PostgreSQL data store, they will all read from the same configuration once connected.
The “already initialized” error is Stalwart correctly detecting that the PostgreSQL data store has been set up - the wizard just doesn’t know you want to join that existing data store rather than start fresh.
Relevant docs:
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.
@helpbot I should be able to directly copy the contents of the config.json from Node 1 to 2 and 3, as that appears to be exactly the information it is looking for. Nothing to add or remove?
Yes, copying config.json from Node 1 to Nodes 2 and 3 is exactly right. The file only needs to contain the DataStore pointer - since all three nodes share the same PostgreSQL data store, that single file is identical across all nodes. No additions or removals needed.
Yes, copying config.json from Node 1 to Nodes 2 and 3 is exactly the right move - with one important thing to verify: each node must have a unique hostname.
The config.json itself only contains the DataStore pointer (your PostgreSQL connection), so it is identical across all nodes and can be copied verbatim. No changes are needed to the file itself.
What Stalwart does care about is that each EC2 instance has a distinct OS hostname. Node IDs are assigned automatically via a lease mechanism keyed to the hostname - if two nodes share the same hostname they will conflict. On EC2, hostnames are typically unique by default (e.g. ip-10-0-1-1.ec2.internal), but it is worth confirming with hostname on each box. If needed, you can override it per-node with the STALWART_HOSTNAME environment variable without touching the OS.
So the complete checklist is:
- Copy
config.json from Node 1 to Nodes 2 and 3 (no edits needed).
- Confirm each node has a unique hostname (or set
STALWART_HOSTNAME to something unique per node).
- Start Nodes 2 and 3 - they will skip bootstrap (because
config.json exists), connect to the shared PostgreSQL store, and join the cluster automatically.
Docs reference: Node ID / ClusterNode
This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong. If you found it useful, mention @helpbot in any reply to ask follow-up questions and the bot will respond again.
It really was that simple. This issue is resolved and I have a cluster. Keep up the great work.