How to run a Mastodon server using Docker (and docker-compose)
This is based on this guide that I used to set my Mastodon server, but without the parts about building from source, and with some parts I was able to smooth out in my experience.
What you need to start
Ensure you have a machine that is running a recent, stable version of docker and docker-compose. If you're not there yet, try this link.
Make a folder that will be the base for all the volumes that we're going to create. I called this mastodon
, and put it in the same folder as all the other folders for services that I run via docker. Change into this directlry so that all the subsequent commands will be in that scope. Get the default docker-compose.yml file to start with and put it in your mastodon
folder. I used this one from the guide linked at the top.
Choosing a Mastodon image
In general, i think it's good to take the latest and greatest version of whatever software you're going to use. Some servers will be using the release candidate of the up-and-coming v4.0.0 version of Mastodon, but I opted to use the current stable release of v3.5.3, and not build mastodon from source, so I changed the lines that read:
build: .
image: tootsuite/mastodon
to read the following instead
image: tootsuite/mastodon:v3.5.3
Having a tag at the end of the image name will specifically reference a particular tagged instance of that image. without a tag, you'll get tootsuite/mastodon:latest
by default. I chose my particular tag because I don't want the underlying image to change without me specifically changing it. the latest
tag will change whenever a new stable version is released, but since there are upgrade procedures associated with each new version, I want upgrading to be a manual process that do purposefully.
Docker Configuration
Get the sample environment variable file from the repository. Save it in your mastodon
folder as .env.production
(i.e., remove .sample
from the end of the filename). Don't worry about the particulars yet...we'll get to that.
Make the following folders under your mastodon
folder:
-
postgres14
-
redis
-
elasticsearch
(only if you plan to use elastic search) -
system
-
assets
Each of these folders will be used to hold some persistent data that the containers will produce and use. If you don't persist data in containers, then it disappears with the container.
I modified the docker-compose.yml
file here to reference my folders so instead of
volumes:
- ./public/system:/mastodon/public/system
on the containers that use the tootsute\mastodon
image, we'll have
volumes:
- ./system:/mastodon/public/system
- ./assets:/mastodon/public/assets
Lastly, we need to set these folders to be owned by the user that is going to run the mastodon services. The following command will do that:
sudo chown -R 911:911 ./system ./assets
This may ask you for your root password.
Mastodon configuration
Finally we're ready to run something. The following command will start up the web
container and its dependencies in order to run mastodon's setup script. You should be able to answer with the default for most things. I had to change the default database name from postgres
to mastodon_production
to match what is in the docker-compose.yml
file.
docker-compose run --rm web rake: mastodon:setup
Run your mastodon server!
Run the entire stack of containers with the command
docker-compose up -d
The -d
puts the containers in the background to run as a daemon, but if you wish to run the stack synchronously to see any errors that might occur, you can omit the -d
.
With the docker-compose.yml
file as it currently is, The website is exposed only on localhost, so you won't be able to even access it over the network. This is intended for a reverse proxy to serve it up to the public, I would highly recommend this because Mastodon doesn't do SSL by default. I'm not going to explain how to do this here, because that deserves its own tutorial, but there are many of them online. You can choose between one of several different bits of software that will all do the trick. In my case, my reverse proxy is on a separate machine, so I needed to expose the mastodon port to the local network. In order to do this, I changed the port configuration of web
container from
ports:
- '127.0.0.1:3000:3000'
to
ports:
- 8030:3000
because I wanted to expose mastodon to all network interfaces on the machine using the public port 8030, while keeping the docker container's port as 3000.
Running docker-compose up -d
will refresh all containers with changed configuration.
Upgrading mastodon
There will come a time when you want to upgrade your mastodon server to a newer version. However, don't get hasty. The first thing you'll always want to do is back up your mastodon
folder. You can do this easily by tarring and gzipping everything into an archive file. First, bring your mastodon server down so we're not trying to zip up a database that is currently changing.
docker-compose down
Then zip up the current mastodon
folder and save it as a tar.gz file in the parent folder.
tar -zcf ../mastodon.tar.gz .
Next, edit the docker-compose.yml
file to reflect the new tag for the version you're upgrading to. Finally we'll need to run some commands to download the new image(s), upgrade the database, and precompile the assets.
docker-compose pull
docker-compose run --rm web rake db:migrate
docker-compose run --rm web rake assets:precompile
Then a final docker-compose up -d
and we should running the upgraded version.
If I've missed anything, or made an grevious errors, please let me know. You can find me on mastodon at @steve@social.dinn.ca
Appendix: My docker-compose.yml
version: '3'
networks:
external_network:
internal_network:
internal: true
services:
db:
restart: always
hostname: db
image: postgres:14-alpine
shm_size: 256mb
networks:
- internal_network
volumes:
- ./postgres14:/var/lib/postgresql/data
environment:
POSTGRES_HOST_AUTH_METHOD: "trust"
POSTGRES_DB: "mastodon_production"
POSTGRES_USER: "mastodon"
POSTGRES_PASSWORD: "[Initial Postgres password]"
redis:
restart: always
hostname: redis
image: redis:7-alpine
networks:
- internal_network
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
volumes:
- ./redis:/data
es:
restart: always
hostname: es
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:
- external_network
- internal_network
healthcheck:
test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
volumes:
- ./elasticsearch:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
ports:
# Don't think this port needs to be exposed
- '9200:9200'
web:
#build: .
hostname: mastodon-web
image: tootsuite/mastodon:v3.5.3
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
ports:
- '8030:3000'
depends_on:
- db
- redis
- es
volumes:
- ./system:/mastodon/public/system
- ./assets:/mastodon/public/assets
environment:
RAILS_ENV: "production"
NODE_ENV: "production"
streaming:
#build: .
hostname: mastodon-streaming
image: tootsuite/mastodon:v3.5.3
restart: "no"
env_file: .env.production
command: node ./streaming
networks:
- external_network
- internal_network
volumes:
- ./system:/mastodon/public/system
- ./assets:/mastodon/public/assets
environment:
RAILS_ENV: "production"
NODE_ENV: "production"
ports:
# Don't think this port needs to be exposed.
- '8031:4000'
depends_on:
- db
- redis
sidekiq:
#build: .
hostname: mastodon-sidekiq
image: tootsuite/mastodon:v3.5.3
restart: always
env_file: .env.production
command: bundle exec sidekiq
depends_on:
- db
- redis
networks:
- external_network
- internal_network
volumes:
- ./system:/mastodon/public/system
- ./assets:/mastodon/public/assets
environment:
RAILS_ENV: "production"
NODE_ENV: "production"