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
# 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
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_ADDRESSemail@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_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 logsto see if you can find the error
In 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
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 firstname.lastname@example.org