Deploy Mastodon
Mastodon is a more demanding deployment than most self-host apps — multiple services, ActivityPub federation, media storage. This guide gets you a single-user or small-community instance on dockmesh.
What you’ll end up with
Section titled “What you’ll end up with”- Mastodon at
social.example.com - PostgreSQL database
- Redis for caching + streaming
- Elasticsearch for full-text search (optional but recommended)
- S3-compatible media storage (off-host, essential)
- SMTP for email confirmations
- Backups of DB + user uploads
Prerequisites
Section titled “Prerequisites”- Host with 4 GB RAM minimum, 8 GB comfortable
- S3-compatible bucket (Wasabi, B2, MinIO) for media — typical usage is 20-100 GB
- SMTP account (Postmark, Mailgun, SendGrid, or your own mail server)
- DNS:
social.example.compointing to your host
Generate secrets first
Section titled “Generate secrets first”Before deploying, run on any machine with Ruby:
docker run --rm -it ghcr.io/mastodon/mastodon:v4.2 bin/rake secret# Run 4 times, saving each output as:# SECRET_KEY_BASE, OTP_SECRET, VAPID_PRIVATE_KEY (from different rake task), VAPID_PUBLIC_KEYFor VAPID keys:
docker run --rm -it ghcr.io/mastodon/mastodon:v4.2 bin/rake mastodon:webpush:generate_vapid_keySave these — you’ll paste them into environment variables.
Compose file
Section titled “Compose file”Stacks → New stack → name mastodon:
services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: mastodon_production POSTGRES_USER: mastodon POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - db:/var/lib/postgresql/data
redis: image: redis:7-alpine restart: unless-stopped volumes: - redis:/data
es: image: docker.elastic.co/elasticsearch/elasticsearch:7.17.22 restart: unless-stopped environment: discovery.type: single-node xpack.security.enabled: "false" ES_JAVA_OPTS: "-Xms512m -Xmx512m" volumes: - es:/usr/share/elasticsearch/data
web: image: ghcr.io/mastodon/mastodon:v4.2 restart: unless-stopped command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000" depends_on: [db, redis, es] env_file: /opt/dockmesh/stacks/local/mastodon/.env.production volumes: - uploads:/mastodon/public/system
streaming: image: ghcr.io/mastodon/mastodon:v4.2 restart: unless-stopped command: node ./streaming depends_on: [db, redis] env_file: /opt/dockmesh/stacks/local/mastodon/.env.production
sidekiq: image: ghcr.io/mastodon/mastodon:v4.2 restart: unless-stopped command: bundle exec sidekiq depends_on: [db, redis] env_file: /opt/dockmesh/stacks/local/mastodon/.env.production volumes: - uploads:/mastodon/public/system
volumes: db: redis: es: uploads:.env.production
Section titled “.env.production”Mastodon reads its config from an env file. Create /opt/dockmesh/stacks/local/mastodon/.env.production:
LOCAL_DOMAIN=social.example.comWEB_DOMAIN=social.example.comSINGLE_USER_MODE=false
SECRET_KEY_BASE=<generated above>OTP_SECRET=<generated above>
VAPID_PRIVATE_KEY=<generated above>VAPID_PUBLIC_KEY=<generated above>
DB_HOST=dbDB_USER=mastodonDB_NAME=mastodon_productionDB_PASS=<same as DB_PASSWORD in stack env>DB_PORT=5432
REDIS_HOST=redisREDIS_PORT=6379
ES_ENABLED=trueES_HOST=esES_PORT=9200
# S3S3_ENABLED=trueS3_BUCKET=mastodon-mediaAWS_ACCESS_KEY_ID=<your S3 key>AWS_SECRET_ACCESS_KEY=<your S3 secret>S3_PROTOCOL=httpsS3_HOSTNAME=s3.us-west-001.backblazeb2.comS3_ENDPOINT=https://s3.us-west-001.backblazeb2.comS3_ALIAS_HOST=media.social.example.com
# SMTPSMTP_SERVER=smtp.postmarkapp.comSMTP_PORT=587SMTP_LOGIN=<your smtp user>SMTP_PASSWORD=<your smtp pass>SMTP_FROM_ADDRESS=social@example.comInitial DB setup
Section titled “Initial DB setup”Before deploying the stack, initialize the database:
# On the dockmesh hostdocker run --rm --network mastodon_default \ --env-file /opt/dockmesh/stacks/local/mastodon/.env.production \ ghcr.io/mastodon/mastodon:v4.2 \ bundle exec rails db:setup(You’ll need to create the network first — or deploy once, let it fail, then run this.)
Deploy
Section titled “Deploy”Deploy the stack. Watch for each container to become healthy:
db— immediateredis— immediatees— ~30s (memory-hungry, watch it doesn’t OOM)web— ~60s (Rails boot)streaming— ~15ssidekiq— ~15s
Reverse proxy
Section titled “Reverse proxy”Stack → Proxy → Add route:
- Domain:
social.example.com - Target:
mastodon_web_1 - Port:
3000 - TLS: Automatic
Then a second route for streaming:
- Domain:
social.example.com - Path:
/api/v1/streaming - Target:
mastodon_streaming_1 - Port:
4000 - TLS: Automatic
Streaming needs WebSocket passthrough — Caddy does this by default.
First admin user
Section titled “First admin user”docker exec -it mastodon_web_1 bin/tootctl accounts create admin \ --email you@example.com --confirmed --role OwnerSave the auto-generated password it prints. Log in at https://social.example.com/auth/sign_in.
Backups
Section titled “Backups”Mastodon is multi-volume:
db— PostgreSQL data (usepg_dumpallhook)redis— OK to skip (volatile cache)es— OK to skip (rebuilds from DB)uploads— critical if you haven’t gone all-in on S3 (old media)
Plus .env.production — back it up separately (contains secrets).
S3 media is backed up by the S3 provider’s own versioning — enable that on your bucket.
Tuning
Section titled “Tuning”Default memory: 2-3 GB for a small instance. For hundreds of users, scale sidekiq workers:
Scaling tab → sidekiq → set replicas to 3-5. Each worker processes a queue subset.
Ongoing maintenance
Section titled “Ongoing maintenance”# Weekly — clean up old cached media from remote instancesdocker exec -it mastodon_web_1 bin/tootctl media remove --days=30
# Monthly — remove accounts the instance no longer knows aboutdocker exec -it mastodon_web_1 bin/tootctl accounts cull
# After federation issues — refresh relationshipsdocker exec -it mastodon_web_1 bin/tootctl accounts refreshSee also
Section titled “See also”- Smart Scaling — scaling Sidekiq workers under load
- Backup & Restore — multi-volume strategy
- Monitoring Stack — add Prometheus + Grafana to watch Mastodon metrics