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
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 prerequisitessudo apt install apt-transport-https ca-certificates curl software-properties-common gnupg-agent -y
# add docker gpg keycurl -fsSL https://download.docker.com/linux/$(awk -F'=''/^ID=/{ print $NF }' /etc/os-release)/gpg | sudo apt-key add -
# add docker software repositorysudo 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 dockersudo apt install docker-ce docker-compose containerd.io -y
# enable and start docker servicesudo systemctl enable docker && sudo systemctl start docker
# add the current user to the docker groupsudo usermod -aG docker $USER# reauthenticate for the new group membership to take effectsu - $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 installedsudo apt remove certbot -y
# install snapdsudo apt install snapd -y
# install snap core and updatesudo snap install core; sudo snap refresh core
# install certbot snapsudo snap install --classic certbot
# create certbot symbolic linksudo ln -s /snap/bin/certbot /usr/bin/certbot
# if a web server process is currently using port 80, stop it before proceeding# generate a certificatesudo 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 groupsudo groupadd ssl-certs
# add $USER and root users to groupsudo usermod -aG ssl-certs $USERsudo usermod -aG ssl-certs root
# verify the members of ssl-certgetent group ssl-certs
# set owner group of /etc/letsencryptsudo chgrp -R ssl-certs /etc/letsencrypt
# set permissions on /etc/letsencryptsudo 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 directoriesmkdir ~/docker/postgres -p && mkdir ~/docker/redis -p && mkdir ~/docker/mastodon/public/system -p && mkdir ~/docker/nginx/conf -p
# pull the mastodon web containerdocker pull tootsuite/mastodon
# generate secrets, run this 2 timesdocker run --rm -it tootsuite/mastodon bundle exec rake secret
# generate VAPID keysdocker 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 laternano ~/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
# 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=redisREDIS_PORT=6379# PostgreSQL# ----------DB_HOST=postgresDB_USER=mastodon_rwDB_NAME=mastodonDB_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.comSMTP_PORT=25SMTP_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=31556952SESSION_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
# set owner of docker directorysudo chown "$USER":"$USER" ~/docker -R
# create containersdocker 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 containerdocker run -d --name redis -v ~/docker/redis:/data --network containers --restart=unless-stopped redis
# initialize the mastodon databasedocker run --rm -it --network containers --env-file ~/docker/mastodon/.env tootsuite/mastodon rails db:migrate
# run the mastodon frontend containerdocker 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 containerdocker exec -it mastodon /bin/bash
# set the RAILS_ENV variableRAILS_ENV=production
# create an owner/admin account# copy the password output for laterbin/tootctl accounts create <%username%> --email <%email address%> --confirmed --role Owner
# exit the containerexit# run the mastodon streaming containerdocker 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 containerdocker 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 configurationwget -O ~/docker/nginx/conf/mastodon.conf https://raw.githubusercontent.com/mastodon/mastodon/main/dist/nginx.conf
# replace some options to work running in docker containerssed -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_DOMAINsed -i "s/server_name example.com;/\server_name <%WEB_DOMAIN%>;/" ~/docker/nginx/conf/mastodon.conf
# update mastodon frontend serversed -i 's/server 127.0.0.1:3000/server mastodon:3000/' ~/docker/nginx/conf/mastodon.conf
# update mastodon stream serversed -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 NAMEsed -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 NAMEsed -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 containerdocker 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 Preferences → Account, 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.
Góp Ý / Bình Luận / Đánh giá