Mastodon is the leading self-hosted, federated social network — but it's one of the more involved apps to operate. It isn't a single process; it's a web app, a streaming server, background workers (Sidekiq), PostgreSQL, Redis, and object storage for media. This guide breaks down the architecture and deploys each component.
Be warned upfront: Mastodon is resource-hungry and federation generates real ongoing traffic. This is a weekend-project-plus, not a five-minute deploy.
Mastodon's architecture
| Component | Role | Scales by |
|---|---|---|
| Web (Puma) | HTTP API + web UI | Replicas |
| Streaming (Node) | Real-time timelines over WebSocket | Replicas |
| Sidekiq | Background jobs: federation, push, media | Replicas / queues |
| PostgreSQL | Primary datastore | Managed instance |
| Redis | Cache + Sidekiq queue | Managed instance |
| Object storage | Media (avatars, attachments) | S3-compatible |
All three app processes run from the same Mastodon image with different commands.
Step 1: Provision data stores first
On [PandaStack](https://dashboard.pandastack.io), create:
- A managed PostgreSQL (16.x recommended).
- A managed Redis.
- An S3-compatible bucket for media (object storage).
Mastodon needs a real, durable PostgreSQL — its whole social graph lives there. Use a tier with adequate storage and connections from the start; the free-tier dev database is too small for a real instance.
Step 2: Generate secrets
Mastodon needs several secrets. Generate them once using the official image:
docker run --rm tootsuite/mastodon:v4.3.0 bundle exec rake secret # SECRET_KEY_BASE
docker run --rm tootsuite/mastodon:v4.3.0 bundle exec rake secret # OTP_SECRET
# VAPID keys for web push:
docker run --rm tootsuite/mastodon:v4.3.0 bundle exec rake mastodon:webpush:generate_vapid_keyMastodon 4.3+ also uses ACTIVE_RECORD_ENCRYPTION_* keys; generate them with the provided rake task.
Step 3: Build the environment file
Key environment variables (store secrets as secret env vars, never in git):
LOCAL_DOMAIN=social.example.com
WEB_DOMAIN=social.example.com
DB_HOST=<managed-postgres-host>
DB_PORT=5432
DB_NAME=mastodon
DB_USER=<user>
DB_PASS=<password>
REDIS_URL=redis://:<password>@<redis-host>:6379
SECRET_KEY_BASE=<generated>
OTP_SECRET=<generated>
VAPID_PRIVATE_KEY=<generated>
VAPID_PUBLIC_KEY=<generated>
# Object storage for media
S3_ENABLED=true
S3_BUCKET=<bucket>
S3_ENDPOINT=<endpoint>
AWS_ACCESS_KEY_ID=<key>
AWS_SECRET_ACCESS_KEY=<secret>
# SMTP for email confirmations
SMTP_SERVER=<smtp-host>
SMTP_PORT=587
SMTP_LOGIN=<user>
SMTP_PASSWORD=<password>
SMTP_FROM_ADDRESS=notifications@example.comLOCAL_DOMAIN is permanent — it's baked into every federated identity. Choose it carefully; you cannot change it later without breaking federation.
Step 4: Initialize the database
Before the app can serve traffic, run the schema setup once (as a pre-deploy/init job using the Mastodon image):
bundle exec rails db:migrateFor a fresh install, rails db:setup (or the guided mastodon:setup) initializes the database.
Step 5: Deploy the three processes
Deploy three container apps, all from tootsuite/mastodon:v4.3.0, each with a different command:
Web:
bundle exec puma -C config/puma.rb
# expose port 3000Streaming:
node ./streaming
# expose port 4000Sidekiq (workers):
bundle exec sidekiq
# no inbound portAll three share the same environment variables and connect to the same PostgreSQL/Redis.
Step 6: Route the domains
Mastodon expects:
/api/v1/streamingto route to the streaming service (port 4000).- Everything else to route to the web service (port 3000).
Configure your ingress/routing so the streaming path goes to the streaming app and the rest goes to web. PandaStack's Kong ingress handles WebSocket upgrades, which the streaming server requires.
Point your DNS at the platform and add the custom domain for automatic SSL. Mastodon strictly requires HTTPS.
Step 7: Create your admin account
Once running, create and promote an admin via the Mastodon image:
bundle exec bin/tootctl accounts create alice --email alice@example.com --confirmed --role OwnerOperating realities — read before committing
- Federation is chatty. Following active accounts pulls a steady stream of inbound activity that Sidekiq processes. Scale Sidekiq replicas if queues back up.
- Media grows fast. Cached remote media accumulates; run
tootctl media removeperiodically and rely on object storage, not container disk. - Sidekiq queues matter. Mastodon uses multiple queues (
default,push,pull,mailers,ingress). For larger instances, run dedicated Sidekiq apps per queue so a backlog in one doesn't starve others. - Backups are non-negotiable. Your PostgreSQL holds irreplaceable social data; use scheduled backups and verify restores.
References
- [Mastodon installation docs](https://docs.joinmastodon.org/admin/install/)
- [Mastodon Docker / config](https://docs.joinmastodon.org/admin/config/)
- [Scaling Mastodon (Sidekiq queues)](https://docs.joinmastodon.org/admin/scaling/)
- [tootctl reference](https://docs.joinmastodon.org/admin/tootctl/)
---
Mastodon rewards the effort with full ownership of your corner of the fediverse, but it genuinely needs managed PostgreSQL, Redis, object storage, and multiple always-on processes. PandaStack provisions the data stores and runs the web/streaming/Sidekiq apps together. If you want to experiment first, the free tier covers the data stores for a small test instance: [dashboard.pandastack.io](https://dashboard.pandastack.io).