WIP: mastodon setup notes

note - this is a WIP while i find time to write this down

notes on installing mastodon in a self-hosted manner where the front end is largely proxied by cloudflare’s tunneld services and object storage is handled on an S3 server someplace in the cloud.

you’re gonna need the following

  • a domain with the ability to control your DNS
  • if you want to use the cloudflare biz, you’re going to need to have the DNS and the account elements setup on cloudflare in advance of this because, well, cloudflare’s going to be the front end of things.
  • S3 storage location. i’m using wasabi, but you can use S3 or cloudflare’s R2 solution adjust accordingly.
  • let’s encrypt misc. i have some funkiness associated with other home services that i stick behind a reverse proxy. if you want to have access to the mastodon service on the local LAN without going out and back for things, you’ll need to install the certs on the hosting node directly. there is some tunneling-fu that i use to handle this.
  • use the stock mastodon docker-compose.yml (available via github)
  • remove the build: references (just comment them out)
  • i bake the version into the image that i’m pulling (e.g., image: tootsuite/mastodon:4.3.0)
  • i use elasticsearch (the es instance) i need to see what’s going on with this for the rpi installation. you might not necessarily want this. i need to figure out if this is available for arm64 architecture.
services:
db:
restart: always
image: postgres:14-alpine
container_name: mstdn-postgres
shm_size: 256mb
networks:
- internal_network
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'postgres']
volumes:
- ./postgres14:/var/lib/postgresql/data
environment:
- 'POSTGRES_HOST_AUTH_METHOD=trust'
redis:
restart: always
image: redis:7-alpine
container_name: mstdn-redis
networks:
- internal_network
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
volumes:
- ./redis:/data
es:
restart: always
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4
container_name: mstdn-search
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:
- external_network
- internal_network
healthcheck:
test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
volumes:
- ./elasticsearch/data:/usr/share/elasticsearch/data
- ./elasticsearch/logs:/usr/share/elasticsearch/logs
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
ports:
- '127.0.0.1:9200:9200'
web:
image: ghcr.io/mastodon/mastodon:v4.3.0
container_name: mstdn-web
restart: always
env_file: .env.production
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
networks:
- external_network
- 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:
- ./public/system:/mastodon/public/system
streaming:
image: ghcr.io/mastodon/mastodon:v4.3.0
restart: always
container_name: mstdn-streaming
env_file: .env.production
command: node ./streaming
networks:
- external_network
- 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
sidekiq:
image: ghcr.io/mastodon/mastodon:v4.3.0
restart: always
container_name: mstdn-sidekiq
env_file: .env.production
command: bundle exec sidekiq
depends_on:
- db
- redis
networks:
- external_network
- internal_network
volumes:
- ./public/system:/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:
# - external_network
# - internal_network
#
# privoxy:
# image: sirboops/privoxy
# volumes:
# - ./priv-config:/opt/config
# networks:
# - external_network
# - internal_network
http:
restart: always
image: nginx:stable-alpine
container_name: mstdn-nginx
networks:
- external_network
- internal_network
ports:
- 443:443
- 80:80
volumes:
- ./nginx/tmp:/var/run/nginx
- ./nginx/conf.d:/etc/nginx/conf.d
- /etc/letsencrypt/:/etc/letsencrypt/
- ./nginx/webroot:/webroot
- ./nginx/cache:/cache
networks:
external_network:
internal_network:
internal: true
  • generate a password for the postgress user
    • i do this from 1password and have this integrated into my production keychain injection
docker run --rm --name postgres           \
-v <volume path>:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=<password>           \
-d <image name>