Skip to content

Instantly share code, notes, and snippets.

@TrillCyborg
Last active October 7, 2024 11:08
Show Gist options
  • Save TrillCyborg/84939cd4013ace9960031b803a0590c4 to your computer and use it in GitHub Desktop.
Save TrillCyborg/84939cd4013ace9960031b803a0590c4 to your computer and use it in GitHub Desktop.
Mastodon Docker Setup

Mastodon Docker Setup

Setting up

Clone Mastodon's repository.

# Clone mastodon to ~/live directory
git clone https://github.com/tootsuite/mastodon.git live
# Change directory to ~/live
cd ~/live
# Checkout to the latest stable branch
git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)

Review the settings in docker-compose.yml. Note that it is not default to store the postgresql database and redis databases in a persistent storage location. If you plan on running your instance in production, you must uncomment the volumes directive in docker-compose.yml.

Getting the Mastodon image

Using a prebuilt image

If you're not making any local code changes or customizations on your instance, you can use a prebuilt Docker image to avoid the time and resource consumption of a build. Images are available from Docker Hub: https://hub.docker.com/r/tootsuite/mastodon/

To use the prebuilt images:

  1. Open docker-compose.yml in your favorite text editor.
    1. Comment out the build: . lines for all images (web, streaming, sidekiq).
    2. Edit the image: tootsuite/mastodon lines for all images to include the release you want. The default is latest which is the most recent stable version, however it recommended to explicitly pin a version: If you wanted to use v2.2.0 for example, you would edit the lines to say: image: tootsuite/mastodon:v2.2.0
    3. Save the file and exit the text editor.
  2. Run cp .env.production.sample .env.production to bootstrap the configuration. Edit the correct values now.
  3. Run docker-compose build. It will now pull the correct image from Docker Hub.
  4. Set correct file-owner with sudo chown -R 991:991 public/system

Building your own image

You must build your own image if you've made any code modifications. To build your own image:

  1. Open docker-compose.yml in your favorite text editor.
    1. Uncomment the build: . lines for all images (web, streaming, sidekiq) if needed.
    2. Save the file and exit the text editor.
  2. Run cp .env.production.sample .env.production to bootstrap the configuration. Edit the correct values now.
  3. Run docker-compose build.
  4. Set correct file-owner with chown -R 991:991 public

Building the app

Now the image can be used to generate a configuration with:

docker-compose run --rm web bundle exec rake mastodon:setup

This is an interactive wizard that will guide you through the basic and necessary options and generate new app secrets. At some point it will output your configuration, copy and paste that configuration into the .env.production file.

The wizard will setup the database schema and precompile assets. After it's done, you can launch Mastodon with:

docker-compose up -d

nginx Configuration

You need to configure nginx to serve your Mastodon instance.

Reminder: Replace all occurrences of example.com with your own instance's domain or sub-domain.

cd to /etc/nginx/sites-available and open a new file:

nano /etc/nginx/sites-available/example.com.conf

Copy and paste the following and make edits as necessary:

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  root /home/mastodon/live/public;
  # Useful for Let's Encrypt
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

  ssl_protocols TLSv1.2;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    try_files $uri @proxy;
  }
  
  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://127.0.0.1:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

Activate the nginx configuration added:

cd /etc/nginx/sites-enabled
ln -s ../sites-available/example.com.conf

This configuration makes the assumption you are using Let's Encrypt as your TLS certificate provider.

If you are going to be using Let's Encrypt as your TLS certificate provider, see the next sub-section. If not edit the ssl_certificate and ssl_certificate_key values accordingly.

Let's Encrypt

This section is only relevant if you are using Let's Encrypt as your TLS certificate provider.

Generation Of The Certificate

We need to generate Let's Encrypt certificates.

Make sure to replace any occurrence of 'example.com' with your Mastodon instance's domain.

Make sure that nginx is stopped at this point:

systemctl stop nginx

We will be creating the certificate twice, once with TLS SNI validation in standalone mode and the second time we will be using the webroot method. This is required due to the way nginx and the Let's Encrypt tool works.

certbot certonly --standalone -d example.com

After that successfully completes, we will use the webroot method. This requires nginx to be running:

systemctl start nginx
# The certbot tool will ask if you want to keep the existing certificate or renew it. Choose to renew it.
certbot certonly --webroot -d example.com -w /home/mastodon/live/public/

Automated Renewal Of Let's Encrypt Certificate

Let's Encrypt certificates have a validity period of 90 days.

You need to renew your certificate before the expiration date. Not doing so will make users of your instance unable to access the instance and users of other instances unable to federate with yours.

We can create a cron job that runs daily to do this:

nano /etc/cron.daily/letsencrypt-renew

Copy and paste this script into that file:

#!/usr/bin/env bash
certbot renew
systemctl reload nginx

Save and exit the file.

Make the script executable and restart the cron daemon so that the script runs daily:

chmod +x /etc/cron.daily/letsencrypt-renew
systemctl restart cron

That is it. Your server will renew your Let's Encrypt certificate.

Resources

@EsmailELBoBDev2
Copy link

Helped me a lot, thank you <3

@sevi-kun
Copy link

sevi-kun commented Mar 4, 2022

Hey, how do I generate the Secrets (SECRET_KEY_BASE and OTP_SECRET) and Web Push (VAPID_PRIVATE_KEY AND VAPID_PUBLIC_KEY) Values?
Sorry, I don't know much about docker-compose..

@EsmailELBoBDev2
Copy link

@sevi-kun run docker-compose run --rm web bundle exec rake mastodon:setup and when you get output put in ur your .env.production file

I wrote a blog about it, here hope it helps you: https://blog.esmailelbob.xyz/how-to-self-host-mastodon-on-docker-compose

@sevi-kun
Copy link

sevi-kun commented Mar 5, 2022

@EsmailELBoBDev2 Thank you so much for your answer. That helped me. But actually, i find myself in an other situation here..

PostgreSQL host: db
PostgreSQL port: 5432
Name of PostgreSQL database: postgres
Name of PostgreSQL user: postgres
Password of PostgreSQL user:
Database connection could not be established with this configuration, try again.
could not translate host name "db" to address: Name or service not known

I get this output in the setup. I don't know if i have to do somehting manualy with the postgres db.

EDIT:

I tried to change the settings to match the .env.production file.. Now I have this:

PostgreSQL host: /var/run/postgresql
PostgreSQL port: 5432
Name of PostgreSQL database: mastodon_production
Name of PostgreSQL user: mastodon
Password of PostgreSQL user:
Database connection could not be established with this configuration, try again.
could not connect to server: No such file or directory
        Is the server running locally and accepting
        connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

@EsmailELBoBDev2
Copy link

@sevi-kun well, for myself when I setup mastodon I did not change any DB settings I left them and it connected at end

I think you get this problem because you did not write DB connection settings right so try keep push enter without edit either db or ridus (not sure if i said it right haha)

@ledlamp
Copy link

ledlamp commented Jul 14, 2022

there's no need to clone entire repository, just copy docker-compose.yml. and it is pointless to copy .env.production.sample and fill it if you are running setup. A blank .env.production file is all you need so docker compose won't complain.

@fsantiago07044
Copy link

fsantiago07044 commented Oct 4, 2022

problem with your instructions; you never create a mastodon user. hence, when you attempt to point nginx / certbot to '/home/mastodon/live/public/', it complains: "/home/mastodon/live/public/ does not exist or is not a directory".

so where to go from here? i assume something along the lines of "adduser --disabled-login mastodon"...? but that path would also suggest that at some point files wind up in there. so what am i missing? i'm guessing the 1st step where you clone the git should have been from this mastodon user shouldn't it? my bad perhaps...

Copy link

ghost commented Nov 6, 2022

For the prebuilt image, shouldn't it be docker-compose up, instead of docker-compose build?

@galenguyer
Copy link

Note that it is not default to store the postgresql database and redis databases in a persistent storage location. If you plan on running your instance in production, you must uncomment the volumes directive in docker-compose.yml.

At least in the latest version (v3.5.3), this is not the case. The default is to create and use a directory in the same directory as your docker-compose.yml.

@rgiersig
Copy link

rgiersig commented Nov 8, 2022

I had to manually add
REDIS_URL=redis://redis:6379/0
to the .env.production file to get redis running without errors.
Also, I forgot the chown -R 991:991 public command at the end, so I got EACCESS errors.

@jvence
Copy link

jvence commented Nov 10, 2022

Any instructions for running nginx as a docker instance using Mastodon's docker-compose.yml?

@abdessalaam
Copy link

abdessalaam commented Nov 13, 2022

problem with your instructions; you never create a mastodon user. hence, when you attempt to point nginx / certbot to '/home/mastodon/live/public/', it complains: "/home/mastodon/live/public/ does not exist or is not a directory".

I have changed '/home/mastodon/live/public/' to '/root/live/public/' - works. Probably good for testing, but not too safe for production?

A very helpful guide, thank you!

@Jookia
Copy link

Jookia commented Nov 15, 2022

Thanks @rgiersig for the 'REDIS_URL=redis://redis:6379/0' tip, that fixed my issue!

As a note for anyone else that sucks like me, run 'sudo git clean -dfx' after shutting down your docker-compose to nuke your postgresql db if it complains it already has a db created

@melroy89
Copy link

You are saying:

Note that it is not default to store the postgresql database and redis databases in a persistent storage location.

However, in the latest releases I do see they enabled volumes mounts by default in their docker-compose.yml.

@melroy89
Copy link

melroy89 commented Nov 18, 2022

See also the latest version for Nginx config over here: https://github.com/mastodon/mastodon/blob/main/dist/nginx.conf

And replace all the lines from try_files $uri =404; to try_files $uri @proxy;.

EDIT: There were too many things wrong in your gist. Therefore, I created a gist fork here:

UPDATED Gist: https://gist.github.com/danger89/6fe7d05bdc0cfd2153b77310abf62990

@magnus919
Copy link

Mastodon is still way, way too complex to set up in Docker.

@melroy89
Copy link

Mastodon is still way, way too complex to set up in Docker.

Well I try to get it documented correctly on their site. But Mastodon devs don't allow me to do so:

mastodon/documentation#1035

@klogdog
Copy link

klogdog commented Nov 20, 2022

Ridiculous that they supposedly had trouble with docker and novice admins. Makes me wonder if they even know how to use docker properly. This is something that really needs to be advanced.

"container-based deployments were a very error prone and difficult method for novice sysadmins and led to a lot of avoidable errors."

What would possibly make them error prone.

@melroy89
Copy link

Ridiculous that they supposedly had trouble with docker and novice admins. Makes me wonder if they even know how to use docker properly. This is something that really needs to be advanced.

"container-based deployments were a very error prone and difficult method for novice sysadmins and led to a lot of avoidable errors."

What would possibly make them error prone.

I'm also kind of flabbergasted and angry at the same time. They don't allow the community to support Mastodon, in this case help writing the docs for Docker support.

@johndelcastillo
Copy link

Thanks for the valuable contribution, very helpful instructions.

@redimongo
Copy link

Would be great if I could use this with NGINX Proxy management.

As I tried to install it adding it to our main docker-compose.yml and it keeps failing to build.

version: '3'
services:
  nextjs: 
    build: ./DRN1Git
  podtoo: 
    build: ./PodToo
  mastodon:
    image: 'tootsuite/mastodon:latest'
    restart: unless-stopped
  app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      # These ports are in format <host-port>:<container-port>
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP

    # Uncomment the next line if you uncomment anything in the section
    # environment:
      # Uncomment this if you want to change the location of 
      # the SQLite DB file within the container
      # DB_SQLITE_FILE: "/data/database.sqlite"

      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'

    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

@felixriehm
Copy link

Thanks! Helped me a lot!

@stawze
Copy link

stawze commented Mar 11, 2023

Following these instructions and similar ones nets me this error:
FATAL: role "mastodon" does not exist
For postgres

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment