<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mediawiki.comfac.net/index.php?action=history&amp;feed=atom&amp;title=Editing_Mastodon_Docker_Deployment_Guide_260402</id>
	<title>Editing Mastodon Docker Deployment Guide 260402 - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://mediawiki.comfac.net/index.php?action=history&amp;feed=atom&amp;title=Editing_Mastodon_Docker_Deployment_Guide_260402"/>
	<link rel="alternate" type="text/html" href="https://mediawiki.comfac.net/index.php?title=Editing_Mastodon_Docker_Deployment_Guide_260402&amp;action=history"/>
	<updated>2026-06-05T09:48:01Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.1</generator>
	<entry>
		<id>https://mediawiki.comfac.net/index.php?title=Editing_Mastodon_Docker_Deployment_Guide_260402&amp;diff=217&amp;oldid=prev</id>
		<title>Justinaquino: Created page with &quot;= Mastodon on Docker — Self-Hosted Deployment Guide =  &#039;&#039;&#039;Behind Nginx Proxy Manager with Let&#039;s Encrypt SSL&#039;&#039;&#039;  Using linuxserver.io image | Ubuntu 24 | Docker Compose  {{Tip|This guide is written so that a thinking model AI agent (such as one running on your local server via Ollama/Open WebUI) can follow these instructions step-by-step and get Mastodon running without human intervention. Every command is verified against a successful real-world deployment. Verify all...&quot;</title>
		<link rel="alternate" type="text/html" href="https://mediawiki.comfac.net/index.php?title=Editing_Mastodon_Docker_Deployment_Guide_260402&amp;diff=217&amp;oldid=prev"/>
		<updated>2026-04-02T12:08:27Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;= Mastodon on Docker — Self-Hosted Deployment Guide =  &amp;#039;&amp;#039;&amp;#039;Behind Nginx Proxy Manager with Let&amp;#039;s Encrypt SSL&amp;#039;&amp;#039;&amp;#039;  Using linuxserver.io image | Ubuntu 24 | Docker Compose  {{Tip|This guide is written so that a thinking model AI agent (such as one running on your local server via Ollama/Open WebUI) can follow these instructions step-by-step and get Mastodon running without human intervention. Every command is verified against a successful real-world deployment. Verify all...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Mastodon on Docker — Self-Hosted Deployment Guide =&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Behind Nginx Proxy Manager with Let&amp;#039;s Encrypt SSL&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
Using linuxserver.io image | Ubuntu 24 | Docker Compose&lt;br /&gt;
&lt;br /&gt;
{{Tip|This guide is written so that a thinking model AI agent (such as one running on your local server via Ollama/Open WebUI) can follow these instructions step-by-step and get Mastodon running without human intervention. Every command is verified against a successful real-world deployment. Verify all outputs at each stage before proceeding.}}&lt;br /&gt;
&lt;br /&gt;
== Why Mastodon Is More Complex Than Other Docker Apps ==&lt;br /&gt;
&lt;br /&gt;
Most Docker apps are a single process that sits behind a reverse proxy. Mastodon is different in three important ways:&lt;br /&gt;
&lt;br /&gt;
# It runs &amp;#039;&amp;#039;&amp;#039;five processes simultaneously&amp;#039;&amp;#039;&amp;#039; inside one container: Rails (web), Puma (app server), Sidekiq (background jobs), a Node.js streaming server, and its own internal nginx. This is managed by s6-overlay.&lt;br /&gt;
# It assumes it owns ports 80 and 443 directly. Placing it behind NPM requires overriding its internal nginx SSL behavior, which it resists.&lt;br /&gt;
# It enforces cryptographic secrets before it will even start. Unlike most apps, Mastodon refuses to boot without SECRET_KEY_BASE, OTP_SECRET, VAPID keys, and ACTIVE_RECORD_ENCRYPTION keys all pre-generated.&lt;br /&gt;
&lt;br /&gt;
{{Warning|Mastodon also takes 30-60 seconds to fully boot. Always check logs before assuming something is wrong.}}&lt;br /&gt;
&lt;br /&gt;
== Prerequisites ==&lt;br /&gt;
&lt;br /&gt;
* Ubuntu 24 server (bare metal or VM)&lt;br /&gt;
* Docker and Docker Compose installed&lt;br /&gt;
* Nginx Proxy Manager (NPM) already running on ports 80 and 443&lt;br /&gt;
* A domain name with an A record pointing to your server&amp;#039;s public IP&lt;br /&gt;
* Gmail account with an App Password generated (for SMTP)&lt;br /&gt;
&lt;br /&gt;
{{Critical|To generate a Gmail App Password: go to myaccount.google.com/apppasswords, create a new app password, and save it in a password manager immediately. Do not paste it into any chat or shared document.}}&lt;br /&gt;
&lt;br /&gt;
== Step 1 — Create the Directory ==&lt;br /&gt;
&lt;br /&gt;
All Mastodon files live in one directory. Create it and navigate into it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
mkdir -p /opt/mastodon&lt;br /&gt;
cd /opt/mastodon&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The volume mount &amp;lt;code&amp;gt;/opt/mastodon/config:/config&amp;lt;/code&amp;gt; will be auto-created by linuxserver on first run and will populate with nginx config, keys, and logs.&lt;br /&gt;
&lt;br /&gt;
== Step 2 — Generate All Secrets Before Starting ==&lt;br /&gt;
&lt;br /&gt;
{{Warning|The linuxserver image entrypoint interferes with rails commands. Always override with &amp;lt;code&amp;gt;--entrypoint=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; and use the full path &amp;lt;code&amp;gt;/app/www/bin/rails&amp;lt;/code&amp;gt;. You must also set the working directory with &amp;lt;code&amp;gt;-w /app/www&amp;lt;/code&amp;gt; for rake tasks.}}&lt;br /&gt;
&lt;br /&gt;
=== SECRET_KEY_BASE (run once) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm --entrypoint=&amp;quot;&amp;quot; lscr.io/linuxserver/mastodon:latest /app/www/bin/rails secret&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== OTP_SECRET (run again for a different value) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm --entrypoint=&amp;quot;&amp;quot; lscr.io/linuxserver/mastodon:latest /app/www/bin/rails secret&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VAPID Keys ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm --entrypoint=&amp;quot;&amp;quot; -w /app/www lscr.io/linuxserver/mastodon:latest /app/www/bin/rails mastodon:webpush:generate_vapid_key&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Output will look like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
VAPID_PRIVATE_KEY=OqZ07QTRwoO...&lt;br /&gt;
VAPID_PUBLIC_KEY=BA7IhTSGcf0...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== ACTIVE_RECORD_ENCRYPTION Keys (new requirement in Mastodon 4.3+) ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker run --rm --entrypoint=&amp;quot;&amp;quot; -w /app/www lscr.io/linuxserver/mastodon:latest /app/www/bin/rails db:encryption:init&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Output will look like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=Xf3Rvl...&lt;br /&gt;
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=TK5DQU...&lt;br /&gt;
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=f8cyBe...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Critical|Copy all seven values now. You will not be able to change them after the database is initialized without risking data loss.}}&lt;br /&gt;
&lt;br /&gt;
== Step 3 — Create docker-compose.yml ==&lt;br /&gt;
&lt;br /&gt;
Create the file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
nano /opt/mastodon/docker-compose.yml&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Paste the following, replacing all placeholder values with your actual secrets and domain:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot;&amp;gt;&lt;br /&gt;
services:&lt;br /&gt;
  mastodon:&lt;br /&gt;
    image: lscr.io/linuxserver/mastodon:latest&lt;br /&gt;
    container_name: mastodon&lt;br /&gt;
    environment:&lt;br /&gt;
      - PUID=1000&lt;br /&gt;
      - PGID=1000&lt;br /&gt;
      - TZ=Asia/Manila&lt;br /&gt;
      - LOCAL_DOMAIN=toot.yourdomain.com&lt;br /&gt;
      - NO_SSL=true&lt;br /&gt;
      - FORCE_SSL=false&lt;br /&gt;
      - TRUSTED_PROXY_IP=127.0.0.1/8,::1/128,192.168.0.0/16&lt;br /&gt;
      - REDIS_HOST=redis&lt;br /&gt;
      - REDIS_PORT=6379&lt;br /&gt;
      - DB_HOST=db&lt;br /&gt;
      - DB_USER=mastodon&lt;br /&gt;
      - DB_NAME=mastodon_production&lt;br /&gt;
      - DB_PASS=your_db_password&lt;br /&gt;
      - DB_PORT=5432&lt;br /&gt;
      - ES_ENABLED=false&lt;br /&gt;
      - SECRET_KEY_BASE=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - OTP_SECRET=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - VAPID_PRIVATE_KEY=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - VAPID_PUBLIC_KEY=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=&amp;lt;from step 2&amp;gt;&lt;br /&gt;
      - SMTP_SERVER=smtp.gmail.com&lt;br /&gt;
      - SMTP_PORT=587&lt;br /&gt;
      - SMTP_LOGIN=your_gmail@gmail.com&lt;br /&gt;
      - SMTP_PASSWORD=your_gmail_app_password&lt;br /&gt;
      - SMTP_FROM_ADDRESS=notifications@yourdomain.com&lt;br /&gt;
      - SMTP_AUTH_METHOD=plain&lt;br /&gt;
      - SMTP_OPENSSL_VERIFY_MODE=peer&lt;br /&gt;
      - S3_ENABLED=false&lt;br /&gt;
    volumes:&lt;br /&gt;
      - /opt/mastodon/config:/config&lt;br /&gt;
    ports:&lt;br /&gt;
      - 8667:80&lt;br /&gt;
    networks:&lt;br /&gt;
      - mastodon-net&lt;br /&gt;
    depends_on:&lt;br /&gt;
      - db&lt;br /&gt;
      - redis&lt;br /&gt;
    restart: unless-stopped&lt;br /&gt;
&lt;br /&gt;
  redis:&lt;br /&gt;
    image: redis:7-alpine&lt;br /&gt;
    container_name: mastodon-redis&lt;br /&gt;
    volumes:&lt;br /&gt;
      - /opt/mastodon/redis:/data&lt;br /&gt;
    networks:&lt;br /&gt;
      - mastodon-net&lt;br /&gt;
    restart: unless-stopped&lt;br /&gt;
&lt;br /&gt;
  db:&lt;br /&gt;
    image: postgres:14-alpine&lt;br /&gt;
    container_name: mastodon-db&lt;br /&gt;
    environment:&lt;br /&gt;
      - POSTGRES_DB=mastodon_production&lt;br /&gt;
      - POSTGRES_USER=mastodon&lt;br /&gt;
      - POSTGRES_PASSWORD=your_db_password&lt;br /&gt;
    volumes:&lt;br /&gt;
      - /opt/mastodon/postgres:/var/lib/postgresql/data&lt;br /&gt;
    networks:&lt;br /&gt;
      - mastodon-net&lt;br /&gt;
    restart: unless-stopped&lt;br /&gt;
&lt;br /&gt;
networks:&lt;br /&gt;
  mastodon-net:&lt;br /&gt;
    driver: bridge&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|Port 8667:80 maps host port 8667 to the container&amp;#039;s internal port 80. This avoids conflict with NPM which already owns ports 80 and 443. Choose any unused host port.}}&lt;br /&gt;
&lt;br /&gt;
== Step 4 — Start the Stack ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd /opt/mastodon&lt;br /&gt;
docker compose up -d&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Watch the logs and wait for the boot sequence to complete (30-60 seconds):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker compose logs -f mastodon&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A successful boot ends with lines like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Connection to localhost (127.0.0.1) 3000 port [tcp/*] succeeded!&lt;br /&gt;
[ls.io-init] done.&lt;br /&gt;
Sidekiq 8.x connecting to Redis with options...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Warning|Do not proceed until you see &amp;lt;code&amp;gt;[ls.io-init] done&amp;lt;/code&amp;gt;. Mastodon takes 30-60 seconds to fully initialize on first boot. The ACTIVE_RECORD_ENCRYPTION crash loop means those keys are missing or not being read from the compose file.}}&lt;br /&gt;
&lt;br /&gt;
== Step 5 — Fix Internal nginx SSL Conflict ==&lt;br /&gt;
&lt;br /&gt;
The linuxserver image generates an nginx config that listens on both port 80 and port 443, and passes &amp;lt;code&amp;gt;X-Forwarded-Proto: http&amp;lt;/code&amp;gt; to Rails. This causes Rails to issue a 301 redirect to HTTPS even when NO_SSL=true and FORCE_SSL=false are set. The fix is to edit the nginx config directly and hardcode X-Forwarded-Proto to https.&lt;br /&gt;
&lt;br /&gt;
{{Warning|This must be done after first boot because the config files are generated on first run into &amp;lt;code&amp;gt;/opt/mastodon/config/nginx/&amp;lt;/code&amp;gt;. You must edit both the active .conf and the .conf.sample so changes survive container restarts.}}&lt;br /&gt;
&lt;br /&gt;
=== Remove 443 listeners and hardcode X-Forwarded-Proto ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Fix active config&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/listen 443 ssl default_server;//g&amp;#039; /config/nginx/site-confs/default.conf&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/listen \[\:\:\]:443 ssl default_server;//g&amp;#039; /config/nginx/site-confs/default.conf&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/include \/config\/nginx\/ssl.conf;//g&amp;#039; /config/nginx/site-confs/default.conf&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/proxy_set_header X-Forwarded-Proto $scheme;/proxy_set_header X-Forwarded-Proto https;/g&amp;#039; /config/nginx/site-confs/default.conf&lt;br /&gt;
&lt;br /&gt;
# Fix sample so it survives restarts&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/listen 443 ssl default_server;//g&amp;#039; /config/nginx/site-confs/default.conf.sample&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/listen \[\:\:\]:443 ssl default_server;//g&amp;#039; /config/nginx/site-confs/default.conf.sample&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/include \/config\/nginx\/ssl.conf;//g&amp;#039; /config/nginx/site-confs/default.conf.sample&lt;br /&gt;
docker exec mastodon sed -i &amp;#039;s/proxy_set_header X-Forwarded-Proto $scheme;/proxy_set_header X-Forwarded-Proto https;/g&amp;#039; /config/nginx/site-confs/default.conf.sample&lt;br /&gt;
&lt;br /&gt;
# Reload nginx without restarting the container&lt;br /&gt;
docker exec mastodon s6-svc -r /run/service/svc-nginx&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Verify the fix ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
curl -v http://localhost:8667 -H &amp;quot;Host: toot.yourdomain.com&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should see &amp;lt;code&amp;gt;HTTP/1.1 200 OK&amp;lt;/code&amp;gt;. If you still see &amp;lt;code&amp;gt;301 Moved Permanently&amp;lt;/code&amp;gt;, check that the sed commands ran correctly:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec mastodon cat /config/nginx/site-confs/default.conf | grep -n &amp;quot;301\|https\|ssl\|443&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Step 6 — Configure Nginx Proxy Manager ==&lt;br /&gt;
&lt;br /&gt;
In NPM, create a new Proxy Host with these settings:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Setting !! Value&lt;br /&gt;
|-&lt;br /&gt;
| Domain Name || toot.yourdomain.com&lt;br /&gt;
|-&lt;br /&gt;
| Scheme || http&lt;br /&gt;
|-&lt;br /&gt;
| Forward Hostname / IP || Your server&amp;#039;s LAN IP (e.g. 192.168.1.100)&lt;br /&gt;
|-&lt;br /&gt;
| Forward Port || 8667 (or whichever host port you chose)&lt;br /&gt;
|-&lt;br /&gt;
| Cache Assets || Off&lt;br /&gt;
|-&lt;br /&gt;
| Block Common Exploits || On&lt;br /&gt;
|-&lt;br /&gt;
| Websockets Support || On (critical for live feed)&lt;br /&gt;
|-&lt;br /&gt;
| SSL Certificate || Request via Let&amp;#039;s Encrypt&lt;br /&gt;
|-&lt;br /&gt;
| Force SSL || On&lt;br /&gt;
|-&lt;br /&gt;
| HTTP/2 Support || On&lt;br /&gt;
|-&lt;br /&gt;
| HSTS Enabled || Off (optional, enable after confirming working)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{{Warning|Websockets Support must be On. Mastodon uses WebSockets for the live timeline feed. Without it the interface loads but does not update in real time.}}&lt;br /&gt;
&lt;br /&gt;
{{Note|The domain must already have a DNS A record pointing to your public IP before requesting a Let&amp;#039;s Encrypt certificate. Verify with: &amp;lt;code&amp;gt;curl -s https://dns.google/resolve?name=toot.yourdomain.com | grep data&amp;lt;/code&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
== Step 7 — Create Admin Account ==&lt;br /&gt;
&lt;br /&gt;
Mastodon starts with zero accounts. Create your owner account from the command line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec -it mastodon /app/www/bin/tootctl accounts create YOUR_USERNAME --email YOUR@EMAIL --confirmed --role Owner&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The command outputs a randomly generated password. Copy it immediately. Log in at &amp;lt;code&amp;gt;https://toot.yourdomain.com&amp;lt;/code&amp;gt; with your email and that password.&lt;br /&gt;
&lt;br /&gt;
{{Warning|After logging in you will see a &amp;#039;pending review&amp;#039; banner even on your own account. This is because registrations require admin approval. Approve yourself with:}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
docker exec -it mastodon /app/www/bin/tootctl accounts approve YOUR_USERNAME&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then change your password immediately in Settings &amp;gt; Account &amp;gt; Account Settings.&lt;br /&gt;
&lt;br /&gt;
== Problems Encountered and Countermeasures ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Problem !! Cause !! Fix&lt;br /&gt;
|-&lt;br /&gt;
| Crash loop: ACTIVE_RECORD_ENCRYPTION keys missing || New requirement in Mastodon 4.3+. Container exits immediately without these three keys set. || Generate with: &amp;lt;code&amp;gt;docker run --rm --entrypoint=&amp;quot;&amp;quot; -w /app/www lscr.io/... /app/www/bin/rails db:encryption:init&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Redis: connect ECONNREFUSED 127.0.0.1:6379 || Redis service not in same compose file, or containers not on same Docker network. || Add redis service to same docker-compose.yml with a shared named network (mastodon-net).&lt;br /&gt;
|-&lt;br /&gt;
| bin/rails: no such file or directory || linuxserver entrypoint intercepts the command before rails runs. || Always use &amp;lt;code&amp;gt;--entrypoint=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; and full path: &amp;lt;code&amp;gt;/app/www/bin/rails&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| No Rakefile found error for rake tasks || Working directory not set; rake cannot find the app root. || Add &amp;lt;code&amp;gt;-w /app/www&amp;lt;/code&amp;gt; to the docker run command.&lt;br /&gt;
|-&lt;br /&gt;
| 301 redirect loop at toot.yourdomain.com || Internal nginx passes X-Forwarded-Proto: http to Rails. Rails sees HTTP and redirects to HTTPS infinitely. || Sed-edit both default.conf and default.conf.sample to hardcode X-Forwarded-Proto https. Reload nginx with s6-svc.&lt;br /&gt;
|-&lt;br /&gt;
| NO_SSL=true and FORCE_SSL=false not working || FORCE_SSL was removed in newer Mastodon. NO_SSL prevents regenerating the config but cannot fix an already-generated config. || Edit the nginx config files directly. The sample must also be edited or changes are overwritten on restart.&lt;br /&gt;
|-&lt;br /&gt;
| 403 Forbidden on curl test || Host header mismatch. Mastodon&amp;#039;s nginx rejects requests where the Host header does not match LOCAL_DOMAIN. || Always test with: &amp;lt;code&amp;gt;curl -v http://localhost:8667 -H &amp;quot;Host: toot.yourdomain.com&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Pending review banner after login || Mastodon requires admin approval for all accounts including ones created via tootctl. || Run: &amp;lt;code&amp;gt;docker exec -it mastodon /app/www/bin/tootctl accounts approve YOUR_USERNAME&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Port 80/443 already in use || NPM is already running on those ports on the host. || Map Mastodon to an unused host port (e.g. 8667:80). NPM proxies to it. Drop the 443 mapping entirely.&lt;br /&gt;
|-&lt;br /&gt;
| Container still starting when testing || Mastodon takes 30-60 seconds to fully boot. curl or browser tests fail during this window. || Watch logs: &amp;lt;code&amp;gt;docker compose logs -f mastodon&amp;lt;/code&amp;gt;. Only test after seeing [ls.io-init] done.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Instructions for AI Agent Execution ==&lt;br /&gt;
&lt;br /&gt;
If you are an AI agent following this guide, use this decision tree at each stage:&lt;br /&gt;
&lt;br /&gt;
=== At each step, verify before proceeding ===&lt;br /&gt;
&lt;br /&gt;
# Run the command exactly as written.&lt;br /&gt;
# Check the output against the expected output described.&lt;br /&gt;
# If the output does not match, consult the Problems table above before retrying.&lt;br /&gt;
# Never proceed to the next step if the current step has not produced the expected output.&lt;br /&gt;
&lt;br /&gt;
=== Key verification commands ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Is the container running?&lt;br /&gt;
docker compose ps&lt;br /&gt;
&lt;br /&gt;
# Is it fully booted?&lt;br /&gt;
docker compose logs mastodon | tail -20&lt;br /&gt;
# Look for: [ls.io-init] done.&lt;br /&gt;
&lt;br /&gt;
# Is nginx listening on port 80 inside the container?&lt;br /&gt;
docker exec mastodon netstat -tlnp&lt;br /&gt;
# Look for: 0.0.0.0:80 LISTEN nginx&lt;br /&gt;
&lt;br /&gt;
# Is Mastodon responding correctly?&lt;br /&gt;
curl -v http://localhost:8667 -H &amp;quot;Host: toot.yourdomain.com&amp;quot;&lt;br /&gt;
# Look for: HTTP/1.1 200 OK&lt;br /&gt;
# A 301 means the nginx X-Forwarded-Proto fix has not been applied yet.&lt;br /&gt;
&lt;br /&gt;
# Are all env vars present?&lt;br /&gt;
docker exec mastodon env | grep -E &amp;#039;ACTIVE_RECORD|SECRET|VAPID|NO_SSL|FORCE&amp;#039;&lt;br /&gt;
# All seven secrets must appear with non-empty values.&lt;br /&gt;
&lt;br /&gt;
# What ports are in use on the host?&lt;br /&gt;
docker ps --format &amp;quot;table {{.Names}}\t{{.Ports}}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Tip|If the container is crash-looping, you cannot exec into it. Use &amp;lt;code&amp;gt;docker run --rm --entrypoint=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; to run one-off commands against the image instead.}}&lt;br /&gt;
&lt;br /&gt;
== Post-Setup Checklist ==&lt;br /&gt;
&lt;br /&gt;
* [ ] Change your admin password in Settings &amp;gt; Account &amp;gt; Account Settings&lt;br /&gt;
* [ ] Enable Two-Factor Authentication in Settings &amp;gt; Account &amp;gt; Two-Factor Auth&lt;br /&gt;
* [ ] Set up server rules and description in Admin &amp;gt; Server Settings&lt;br /&gt;
* [ ] Close registrations or set to invite-only in Admin &amp;gt; Server Settings &amp;gt; Registrations&lt;br /&gt;
* [ ] Revoke any SMTP credentials exposed during setup and generate new ones&lt;br /&gt;
* [ ] Back up /opt/mastodon/config and /opt/mastodon/postgres regularly&lt;br /&gt;
* [ ] Back up your docker-compose.yml and store it in Forgejo or another version-controlled location&lt;br /&gt;
&lt;br /&gt;
{{Critical|The ACTIVE_RECORD_ENCRYPTION keys and all secrets in docker-compose.yml are the most critical backup. If these are lost and the database is intact, recovery is extremely difficult.}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Open Source]]&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
[[Category:Docker]]&lt;br /&gt;
[[Category:Self-Hosting]]&lt;br /&gt;
[[Category:System Administration]]&lt;/div&gt;</summary>
		<author><name>Justinaquino</name></author>
	</entry>
</feed>