Mastodon on Docker
2024-11-26T12:35:08-07:00 | 7 minute read | Updated at 2024-11-27T12:51:56-07:00
My adventure self-hosting Mastodon using Docker
I like a good adventure, and an adventure self-hosting something new is never a bad idea. This post will be about self-hosting Mastodon (and the resources it requires) as well as using nginx to control the front door. I’d like to thank Raphael Fleischlin (Raeffs Dev Site) for his post - it got me moving toward this project - with the big difference being that I wanted to use the consolidated docker container published by LinuxServer.io.
This post assumes you are familiar with (well, actually prefer) docker compose. You can run these various commands interactively, but everything described here will be based on docker compose.
Pre-requisites
- Linux box running Ubuntu 20.04.6 LTS (you more than likely can get away with a newer LTS version - this is what I specifically used).
- Docker installed on that box. I’m not super crazy about snap yet, so I do it manaully as described here. Additionally, I also follow the post-installation steps hightlighted here.
- Depending on the version of docker compose you install, the commands will either be “docker compose” (with no dash between them) or “docker-compose” (with a dash). I put an alias in my .bashrc file so that all of my shortcut macros work regardless - so on this box I’ve got this alias to smooth things out:
alias docker-compose='docker compose'
Assumptions
- A data directory. Mine is off the root - so /data. All “permanent” data will be stored here for easy upgrading from docker container to docker container.
- A folder for my docker files and Let’s encrypt script. My folder is off my personal directory so /home/chris/docker. I’ll use ~/docker below to reference this directory. If you have any issues, replace ~ with /home/[youusername].
- I use nano as my text editor of choice. You do you - and then adjust the commands below.
- nginx is going to listen and manage port 80 and 443.
- I’m using the linuxserver.io docker image found here.
- Your DNS server is pointing to the IP address of this new box you are standing up.
The preliminary work
- Create the directories noted in the assumptions block. For reference here you go:
sudo mkdir /data mkdir ~/docker
- Create and edit a docker compose file:
nano ~/docker/docker-compose.yml
- Paste the following into the docker-compose.yml file, change the 11 instances marked “Change this”, if desired change the 1 instance marked “Change this as needed”, and then save and exit (the “Change this later” items will be changed later):
version: '3.8' services: nginx: image: nginx:latest restart: unless-stopped container_name: nginx networks: - mastodon depends_on: - mastodon volumes: - /data/nginx/nginx.conf:/etc/nginx/nginx.conf - /data/nginx/certs:/etc/nginx/certs ports: - 80:80 - 443:443 redis: restart: unless-stopped image: redis:7-alpine container_name: redis networks: - mastodon healthcheck: test: ['CMD', 'redis-cli', 'ping'] volumes: - /data/mastodon/redis:/data ports: - 6379:6379 mastodon-db: image: postgres:alpine container_name: mastodon-db command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all networks: - mastodon volumes: - /data/mastodon/postgres:/var/lib/postgresql/data environment: POSTGRES_USER: [db-user-goes-here] ## Change this POSTGRES_PASSWORD: [db-password-goes-here] ## Change this POSTGRES_DB: mastodon mastodon: image: lscr.io/linuxserver/mastodon:latest container_name: mastodon networks: - mastodon environment: - PUID=1000 - PGID=1000 - TZ=[your-timezone] ## Change this - LOCAL_DOMAIN=[domain-name] ## Change this - REDIS_HOST=redis ## This picks up the name from the above container - REDIS_PORT=6379 - DB_HOST=mastodon-db ## This picks up the name from the above container - DB_USER=[db-user-goes-here] ## Change this - DB_NAME=mastodon - DB_PASS=[db-password-goes-here] ## Change this - DB_PORT=5432 - ES_ENABLED=false - SECRET_KEY_BASE=[mastodon-secret-goes-here] ## Change this later - OTP_SECRET=[mastodon-otp-secret-goes-here] ## Change this later - VAPID_PRIVATE_KEY=[vapid-private-key-goes-here] ## Change this later - VAPID_PUBLIC_KEY=[vapid-public-key-goes-here] ## Change this later - SMTP_SERVER=[smtp-server-goes-here] ## Change this - SMTP_PORT=587 ## Change this as needed - SMTP_LOGIN=[smtp-login-goes-here] ## Change this - SMTP_PASSWORD=[smtp-password-goes-here] ## Change this - SMTP_FROM_ADDRESS=[smtp-from-address-goes-here] ## Change this - S3_ENABLED=false - WEB_DOMAIN=[domain-name] ## Change this (match the above value for local_domain) volumes: - /data/mastodon/mastodon/config:/config depends_on: - redis - mastodon-db ports: - 3000:3000 restart: unless-stopped networks: mastodon: external: false
- Create and edit a nginx.conf file:
sudo mkdir -p /data/nginx sudo nano /data/nginx/nginx.conf
- Paste the following into the nginx.conf file, change the domain name on the 5 indicated lines, then save and exit:
events { worker_connections 1024; } http { ## myprivate.social :: Port 80 server { listen 80; server_name [domain-name]; ## Change this return 301 https://[domain-name]; ## Change this } ## myprivate.social :: Port 443 server { listen 443 ssl http2; server_name [domain-name]; ## Change this ssl_certificate /etc/nginx/certs/[domain-name]/fullchain.pem; ## Change this ssl_certificate_key /etc/nginx/certs/[domain-name]/privkey.pem; ## Change this location / { add_header 'Cache-Control' 'no-cache, no-store, must-revalidate'; add_header 'Content-Security-Policy' 'connect-src *'; add_header 'Expires' '0'; add_header 'Pragma' 'no-cache'; add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains'; add_header 'X-Content-Type-Options' 'nosniff'; add_header 'X-Frame-Options' 'SAMEORIGIN'; add_header 'X-XSS-Protection' '1; mode=block'; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-Port $server_port; proxy_set_header X-Real-Scheme $scheme; proxy_set_header X-NginX-Proxy true; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Ssl on; client_max_body_size 5m; proxy_pass http://mastodon; } } }
- Get an API key from your DNS provider. This example is assuming you are using NS1 (ns1.com). There are references to change ’nsone’ throughout the rest of this script. If you are using a different provider, adjust accordingly. The provider list that this came from can be found here.
- Create and edit a file with the DNS API key
sudo mkdir -p /data/LetsEncrypt/etc sudo nano /data/LetsEncrypt/etc/dns.ini
- Paste the following into the dns.ini file, adjust the values as indicated and save and exit - sometimes the “key” needs to change too (e.g. if you are using a different DNS provider like CloudFlare):
dns_nsone_api_key = [ApiKey] ## Change the API Key and nsone word
- Create, make runnable, and edit a certbot-script.sh file:
touch ~/docker/certbot-script.sh chmod 774 ~/docker/certbot-script.sh nano ~/docker/certbot-script.sh
- Paste the following into the certbot-script.sh file, change the 10 “Change this” lines (one has 2 changes), change the 8 dns plug-in lines from ’nsone’ to the appropriate value, then save and exit.
#!/bin/bash nginx_dir=/data/nginx/certs/[domain-name] ## Change this certbot_chain=/data/LetsEncrypt/etc/live/[domain-name]/chain.pem ## Change this certbot_fullchain=/data/LetsEncrypt/etc/live/[domain-name]/fullchain.pem ## Change this certbot_cert=/data/LetsEncrypt/etc/live/[domain-name]/cert.pem ## Change this certbot_key=/data/LetsEncrypt/etc/live/[domain-name]/privkey.pem ## Change this nginx_chain=/data/nginx/certs/[domain-name]/chain.pem ## Change this nginx_fullchain=/data/nginx/certs/[domain-name]/fullchain.pem ## Change this nginx_cert=/data/nginx/certs/[domain-name]/cert.pem ## Change this nginx_key=/data/nginx/certs/[domain-name]/privkey.pem ## Change this if [[ ! -f "$certbot_cert" ]]; then echo "Creating new certificate..." docker run --rm -it \ --name certbot \ -v "/data/LetsEncrypt/etc:/etc/letsencrypt" \ -v "/data/LetsEncrypt/var/lib:/var/lib/letsencrypt" \ certbot/dns-nsone certonly \ --dns-nsone \ --dns-nsone-credentials /etc/letsencrypt/dns.ini \ --dns-nsone-propagation-seconds 10 \ -d [domain-name] -d *.[domain-name] \ --agree-tos echo "Creating new certificate...complete!" else echo "Renewing certificate..." docker run --rm -it \ --name certbot \ -v "/data/LetsEncrypt/etc:/etc/letsencrypt" \ -v "/data/LetsEncrypt/var/lib:/var/lib/letsencrypt" \ certbot/dns-nsone renew \ --dns-nsone \ --dns-nsone-credentials /etc/letsencrypt/dns.ini \ --dns-nsone-propagation-seconds 10 \ --agree-tos echo "Renewing certificate...complete!" fi if cmp -s "$certbot_cert" "$nginx_cert"; then echo "Certificate is already up-to-date." else echo "Updating certificate..." rm -rf $nginx_dir mkdir -p $nginx_dir cp $certbot_chain $nginx_chain cp $certbot_cert $nginx_cert cp $certbot_key $nginx_key cp $certbot_fullchain $nginx_fullchain chown -R chris:chris /data/nginx chmod -R 755 $nginx_dir echo "Done!" fi
The execution
Now that all the preliminary work is done, lets stand the system up.
- Get a digital cert from Let’s Encrypt. Ensure it completes successfully.
sudo ~/docker/certbot-script.sh
- Start up the redis and mastodon-db portions of the system. Once they both indicate they are “ready for connections” you can ctrl-c and move to the next step (Postgres will emit: “database system is ready to accept connections” and Redis will emit “Ready to accept connections”).
docker-compose -f ~/docker/docker-compose.yml up redis mastodon-db
- Grab the secret keys needed for the mastodon server. Run this command twice:
docker run --rm -it --entrypoint /bin/bash lscr.io/linuxserver/mastodon generate-secret
- Grab the Vapid keys needed for the mastodon server. Run this once:
docker run --rm -it --entrypoint /bin/bash lscr.io/linuxserver/mastodon generate-vapid
- Take the secrets needed from the previous two commands and put them into the docker-compose.yml file created earlier into the “Change this later” values. The secret key base and OTP secret don’t matter which one is which.
- Start all containers. The database objects will be created into the database. It’ll take a few minutes for everything to settle down. Once you see the cessation of activity, it is safe to ctrl-c and exit the docker processes.
docker-compose -f ~/docker/docker-compose.yml up
- If there were any errors encountered in the above, fix them and repeat the step until you aren’t getting any errors.
- Once everything looks good, you can start the containers in “background mode”:
docker-compose -f ~/docker/docker-compose.yml up -d
- Create the “owner” of the mastodon system - save the temporary password to use in the next step.
docker exec -it mastodon tootctl accounts create [name] --email [email-address] --confirmed --role Owner ## Replace [name] and [email-address]
- You should be able to navigate to the web site, log in with the user created above. Don’t forget to change the temporary password.
Epilog
There are a couple of things I’ll go back and tweak - there are some areas of “verboseness” that could be simplified - where I opted for quick cut & paste, instead of using variables (like in the certbot script).
I hope you found this helpful and got you running quickly. If you want to buy me a coffee, that’d be great. As always, feel free to hit me up on Mastodon.