Uncategorized

Complete Guide to Installing and Securing Mastodon with Docker on Ubuntu

Before you start

  • 12 min read
  • 76 to run in terminal
  • 10 file contents

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

Introduction#

Mastodon is an open-source, decentralized social network that gives users control over their data and content. Unlike centralized platforms such as Twitter, Mastodon operates on a federated model where independent servers (instances) connect to form a larger network.

Docker provides an excellent deployment method for Mastodon because it offers:

  • Isolation between components
  • Consistency across environments
  • Simplified management and maintenance
  • Easier upgrades and backups
  • Enhanced security through containerization

This comprehensive guide will walk you through setting up your own Mastodon instance using Docker on Ubuntu 20.04 or newer.

Instruction Layout

Prerequisites#

Before beginning installation, ensure you have:

  • A server running Ubuntu 20.04 or newer
  • At least 2GB of RAM and 2 CPU cores (4GB+ recommended for production)
  • At least 20GB of free storage space (more for a growing instance)
  • A registered domain name pointing to your server
  • Basic familiarity with Linux command line
  • Root or sudo access to your server
  • An SMTP provider for sending emails (required for account verification)
  • Ports 80 and 443 open on your firewall for HTTP/HTTPS traffic

Hardware Recommendations#

For optimal performance, consider these resource guidelines:

  • Minimum: 2GB RAM, 2 CPU cores, 20GB storage
  • Recommended: 4GB RAM, 2-4 CPU cores, 40GB+ storage
  • For larger instances: 8GB+ RAM, 4+ CPU cores, 100GB+ storage with separate database server

Networking Requirements#

Your server must have these ports accessible:

  • Port 22: SSH access (restrict to your IP if possible)
  • Port 80: HTTP (for initial setup and redirecting to HTTPS)
  • Port 443: HTTPS (for secure access to your instance)

System Preparation#

Let's start by updating your system and installing essential utilities.

Run in terminalbash
sudo apt update && sudo apt upgrade -y

This updates your package lists and upgrades installed packages to their latest versions.

Run in terminalbash
sudo apt install -y ca-certificates curl gnupg lsb-release apt-transport-https software-properties-common

This installs necessary utilities for adding repositories and downloading packages securely.

Setting Up Firewall (UFW)#

Configure a basic firewall to protect your server:

Run in terminalbash
sudo ufw status

Checks if the firewall is active. If the output shows "inactive," proceed with the following steps.

Run in terminalbash
sudo ufw allow OpenSSH

Allows SSH connections so you don't get locked out of your server.

Run in terminalbash
sudo ufw allow 80/tcp

Opens HTTP port for initial setup and Let's Encrypt verification.

Run in terminalbash
sudo ufw allow 443/tcp

Opens HTTPS port for secure web traffic.

Run in terminalbash
sudo ufw enable

Activates the firewall. You'll be prompted to confirm with 'y' - this might disconnect your SSH session if you haven't allowed OpenSSH first.

Run in terminalbash
sudo ufw status

Verifies the firewall is active and shows the allowed services.

Installing Docker and Docker Compose#

Docker allows us to run Mastodon and its dependencies in isolated containers.

Install Docker#

Run in terminalbash
sudo apt remove docker docker-engine docker.io containerd runc

Removes any old Docker versions that might conflict with the installation.

Run in terminalbash
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Downloads and installs Docker's official GPG key for package verification.

Run in terminalbash
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

Adds Docker's official repository to your system.

Run in terminalbash
sudo apt update

Updates package lists to include Docker packages.

Run in terminalbash
sudo apt install -y docker-ce docker-ce-cli containerd.io

Installs Docker Engine and related packages.

Run in terminalbash
sudo systemctl status docker

Verifies Docker is running. You should see "active (running)" in the output.

Install Docker Compose Plugin#

Run in terminalbash
sudo apt install -y docker-compose-plugin

Installs the Docker Compose plugin through apt.

Run in terminalbash
docker compose version

Verifies Docker Compose is installed properly. You should see version information.

Add Your User to Docker Group (Optional)#

This step allows you to run Docker commands without sudo.

Run in terminalbash
sudo usermod -aG docker $USER

Adds your current user to the Docker group. You'll need to log out and back in for this to take effect.

Setting Up Mastodon#

Now let's create the directory structure and download Mastodon.

Run in terminalbash
sudo mkdir -p /opt/mastodon

Creates a directory for Mastodon files.

Run in terminalbash
sudo chown $USER:$USER /opt/mastodon

Gives your user ownership of the Mastodon directory.

Run in terminalbash
cd /opt/mastodon

Changes to the Mastodon directory.

Create Directory Structure#

Run in terminalbash
mkdir -p public/system

Creates a directory for user uploads and other files.

Run in terminalbash
mkdir -p postgres

Creates a directory to store PostgreSQL data.

Run in terminalbash
mkdir -p redis

Creates a directory to store Redis data.

Download Mastodon Docker Configuration#

Run in terminalbash
curl -L https://raw.githubusercontent.com/mastodon/mastodon/main/docker-compose.yml -o docker-compose.yml

Downloads the official Mastodon docker-compose.yml file.

Run in terminalbash
curl -L https://raw.githubusercontent.com/mastodon/mastodon/main/.env.production.sample -o .env.production

Downloads the sample environment configuration file.

Configure Docker Compose#

Now we need to modify the docker-compose.yml file to use prebuilt images instead of building locally.

Run in terminalbash
sed -i 's/^  build: .$/  # build: /' docker-compose.yml

Comments out the build directive for each service.

Run in terminalbash
sed -i 's/^  image: tootsuite\/mastodon$/  image: tootsuite\/mastodon:latest/' docker-compose.yml

Changes the image reference to use the latest prebuilt image. For production, you might want to specify a specific version.

Setting File Permissions#

Run in terminalbash
sudo chown -R 991:991 public

Sets the correct permissions for the public directory. 991 is the default user ID for Mastodon in the Docker container.

Configuring Mastodon#

Now we'll set up the configuration file for Mastodon.

Generate Secrets#

Run in terminalbash
docker compose run --rm web rake secret

Generates a random string for the SECRET_KEY_BASE. Copy this output.

Run in terminalbash
docker compose run --rm web rake secret

Run this command again to generate a random string for OTP_SECRET. Copy this output.

Run in terminalbash
docker compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key

Generates VAPID keys for Web Push notifications. Copy both the private and public keys.

Edit the Environment Configuration#

Now you need to edit the .env.production file to configure your Mastodon instance:

Run in terminalbash
nano .env.production

Opens the environment file for editing. You'll need to update the following settings:

  1. LOCAL_DOMAIN: Set to your domain name
  2. DB_HOST: Set to "db"
  3. DB_USER: Set to "postgres"
  4. DB_NAME: Set to "postgres"
  5. DB_PASS: Leave empty for this setup
  6. REDIS_HOST: Set to "redis"
  7. SECRET_KEY_BASE: Paste the first secret generated earlier
  8. OTP_SECRET: Paste the second secret generated earlier
  9. VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY: Paste the VAPID keys generated earlier
  10. SMTP settings: Fill in with your email provider's details

SMTP Configuration Example#

Here's an example SMTP configuration using Gmail (you should use an application-specific password):

File contentbash
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
[email protected]
SMTP_PASSWORD=your_app_password
SMTP_FROM_ADDRESS=Mastodon 
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto

Initial Setup#

Now that we have the configuration in place, let's set up the database and create an admin account.

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

This interactive command will verify your configuration, set up the database, and prompt you to create an admin user.

When prompted:

  • Confirm your configuration settings
  • Choose to create an admin user when asked
  • Enter your desired username, email, and password for the admin account
  • For other prompts, accept the default values by pressing Enter

Starting Mastodon#

Now we're ready to start Mastodon:

Run in terminalbash
docker compose up -d

Starts all Mastodon services in detached mode. This may take a few minutes the first time.

Run in terminalbash
docker compose ps

Checks that all services are running. All containers should show "Up" status.

Setting Up Nginx as a Reverse Proxy#

To expose Mastodon securely to the internet, we'll set up Nginx as a reverse proxy with HTTPS.

Install Nginx and Certbot#

Run in terminalbash
sudo apt install -y nginx certbot python3-certbot-nginx

Installs Nginx web server and Certbot for obtaining SSL certificates.

Create Nginx Configuration#

Run in terminalbash
sudo nano /etc/nginx/sites-available/mastodon

Creates a new Nginx configuration file for Mastodon.

Paste the following configuration, replacing yourdomain.com with your actual domain:

File contentbash
server {
    listen 80;
    listen [::]:80;
    server_name yourdomain.com;
    
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }
    
    location / {
        return 301 https://$host$request_uri;
    }
}

Save and exit (Ctrl+X, then Y, then Enter).

Run in terminalbash
sudo ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/

Enables the Nginx configuration by creating a symbolic link.

Run in terminalbash
sudo nginx -t

Tests the Nginx configuration for syntax errors.

Run in terminalbash
sudo systemctl reload nginx

Reloads Nginx to apply the new configuration.

Obtain SSL Certificate#

Run in terminalbash
sudo certbot --nginx -d yourdomain.com

Runs Certbot to obtain an SSL certificate for your domain. Follow the prompts to complete the process.

After Certbot completes, it will update your Nginx configuration with SSL settings. Verify that the configuration was updated correctly:

Run in terminalbash
sudo nano /etc/nginx/sites-available/mastodon

The file should now include SSL settings and a server block for HTTPS. If needed, adjust the configuration to include proper proxy settings:

File contentbash
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name yourdomain.com;
    
    # SSL settings added by Certbot
    
    # Mastodon proxy settings
    location / {
        proxy_pass http://127.0.0.1:3000;
        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_buffering off;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        
        client_max_body_size 16m;
    }
    
    location /api/v1/streaming {
        proxy_pass http://127.0.0.1:4000;
        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_buffering off;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Save and exit.

Run in terminalbash
sudo nginx -t

Tests the updated Nginx configuration.

Run in terminalbash
sudo systemctl reload nginx

Reloads Nginx to apply the changes.

Security Hardening#

Let's add additional security measures to protect your Mastodon instance.

Configure Docker Container Security#

Edit the docker-compose.yml file to improve container security:

Run in terminalbash
nano docker-compose.yml

Add the following security options to each service:

File contentbash
security_opt:
  - no-new-privileges:true

This prevents processes in the container from gaining additional privileges.

Set Up Fail2ban to Protect Against Brute Force Attacks#

Run in terminalbash
sudo apt install -y fail2ban

Installs Fail2ban, which helps protect against brute force login attempts.

Run in terminalbash
sudo nano /etc/fail2ban/jail.local

Creates a configuration file for Fail2ban.

Add the following content:

File contentbash
[sshd]
enabled = true
maxretry = 3
findtime = 1h
bantime = 24h

[nginx-http-auth]
enabled = true

Save and exit.

Run in terminalbash
sudo systemctl restart fail2ban

Restarts Fail2ban to apply the configuration.

Data Persistence and Backups#

Ensuring data persistence and regular backups is crucial for any production service.

Create a Backup Script#

Run in terminalbash
nano /opt/mastodon/backup.sh

Creates a backup script.

Add the following content:

File contentbash
#!/bin/bash
# Mastodon backup script
BACKUP_DIR="/opt/mastodon/backups"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="mastodon_backup_$DATE.tar.gz"

# Create backup directory if it doesn't exist
mkdir -p $BACKUP_DIR

# Go to Mastodon directory
cd /opt/mastodon

# Create database backup
docker compose exec -T db pg_dump -U postgres postgres > $BACKUP_DIR/mastodon_db_$DATE.sql

# Backup configuration files
cp .env.production $BACKUP_DIR/.env.production_$DATE

# Backup public uploads
tar -czf $BACKUP_DIR/$BACKUP_FILE public/system

# Keep only the last 7 backups
find $BACKUP_DIR -name "mastodon_backup_*" -type f -mtime +7 -delete
find $BACKUP_DIR -name "mastodon_db_*" -type f -mtime +7 -delete
find $BACKUP_DIR -name ".env.production_*" -type f -mtime +7 -delete

echo "Backup completed: $BACKUP_FILE"

Save and exit.

Run in terminalbash
chmod +x /opt/mastodon/backup.sh

Makes the backup script executable.

Set Up a Cron Job for Regular Backups#

Run in terminalbash
sudo crontab -e

Opens the root crontab file.

Add the following line to run backups daily at 2 AM:

File contentbash
0 2 * * * /opt/mastodon/backup.sh > /var/log/mastodon_backup.log 2>&1

Save and exit.

Common Issues and Troubleshooting#

Here are the most frequent issues users encounter and their solutions.

Checking Logs#

To check logs for troubleshooting:

Run in terminalbash
docker compose logs

Shows logs from all services.

Run in terminalbash
docker compose logs web

Shows logs from the web service only.

Run in terminalbash
docker compose logs -f

Shows logs and follows them in real-time (press Ctrl+C to exit).

Common Issues and Solutions#

Database Connection Issues#

Issue: Mastodon cannot connect to the database.

Diagnosis:

Run in terminalbash
docker compose logs web | grep "PG::ConnectionBad"

Solution: Check DB_HOST, DB_USER, and DB_PASS in .env.production. Ensure they match the configuration in docker-compose.yml.

Redis Connection Issues#

Issue: Mastodon cannot connect to Redis.

Diagnosis:

Run in terminalbash
docker compose logs web | grep "Redis::CannotConnectError"

Solution: Verify REDIS_HOST in .env.production is set to "redis".

Email Not Working#

Issue: Registration emails and notifications aren't being sent.

Diagnosis:

Run in terminalbash
docker compose logs sidekiq | grep "SMTP"

Solution: Check your SMTP configuration in .env.production. Some providers may require enabling less secure apps or generating app-specific passwords.

Insufficient Disk Space#

Issue: Docker operations fail due to low disk space.

Diagnosis:

Run in terminalbash
df -h

Solution: Clean unused Docker resources:

Run in terminalbash
docker system prune -a

Web Push Not Working#

Issue: Push notifications don't work.

Diagnosis: Check if VAPID keys are properly configured:

Run in terminalbash
grep VAPID .env.production

Solution: If keys are missing or invalid, regenerate them:

Run in terminalbash
docker compose run --rm web bundle exec rake mastodon:webpush:generate_vapid_key

Update the values in .env.production and restart Mastodon.

Maintenance Tasks#

Regular maintenance ensures your Mastodon instance stays healthy and secure.

Updating Mastodon#

Run in terminalbash
cd /opt/mastodon

Changes to the Mastodon directory.

Run in terminalbash
docker compose pull

Pulls the latest container images.

Run in terminalbash
docker compose down

Stops all containers.

Run in terminalbash
docker compose up -d

Starts containers with updated images.

Run in terminalbash
docker compose run --rm web rails db:migrate

Runs database migrations after updating.

Run in terminalbash
docker compose run --rm web rails assets:precompile

Recompiles static assets after updating.

Monitoring Server Health#

Run in terminalbash
htop

Monitors system resource usage in real-time. (Install with sudo apt install htop if not available)

Run in terminalbash
docker stats

Shows resource usage of Docker containers.

Run in terminalbash
df -h

Checks disk space usage.

User Management#

To create a new administrator account:

Run in terminalbash
docker compose run --rm web bin/tootctl accounts create username [email protected] --confirmed --role=admin

Creates a new admin user with the specified username and email.

To moderate content:

Run in terminalbash
docker compose run --rm web bin/tootctl media remove

Removes remote media that's no longer referenced.

Known Issues and Workarounds#

High Memory Usage#

Issue: Mastodon services, especially Sidekiq, can use a lot of memory on low-spec servers.

Workaround: Adjust the concurrency in docker-compose.yml for the Sidekiq service:

Run in terminalbash
nano docker-compose.yml

Find the Sidekiq section and add an environment variable:

File contentbash
environment:
  - SIDEKIQ_CONCURRENCY=5

Save and restart:

Run in terminalbash
docker compose restart sidekiq

Slow Initial Load Times#

Issue: Initial page loads can be slow.

Workaround: Enable caching by adding these to .env.production:

File contentbash
CACHE_REDIS_HOST=redis
CACHE_REDIS_PORT=6379
CACHE_REDIS_DB=0

File Upload Size Limitations#

Issue: Users cannot upload large files.

Workaround: Adjust Nginx client_max_body_size:

Run in terminalbash
sudo nano /etc/nginx/sites-available/mastodon

Find and modify the client_max_body_size directive:

File contentbash
client_max_body_size 40m;

Save and reload Nginx:

Run in terminalbash
sudo systemctl reload nginx

Conclusion#

Congratulations! You now have a fully functioning Mastodon instance running on Docker with security best practices in place. This setup provides a solid foundation for running your own social media server while maintaining control over your data and user experience.

Remember to:

  • Regularly update your system and Mastodon
  • Monitor server resources
  • Keep backups of your data
  • Stay informed about security advisories

By following these practices, you'll ensure a stable, secure, and reliable Mastodon experience for you and your users.