Knowledgebase

How to Install Mastodon with Docker on Ubuntu 22.04 Print

  • 0

Introduction

Mastodon is a free, open-source, decentralized social network. Mastodon allows users to set up self-hosted servers to communicate with each other through the federated network.

This article shows how to set up a Mastodon instance on Ubuntu with Docker.

At the end of this article, you will have:

  • Set up a Mastodon instance.

  • Set up Elasticsearch for your Mastodon instance.

  • Managed your Mastodon instance with tootctl.

  • Automated the Mastodon maintenance.

  • Configured Nginx and Let's Encrypt SSL

  • Secured the server with ufw and fail2ban.

Prerequisites

Before beginning this guide, you should have the following:

Install Docker and Docker Compose

Docker is an open-source platform for developing, shipping, and running applications. Docker enables you to run Mastodon in an isolated and optimized environment.

Follow the below steps to install Docker on your server.

  1. Uninstall old applications such as docker, docker.io, and docker-engine.

    sudo apt-get remove docker docker-engine docker.io containerd runc
    
  2. Set up the repository

    sudo apt-get update
    
    sudo apt-get install -y ca-certificates curl gnupg lsb-release
    
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    
    sudo apt-get update
    
  3. Install the latest version of Docker Engine

    sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
    

Prepare a Directory for Mastodon

This section shows how to create a folder for Mastodon and some necessary environment files to follow this article.

  1. Create a folder for Mastodon. This article uses /opt/mastodon as the main folder.

    mkdir /opt/mastodon
    
  2. Create an empty file named .env.es and .env.mastodon for environment variables.

    touch /opt/mastodon/.env.es
    
    touch /opt/mastodon/.env.mastodon
    

Deploy a PostgreSQL Database

This section shows two options for using PostgreSQL Database with Mastodon:

  • Option 1: Use a Rcs Managed Database for PostgreSQL to automate the administration.

  • Option 2: Deploy your own PostgreSQL database with Docker.

Option 1: Use Rcs Managed PostgreSQL Database

  1. Navigate to Databases in your Customer Portal and deploy a Rcs Managed Database for PostgreSQL.

  2. Get your PostgreSQL database credentials to use in the later section.

  3. Create a file named docker-compose.yml at /opt/mastodon/docker-compose.yml with the following contents. Replace tootsuite/mastodon:v4.0 with another tag if you want.

    version: '3'
    
    networks:
    
      external_network:
    
      internal_network:
    
        internal: true
    
    services:
    
      redis:
    
        restart: always
    
        image: redis:7-alpine
    
        networks:
    
          - internal_network
    
        healthcheck:
    
          test: [ 'CMD', 'redis-cli', 'ping' ]
    
        volumes:
    
          - ./data/redis:/data
    
      es:
    
        restart: always
    
        image: docker.elastic.co/elasticsearch/elasticsearch:7.17.8
    
        environment:
    
          - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
    
          - "xpack.license.self_generated.type=basic"
    
          - "xpack.security.enabled=false"
    
          - "xpack.watcher.enabled=false"
    
          - "xpack.graph.enabled=false"
    
          - "xpack.ml.enabled=false"
    
          - "bootstrap.memory_lock=true"
    
          - "cluster.name=mastodon-es"
    
          - "discovery.type=single-node"
    
          - "thread_pool.write.queue_size=1000"
    
        env_file:
    
          - .env.es
    
        networks:
    
          - external_network
    
          - internal_network
    
        healthcheck:
    
          test:
    
            [
    
              "CMD-SHELL",
    
              "curl --silent --fail localhost:9200/_cluster/health || exit 1"
    
            ]
    
        volumes:
    
          - /opt/mastodon/data/elasticsearch:/usr/share/elasticsearch/data
    
        ulimits:
    
          memlock:
    
            soft: -1
    
            hard: -1
    
          nofile:
    
            soft: 65536
    
            hard: 65536
    
        ports:
    
          - '127.0.0.1:9200:9200'
    
    
    
      console:
    
        image: tootsuite/mastodon:v4.0
    
        env_file: .env.mastodon
    
        command: /bin/bash
    
        restart: "no"
    
        depends_on:
    
          - redis
    
        networks:
    
          - internal_network
    
          - external_network
    
        volumes:
    
          - ./data/public/system:/mastodon/public/system
    
    
    
      web:
    
        image: tootsuite/mastodon:v4.0
    
        restart: always
    
        env_file: .env.mastodon
    
        command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    
        networks:
    
          - internal_network
    
          - external_network
    
        healthcheck:
    
          # prettier-ignore
    
          test:
    
            [
    
              'CMD-SHELL',
    
              'wget -q --spider --proxy=off localhost:3000/health || exit 1'
    
            ]
    
        ports:
    
          - '127.0.0.1:3000:3000'
    
        depends_on:
    
          - es
    
          - redis
    
        volumes:
    
          - ./data/public/system:/mastodon/public/system
    
    
    
      streaming:
    
        image: tootsuite/mastodon:v4.0
    
        restart: always
    
        env_file: .env.mastodon
    
        command: node ./streaming
    
        networks:
    
          - external_network
    
          - internal_network
    
        healthcheck:
    
          # prettier-ignore
    
          test:
    
            [
    
              'CMD-SHELL',
    
              'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1'
    
            ]
    
        ports:
    
          - '127.0.0.1:4000:4000'
    
    
    
      sidekiq:
    
        image: tootsuite/mastodon:v4.0
    
        restart: always
    
        env_file: .env.mastodon
    
        command: bundle exec sidekiq
    
        networks:
    
          - external_network
    
          - internal_network
    
        volumes:
    
          - ./data/public/system:/mastodon/public/system
    
        healthcheck:
    
          test: [ 'CMD-SHELL', "ps aux | grep '[s]idekiq 6' || false" ]
    

Option 2: Deploy PostgreSQL Database with Docker

  1. Create a file named docker-compose.yml at /opt/mastodon/docker-compose.yml with the following contents. Replace tootsuite/mastodon:v4.0 with another tag if you want.

    version: '3'
    
    networks:
    
      external_network:
    
      internal_network:
    
        internal: true
    
    services:
    
      db:
    
        restart: always
    
        image: postgres:14-alpine
    
        shm_size: 256mb
    
        networks:
    
          - internal_network
    
        healthcheck:
    
          test: [ 'CMD', 'pg_isready', '-U', 'postgres' ]
    
        volumes:
    
          - ./data/postgres:/var/lib/postgresql/data
    
        environment:
    
          - 'POSTGRES_HOST_AUTH_METHOD=trust'
    
        env_file:
    
          - .env.db
    
      redis:
    
        restart: always
    
        image: redis:7-alpine
    
        networks:
    
          - internal_network
    
        healthcheck:
    
          test: [ 'CMD', 'redis-cli', 'ping' ]
    
        volumes:
    
          - ./data/redis:/data
    
      es:
    
        restart: always
    
        image: docker.elastic.co/elasticsearch/elasticsearch:7.17.8
    
        environment:
    
          - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
    
          - "xpack.license.self_generated.type=basic"
    
          - "xpack.security.enabled=false"
    
          - "xpack.watcher.enabled=false"
    
          - "xpack.graph.enabled=false"
    
          - "xpack.ml.enabled=false"
    
          - "bootstrap.memory_lock=true"
    
          - "cluster.name=mastodon-es"
    
          - "discovery.type=single-node"
    
          - "thread_pool.write.queue_size=1000"
    
        env_file:
    
          - .env.es
    
        networks:
    
          - external_network
    
          - internal_network
    
        healthcheck:
    
          test:
    
            [
    
              "CMD-SHELL",
    
              "curl --silent --fail localhost:9200/_cluster/health || exit 1"
    
            ]
    
        volumes:
    
          - /opt/mastodon/data/elasticsearch:/usr/share/elasticsearch/data
    
        ulimits:
    
          memlock:
    
            soft: -1
    
            hard: -1
    
          nofile:
    
            soft: 65536
    
            hard: 65536
    
        ports:
    
          - '127.0.0.1:9200:9200'
    
    
    
      console:
    
        image: tootsuite/mastodon:v4.0
    
        env_file: .env.mastodon
    
        command: /bin/bash
    
        restart: "no"
    
        depends_on:
    
          - db
    
          - redis
    
        networks:
    
          - internal_network
    
          - external_network
    
        volumes:
    
          - ./data/public/system:/mastodon/public/system
    
      web:
    
        image: tootsuite/mastodon:v4.0
    
        restart: always
    
        env_file: .env.mastodon
    
        command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    
        networks:
    
          - internal_network
    
          - external_network
    
        healthcheck:
    
          # prettier-ignore
    
          test:
    
            [
    
              'CMD-SHELL',
    
              'wget -q --spider --proxy=off localhost:3000/health || exit 1'
    
            ]
    
        ports:
    
          - '127.0.0.1:3000:3000'
    
        depends_on:
    
          - db
    
          - redis
    
          - es
    
        volumes:
    
          - ./data/public/system:/mastodon/public/system
    
    
    
      streaming:
    
        image: tootsuite/mastodon:v4.0
    
        restart: always
    
        env_file: .env.mastodon
    
        command: node ./streaming
    
        networks:
    
          - external_network
    
          - internal_network
    
        healthcheck:
    
          # prettier-ignore
    
          test:
    
            [
    
              'CMD-SHELL',
    
              'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1'
    
            ]
    
        ports:
    
          - '127.0.0.1:4000:4000'
    
        depends_on:
    
          - db
    
          - redis
    
      sidekiq:
    
        image: tootsuite/mastodon:v4.0
    
        restart: always
    
        env_file: .env.mastodon
    
        command: bundle exec sidekiq
    
        networks:
    
          - external_network
    
          - internal_network
    
        volumes:
    
          - ./data/public/system:/mastodon/public/system
    
        healthcheck:
    
          test: [ 'CMD-SHELL', "ps aux | grep '[s]idekiq 6' || false" ]
    
        depends_on:
    
          - db
    
          - redis
    
  2. Create a file named .env.db at /opt/mastodon/.env.db with the following contents. Replace <YOUR_DATABASE_PASSWORD> with a secure secret for the database. Note that your PostgreSQL username is postgres.

    POSTGRES_USER=postgres
    
    POSTGRES_PASSWORD=<YOUR_DATABASE_PASSWORD>
    
  3. Start the PostgreSQL Database with Docker Compose.

    docker compose -f /opt/mastodon/docker-compose.yml up -d db
    

Deploy Elasticsearch with Docker

Elasticsearch enables full-text search in Mastodon. This section shows how to prepare the system and deploy Elasticsearch with Docker.

  1. Create a file named .env.es at /opt/mastodon/.env.es with the following contents. Replace <YOUR_ELASTIC_SEARCH_PASSWORD> with a secure secret for Elasticsearch.

    ELASTIC_PASSWORD=<YOUR_ELASTIC_SEARCH_PASSWORD>
    
  2. Create a folder at /opt/mastodon/data/elasticsearch to enable persistent storage for Elasticsearch.

    mkdir -p /opt/mastodon/data/elasticsearch
    
  3. Change the folder permission of the /opt/mastodon/data/elasticsearch.

    sudo chown -R 1000:1000 /opt/mastodon/data/elasticsearch
    
  4. Increase vm.max_map_count with sysctl.

    sysctl -w vm.max_map_count=262144
    
  5. Open the file /etc/sysctl.conf with your favorite editor and set the value for vm.max_map_count as follows:

    vm.max_map_count=262144
    
  6. Start the Elasticsearch with Docker Compose.

    docker compose -f /opt/mastodon/docker-compose.yml up -d es
    
  7. Create search indices for Elasticsearch. Ignore the error ProgressBar::InvalidProgressError if it occurs.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl search deploy
    

Prepare Mastodon Secret Keys

Run the following command twice to generate two random secrets. In the next section, note these secrets to replace with the text <YOUR_RANDOM_SECRET>.

docker compose -f /opt/mastodon/docker-compose.yml run --rm console bundle exec rake secret

Here are two examples of random secrets.

009aa164cea560916c4e9cc9232a163783f8164cd6c2751c4cdb85689deca44f578108c0c7e0fecefff34b14da6cae661d10090e38f128e1ec286faf19a4b97c

c962fbb4c4692fbfa8333f50c0358ca38cbe5b88a11e7238752c53897b5ae55fc21e7d893c1673493db195bc39123263198a2bc4a0873c4f0e9038c1dd98fdd4

Run the following command to generate the Voluntary Application Server Identity (VAPID) keys to send and receive website push notifications. In the next section, note these secrets to replace with the text <YOUR_VAPID_PRIVATE_KEY> and <YOUR_VAPID_PUBLIC_KEY>.

docker compose -f /opt/mastodon/docker-compose.yml run --rm console bundle exec rake mastodon:webpush:generate_vapid_key

Here are an example of VAPID keys

VAPID_PRIVATE_KEY=_zy6kJtBrakQy18PWu1zj4VpNecMIEUHK0xKI_8-8KA=

VAPID_PUBLIC_KEY=BCHtrfabm8Q7BAkEEQu2IChJzUeOiB-tBFTIxMuQqxFaXqfsfkYeZfsmwGTGliwPICcw7uFRaaFO754NXUzsSQE=

Prepare Mastodon Environment Variables

Edit the file named .env.mastodon at /opt/mastodon/.env.mastodon with the following contents:

# 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=<YOUR_DOMAIN>



# Redis

# -----

REDIS_HOST=redis

REDIS_PORT=6379



# PostgreSQL

# ----------

DB_HOST=<YOUR_DATABASE_HOST>

DB_USER=<YOUR_DATABASE_USERNAME>

DB_NAME=<YOUR_DATABASE_DBNAME>

DB_PASS=<YOUR_DATABASE_PASSWORD>

DB_PORT=<YOUR_DATABASE_PORT>



# Elasticsearch (optional)

# ------------------------

ES_ENABLED=true

ES_HOST=es

ES_PORT=9200

# Authentication for ES (optional)

ES_USER=elastic

ES_PASS=<YOUR_ELASTIC_SEARCH_PASSWORD>



# Secrets

# -------

# Make sure to use `rake secret` to generate secrets

# -------

SECRET_KEY_BASE=<YOUR_RANDOM_SECRET>

OTP_SECRET=<YOUR_RANDOM_SECRET>



# Web Push

# --------

# Generate with `rake mastodon:webpush:generate_vapid_key`

# --------

VAPID_PRIVATE_KEY=<YOUR_VAPID_PRIVATE_KEY>

VAPID_PUBLIC_KEY=<YOUR_VAPID_PUBLIC_KEY>



# Sending mail

# ------------

SMTP_SERVER=<YOUR_SMTP_SERVER>

SMTP_PORT=587

SMTP_LOGIN=<YOUR_SMTP_LOGIN>

SMTP_PASSWORD=<YOUR_SMTP_PASSWORD>

SMTP_FROM_ADDRESS=<YOUR_SMTP_EMAIL>



# File storage (optional)

# -----------------------

S3_ENABLED=true

S3_BUCKET=<YOUR_OBJECT_STORAGE_BUCKET>

AWS_ACCESS_KEY_ID=<YOUR_OBJECT_STORAGE_ACCESS_KEY>

AWS_SECRET_ACCESS_KEY=<YOUR_OBJECT_STORAGE_SECRET_KEY>

S3_ALIAS_HOST=<YOUR_OBJECT_STORAGE_URL>



# 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

Replace the text placeholder in /opt/mastodon/.env.mastodon as follows:

  • <YOUR_DOMAIN>: your domain name.

  • <YOUR_DATABASE_HOST>: your PostgreSQL database host. Use db if you deploy a PostgreSQL with Docker.

  • <YOUR_DATABASE_USERNAME>: your PostgreSQL username from the previous section.

  • <YOUR_DATABASE_DBNAME>: your PostgreSQL database name from the previous section.

  • <YOUR_DATABASE_PASSWORD>: your PostgreSQL database password from the previous section.

  • <YOUR_DATABASE_PORT>: your PostgreSQL database port from the previous section.

  • <YOUR_ELASTIC_SEARCH_PASSWORD>: your Elasticsearch password from the previous section.

  • <YOUR_RANDOM_SECRET>: Mastodon secret keys from the previous section.

  • <YOUR_VAPID_PRIVATE_KEY>: VAPID private Key from the previous section.

  • <YOUR_VAPID_PUBLIC_KEY>: VAPID public Key from the previous section.

  • <YOUR_SMTP_LOGIN>: your SMPL account credentials.

  • <YOUR_SMTP_PASSWORD>: your SMPL account credentials.

  • <YOUR_SMTP_EMAIL>: your SMPL email address.

  • <YOUR_SMTP_SERVER>: your SMPL server information.

  • <YOUR_OBJECT_STORAGE_BUCKET>: your Rcs Object Storage bucket name.

  • <YOUR_OBJECT_STORAGE_ACCESS_KEY>: your Rcs Object Storage Access Key.

  • <YOUR_OBJECT_STORAGE_SECRET_KEY>: your Rcs Object Storage Secret Key.

  • <YOUR_OBJECT_STORAGE_URL>: your Rcs Object Storage URL.

Deploy Mastodon with Docker Compose

If you use the Rcs Managed PostgreSQL database, run the following command to set up the database.

docker compose -f /opt/mastodon/docker-compose.yml run --rm console bundle exec rake db:migrate

If you deploy PostgreSQL with Docker, run the following command to set up the database.

docker compose -f /opt/mastodon/docker-compose.yml run --rm console bundle exec rake db:setup

Deploy Mastodon services with Docker Compose.

docker compose -f /opt/mastodon/docker-compose.yml up -d

Deploy Nginx Reverse Proxy

  1. Install Nginx.

    sudo apt-get update
    
    sudo apt-get install -y nginx
    
  2. Create a file named mastodon at /etc/nginx/sites-available/mastodon with the following contents. Replace example.com with your domain.

    server {
    
        server_name example.com;
    
    
    
        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 $scheme;
    
        proxy_set_header Proxy "";
    
        proxy_http_version 1.1;
    
        proxy_set_header Upgrade $http_upgrade;
    
        proxy_set_header Connection "upgrade";
    
    
    
            location / {
    
                proxy_pass http://localhost:3000;
    
                proxy_pass_header Server;
    
    
    
                proxy_buffering on;
    
                proxy_redirect off;
    
            }
    
    
    
            location ^~ /api/v1/streaming {
    
    
    
                proxy_pass http://localhost:4000;
    
                proxy_buffering off;
    
                proxy_redirect off;
    
            }
    
    }
    
  3. Link to sites-enabled to enable the virtual host.

    ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/
    
  4. Reload the nginx service.

    systemctl restart nginx
    
  5. Config ufw firewall to allow Nginx ports. Skip if your server doesn't have ufw.

    sudo ufw allow 'Nginx Full'
    

Secure Mastodon with Let's Encrypt SSL

  1. Install Certbot.

    sudo apt-get install -y certbot python3-certbot-nginx
    
  2. Run Certbot to automatically enable Let's Encrypt SSL for your domain. Replace example.com with your domain.

    sudo certbot --nginx -d example.com
    
  3. Reload the nginx service.

    systemctl restart nginx
    
  4. Navigate to https://<YOUR_DOMAIN> to access your Mastodon instance.

Manage Your Mastodon Instance

Here are some useful commands to manage your Mastodon instance.

  • Use Docker Compose run command to access the toolctl.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl
    
  • Create the owner user. Replace example_user with your user name and admin@gmail.com with your email address.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl accounts create example_user --email admin@gmail.com --confirmed --role Owner
    
  • Create search indices for Elasticsearch.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl search deploy
    
  • Disable registrations.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl settings registrations close
    
  • Remove cached media files.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl media remove
    
  • Remove local thumbnails for preview cards.

    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl preview_cards remove
    

Setup Maintenance Automation

  1. Make a script file named auto-cleanup.sh at /opt/mastodon/auto-cleanup.sh with the following contents:

    #!/bin/sh
    
    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl media remove
    
    docker compose -f /opt/mastodon/docker-compose.yml run --rm console bin/tootctl preview_cards remove
    
  2. Make the script /opt/mastodon/auto-cleanup.sh executable.

    chmod +x /opt/mastodon/auto-cleanup.sh
    
  3. Open crontab

    crontab -e
    
  4. Add a new crontab job to run auto-cleanup.sh every day at 00:00.

    0 0 * * * /opt/mastodon/auto-cleanup.sh
    

Configure Your Server Firewall

  1. Turn on automatic security updates.

    sudo dpkg-reconfigure -plow unattended-upgrades
    
  2. Set up a firewall with ufw.

    sudo apt-get install ufw
    
    sudo ufw default allow outgoing
    
    sudo ufw default deny incoming
    
    sudo ufw allow 22 comment 'SSH'
    
    sudo ufw allow http comment 'HTTP'
    
    sudo ufw allow https comment 'HTTPS'
    
    sudo ufw enable
    
  3. Check your firewall status

    sudo ufw status
    
  4. Install fail2ban to secure your server

    sudo apt-get install -y fail2ban
    

Configure fail2ban to Use ufw

  1. Copy the main configuration to avoid unexpected changes during package updates.

    sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
    
  2. Edit the configuration file with your favorite text editor

    sudo nano /etc/fail2ban/jail.local
    
  3. Change the banaction and banaction_allports settings to ufw in the file /etc/fail2ban/jail.local as follows:

    banaction = ufw
    
    banaction_allports = ufw
    
  4. Restart the fail2ban service.

    sudo systemctl restart fail2ban
    

Further reading

For more details about how to use Cache-Control headers with Nginx, see this Nginx Configuration file at the official Mastodon GitHub repository.


Was this answer helpful?
Back

Powered by WHMCompleteSolution