Some years ago, I would like to setup Mastodon on my own server to try it but I gave up because I thought it was too complicated.
I re-tried and used a docker stack to do it. Let’s see how.
Prerequisites
I assume you have docker and docker-compose installed, with a reverse-proxy like traefik, haproxy, nginx, or anything else in front to handle your SSL connection.
docker-compose file
You will find a docker-compose.yaml
file sample on Mastodon’s github repository: https://github.com/mastodon/mastodon/blob/main/docker-compose.yml
I updated it to fit my needs:
- Use official mastodon image with a tag instead of building my own image
- Use docker volumes instead of mount local directories
- Define labels for my local traefik instance (for web and for streaming setvices)
- Create a dedicated network called mastodon instead of their
external_network
Here is my docker-compose.yaml
:
version: '3'
services:
db:
restart: always
image: postgres:14-alpine
shm_size: 256mb
networks:
- internal_network
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'postgres']
volumes:
- mastodon_pg:/var/lib/postgresql/data
environment:
- 'POSTGRES_HOST_AUTH_METHOD=trust'
- 'POSTGRES_USER=mastodon'
- 'POSTGRES_PASSWORD=a-strong-password'
redis:
restart: always
image: redis:7-alpine
networks:
- internal_network
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
volumes:
- mastodon_redis:/data
es:
restart: always
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4
environment:
- "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
- "xpack.license.self_generated.type=basic"
- "xpack.security.enabled=false"
- "xpack.watcher.enabled=false"
- "xpack.graph.enabled=false"
- "xpack.ml.enabled=false"
- "bootstrap.memory_lock=true"
- "cluster.name=es-mastodon"
- "discovery.type=single-node"
- "thread_pool.write.queue_size=1000"
networks:
- mastodon
- internal_network
healthcheck:
test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
volumes:
- mastodon_es:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
#ports:
# - '127.0.0.1:9200:9200'
web:
image: tootsuite/mastodon:v4.0.2
restart: always
env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
networks:
- mastodon
- internal_network
healthcheck:
# prettier-ignore
test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
#ports:
# - '127.0.0.1:3000:3000'
depends_on:
- db
- redis
- es
volumes:
- mastodon_public_system:/opt/mastodon/public/system
labels:
traefik.enable: "true"
traefik.http.routers.mastodon-http.entrypoints: "web"
traefik.http.routers.mastodon-http.rule: "Host(`mastodon.open-web.fr`)"
traefik.http.routers.mastodon-http.middlewares: "SslHeader@file"
traefik.http.routers.mastodon-https.middlewares: "SslHeader@file"
traefik.http.routers.mastodon-https.entrypoints: "websecure"
traefik.http.routers.mastodon-https.rule: "Host(`mastodon.open-web.fr`)"
traefik.http.routers.mastodon-https.tls: "true"
traefik.http.routers.mastodon-https.tls.certresolver: "letsencrypt"
traefik.http.services.mastodon-https.loadbalancer.server.port: 3000
streaming:
image: tootsuite/mastodon:v4.0.2
restart: always
env_file: .env.production
command: node ./streaming
networks:
- mastodon
- internal_network
healthcheck:
# prettier-ignore
test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
#ports:
# - '127.0.0.1:4000:4000'
depends_on:
- db
- redis
labels:
traefik.enable: "true"
traefik.http.routers.mastodonstream-http.entrypoints: "web"
traefik.http.routers.mastodonstream-http.rule: "(Host(`mastodon.open-web.fr`) && PathPrefix(`/api/v1/streaming`))"
traefik.http.routers.mastodonstream-http.middlewares: "SslHeader@file"
traefik.http.routers.mastodonstream-https.middlewares: "SslHeader@file"
traefik.http.routers.mastodonstream-https.entrypoints: "websecure"
traefik.http.routers.mastodonstream-https.rule: "(Host(`mastodon.open-web.fr`) && PathPrefix(`/api/v1/streaming`))"
traefik.http.routers.mastodonstream-https.tls: "true"
traefik.http.routers.mastodonstream-https.tls.certresolver: "letsencrypt"
traefik.http.services.mastodonstream-https.loadbalancer.server.port: 4000
sidekiq:
image: tootsuite/mastodon:v4.0.2
restart: always
env_file: .env.production
command: bundle exec sidekiq
depends_on:
- db
- redis
networks:
- mastodon
- internal_network
volumes:
- mastodon_public_system:/opt/mastodon/public/system
healthcheck:
test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
## Uncomment to enable federation with tor instances along with adding the following ENV variables
## http_proxy=http://privoxy:8118
## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
# tor:
# image: sirboops/tor
# networks:
# - mastodon
# - internal_network
#
# privoxy:
# image: sirboops/privoxy
# volumes:
# - ./priv-config:/opt/config
# networks:
# - mastodon
# - internal_network
volumes:
mastodon_es:
mastodon_public_system:
mastodon_pg:
mastodon_redis:
networks:
mastodon:
external:
name: mastodon
internal_network:
internal: tru
sysctl configuration for elasticsearch
To let elasticsearch works, you need to define vm.max_map_count
sysctl setting. Create a /etc/sysctl.d/99-mastodon-es.conf
file:
cat << EOF > /etc/sysctl.d/99-mastodon-es.conf
vm.max_map_count = 262144
EOF
Then apply the new configuration:
sysctl --system
First setup
Before launch docker-compose up -d
, you need to initialize the database and generate a .env.production
file.
Create an empty .env.production
file, we will fill it later
touch .env.production
Next, in the command below:
- You will reply to some questions such as your instance url, name, etc.
- You will populate your database
- Your
.env.production
will be generated and displayed
Let’s go:
docker-compose run --rm web bash -c "bundle exec rake mastodon:setup && cat .env.production"
The above command will display your .env.production
content. Fill your .env.production
file with this :-)
Launch your instance
You can now launch your mastodon instance with docker-compose up -d
Backup your instance
Having a backup strategy is very important! Read the fucking documentation for this: https://docs.joinmastodon.org/admin/backups/
Upgrade your instance
It is also important to let your instance up-to-date. Enable notification from the Mastodon Github Repository and read the releases notes for upgrade instructions.
Most of the time, upgrade process will be to increase your mastodon docker image version then run these commands:
docker-compose pull
docker-compose run --rm -e SKIP_POST_DEPLOYMENT_MIGRATIONS=true web rails db:migrate
docker-compose run --rm web rails db:migrate
docker-compose up -d