Mastodon is a relatively young social network that aims to be a privacy concious, federated Twitter. Most people opt to join one of the larger servers (tooter.today can help find one if you want) but you and I are not most people. We like our privacy and our self hosted services, so, we're going to setup a personal Mastodon server that ticks off all the boxes:
This guide assumes some basic knowledge of Linux and DNS, but I'll try to be as descriptive as possible for people who are going into this blind. If you have any questions feel free to contact me!
Here I'm going to use Digital Ocean's $10 Droplet, but feel free to substitute it with whever VPS provider you would like as long as the server you choose includes at least 1 GB of RAM. Thankfully, Digital Ocean provides "One-click Apps" which has decent defaults out of the box for various apps. For us, we'll select one that has Docker built in on top of Ubuntu 16.04 LTS to speed things up but you can follow the official docs to get yourself up and going on other VPS providers. Just be sure to install both Docker and docker-compose as we'll be using them throughout this guide.
When configuring your server, ignore block storage and choose the region closest to you (Cloudharmony can help you figure out shortest ping times if you want to get super precise). For additional options you can choose to backup your server if you're interested in the additional security and cost, but be certain to choose IPV6 and Monitoring as they're free and easy upgrades that will provide a lot of value. I also recommend adding an SSH key (Digital Ocean's official docs explain how to set up your SSH keys really well) but that's up to you.
For starting up your own server, you'll also need a domain to call your own. Just like email, Mastodon servers identify you by a username at a particular domain. If you don't have one already, I recommend Hover.com (here's my referral link) but feel free to use whatever you're comfortable with. Sign up for an account, purchase your domain and go into your domain's DNS settings. For your Mastodon server you'll need two domains:
For each of these domains you'll need:
Set those up in your console and we can confirm that this is fully functional once your server is up and running.
Mastodon has built in support for SMTP so that you can get emails when you signup and for other activity on your server. This isn't strictly necessary, but is a good practice to do especially if you want to eventually open up your server to others. If you don't have your own SMTP service, I recommend Mailgun. After signing up, they will guide you through setting up your SMTP service and link it with your domain provider so you can send emails from your domain name. If you don't end up using this, I'll point out below where you might do something a bit different.
Now we'll actually get going with the Mastodon specific parts of this guide. First we'll SSH into our server and clone down the Mastodon repository from Github.
ssh root@<your ip address here>
git clone https://github.com/tootsuite/mastodon.git
cd mastodon
To ensure we're working with stable code, we'll use git to checkout the latest release of the code. As of this writing, the latest release is v1.1.2 but you can find the latest on Mastodon's release page.
git checkout v1.5.1
You can also just run off of the latest code from Github, but be prepared for unstable behavior and contributing back on Github with bug reports and/or code.
The primary method of configuring Mastodon is through environment variables. Thankfully, we can get this done automatically for us via a .env file and Mastodon provides some sane template files for us to go off of. First, we'll copy the existing template to work off of and edit the file. You'll likely have a choice between vim and nano and nano is a great one for beginners
cp .env.production.sample .env.production
nano .env.production
Let's break down this file into the specific parts that you should edit.
These are some relatively sane defaults out of the box so we won't touch the database or Redis configuration for now.
# Service dependencies
REDIS_HOST=redis
REDIS_PORT=6379
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
DB_PASS=
DB_PORT=5432
Here we'll fill in the main domain name for your Mastodon server, which will probably be something like https://mymastodonserver.com or https://social.mymastodonserver.com
# Federation
LOCAL_DOMAIN=<Your domain here>
LOCAL_HTTPS=true
We'll generate some secrets so that Mastodon can properly encrypt our communications. Run docker-compose run --rm web rake secret
three times and fill in each of the application secret fields with a generated secret
# Application secrets
PAPERCLIP_SECRET=firstgeneratedsecrethere
SECRET_KEY_BASE=secondgeneratedsecrethere
OTP_SECRET=thirdgeneratedsecrethere
Follow this with generating your VAPID keys for push notifications using docker-compose run --rm web rake mastodon:webpush:generate_vapid_key
VAPID_PRIVATE_KEY=privatekey
VAPID_PUBLIC_KEY=publickey
This variable turns your server into Single User Mode which disables signups and causes everyone who visits your site to go automatically to your profile. We'll want this but only after we've created an account.
# Registrations
# Single user mode will disable registrations and redirect frontpage to the first profile
# We will uncomment this once our user is created
#SINGLE_USER_MODE=true
If you have an SMTP server (such as the Mailgun account you might have created earlier) then fill this out with your account information. If you don't, leave these unchanged.
# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=<Your Login here>
SMTP_PASSWORD=<Your password here>
SMTP_FROM_ADDRESS=notifications@example.com
Earlier you setup a subdomain for your Mastodon streaming API. Be sure to fill this field in with that domain with the https protocol. I personally encountered issues when I didn't do this on my server but this is theoretically optional.
STREAMING_API_BASE_URL=https://<your domain here>
If you are using nano, you can now save and exit the file by typing Ctrl + O to Save and Ctrl + X to exit.
By default anything in Docker's filesystem that isn't mounted on your main computer is deleted when Docker is stopped. To prevent data loss, uncomment the lines in your docker-compose.yml file that define the volumes for your db and redis containers will use.
version: '3'
services:
db:
restart: always
image: postgres:alpine
volumes:
- ./postgres:/var/lib/postgresql/data
redis:
restart: always
image: redis:alpine
volumes:
- ./redis:/data
By default Mastodon communicates on ports 3000 and 4000, which means that if the server was currently running and we visited our domain we would have to go to http://mymastodonserver.com:3000 in order to see anything. That's rather annoying, so we'll use a reverse proxy server to route traffic into each of our containers when we visit the appropriate domain. First we'll setup the container that will do this for us
Add the following lines to the end of your docker-compose file
nginx-proxy:
image: jwilder/nginx-proxy:latest
container_name: nginx-proxy
ports:
- "443:443"
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock
- /data/certs:/etc/nginx/certs:ro
- /data/config/nginx-proxy/html/:/usr/share/nginx/html
- /data/config/nginx-proxy/vhost.d/:/etc/nginx/vhost.d
- /data/config/nginx-proxy/conf.d/:/etc/nginx/conf.d
restart: always
environment:
- ENABLE_IPV6=true
This container will look at the other containers we have setup and utilize environment variables that have been defined to properly determine where to route traffic. We could do that in the .env.production file but I would rather keep all of the configuration for Docker in our docker-compose.yml file. Before that, we'll change the web container to only expose port 3000 instead of opening that port to the outside world.
web:
restart: always
build: .
env_file: .env.production
command: bundle exec rails s -p 3000 -b '0.0.0.0'
expose:
- "3000"
depends_on:
- db
- redis
volumes:
- ./public/assets:/mastodon/public/assets
- ./public/packs:/mastodon/public/packs
- ./public/system:/mastodon/public/system
Then we'll add the environment variables on the web and streaming containers so Nginx knows where the route internet traffic. Add a environment
config to each container with the variables VIRTUAL_HOST
defined. In this example we'll also define the optional environment variable VIRTUAL_PORT
that points to the exposed port on each container.
web:
restart: always
build: .
env_file: .env.production
command: bundle exec rails s -p 3000 -b '0.0.0.0'
environment:
- VIRTUAL_HOST=<your main mastodon server domain name>
- VIRTUAL_PORT=3000
expose:
- "3000"
depends_on:
- db
- redis
volumes:
- ./public/assets:/mastodon/public/assets
- ./public/packs:/mastodon/public/packs
- ./public/system:/mastodon/public/system
streaming:
restart: always
build: .
env_file: .env.production
command: npm run start
environment:
- VIRTUAL_HOST=<your streaming server domain name>
- VIRTUAL_PORT=4000
ports:
- "4000:4000"
depends_on:
- db
- redis
At this point, if we were to launch the servers we would be able to view them at http://mymastodonserver.com. Unfortunately we would not be able to view them using https because we don't have a certificate to encrypt traffic. Since we don't want people spying on our server, we'll do that next.
Let's Encrypt is a free and automatic way to get SSL certificates and just like Nginx there's a container that will handle this automatically for us. Append the following lines to your docker-compose.yml file to create that container.
letsencrypt:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt
volumes_from:
- nginx-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /data/certs:/etc/nginx/certs:rw
Just like the Nginx container, this utilizes environment variables to know which container is matched to which SSL certificate. Add the LETSENCRYPT_HOST
and LETSENCRYPT_EMAIL
variables to our environment block. LETSENCRYPT_EMAIL
in this case refers to your personal email.
web:
restart: always
build: .
env_file: .env.production
command: bundle exec rails s -p 3000 -b '0.0.0.0'
environment:
- VIRTUAL_HOST=<your main mastodon server domain name>
- VIRTUAL_PORT=3000
- LETSENCRYPT_HOST=<your main mastodon server domain name>
- LETSENCRYPT_EMAIL=<your email>
expose:
- "3000"
depends_on:
- db
- redis
volumes:
- ./public/assets:/mastodon/public/assets
- ./public/packs:/mastodon/public/packs
- ./public/system:/mastodon/public/system
streaming:
restart: always
build: .
env_file: .env.production
command: npm run start
environment:
- VIRTUAL_HOST=<your streaming server domain name>
- VIRTUAL_PORT=4000
- LETSENCRYPT_HOST=<your streaming server domain name>
- LETSENCRYPT_EMAIL=<your email>
ports:
- "4000:4000"
depends_on:
- db
- redis
We're almost done!
Mastodon requires us to do some final setup that needs to be run each time Mastodon is updated or setup for the first time. We'll setup the database, precompile the assets to be served and actually start our server for the first time.
docker-compose run --rm web rails db:migrate
docker-compose run --rm web rails assets:precompile
docker-compose up # or docker-compose up --force-recreate if updating Mastodon
This will take some time, but after it finishes you should see a stream of logs from Docker that details the instantiation of each container. Wait about 15 minutes after that starts and you should have access to your Mastodon instance at your domain! Once you've confirmed this works, you can exit the logs by typing Ctrl + C and then run your server in the background by running
docker-compose up -d
This will prevent your server from being killed whenever you close your terminal.
If you have the unfortunate case that your server isn't up and running at your domain, look back over this guide and confirm that all of your configurations are correct. If you don't see what's wrong, it's possible that there's a problem with this guide or it's outdated. Look over the Official Mastodon docs and their Production Guide and be sure to let me know if you see any problems.
Now you can go to your domain and signup for an account. Once you've entered your desired username and password you should receive an email to confirm your account. If you didn't:
docker-compose logs
to see if you can find the errorIn order to confirm your user without an email run:
docker-compose run web rails mastodon:confirm_email USER_EMAIL=<your email here>
To make yourself an admin, run the following command
docker-compose run web rails mastodon:make_admin USERNAME=<your username>
Making yourself an admin allows some greater control over your server as well as some additional diagnostics that may help if you're encountering an error.
Final step is to enable Single User Mode. Modify your .env.production file and uncomment this line to enable it
SINGLE_USER_MODE=true
Now any user that visits your server will be automatically sent to your profile and signups will be prevented. Congratulations! You now have a fully functional server!
Hopefully this was a helpful guide to getting you setup with your own selfhosted Mastodon instance. You can now enjoy totaly prviacy and control over your own account. If you have any feedback or questions, feel free to ping me at donniewest@social.donniewest.com