Instructions for hosting a small Mastodon server instance for free (well, almost) using docker compose
Mastodon is a micro-blogging application (similar in style to Twitter) that uses ActivityPub as the federation protocol on the backend. Mastodon is just one of several social networking applications in the Fediverse. You can learn more at fediverse.party.
This document provides step-by-step instructions for hosting an instance of Mastodon for almost no cost (under $11USD for the first 2 years).
I tried to follow numerous instructional documents that had been assembled over the years, but I needed to combine (and tweak) steps from each of these documents in order to arrive at a working configuration. The steps explained below were drawn from the following documents, and I thank each of the authors for their contributions to this space.
https://peterbabic.dev/blog/running-mastodon-with-docker-compose/
https://www.howtoforge.com/how-to-install-mastodon-social-network-with-docker-on-ubuntu-1804/
https://sleeplessbeastie.eu/2022/05/02/how-to-take-advantage-of-docker-to-install-mastodon/
https://mpp-service.de/en/mastodon-character-limit-increase-in-a-docker-environment/
Familiarity with Linux, domain registration, DNS management, and web hosting, including SSL encryption.
ssh-keygen in Linux)Register at https://cloudflare.com
Let’s Encrypt offers free SSL certificates for domain owners, but they do not make it easy get certificates for Top Level Domains (TLDs) you can get for free at various websites. I prefer the easy web UI method, and that requires a domain with a non-free TLD. Namecheap has been selling domains ending in .xyz for $10.38 for the first 2 years and they work fine for this. Domains that end in .social cost just a little more and tend to be more popular on the Fediverse.
Cloudflare will give you the two DNS servers assigned to manage your account. The process to change the DNS servers managing your domain will vary by registrar and is done at your registrar’s site. Update the DNS servers at your registrar to point to the two servers given to you by Cloudflare.
Note: You must set Shields Down for this site if you use the Brave browser. The website will not function properly with the shields up.
Ensure that you have IPv4 https ingress enabled for your virtual cloud network. In Oracle Cloud, from the main menu
mastodon if you do not plan to host other Fedi services; use fediverse if you doEdit in the Image and Shape sectionChange image button next to Oracle Linux 8Select image at the bottomChange shape button next to AMD VM StandardAmpere from the Shape Series options listShape name listSelect Shape at the bottomNetworking section of the configuration panel~/.ssh/id_rsa.pub)Create at the bottomA for the record typefediverse for the hostname, and paste the IP address for your cloud server instanceDNS Only for the Proxy status optionCNAME for the record typemstdn for the hostname and fediverse. followed by the full domain name you purchased (i.e. fediverse.mydomain.com)DNS Only for the Proxy status optiondocker and docker-composedocker installationssh ubuntu@mstdn.<mydomain.tld> (replace <mydomain.tld> with the domain you purchased)
Note that if the name fails to resolve within 10 minutes after updating your DNS, double check your work at Cloudflare
sudo apt update && sudo apt dist-upgrade -yOk then <tab> and <enter> on the next screensudo rebootssh ubuntu@mstdn.mydomain.com (again, replacing mydomain.com)Note: If the upgrade does not proceed through to the end as expected, you may need to destroy and recreate the image. This happened to me once while developing these instructions.
dockerInstall dependencies:
sudo apt install ca-certificates curl ufw apt-transport-https software-properties-common git -yConfigure firewall:
sudo ufw allow OpenSSHsudo ufw enablesudo ufw allow httpsudo ufw allow httpssudo ufw statusto verify the ufw status
Install docker for linux as per the instructions at https://docs.docker.com/engine/install/ubuntu/
Add the GPG key for the docker repository to apt:
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Add the docker repository:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the package index
sudo apt-get update
Note: I do not like to add my non-root user to the docker group as this grants broad powers to the user. Just use sudo for commands that require root privileges, such as docker and docker compose.
Install Docker Engine, containerd, and Docker Compose:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Run sudo docker run hello-world and check that docker hello world runs
Run docker compose version and ensure the current version number is returned
mastodonmkdir mastodoncd mastodonwget https://raw.githubusercontent.com/mastodon/mastodon/main/docker-compose.ymldocker-compose.yml for editing using vi or nano, whichever you’re comfortable usingbuild: .: sed -i 's/build: ./#build: ./' docker-compose.ymldate | md5sumYou will see output like this:
e351f61406c8ba6bdc489fdc4606c7c3 -
Copy the long string into your clipboard. Do not include the space or - at the end. You will paste it where you see <pg pwd> below (two places!)
sudo docker run --name postgres14 -v /home/ubuntu/mastodon/postgres14:/var/lib/postgresql/data -e POSTGRES_PASSWORD=<pg pwd> --rm -d postgres:14-alpine
sudo docker exec -it postgres14 psql -U postgres
At the postgres=# command line, run:
CREATE USER mastodon WITH PASSWORD '<pg pwd>' CREATEDB;
Note: include the single quotes ' in the above command. Look for CREATE ROLE in the response. If successful, type exit.
Next, stop the container:
sudo docker stop postgres14
Prepare the configuration file:
touch .env.productionssh into your host againStart the web application setup:
sudo docker compose run --rm -e DISABLE_DATABASE_ENVIRONMENT_CHECK=1 web bundle exec rake mastodon:setupAnswer the prompts:
Domain name: - enter mstdn.<yourdomain.tld> replacing <yourdomain.tld> with the domain name you purchasedHit <Enter> to accept all of the defauts below:
Single user mode - hit enter for (N)oAre you using Docker? - hit enter for (Y)esPostgreSQL host - hit enter for the defaultPostgreSQL port - hit enter for the defaultName of PostgreSQL database - hit enter for the defualtName of PostgreSQL user - hit enter for the defaultPassword of PostgreSQL user - copy/paste in your PostgreSQL password generated earlierRedis host - hit enter for the defaultRedis port - hit enter for the defaultRedis password - hit enter for the defaultStore uploaded files on the cloud? - hit enter for the defaultEmail configuration - Continue hitting Enter on the defaults:
Send emails from localhost? - hit enter for the defaultSMTP server - hit enter for the defaultSMTP port - hit enter for the defaultSMTP username - hit enter for the defaultSMTP password - hit enter for the defaultSMTP authentication - hit enter for the defaultSMTP OpenSSL verify mode - hit enter for the default noneEnable STARTTLS - hit enter for the default autoFrom for notification emails - hit enter for the defaultHit n when asked to send a test e-mail. The configuration above is invalid and will not work. That’s okay.
Send a test e-mail now? - hit N for No
Enter n when asked about receiving important updates.
You will see a notice that it will be written to .env.production. Hit enter to display the contents.
When you see the suggested contents of .env.production shown, WAIT
.env.production into the clipboardssh sessioncd mastodon.env.production with vi or nano, whichever you’re comfortable using.env.production from the clipboardCtrl-s, Ctrl-x in nano or <Esc>:wq<Enter> in vi)sudo docker compose run --rm web bundle exec rake secret.env.production again for editingSECRETS sectionPAPERCLIP_SECRET= and paste the value you just created.env.production filessh sessionContinue with the setup process
Prepare the database now? - Hit enter for (Y)es. You should see Done! after a few seconds.Create an admin user? - Hit n for (N)o and hit enterThe setup script will exit.
Run sudo docker compose up -d && sleep 15 && sudo docker compose down
Ensure some critical file permissions are correct:
sudo chown -R 70:70 ./postgres14
sudo chown -R 991:991 ./public
sudo docker compose up -d
docker compose stackdate | md5sum twice to generate two new passwords: one for the NPM MySQL root account and one for the databasedocker-compose.yml with vi or nano, whichever you preferNPM immediately above the networks: section at the end of the services: section in your docker-compose.yml. Ensure the volumes: section is fully outdented to the first column. Replace the NPM MySQL root password with one of the hashes above and replace the NPM MySQL non-root password (in two places!) with the other hash from above. npm-db:
image: jc21/mariadb-aria:latest
restart: always
environment:
- MYSQL_ROOT_PASSWORD=<NPM MySQL root pwd>
- MYSQL_DATABASE=npm
- MYSQL_USER=npm
- MYSQL_PASSWORD=<NPM MySQL pwd>
volumes:
- npm-db:/var/lib/mysql
networks:
- internal_network
npm-app:
image: jc21/nginx-proxy-manager:latest
restart: always
ports:
- "80:80"
- "81:81"
- "443:443"
environment:
- DB_MYSQL_HOST=npm-db
- DB_MYSQL_PORT=3306
- DB_MYSQL_USER=npm
- DB_MYSQL_PASSWORD=<NPM MySQL DB pwd>
- DB_MYSQL_NAME=npm
volumes:
- npm-data:/data
- npm-ssl:/etc/letsencrypt
networks:
- external_network
- internal_network
volumes:
npm-db:
npm-ssl:
npm-data:
docker compose up -d to add NPM services to your stackexit command to logout of the server instancessh to log back in, but add -L 8081:localhost:81 to the end of the ssh command line. This will let you configure NPM via the web UI.http://localhost:8081 in your browseradmin@example.com and changeme as the credentialsMy ProfileAPI Tokens on the leftCreate Token on the rightUse template next to Edit Zone DNS (the top menu item)Permissions, click + Add moreAccount and choose ZoneSelect item and choose ZoneSelect and choose ReadZone Resources, click Select and choose your domain nameStart date to bring up the calendarContinue to summaryCreate token on the next pageSSL Certificates sectionAdd SSL Certificate buttonLet's Encrypt from the dropdown menumstdn.<yourdomain.tld> for the domain name, replacing <yourdomain.tld> with the domain you boughtUse a DNS Challenge sliderCloudflare from the dropdown list of providersSave button at the bottomwebDashboard to return to your NPM Dashboard viewProxy hosts button on the leftAdd proxy host button at the right end of the menumstdn.<yourdomain.tld> in the domain name field, replacing <yourdomain.tld> with the domain you bought Note: You must click on the name that you just typed as it appears in the dropdown list to lock it into the field. Do NOT tab out of that field without clicking it.web in the Forward Hostname/IP field and 3000 in the Forward port fieldBlock Common ExploitsSSL tab up topNone under SSL Certificate and choose the domain name you entered on the Details tab (i.e. mstdn.<yourdomain.tld>)Force SSL, HTTP/2 Support, and HSTS EnabledSave buttonmstdn.<yourdomain.tld> replacing <yourdomain.tld> with the domain you boughtssh terminal sessionsudo docker exec -it mastodon-web-1 /bin/bashRAILS_ENV=production bin/tootctl accounts create admin2 --email <your-admin@email> --role=Owner
OK and your password. SAVE THE PASSWORD.RAILS_ENV=production bin/tootctl accounts modify admin2 --confirmRAILS_ENV=production bin/tootctl accounts modify admin2 --approvePreferences at the bottom of the menu on the rightInvite people in the menu on the leftssh shellsudo docker exec -it mastodon-web-1 /bin/bash (if you have exited the first session)RAILS_ENV=production bin/tootctl accounts modify <your non-root username> --confirmRAILS_ENV=production bin/tootctl accounts modify <your non-root username> --approveOK) after both commandshttps://mstdn.<yourdomain.tld> replacing <yourdomain.tld> with your actual domainMany instances allow more than 500 characters for a toot, but Mastodon does not allow this out of the box, and this isn’t configurable.
However, it is fairly straightforward to increase the limit on a standard docker install (and, if you’ve got to this point, you have a standard docker install), so if you want a higher character limit, then this next option is for you.
cd mastodon2000.sh with vi or nano, whichever you prefer e.g. nano 2000.sh#!/bin/bash
sudo docker compose exec web sed -i 's/500/2000/g' app/javascript/mastodon/features/compose/components/compose_form.js
sudo docker compose exec web sed -i 's/500/2000/g' app/validators/status_length_validator.rb
sudo docker compose exec web sed -i 's/:registrations/:registrations, :max_toot_chars /g' app/serializers/rest/instance_serializer.rb
sudo docker compose exec web sed -i 's/private/def max_toot_chars\n 2000\n end\n\n private/g' app/serializers/rest/instance_serializer.rb
sudo docker compose exec web bundle exec rails assets:precompile && echo "pass" || echo "fail"
sudo docker compose restart
chmod +x 2000.shsudo .\2000.shKeeping your instance updated is pretty easy with docker and docker compose.
ssh into your Oracle Cloud instancecd mastodonsudo docker compose pullsudo docker compose stopsudo docker compose startWait up to a minute before reloading the web page or attempting to use the app. If you try too soon, you may see an error 502 bad gateway when you try to reload the page. Also, use the keyboard shortcut of Ctrl-Shift-R to flush the browser cache for the page before reloading.
That should do it. If you are generally unfamiliar with these tools as a system administrator, you may wish to wait for a few days after major updates have been released, as that gives time for possible bugs to be discovered and addressed.
Enjoy your time in the Fediverse! Don’t forget to visit (Fediverse.party)[https://fediverse.party] to see what other apps are out there that can provide the social media connections you’re seeking.