Watch
1
0
Fork
You've already forked mastodon-docker-guide
0
A handy guide for getting mastodon, open search and LibreTranslate set up for your own fediverse server.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Ric Harvey 92a672ad34
Centre the author contact card
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:08:29 +00:00
img Add author contact card with avatar and social links 2026-03-26 17:04:24 +00:00
install.sh Complete the setup guide and add automated install script 2026-03-26 16:45:30 +00:00
README.md Centre the author contact card 2026-03-26 17:08:29 +00:00

Mastodon

Mastodon on Docker: The Complete Self-Hosting Guide

So many admins of mastodon instances prefer to use a server and install the OS and mastodon straight onto it. I on the other hand prefer to containerize my setup with docker. I find it neater and easier to upgrade, plus if I want to run other services on the same server I'm free to do so without messing up dependencies or versions. Now there are some ok(ish) guides on how to do this out there but I wanted to pull together a few guides in order to produce a complete setup, a traefik reverse proxy in front of mastodon with full text search and translation enabled using libretranslate, I also wanted to use object storage for my assets, in my case I'm running on AWS so I'm using S3. I also set myself the challenge of removing software with dubious licenses such as Redis and ElasticSearch and opting for the more FOSS friendly Valkey and OpenSearch components. So here is my setup.

Follow me on social media for more content like this Ric Harvey Ric Harvey
Mastodon
LinkedIn

Quick Start with the Install Script

If you just want to get up and running quickly, there's an automated install script that handles everything in this guide for you. It will walk you through an interactive setup, ask you what features you want to enable, generate all the secrets, and spin up the entire stack.

sudo /bin/bash -c "$(curl -fsSL https://codeberg.org/ric_harvey/mastodon-docker-guide/raw/branch/main/install.sh)"

The script will:

  • Install Docker if it's not already present (Debian/Ubuntu)
  • Ask for your domain names, email config, and optional features
  • Let you choose whether to enable S3 object storage, SMTP email (SES or any provider), and LibreTranslate for post translation
  • Generate all required secrets and encryption keys automatically
  • Create the full Traefik + Mastodon + OpenSearch + Valkey stack
  • Initialize the database and search index
  • Start everything up and give you the next steps

Warning

Running scripts directly from the internet requires trust. If you don't know me or haven't reviewed the script, don't pipe it to bash. Instead, read through the guide below and build the setup manually -- you'll learn more about how it all fits together that way. You can also review the script source before running it.

If you'd prefer to understand each step and set things up yourself, read on.


The Setup

A picture speaks a thousand words so let me show you the main components of this setup. Each bit of software is running in its own container:

flowchart TD
    Users --> |HTTPS port: 80/443| A
    A[Traefik] -->|Web Interface port: 3000| B(mastodon)
    A -->|Streaming port: 4000| C(mastodon-streaming)
    B --> D(sidekiq)
    C --> D
    B --> E[postgres]
    C --> E
    B --> F[valkey]
    C --> F
    B --> G[opensearch]
    B --> H[S3]
    B --> I[libretranslate]
    Users --> | assets | H

Now the easiest way to run all these components and have them interconnected is to set up the entire system with docker compose but before we get going we are going to have to set up the basics. Now I built this on a debian 12 system but most of the compose setup will be identical, you'll just need to tweak the install commands for docker and compose.

Install Docker and Compose

  1. First let's make sure there's no unofficial packages in place that will conflict by removing them.
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done
  1. Now let's set up Docker's official apt repository:
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
  1. Now install the toolchain:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin vim
  1. Verify that the installation is successful by running the hello-world image:
sudo docker run hello-world

Note

If you want to run docker as a none root user you'll need to add that user to the docker group by running the following command: sudo usermod -aG docker $USER

Setup storage directories on the host

I like to try and keep the persistent data neat and tidy on my system so I make sure to group my configs and data directories together. For this I store them in /opt/docker/<application_name> in this case mastodon

  • Let's start by creating that folder structure. For ease we are going to use sudo to become root for these commands (you could just prefix all instructions with sudo if you really wanted):
sudo su -p
mkdir -p /opt/docker/traefik
mkdir -p /opt/docker/mastodon
cd /opt/docker/mastodon
  • Now we are in the main directory let's create the directories where the containers are going to write their persistent data:
cd /opt/docker/traefik
mkdir data
mkdir logs
cd /opt/docker/mastodon
mkdir -p public/system
mkdir postgres
mkdir -p opensearch/data
mkdir valkey
mkdir -p lt/data/{key,local}
mkdir lt/api_keys
  • Now let's set the permissions on those directories so the containers can write their data to them without having to tweak the containers:
chown -R 991:991 public
chown -R 70:root postgres
chown -R 1000:root opensearch
chown -R 999:root valkey
chown -R 1032:1032 lt

Setup DNS

Before we get into the service configuration, make sure your DNS is set up and propagating. You'll need at minimum:

  • An A record (and optionally AAAA for IPv6) for your Mastodon domain (e.g. mastodon.example.com) pointing to your server's IP address.
  • An A record for your traefik dashboard domain (e.g. traefik.example.com) pointing to the same server.
  • If you're using CloudFront for media, a CNAME record for the assets domain will be needed later (covered in the CloudFront section).

DNS can take time to propagate, so it's best to do this early. Traefik needs the domain to resolve to your server in order to obtain a Let's Encrypt certificate.

Configure the firewall

If you're running a firewall (and you should be), make sure ports 80 and 443 are open for traefik:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload

Note

If you're using a cloud provider like AWS, you'll also need to configure your security group to allow inbound traffic on ports 80 and 443.

Setting up traefik

To access your site and ensure you have a valid SSL certificate we are going to run traefik as a reverse proxy, this means we can also force all traffic to be HTTPS which is great for security. Traefik will automatically take care of getting a cert from LetsEncrypt and rotating it when it's due to expire. All you have to do is provide an email address for registration.

Ensure you're in the /opt/docker/traefik directory and create a new file called compose.yml

vi compose.yml

now add the following to the file:

Tip

To enter insert mode in vim press i

services:
  traefik:
    image: "traefik:v3.6"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.rule=Host(`${HOSTNAME}`)"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_PASSWORD}"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
      - "traefik.http.routers.traefik-secure.entrypoints=websecure"
      - "traefik.http.routers.traefik-secure.rule=Host(`${HOSTNAME}`)"
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
      - "traefik.http.routers.traefik-secure.tls=true"
      - "traefik.http.routers.traefik-secure.tls.certresolver=myresolver"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      # Define the port inside of the Docker service to use
      - "traefik.http.services.traefik-secure.loadbalancer.server.port=8080"
    env_file: .env
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /opt/docker/traefik/logs:/logs:rw
      - /opt/docker/traefik/data/acme.json:/acme.json:rw
      - /opt/docker/traefik/data/traefik.yml:/traefik.yml:rw
      - /opt/docker/traefik/data/config.yml:/config.yml:rw
    networks:
      - proxy

networks:
  proxy:
    external: false

Save and exit the file.

Tip

To get out of vim just type :wq!

You're also going to need to set up some environment variables for this to work, so once you've saved the file above you'll need to create a new file called .env

vi .env

Populate it with these details, and remember to update the domain:

HOSTNAME=traefik.example.com
TRAEFIK_PASSWORD=admin:<GENERATED_PASSWORD>

You'll need to generate a password for traefik basic_auth to be able to login to the dashboard API. First install the htpasswd tool and then generate the password:

sudo apt-get install apache2-utils
echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g

Paste the output user:password keypair into your .env file and save and exit.

There's a couple of other files (4 actually) we now need to prep. 3 are going to be empty files that the container will need and one will be the initial config startup for traefik.

cd /opt/docker/traefik/data
touch dynamic-config.yml
touch acme.json
chmod 600 acme.json
cd /opt/docker/traefik/logs
touch traefik.log

Note

The chmod 600 on acme.json is essential. Traefik will refuse to start if this file has open permissions.

Now for the important file to pull all this together and get traefik working.

vi traefik.yml

Enter the following information and update your email address:

api:
  dashboard: true
  debug: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

log:
  level: ERROR
  filePath: "/logs/traefik.log"
  format: common

serversTransport:
  insecureSkipVerify: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedbydefault: false
  file:
    filename: dynamic-config.yml

certificatesResolvers:
  myresolver:
    acme:
      email: <YOUR_EMAIL_ADDRESS>
      storage: acme.json
      httpChallenge:
        # used during the challenge
        entryPoint: web

Now traefik is ready to run and accept HTTP and HTTPS connections. To get it up and running is pretty simple with docker compose, run the following command:

cd /opt/docker/traefik
docker compose up -d

Test Traefik

To test traefik you can browse direction to https://traefik.example.com where you will be prompted to enter your basic_auth details in your browser.

ScreenSHot of Basic Auth Prompt

Setup object storage

Mastodon stores user uploads (avatars, headers, media attachments etc.) and we want to offload these to S3 rather than storing them on the local filesystem. This keeps our server lean and makes backups simpler.

Create a S3 Bucket

  1. Head to the S3 console and click Create bucket.

  2. Give it a name (e.g. mastodon-assets-yourdomain) and select your preferred region.

  3. Under Object Ownership select ACLs enabled and Bucket owner preferred. Mastodon uses ACLs to set public-read on uploaded media.

  4. Uncheck Block all public access and acknowledge the warning. Media attachments need to be publicly readable.

  5. Leave everything else as default and click Create bucket.

  6. Once created, go to the bucket's Permissions tab and add the following CORS configuration:

[
    {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "PUT"],
        "AllowedOrigins": ["https://your-mastodon-domain.com"],
        "ExposeHeaders": ["ETag"],
        "MaxAgeSeconds": 3000
    }
]

Note

Replace your-mastodon-domain.com with your actual Mastodon domain.

Generate an IAM key

Mastodon needs programmatic access to your S3 bucket. We'll create a dedicated IAM user with a minimal policy.

  1. Go to the IAM console and click Users > Create user.

  2. Name it something like mastodon-s3 and click Next.

  3. Select Attach policies directly and click Create policy. Use the following JSON policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "MastodonS3Access",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:PutObjectAcl",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::mastodon-assets-yourdomain",
                "arn:aws:s3:::mastodon-assets-yourdomain/*"
            ]
        }
    ]
}
  1. Name the policy (e.g. MastodonS3Policy), create it, then go back and attach it to the mastodon-s3 user.

  2. Go to the user's Security credentials tab, click Create access key, select Application running outside AWS, and create the key.

Note

Save the Access Key ID and Secret Access Key somewhere safe. You'll need them for the Mastodon environment variables later.

Setup CloudFront

CloudFront serves as a CDN in front of your S3 bucket. This speeds up media delivery for your users and reduces S3 costs.

  1. Go to the CloudFront console and click Create distribution.

  2. For Origin domain, select your S3 bucket from the dropdown.

  3. Under Origin access, select Public (since we already configured the bucket for public access).

  4. Under Default cache behavior:

    • Viewer protocol policy: Redirect HTTP to HTTPS
    • Allowed HTTP methods: GET, HEAD
    • Cache policy: CachingOptimized
  5. Under Settings:

    • Add your desired Alternate domain name (CNAME), e.g. assets.your-mastodon-domain.com
    • Select or request an SSL certificate via ACM for that domain
    • Leave everything else as default
  6. Click Create distribution.

  7. Once deployed, create a CNAME DNS record pointing assets.your-mastodon-domain.com to the CloudFront distribution domain (e.g. d1234abcdef.cloudfront.net).

Note

The CloudFront URL (e.g. assets.your-mastodon-domain.com) is what you'll use for the S3_ALIAS_HOST variable in your Mastodon config later.

Setting up email with SES

Mastodon sends emails for account confirmations, notifications, and password resets. We'll use AWS Simple Email Service (SES) for this.

Setup + Verify Domain

  1. Go to the SES console and make sure you're in the same region you want to send email from.

  2. Click Identities > Create identity.

  3. Select Domain and enter your Mastodon domain (e.g. your-mastodon-domain.com).

  4. Under Verifying your domain, leave Use a custom MAIL FROM domain unchecked unless you specifically want one.

  5. Ensure DomainKeys Identified Mail (DKIM) signing is enabled with Easy DKIM selected.

  6. Click Create identity.

  7. SES will provide you with a set of CNAME records for DKIM verification. Add these to your domain's DNS. SES will also provide a TXT record for domain verification if required.

  8. Wait for the identity status to change to Verified (this usually takes a few minutes but can take up to 72 hours for DNS propagation).

Note

New SES accounts are placed in a sandbox which only allows sending to verified email addresses. To send to anyone (which Mastodon needs), you'll need to request production access from the SES console.

Generate SMTP credentials

  1. In the SES console, go to SMTP settings in the left sidebar.

  2. Note down the SMTP endpoint (e.g. email-smtp.eu-west-1.amazonaws.com) and Port (587 for STARTTLS).

  3. Click Create SMTP credentials.

  4. This will take you to IAM to create a user. Give it a name like mastodon-ses-smtp and click Create user.

  5. You'll be shown the SMTP username and SMTP password.

Note

Save these credentials immediately -- you won't be able to see the password again. These are different from your regular IAM access keys. You'll need the SMTP endpoint, username, and password for the Mastodon environment variables later.

Create configuration files

Before we create the compose file, OpenSearch requires a kernel setting to be increased from the default. Without this, the OpenSearch container will fail to start:

echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -w vm.max_map_count=262144

Now, this first file is the all important docker compose file, so let's go ahead and create compose.yml and paste in the following content.

cd /opt/docker/mastodon
touch .env.production
touch libretranslate.env
vi compose.yml

Now let's paste in the following content:

services:
  db:
    restart: unless-stopped
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - mastodon
    healthcheck:
      test:
        - CMD
        - pg_isready
        - -U
        - postgres
    volumes:
      - /opt/docker/mastodon/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_HOST_AUTH_METHOD=trust
  valkey:
    restart: unless-stopped
    image: valkey/valkey:8-alpine
    networks:
      - mastodon
    healthcheck:
      test:
        - CMD
        - valkey-cli
        - ping
    volumes:
      - /opt/docker/mastodon/valkey:/data
  web:
    image: ghcr.io/mastodon/mastodon:v4.5.8
    restart: unless-stopped
    env_file: .env.production
    command: bundle exec puma -C config/puma.rb
    networks:
      - traefik_proxy
      - mastodon
    healthcheck:
      test:
        - CMD-SHELL
        - "curl -s --noproxy localhost localhost:3000/health | grep -q 'OK' || exit 1"
    depends_on:
      - db
      - valkey
      - os
    volumes:
      - /opt/docker/mastodon/public/system:/mastodon/public/system
    labels:
      - traefik.enable=true
      - traefik.http.routers.mastodon.entrypoints=web
      - traefik.http.routers.mastodon.rule=Host(`<MASTODON_DOMAIN>`)
      - traefik.http.middlewares.mastodon-https-redirect.redirectscheme.scheme=https
      - traefik.http.routers.mastodon.middlewares=mastodon-https-redirect
      - traefik.http.routers.mastodon-secure.entrypoints=websecure
      - traefik.http.routers.mastodon-secure.rule=Host(`<MASTODON_DOMAIN>`)
      - traefik.http.routers.mastodon-secure.tls=true
      - traefik.http.routers.mastodon-secure.tls.certresolver=myresolver
      - traefik.http.routers.mastodon-secure.service=mastodon
      - traefik.http.services.mastodon.loadbalancer.server.port=3000
      - traefik.docker.network=traefik_proxy
  streaming:
    image: ghcr.io/mastodon/mastodon-streaming:v4.5.8
    restart: unless-stopped
    env_file: .env.production
    command: node ./streaming/index.js
    networks:
      - traefik_proxy
      - mastodon
    healthcheck:
      test:
        - CMD-SHELL
        - "curl -s --noproxy localhost localhost:4000/api/v1/streaming/health | grep -q 'OK' || exit 1"
    depends_on:
      - db
      - valkey
    labels:
      - traefik.enable=true
      - traefik.http.routers.mastodon-api.entrypoints=web
      - traefik.http.routers.mastodon-api.rule=(Host(`<MASTODON_DOMAIN>`)
        && PathPrefix(`/api/v1/streaming`))
      - traefik.http.middlewares.mastodon-api-https-redirect.redirectscheme.scheme=https
      - traefik.http.routers.mastodon-api.middlewares=mastodon-api-https-redirect
      - traefik.http.routers.mastodon-api-secure.entrypoints=websecure
      - traefik.http.routers.mastodon-api-secure.rule=(Host(`<MASTODON_DOMAIN>`)
        && PathPrefix(`/api/v1/streaming`))
      - traefik.http.routers.mastodon-api-secure.tls=true
      - traefik.http.routers.mastodon-api-secure.tls.certresolver=myresolver
      - traefik.http.routers.mastodon-api-secure.service=mastodon-api
      - traefik.http.services.mastodon-api.loadbalancer.server.port=4000
      - traefik.docker.network=traefik_proxy
  sidekiq:
    image: ghcr.io/mastodon/mastodon:v4.5.8
    restart: always
    env_file: .env.production
    command: bundle exec sidekiq
    depends_on:
      - db
      - valkey
      - os
    networks:
      - mastodon
    volumes:
      - /opt/docker/mastodon/public/system:/mastodon/public/system
    healthcheck:
      test:
        - CMD-SHELL
        - "ps aux | grep '[s]idekiq 8' || false"
  os:
    restart: always
    image: opensearchproject/opensearch:2.19.4
    container_name: opensearch-node1
    environment:
      - node.name=opensearch-node1
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      - DISABLE_SECURITY_PLUGIN=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=<OPENSEARCH_PASSWORD>
    networks:
      - mastodon
    healthcheck:
      test:
        - CMD-SHELL
        - "curl --silent --fail localhost:9200/_cluster/health || exit 1"
    volumes:
      - /opt/docker/mastodon/opensearch/data:/usr/share/opensearch/data
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
  libretranslate:
    image: libretranslate/libretranslate:v1.9.5
    restart: unless-stopped
    healthcheck:
      test:
        - CMD-SHELL
        - ./venv/bin/python scripts/healthcheck.py
    env_file:
      - libretranslate.env
    volumes:
      - /opt/docker/mastodon/lt/data:/home/libretranslate/.local
      - /opt/docker/mastodon/lt/api_keys:/app/db
    networks:
      - mastodon
networks:
  mastodon:
    external: false
  traefik_proxy:
    external: true

Note

There are a few variables you'll need to set up in the above file to match your environment and secure it, make sure you change these values:

  • <MASTODON_DOMAIN> - your Mastodon domain name (appears 4 times in the traefik labels)
  • <OPENSEARCH_PASSWORD> - a strong password for OpenSearch

Once you've changed these variables you'll need to save and exit vim.

Setting up the environment variables

Now to make this easy I've included a skeleton .env.production file for you to copy and populate with your own variables. But first we need to generate some keys and set up the DB.

Generate keys for mastodon

First we'll need to generate some keys to secure mastodon. You'll need to run the following command twice, once for the SECRET_KEY_BASE and once for OTP_SECRET.

Note

make a note of them you'll need them for the config file

docker compose run --rm -- web bundle exec rails secret

Now we need to generate the VAPID_PRIVATE_KEY.

Note

yet again make a note of this output

docker compose run --rm -- web bundle exec rails mastodon:webpush:generate_vapid_key

Initialize the database

Now we need to start setting up the database ready for mastodon to use. Run the following command which will create the DB and populate the initial tables:

docker compose run --rm -e RAILS_ENV=production -- web bundle exec rails db:setup

Generate DB AR Encryption Values

Since mastodon 4.3.0 you'll also need to set up Active Record (AR) encryption keys. The following command will generate you the required keys.

docker compose run --rm -e RAILS_ENV=production -- web bundle exec rails db:encryption:init

Note

Make a note of these values as we'll need them for the configuration file.

Setup Libre Translate

Mastodon allows you to translate posts into your native language by using libretranslate. You can run this service locally in your stack with very little setup. Let's look at the steps

Create the environments file
vi libretranslate.env

and enter the following details, you don't need to edit anything in this file:

LT_DEBUG=true
LT_UPDATE_MODELS=true
LT_SSL=true
LT_SUGGESTIONS=false
LT_METRICS=true

LT_API_KEYS=true

LT_THREADS=12
LT_FRONTEND_TIMEOUT=2000

#LT_REQ_LIMIT=400
#LT_CHAR_LIMIT=1200

LT_API_KEYS_DB_PATH=/app/db/api_keys.db
Run the container

Now let's start just the LibreTranslate container so we can set it up. We need to bring it up first before we can exec into it:

docker compose up -d libretranslate
docker compose exec -it libretranslate bash
Install all the language packs

We now need to download the language packs for LibreTranslate to use.

for i in $(/app/venv/bin/argospm list); do /app/venv/bin/argospm install $i; done

Note

Note: this will take a fair bit of time so please do be patient

Generate an API key

Mastodon will need an API key to access LibreTranslate. Still within the container let's create an API key that can make 120 requests per min:

/app/venv/bin/ltmanage keys add 120

Note

Make a note of the output as this is the API key

Create the mastodon environment variables

We now need to gather those credentials we've just created and edit our .env.production file. Open that file and copy the below contents into that file and change the values in the < >.

vim .env.production

Contents:

LOCAL_DOMAIN=<MASTODON_DOMAIN>
SINGLE_USER_MODE=false
SECRET_KEY_BASE=<GENERATED_SECRET_KEY_BASE>
OTP_SECRET=<GENERATED_OTP_SECRET>
VAPID_PRIVATE_KEY=<GENERATED_VAPID_PRIVATE_KEY>
VAPID_PUBLIC_KEY=<GENERATED_VAPID_PUBLIC_KEY>
DB_HOST=db
DB_PORT=5432
DB_NAME=postgres
DB_USER=postgres
DB_PASS=<DB_PASSWORD>
REDIS_HOST=valkey
REDIS_PORT=6379
# This is blank on purpose
REDIS_PASSWORD=
S3_ENABLED=true
S3_PROTOCOL=https
S3_BUCKET=<AWS_S3_BUCKET>
S3_REGION=<AWS_REGION>
S3_HOSTNAME=<s3.AWS-REGION-1.amazonaws.com>
AWS_ACCESS_KEY_ID=<AWS_ACCESS_KEY>
AWS_SECRET_ACCESS_KEY=<AWS_SECRET_KEY>
# Only needed if you use cloudfront
S3_ALIAS_HOST=<CLOUDFRONT_URL>
SMTP_SERVER=<EMAIL_SERVER>
SMTP_PORT=587
SMTP_LOGIN=<EMAIL_USERNAME>
SMTP_PASSWORD=<EMAIL_PASSWORD>
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS=<EMAIL_ADDRESS>
ES_ENABLED=true
ES_HOST=http://os
ES_PORT=9200
ES_PRESET=single_node_cluster
ES_USER=admin
ES_PASS=<OPENSEARCH_PASSWORD>
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=<GENERATED_AR_KEY>
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=<GENERATED_AR_SALT>
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=<GENERATED_AR_PRIMARY_KEY>
LIBRE_TRANSLATE_ENDPOINT=http://libretranslate:5000
LIBRE_TRANSLATE_API_KEY=<LT_API_KEY>

Starting everything up

Now that all the configuration is in place, let's bring the whole stack online.

  1. First, make sure the traefik proxy network exists. This is the shared network that traefik and mastodon will communicate over:
docker network create traefik_proxy
  1. Make sure traefik is running:
cd /opt/docker/traefik
docker compose up -d
  1. Now start the mastodon stack:
cd /opt/docker/mastodon
docker compose up -d
  1. Check that all containers are running and healthy:
docker compose ps

You should see all services with a status of Up and eventually healthy.

Post-install tasks

Once everything is up and running there are a few more things to do to get your instance fully operational.

Create and approve your admin account

First, sign up for an account through the web interface at your Mastodon domain. Then approve it and grant admin privileges:

docker compose exec web tootctl accounts approve <YOUR_USERNAME>
docker compose exec web tootctl accounts modify <YOUR_USERNAME> --role Owner

Deploy the search index

To enable full text search you'll need to build the OpenSearch index:

docker compose exec web tootctl search deploy

Note

This can take a while on an existing instance with lots of posts. On a fresh install it should be quick.

Fix the OpenSearch yellow cluster warning

Since we're running a single-node OpenSearch cluster, you'll see a yellow health status because there's no replica node. To fix this, set the number of replicas to 0:

docker compose exec os curl -X PUT "http://localhost:9200/_settings" -H 'Content-Type: application/json' -d'{
    "index" : {
        "number_of_replicas" : 0
    }
}'

Upgrading

One of the benefits of running everything in Docker is that upgrades are straightforward. To upgrade mastodon to a newer version:

  1. Update the image tags in your compose.yml file to the new version.

  2. Pull the new images:

docker compose pull
  1. Run any pending database migrations:
docker compose run --rm web bundle exec rails db:migrate
  1. Restart the stack:
docker compose up -d

Note

Always check the Mastodon release notes before upgrading as some releases may require additional migration steps.

Useful commands

Here are some handy tootctl commands for managing your instance:

# List accounts
docker compose exec web tootctl accounts list

# Remove cached remote media older than 7 days (saves disk/S3 space)
docker compose exec web tootctl media remove --days=7

# Clear preview cards older than 14 days
docker compose exec web tootctl preview_cards remove --days=14

# Rebuild the search index
docker compose exec web tootctl search deploy