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.
alias docker-compose='docker compose'
sudo mkdir /data
mkdir ~/docker
nano ~/docker/docker-compose.yml
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
sudo mkdir -p /data/nginx
sudo nano /data/nginx/nginx.conf
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;
}
}
}
sudo mkdir -p /data/LetsEncrypt/etc
sudo nano /data/LetsEncrypt/etc/dns.ini
dns_nsone_api_key = [ApiKey] ## Change the API Key and nsone word
touch ~/docker/certbot-script.sh
chmod 774 ~/docker/certbot-script.sh
nano ~/docker/certbot-script.sh
#!/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
Now that all the preliminary work is done, lets stand the system up.
sudo ~/docker/certbot-script.sh
docker-compose -f ~/docker/docker-compose.yml up redis mastodon-db
docker run --rm -it --entrypoint /bin/bash lscr.io/linuxserver/mastodon generate-secret
docker run --rm -it --entrypoint /bin/bash lscr.io/linuxserver/mastodon generate-vapid
docker-compose -f ~/docker/docker-compose.yml up
docker-compose -f ~/docker/docker-compose.yml up -d
docker exec -it mastodon tootctl accounts create [name] --email [email-address] --confirmed --role Owner ## Replace [name] and [email-address]
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.