Self-hosted team chat is one of those categories where the software has been solid for years and the operational story is what actually decides whether you do it. This page is about the operational story: what you run, what it costs, how big a box you need, and where each of the open source options puts its sharp edges.
Why teams self-host chat
In practice there are four reasons, and they're worth separating because only some of them are good ones.
- 1Data residency and control. The messages have to be in a specific jurisdiction, on infrastructure you can point at. This is the reason that survives scrutiny — no SaaS contract fully substitutes for the data being on your disk.
- 2Network isolation. Air-gapped environments, defence, industrial, or anywhere the chat tool has to work when the internet doesn't.
- 3Cost at scale. Per-seat pricing compounds; a server doesn't. Past roughly 50 people the maths gets loud.
- 4Vendor independence. Pricing changes, acquisitions, region shutdowns and deprecated features are all things that happen to other people until they happen to you.
A reason that does not survive scrutiny: “it'll be cheaper because it's free.” Software licences are rarely the expensive part of self-hosting. The expensive part is the person who gets paged.
What a self-hosted chat stack actually contains
Most team chat systems decompose into the same five pieces. The differences between products are mostly about how many separate services each piece needs.
| Piece | What it does | In Fluid Chat |
|---|---|---|
| Database | Messages, channels, users, memberships | Postgres — the only hard dependency |
| Search | Finding any of the above again | Also Postgres (full-text search) |
| Realtime | Presence, typing, live delivery | Socket.IO, optional Redis for fan-out |
| Object storage | Uploads and exports | Any S3-compatible store, MinIO included |
| Background jobs | Reminders, retention, exports, email | One worker process |
The open source options, honestly
Four serious choices, each good at something different. The architecture is the durable distinction — pricing pages get rewritten, database engines don't.
| Fluid Chat | Mattermost | Rocket.Chat | Zulip | |
|---|---|---|---|---|
| Language | TypeScript | Go | JavaScript | Python |
| Database | Postgres | Postgres / MySQL | MongoDB | Postgres |
| Search | Postgres FTS | Postgres FTS or Elasticsearch | MongoDB text or Elasticsearch | Postgres FTS |
| Conversation model | Channels + threads | Channels + threads | Channels + threads | Channels + topics |
| Voice / video | No | Yes | Yes | Via integrations |
| Admin features gated by tier | None | Some | Some | Some (cloud) |
| Ops surface | Small | Medium | Medium–large | Medium |
Choose Mattermost for the most established enterprise track record and the widest compliance story. Choose Rocket.Chat if you need voice, video and omnichannel in one deployment, and you're comfortable operating MongoDB. Choose Zulip if your team genuinely thinks in topics — its threading model is the strongest in the category and a legitimate reason to pick it over everything here. Choose Fluid for the smallest ops surface and no feature tiers at all.
Full comparisons
Installing Fluid Chat
Three commands to a running workspace. The first account you create owns it, with #general and #random already in place.
# clone, configure, rungit clone https://github.com/azianmike/fluid-chat.gitcd fluid-chat && cp .env.example .envdocker compose up app ready on http://localhost:3000 realtime websockets on :3001 worker jobs running postgres migrations appliedFrom there, the repo's docs cover SMTP, S3/MinIO, HTTPS and domains, backup, restore and upgrade — the install guide is the entry point.
Sizing and cost
Team chat looks like a heavy realtime workload and behaves like a light one. A busy 50-person team writes a few thousand messages a day — a rounding error for Postgres.
| Team size | Starting shape | Rough monthly infrastructure |
|---|---|---|
| Up to ~25 | 1 vCPU / 2 GB, Postgres on the same box | $6–15 |
| 25–100 | 2 vCPU / 4 GB, managed Postgres | $30–70 |
| 100–500 | 2+ app processes, Redis, managed Postgres | $100–250 |
| 500+ | Load-balanced app tier, Postgres with real IOPS | Scales with the database, not the headcount |
Infrastructure figures are illustrative and depend entirely on your provider — the shape is the point, not the digits. The shape is: cost tracks usage, not headcount. A per-seat plan does the opposite, which is why the two lines cross and never uncross.
The cost people forget is time. Budget a few hours for the initial setup and TLS, then something like an hour a month for patching and upgrades. That's the real invoice.
The four ops jobs you're signing up for
- 1Backups. For Fluid that's a Postgres dump plus your object store — there's no second datastore holding half the messages. Then restore it once, on purpose, before you ever need to.
- 2TLS and domains. A reverse proxy in front of the app and the realtime port. This is where most self-hosting afternoons go; the repo has a doc for it.
- 3Upgrades. Migrations run on start. Read the upgrade doc, take a backup first, do it on a Tuesday.
- 4Patching the host. Not glamorous, entirely yours.
One database. Three processes. Two minutes.
Clone it and run docker compose up, or start on the free hosted version and migrate to your own infrastructure whenever you feel like it.