Bookmark

Self-host Mastodon with Docker: PostgreSQL, Redis, SSL, and Nginx

Mastodon is federated, so this tutorial is more than one Docker container: it needs a stable domain, PostgreSQL, Redis, secrets, VAPID, streaming, Sidekiq, and an HTTPS reverse proxy. LOCAL_DOMAIN should not be changed casually after the instance is in use.1

Youtube video player

The i12bretro video was validated for title and author. I keep the original player and do not re-host the video.2

1. Domain and Docker prerequisites

The Certbot host must be reachable from the Internet on port 80 or 443 for Let's Encrypt validation. In a HomeLab I forward port 80 to the host during the handshake, verify DNS, and stop any web server already using port 80.

Install Docker with the source block:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# install prerequisites
sudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg-agent -y
# add docker gpg key
curl -fsSL https://download.docker.com/linux/$(awk -F'=' '/^ID=/{ print $NF }' /etc/os-release)/gpg | sudo apt-key add -
# add docker software repository
sudo add-apt-repository "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/$(awk -F'=' '/^ID=/{ print $NF }' /etc/os-release) $(lsb_release -cs) stable"
# install docker
sudo apt install docker-ce docker-compose containerd.io -y
# enable and start docker service
sudo systemctl enable docker && sudo systemctl start docker
# add the current user to the docker group
sudo usermod -aG docker $USER
# reauthenticate for the new group membership to take effect
su - $USER

2. Obtain the SSL certificate

Remove the apt Certbot, install snapd/core/Certbot, create the symlink, and run standalone mode with the real domain:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# remove apt version of certbot if installed
sudo apt remove certbot -y
# install snapd
sudo apt install snapd -y
# install snap core and update
sudo snap install core; sudo snap refresh core
# install certbot snap
sudo snap install --classic certbot
# create certbot symbolic link
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# if a web server process is currently using port 80, stop it before proceeding
# generate a certificate
sudo certbot certonly --standalone --preferred-challenges http -d <%DNS NAME%>

When prompted, enter an email, accept the terms, and choose whether to receive Certbot email. Record the certificate file locations it prints.

Create the certificate-reading group as in the source:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# create ssl-certs group
sudo groupadd ssl-certs
# add $USER and root users to group
sudo usermod -aG ssl-certs $USER
sudo usermod -aG ssl-certs root
# verify the members of ssl-cert
getent group ssl-certs
# set owner group of /etc/letsencrypt
sudo chgrp -R ssl-certs /etc/letsencrypt
# set permissions on /etc/letsencrypt
sudo chmod -R g=rX /etc/letsencrypt

3. Prepare Mastodon and .env

Create PostgreSQL, Redis, public system, and Nginx directories; pull the image, run rake secret twice, generate VAPID keys, and open .env:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# create working directories
mkdir ~/docker/postgres -p && mkdir ~/docker/redis -p && mkdir ~/docker/mastodon/public/system -p && mkdir ~/docker/nginx/conf -p
# pull the mastodon web container
docker pull tootsuite/mastodon
# generate secrets, run this 2 times
docker run --rm -it tootsuite/mastodon bundle exec rake secret
# generate VAPID keys
docker run --rm -it tootsuite/mastodon bundle exec rake mastodon:webpush:generate_vapid_key
# create a mastodon .env file
# copy the generated secrets and keys into the .env file
# make sure to set the LOCAL_DOMAIN as this cannot be changed later
nano ~/docker/mastodon/.env

Paste the .env sample and edit LOCAL_DOMAIN, WEB_DOMAIN, PostgreSQL, secret, Web Push, and SMTP settings. Variable names remain intact, but source passwords are not committed; DB_PASS, database passwords, and any SMTP secrets must be local values:1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# This is a sample configuration file. You can generate your configuration
# with the `rake mastodon:setup` interactive setup wizard, but to customize
# your setup even further, you'll need to edit it manually. This sample does
# not demonstrate all available configuration options. Please look at
# https://docs.joinmastodon.org/admin/config/ for the full documentation.
# Note that this file accepts slightly different syntax depending on whether
# you are using `docker-compose` or not. In particular, if you use
# `docker-compose`, the value of each declared variable will be taken verbatim,
# including surrounding quotes.
# See: https://github.com/mastodon/mastodon/issues/16895
# Federation
# ----------
# This identifies your server and cannot be changed safely later
# ----------
LOCAL_DOMAIN=i12bretro.local
# ----------
# Optional, if different than LOCAL_DOMAIN
# ----------
#WEB_DOMAIN=toots.webredirect.org
# Redis
# -----
REDIS_HOST=redis
REDIS_PORT=6379
# PostgreSQL
# ----------
DB_HOST=postgres
DB_USER=mastodon_rw
DB_NAME=mastodon
DB_PASS=[REDACTED]
DB_PORT=5432
# Secrets
# -------
# Make sure to use `rake secret` to generate secrets
# -------
SECRET_KEY_BASE=
OTP_SECRET=
# Web Push
# --------
# Generate with `rake mastodon:webpush:generate_vapid_key`
# --------
VAPID_PRIVATE_KEY=
VAPID_PUBLIC_KEY=
# Sending mail
# ------------
SMTP_SERVER=smtp.example.com
SMTP_PORT=25
SMTP_LOGIN=
SMTP_PASSWORD=
SMTP_FROM_ADDRESS=mastodon@example.com
# IP and session retention
# -----------------------
# Make sure to modify the scheduling of ip_cleanup_scheduler in config/sidekiq.yml
# to be less than daily if you lower IP_RETENTION_PERIOD below two days (172800).
# -----------------------
IP_RETENTION_PERIOD=31556952
SESSION_RETENTION_PERIOD=31556952

Press CTRL+O, Enter, CTRL+X to save .env. Mastodon's full configuration documentation is an additional source linked by i12bretro; the compact sample is not a complete production policy.

4. Run PostgreSQL, Redis, and Mastodon

Create the network, start the database, run the migration, launch the frontend, create an owner/admin, then start streaming and Sidekiq. Replace the username/email placeholders and store the generated password safely; do not write it into Markdown:1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# set owner of docker directory
sudo chown "$USER":"$USER" ~/docker -R
# create containers
docker network create containers
# run the postgesql container
# Replace the source password value with your own local secret before running.
docker run -d --name postgres -e POSTGRES_USER=mastodon_rw -e POSTGRES_PASSWORD=[REDACTED] -e POSTGRES_DB=mastodon -v ~/docker/postgres:/var/lib/postgresql/data --network containers --restart=unless-stopped postgres:latest
# run the redis container
docker run -d --name redis -v ~/docker/redis:/data --network containers --restart=unless-stopped redis
# initialize the mastodon database
docker run --rm -it --network containers --env-file ~/docker/mastodon/.env tootsuite/mastodon rails db:migrate
# run the mastodon frontend container
docker run -d --name mastodon --env-file ~/docker/mastodon/.env -p 3000:3000 -v ~/docker/mastodon/public/system:/mastodon/public/system --network containers --restart=unless-stopped tootsuite/mastodon bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
# connect to shell inside mastodon container
docker exec -it mastodon /bin/bash
# set the RAILS_ENV variable
RAILS_ENV=production
# create an owner/admin account
# copy the password output for later
bin/tootctl accounts create <%username%> --email <%email address%> --confirmed --role Owner
# exit the container
exit
# run the mastodon streaming container
docker run -d --name mastodon-stream --env-file ~/docker/mastodon/.env -p 4000:4000 --network containers --restart=unless-stopped tootsuite/mastodon node ./streaming
# run the mastodon sidekiq container
docker run -d --name mastodon-sidekiq --env-file ~/docker/mastodon/.env --network containers -v ~/docker/mastodon/public/system:/mastodon/public/system --restart=unless-stopped tootsuite/mastodon bundle exec sidekiq

5. Nginx reverse proxy

Download Mastodon's official Nginx configuration, update upstreams for the Docker network, replace the domain and certificate paths, and create the proxy container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# download the default mastodon nginx configuration
wget -O ~/docker/nginx/conf/mastodon.conf https://raw.githubusercontent.com/mastodon/mastodon/main/dist/nginx.conf
# replace some options to work running in docker containers
sed -i "s/try_files \$uri =404;/try_files \$uri @proxy;/" ~/docker/nginx/conf/mastodon.conf
# update the server_name with the URL being used to reach mastodon
# make sure to replace WEB_DOMAIN
sed -i "s/server_name example.com;/\server_name <%WEB_DOMAIN%>;/" ~/docker/nginx/conf/mastodon.conf
# update mastodon frontend server
sed -i 's/server 127.0.0.1:3000/server mastodon:3000/' ~/docker/nginx/conf/mastodon.conf
# update mastodon stream server
sed -i 's/server 127.0.0.1:4000/server mastodon-stream:4000/' ~/docker/nginx/conf/mastodon.conf
# update the ssl certificate path
# make sure to replace DNS NAME
sed -i 's/# ssl_certificate\s*\/etc\/letsencrypt\/live\/example.com\/fullchain.pem;/ssl_certificate\t\/etc\/letsencrypt\/live\/<%DNS NAME%>\/fullchain.pem;/' ~/docker/nginx/conf/mastodon.conf
# update the ssl key path
# make sure to replace DNS NAME
sed -i 's/# ssl_certificate_key\s*\/etc\/letsencrypt\/live\/example.com\/privkey.pem;/ssl_certificate_key\t\/etc\/letsencrypt\/live\/<%DNS NAME%>\/privkey.pem;/' ~/docker/nginx/conf/mastodon.conf
# create nginx proxy container
docker run --name nginx -p 80:80 -p 443:443 --network containers -v ~/docker/nginx/conf:/etc/nginx/conf.d:ro -v /etc/letsencrypt:/etc/letsencrypt:ro -d nginx

Read mastodon.conf after each sed; replace <%WEB_DOMAIN%> and <%DNS NAME%> with the actual domain before running the container. Check that only the required proxy listens on ports 80/443.

6. Log in and change the password

Open https://<%WEB_DOMAIN%>, select Sign in, and use the owner email/password. Go to PreferencesAccount, enter the current password, set and confirm a new password, click Save Changes, log out, and log in again.

Post-deployment checks

Check docker ps, each container's logs, the certificate chain, HTTPS redirect, PostgreSQL/Redis health, streaming, and Sidekiq. Back up ~/docker/mastodon and .env outside the repository with restrictive permissions; do not expose ports 3000/4000 directly and never commit .env.

Sources


  1. https://i12bretro.github.io/tutorials/0853.html — i12bretro tutorial 0853. Independently edited from the numbered HTML page; source credentials are replaced with safe placeholders. ↩︎ ↩︎ ↩︎

  2. https://www.youtube.com/watch?v=_K5la-WchuY — Run Mastodon - A Federated Twitter Alternative - in Docker — i12bretro ↩︎


0 Bình luận

Góp Ý / Bình Luận / Đánh giá