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 32and 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:
- Log in to YunoHost Admin.
- Install the "Redirect" app.
- 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, notowneroradmin) 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 |
No comments to display
No comments to display