
Should I self-host Slack?
Per-seat pricing means the math flips hard around 4 seats — a 10-person team escapes $1,050/yr for one weekend of setup. Worth it if someone genuinely owns the pager; chat is the one service your team notices being down within 90 seconds.
The math (nothing hidden, including your time)
| Slack (Pro, per seat) | $8.75/mo |
| VPS share + storage | −$2.50/mo |
| Your maintenance: 45 min/mo at $20/h | −$15.00/mo |
| Net saving | $-8.75/mo |
| Setup: 65 min measured (one-time) | $21.67 |
| Break-even | never |
| Markup Index (price ÷ real self-host cost) | 0.5× |
Price: source, checked 2026-08-05. Inputs are stored in git; every number above is derived, never hand-written.
What you lose
- Slack's mobile push reliability — Mattermost push routes through their relay or you build your own, and half-working push kills adoption
- The integration directory: webhooks exist, but every SaaS ships a Slack app and maybe a Mattermost one
- Huddles-grade calls without extra setup
- Message history import is one-way and lossy — the escape is easier before the archive matters
What you're paying Slack for
- channels & DMs
- mobile apps with reliable push
- search across full history
- integrations & incoming webhooks
- huddles/calls

Ranked alternatives
Channels, threads and file sharing for a team, on a server you own, with no per-seat meter and no ninety-day history cliff.
Hardware
Runs comfortably on a real VPS (2 GB+).
The tested compose file (our evidence, CI-booted)
# Mattermost Team Edition — self-hosted team chat (replaces Slack)
services:
mattermost:
image: mattermost/mattermost-team-edition:latest
container_name: mattermost
restart: unless-stopped
depends_on:
mm-postgres:
condition: service_healthy
environment:
MM_SQLSETTINGS_DRIVERNAME: postgres
MM_SQLSETTINGS_DATASOURCE: postgres://mmuser:mmpass@mm-postgres:5432/mattermost?sslmode=disable&connect_timeout=10
MM_SERVICESETTINGS_SITEURL: http://localhost:8065
MM_SERVICESETTINGS_ENABLELOCALMODE: "true"
ports:
- "8065:8065"
volumes:
- mm-data:/mattermost/data
- mm-logs:/mattermost/logs
- mm-config:/mattermost/config
- mm-plugins:/mattermost/plugins
- mm-client-plugins:/mattermost/client/plugins
- mm-bleve:/mattermost/bleve-indexes
healthcheck:
# The image ships no shell, curl or wget — exec-form mmctl over the local socket
# is the only probe that works (requires MM_SERVICESETTINGS_ENABLELOCALMODE above).
test: ["CMD", "/mattermost/bin/mmctl", "system", "status", "--local"]
interval: 5s
timeout: 10s
retries: 36
start_period: 30s
mm-postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: mmuser
POSTGRES_PASSWORD: mmpass
POSTGRES_DB: mattermost
volumes:
- mm-pg:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mmuser"]
interval: 5s
timeout: 5s
retries: 12
volumes:
mm-data:
mm-logs:
mm-config:
mm-plugins:
mm-client-plugins:
mm-bleve:
mm-pg:
Want a guided install instead? caniselfhostit.com/slack has AI-agent prompts that assume a bare machine — that's their half of the stool, and it's good.
Verification
Protocol v1 · verified by zernonia on 2026-08-10 · setup measured at 65 min · AI assistant: claude-code (allowed & stated)
What broke
- The image ships no shell, curl or wget, so a CMD-SHELL healthcheck fails forever while the server is fine — exec-form mmctl system status --local (with local mode enabled) is the probe that works
- First admin account creation is a UI step — plan it into the rollout, whoever clicks first owns the workspace
Full timed log
# Timed setup log: Slack → Mattermost **Protocol:** v1 · **Verified by:** zernonia · **Date:** 2026-08-10 **Assistant:** claude-code · **Environment:** containerized runner, 2 vCPU class, Docker 29.3 / Compose v5.1 ## Timeline | Step | Time | |---|---| | Draft compose (mattermost + postgres, datasource string, site URL) | 15 min | | **Boot #1: server up and serving, but never reported healthy** (see What broke) | 15 min to diagnose | | Switch healthcheck to `mmctl system status --local`, re-up | 3 min | | Boot #2 to healthy + core workflow: create admin + team, post in a channel, second browser sees it live | 25 min | | Re-run from clean volumes | 7 min | | **Total: ~65 min** | | ## Measurements - 2 containers · Mattermost idle RAM ~450 MB + postgres ~40 MB — wants the full 2 GB box - Server boots and serves within ~60 s; plugin unpacking continues for a while after ## What broke 1. **The image ships no shell, curl or wget** — a `CMD-SHELL` healthcheck fails with "stat /bin/sh: no such file or directory" forever while the server is actually fine. Exec-form `mmctl system status --local` (with `MM_SERVICESETTINGS_ENABLELOCALMODE=true`) is the healthcheck that works. 2. **First admin is a race**: whoever completes the first-run screen owns the workspace. Plan the rollout; don't send the URL to the team before you've clicked through. ## Verdict-relevant notes - Channels, DMs, search, webhooks: solid. Mobile push routes through Mattermost's relay (or you run your own push proxy) — test it before you migrate anyone who matters. - The KINDA is arithmetic: per-seat pricing versus a flat server cost. At 1 seat you're losing money; at 10 seats you're keeping ~$1,000/yr for one weekend plus ops ownership.
Verdict history
- 2026-08-10: unscored → KINDA — Initial verdict: per-seat math flips hard at ~4 seats, if someone owns the pager.