Skip to main content

How to Self-Host Matrix + Element: Open Source Slack Alternative 2026

·OSSAlt Team
matrixelementsynapseconduitmessagingslackself-hostingdocker2026

TL;DR

Matrix is an open, decentralized messaging protocol. Synapse (Apache 2.0, ~12K stars, Python) is the reference homeserver. Element (Apache 2.0) is the most popular Matrix client — web, iOS, Android, and desktop. Together they provide Slack-like team messaging with end-to-end encryption, bridges to external platforms (Slack, Discord, WhatsApp, Telegram), and federation across Matrix homeservers worldwide. Slack Pro costs $7.25/user/month. Self-hosted Matrix is free.

Key Takeaways

  • Matrix: Open protocol — your server, your data, federated with matrix.org and others
  • Synapse: Python-based reference server — feature-complete, higher RAM (~500MB–1GB)
  • Conduit: Rust-based homeserver — lightweight (~50MB RAM), fewer features than Synapse
  • Element: Best Matrix client — web app + iOS/Android + desktop
  • Bridges: Connect Matrix rooms to Slack, Discord, WhatsApp, Telegram channels
  • E2EE: End-to-end encryption in all 1:1 chats and rooms with cross-signing

Matrix vs Mattermost vs Slack

FeatureMatrix + SynapseMattermost CESlack Pro
LicenseApache 2.0MITProprietary
CostFree (hosting)Free (hosting)$7.25/user/mo
FederationYes (Matrix protocol)NoNo
Bridges30+ (Slack, Discord, WA)LimitedYes (paid)
E2EEYesYes (beta)No
ThreadsYesYesYes
CallsYes (Element Call)YesYes
RAM~500MB–1GB~500MB

Docker Setup

# docker-compose.yml
services:
  synapse:
    image: matrixdotorg/synapse:latest
    container_name: synapse
    restart: unless-stopped
    ports:
      - "8448:8448"    # Federation
    volumes:
      - synapse_data:/data
    environment:
      SYNAPSE_CONFIG_PATH: /data/homeserver.yaml
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: synapse
      POSTGRES_USER: synapse
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=C"
    volumes:
      - synapse_db:/var/lib/postgresql/data

volumes:
  synapse_data:
  synapse_db:

Generate Initial Config

# Generate homeserver.yaml:
docker run --rm \
  -v synapse_data:/data \
  -e SYNAPSE_SERVER_NAME=matrix.yourdomain.com \
  -e SYNAPSE_REPORT_STATS=no \
  matrixdotorg/synapse:latest generate

homeserver.yaml (key settings)

server_name: "matrix.yourdomain.com"
public_baseurl: "https://matrix.yourdomain.com"

database:
  name: psycopg2
  args:
    user: synapse
    password: "${POSTGRES_PASSWORD}"
    database: synapse
    host: db
    cp_min: 5
    cp_max: 10

# Disable open registration:
enable_registration: false

# Allow registration with admin token:
registration_shared_secret: "generate-a-random-secret"

# Enable E2EE key backup:
enable_media_repo: true

listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    resources:
      - names: [client, federation]
        compress: false

Part 2: Caddy Configuration

Matrix requires special routing for federation:

matrix.yourdomain.com {
    # Client-Server API:
    reverse_proxy /_matrix/* localhost:8008
    reverse_proxy /_synapse/* localhost:8008
    
    # Well-known delegation:
    handle /.well-known/matrix/client {
        header Content-Type application/json
        header Access-Control-Allow-Origin *
        respond `{"m.homeserver":{"base_url":"https://matrix.yourdomain.com"}}`
    }
    
    handle /.well-known/matrix/server {
        header Content-Type application/json
        respond `{"m.server":"matrix.yourdomain.com:443"}`
    }
}

Option B: Conduit — Lightweight Rust Server

Conduit (Apache 2.0, Rust) is a Matrix homeserver for personal/small team use. ~50MB RAM, single binary.

services:
  conduit:
    image: conduitim/conduit:latest
    container_name: conduit
    restart: unless-stopped
    ports:
      - "6167:6167"
    volumes:
      - conduit_data:/var/lib/conduit
    environment:
      CONDUIT_SERVER_NAME: "matrix.yourdomain.com"
      CONDUIT_DATABASE_PATH: "/var/lib/conduit"
      CONDUIT_DATABASE_BACKEND: "rocksdb"
      CONDUIT_PORT: "6167"
      CONDUIT_MAX_REQUEST_SIZE: "20971520"
      CONDUIT_ALLOW_REGISTRATION: "false"
      CONDUIT_ALLOW_FEDERATION: "true"
      CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
matrix.yourdomain.com {
    reverse_proxy /_matrix/* localhost:6167
    reverse_proxy /_conduit/* localhost:6167
    
    handle /.well-known/matrix/client {
        respond `{"m.homeserver":{"base_url":"https://matrix.yourdomain.com"}}`
    }
}

Conduit tradeoffs: No bridges, no admin API, limited to ~100 users. Great for personal/family use. Use Synapse for team deployments with bridges.


Part 3: Create Admin Account

# With Synapse — create admin user:
docker exec synapse register_new_matrix_user \
  -c /data/homeserver.yaml \
  -u admin \
  -p your-password \
  -a \
  http://localhost:8008

Part 4: Element Web Client

services:
  element-web:
    image: vectorim/element-web:latest
    container_name: element-web
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./element-config.json:/app/config.json:ro
{
  "default_server_config": {
    "m.homeserver": {
      "base_url": "https://matrix.yourdomain.com",
      "server_name": "matrix.yourdomain.com"
    }
  },
  "brand": "Matrix",
  "default_theme": "dark",
  "room_directory": {
    "servers": ["matrix.yourdomain.com"]
  }
}
chat.yourdomain.com {
    reverse_proxy localhost:8080
}

Part 5: Bridges

Bridges connect Matrix rooms to external platforms. Users in the Matrix room see messages from Slack/Discord users and vice versa.

Discord Bridge

services:
  mautrix-discord:
    image: dock.mau.dev/mautrix/discord:latest
    container_name: mautrix-discord
    restart: unless-stopped
    volumes:
      - ./discord-bridge:/data
# Generate config:
docker run --rm -v ./discord-bridge:/data dock.mau.dev/mautrix/discord:latest

# Edit config.yaml:
#   homeserver.address: https://matrix.yourdomain.com
#   homeserver.domain: matrix.yourdomain.com
#   appservice.id: discord

# Register bridge with Synapse (add to homeserver.yaml):
# app_service_config_files:
#   - /data/discord-bridge/registration.yaml

Slack Bridge

services:
  mautrix-slack:
    image: dock.mau.dev/mautrix/slack:latest
    volumes:
      - ./slack-bridge:/data

Available bridges:

  • mautrix-discord — Discord bridge
  • mautrix-slack — Slack bridge
  • mautrix-whatsapp — WhatsApp bridge
  • mautrix-telegram — Telegram bridge
  • mautrix-signal — Signal bridge
  • mx-puppet-twitter — Twitter/X DMs

Part 6: Element Call (Video Calls)

Element Call provides WebRTC video calling without third-party infrastructure:

  1. In Element → Start a call in any room
  2. Or install Element Call standalone: git clone https://github.com/element-hq/element-call
  3. Requires a TURN server for calls outside your LAN:
services:
  coturn:
    image: coturn/coturn:latest
    network_mode: host
    command: >
      -n --log-file=stdout
      --realm=matrix.yourdomain.com
      --static-auth-secret="${TURN_SECRET}"

In homeserver.yaml:

turn_uris:
  - "turn:turn.matrix.yourdomain.com:3478?transport=udp"
  - "turn:turn.matrix.yourdomain.com:3478?transport=tcp"
turn_shared_secret: "your-turn-secret"
turn_user_lifetime: 86400000

Maintenance

# Update Synapse:
docker compose pull
docker compose up -d

# Backup:
# PostgreSQL:
docker exec synapse-db-1 pg_dump -U synapse synapse | gzip > synapse-db-$(date +%Y%m%d).sql.gz

# Media files:
tar -czf synapse-media-$(date +%Y%m%d).tar.gz \
  $(docker volume inspect synapse_synapse_data --format '{{.Mountpoint}}')/media_store

# Purge old room history (reduce database size):
# Admin API: POST /_synapse/admin/v1/purge_history/{roomId}

See all open source team communication tools at OSSAlt.com/alternatives/slack.

Comments