Skip to main content

Deploying a Customized Mastodon Instance on YunoHost (via Docker)

1. Context & Objectives

This guide documents the process of deploying a Mastodon social media server on a VPS running YunoHost. Instead of using the official YunoHost app (which can be slow to update), we used Docker Compose to maintain control over the version and configuration.

Key Goals:

  • Host Mastodon at mstdn.obulou.org.
  • Use the LinuxServer.io image (lighter, easier permission management).
  • Route traffic through YunoHost’s Nginx reverse proxy.
  • Customise the character limit (300 chars) without full recompilation.

2. Preparation

Directory Structure

We created a dedicated directory to keep the setup clean.

mkdir -p /opt/mastodon
cd /opt/mastodon

The Environment File (.env)

We created a .env file to store secrets.

  • Crucial Step: We encountered a "boot loop" where Mastodon refused to start because it lacked three specific security keys. We had to generate these manually using openssl rand -hex 32 and add them to the file.

.env Configuration:

# User permissions (match your host user, usually 1000)
PUID=1000
PGID=1000
TZ=Asia/Kuala_Lumpur

# Domain Settings
LOCAL_DOMAIN=mstdn.obulou.org
WEB_DOMAIN=mstdn.obulou.org
# We set this to false to let YunoHost handle the SSL handshake
LOCAL_HTTPS=false

# Database & Redis (Internal Docker Hostnames)
DB_HOST=db
DB_PORT=5432
DB_NAME=mastodon
DB_USER=mastodon
DB_PASS=your_secure_password
REDIS_HOST=redis
REDIS_PORT=6379

# SMTP Settings (Required for sign-ups)
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_LOGIN=your_email@gmail.com
SMTP_PASSWORD=your_app_password
SMTP_FROM_ADDRESS=your_email@gmail.com
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=peer

# Secrets (Generate these with 'openssl rand -hex 64')
SECRET_KEY_BASE=your_generated_secret
OTP_SECRET=your_generated_otp_secret

# New Mandatory Keys (Generate with 'openssl rand -hex 32')
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=your_key_1
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=your_key_2
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=your_key_3


3. Docker Compose Configuration

We created a docker-compose.yml file. Note that we mapped port 56736 on the host to port 80 on the container.

services:
  db:
    image: postgres:14-alpine
    restart: unless-stopped
    shm_size: 256mb
    volumes:
      - ./postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=mastodon
      - POSTGRES_USER=mastodon
      - POSTGRES_PASSWORD=${DB_PASS}
    networks:
      - mastodon_net

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - ./redis_data:/data
    networks:
      - mastodon_net

  mastodon:
    image: lscr.io/linuxserver/mastodon:latest
    container_name: mastodon
    restart: unless-stopped
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=${TZ}
      - LOCAL_DOMAIN=${LOCAL_DOMAIN}
    env_file:
      - .env
    volumes:
      - ./config:/config
      - ./public/system:/mastodon/public/system
    ports:
      - 56736:80
    depends_on:
      - db
      - redis
    networks:
      - mastodon_net

networks:
  mastodon_net:
    driver: bridge


4. Solving the "Redirect Loop" (The Nginx Fix)

After starting the container, we faced a net::ERR_TOO_MANY_REDIRECTS error.

The Cause: YunoHost handles SSL (HTTPS) and passes traffic to Docker via HTTP. The default Nginx config inside the Docker container saw the HTTP traffic and immediately redirected it back to HTTPS, creating an infinite loop.

The Solution: We replaced the internal Nginx configuration to accept traffic on Port 80 without forcing a redirect.

File Location (on host): /opt/mastodon/config/nginx/site-confs/default.conf

New Content:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    root /app/www/public;
    index index.html index.htm;

    location / {
        try_files $uri @rails;
    }

    location @rails {
        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 $http_x_forwarded_proto;
        proxy_set_header Proxy "";
        proxy_pass_header Server;

        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;
    }
    
    # (Streaming API block also added here, same proxy headers)
}

After editing this file, we restarted the container.


5. Connecting YunoHost (Reverse Proxy)

To expose the container to the web:

  1. Log in to YunoHost Admin.
  2. Install the "Redirect" app.
  3. Settings:
  • Domain: mstdn.obulou.org
  • Path: /
  • Type: Proxy
  • Destination: http://127.0.0.1:56736

6. Creating the Owner Account

We had to create the first user via the command line.

  • Note: The role name is case-sensitive (Owner, not owner or admin) in newer Mastodon versions.
docker exec -it mastodon tootctl accounts create your_username --email your@email.com --confirmed --role Owner

This command outputs the temporary password used to log in.


7. Customisation: Changing Character Limit (No Recompile)

We successfully changed the character limit from 500 to 300 without recompiling the assets (which requires heavy RAM usage).

We used sed to edit the Ruby files in-place inside the container.

The Command:

docker exec -it mastodon sed -i 's/MAX_CHARS = 500/MAX_CHARS = 300/' /app/www/app/validators/status_length_validator.rb && \
docker exec -it mastodon sed -i 's/500/300/' /app/www/app/serializers/rest/instance_serializer.rb && \
cd /opt/mastodon && \
docker compose restart mastodon

Result:

  • Mobile Apps: Correctly enforce the 300-character limit.
  • Web Interface: The limit is enforced by the backend (posts >300 chars fail), and in our testing, the web UI counter also updated successfully after a server restart.

8. Summary of Commands

Action Command
Start Server docker compose up -d
Restart Server docker compose restart mastodon
View Logs docker compose logs -f mastodon
Run Migration docker exec -it mastodon rails db:migrate
Approve User docker exec -it mastodon tootctl accounts approve username