In this guide we will only focus on using the prebuilt images from Docker Hub.
Prerequisites: You have Git, Docker, Docker compose and Nginx pre-installed.
Clone Mastodon's repository.
# Clone mastodon git repo
git clone https://github.com/tootsuite/mastodon.git
# Change directory to mastodon
cd mastodon
# Checkout to the latest stable branch
git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)
Open the docker-compose.yml
file in your favorite text editor.
- Comment out the
build: .
lines for all images (web, streaming, sidekiq). You can do this by adding:#
before those lines. - Edit the
image: tootsuite/mastodon
lines for all images to include the release you want. The default islatest
which is the most recent stable version, however it recommended to explicitly pin a version: If you wanted to use v4.0 for example, you would edit the lines to say:image: tootsuite/mastodon:v4.0
- Do maybe want to change the default volume mount location in the Docker compose file, see the
web
section. Default is./public/system
. If you change this to another location (let's say:/var/www/mastodon/public/system
). Update thevolumes
section of theweb
section to:
volumes:
- /var/www/mastodon/public/system:/mastodon/public/system
- Save the
docker-compose.yml
file and exit your text editor.
We will now create the web folder on your host machine, we specified earlier in the docker-compose.yml
file.
- To create this folder, execute:
mkdir -p /var/www/mastodon/public/system
Or when using the default settings, create a local public/system
folder hierarchy:
mkdir -p public/system
- You also need to set the correct permissions to the public web folder. When you change the volume mount to the location I described earlier, use:
sudo chown -R 991:991 /var/www/mastodon/public
Or when you kept the default settings execute instead:
sudo chown -R 991:991 public
- It is adviced to use the sample configuration file as our starting point (so we have all the comments and options available). Thus, let's make a copy the example config using:
cp .env.production.sample .env.production
- You could use the
mastodon:setup
task to help you generate several important configuration variables for the.env.production
file.Hint: If it asks you to "Save configuration?" You can say: No/N.docker run -it --rm tootsuite/mastodon bundle exec rake mastodon:setup
- Open your local
.env.production
file and apply the generated settings. Pay extra attention to the following settings:LOCAL_DOMAIN
,DB_*
,ES_ENABLED
,S3_ENABLED
,SECRET_KEY_BASE
andOTP_SECRET
,VAPID_PRIVATE_KEY
andVAPID_PUBLIC_KEY
.LOCAL_DOMAIN
should point to your public (sub)domain you want to use to serve Mastodon on.- PostgreSQL config for Docker should be:
DB_HOST=db DB_USER=postgres DB_NAME=postgres DB_PASS= DB_PORT=5432
- Redis can be set to
redis
for Docker:REDIS_HOST=redis
- Set both
ES_ENABLED
andS3_ENABLED
to false, if you aren't using Elasticsearch or Amazon. - The
SECRET_KEY
,OPT_SECRET
,VAPID_*_KEY
's are generated by the setup, which you can copy one-by-one. - Try to enable SMTP as well. Example of Gmail config. Generate an Application password at your Google Account settings:
SMTP_SERVER=smtp.gmail.com SMTP_PORT=587 SMTP_LOGIN[email protected] SMTP_PASSWORD=your_password SMTP_AUTH_METHOD=plain SMTP_OPENSSL_VERIFY_MODE=none SMTP_ENABLE_STARTTLS=auto SMTP_FROM_ADDRESS=Mastodon <[email protected]>
- When running in production it's advised to log level to
warn
(defaultinfo
):RAILS_LOG_LEVEL=warn
- Depending on your servers load and usage, you could also increase some limits. (It's up to you to select the values that fit your needs):
MAX_THREADS=15 SIDEKIQ_CONCURRENCY=10
- Save all the changes to
.env.production
and exit your text editor.
More info about the configuration environment settings.
Continue with the Database setup, chapter below.
Before starting Mastodon for the first time. We need to setup the database once. You should now have configured both the docker-compose.yml
and .env.production
before continuing with this step.
To setup the database we wil run the db:setup
task together with Docker compose command, execute:
docker-compose run --rm web bundle exec rails db:setup
If the database setup went successfully; well done! We can now start Mastodon for the first time.
You can launch Mastodon with:
docker-compose up
If everything seems to run fine, you can start the containers with the -d
flag (for detach, so the containers will run in the background):
docker-compose up -d
Important: You can now go to the last step, setting-up Nginx as your reverse proxy. See below "Nginx Setup".
As discussed earlier. We will use the default Docker PostgreSQL configuration. Meaning the .env.production
config file should contain the following database settings:
DB_HOST=db
DB_USER=postgres
DB_NAME=postgres
DB_PASS=
DB_PORT=5432
As discussed earlier, Redis can be set to redis
when using Docker compose:
REDIS_HOST=redis
As described earlier, here is an example of the Gmail config in .env.production
as the SMTP service provider, used for outgoing emails:
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_LOGIN[email protected]
SMTP_PASSWORD=your_password
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS=Mastodon <[email protected]>
Generate an Application password at your Google Account settings. Try to avoid using your account password directly.
Mastodon docker-compose.yml
configuration file by default configures two Docker bridge networks. An external_network
and an internal_network
. The db
and redis
containers will not be part of the external network, however web
, streaming
and the sidekiq
containers are part of the external_network
bridge network and thus available on your host machine. After all that is what the Docker external bridge does as long as you publish the ports (which is also part of the docker-compose
file).
The internal_network
allows all different Mastodon containers (web
, db
, redis
,..) to contact each other, while still be in an isolated Docker network.
The containers that are part of the external_network
should now be accessible to you from the host machine.
I created a simple high-level network overview of the self-defined Docker bridge networks that will be created for you:
If you didn't understand a word of what I told you, don't panic, everything is done automatically for you when starting the containers.
You need to configure Nginx to serve your Mastodon instance. We assume you already have installed Nginx.
cd
to /etc/nginx/sites-available
and open a new file:
sudo nano /etc/nginx/sites-available/example.com.conf
We adapted the official Nginx configuration slightly to fit our needs.
Reminder: Replace all occurrences of example.com with your own instance's domain or sub-domain.
Reminder #2: Depending on your public
volume folder for the web instance, you want to change /var/www/mastodon/public
in the Nginx example below to the location you setup earlier in this guide. Currently, we assume you changed the Docker compose web volume to: /var/www/mastodon/public/system:/mastodon/public/system
.
Copy and paste the following and make additional edits where needed:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream backend {
server 127.0.0.1:3000 fail_timeout=0;
}
upstream streaming {
server 127.0.0.1:4000 fail_timeout=0;
}
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/mastodon/public;
location /.well-known/acme-challenge/ { allow all; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
# intermediate SSL config (Mozilla Guideline)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Uncomment these lines once you acquire a certificate:
# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
keepalive_timeout 70;
sendfile on;
client_max_body_size 80m;
root /var/www/mastodon/public;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon;
location / {
try_files $uri @proxy;
}
# If Docker is used for deployment and Rails serves static files,
# then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`.
location = /sw.js {
add_header Cache-Control "public, max-age=604800, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/assets/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/avatars/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/emoji/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/headers/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/packs/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/shortcuts/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/sounds/ {
add_header Cache-Control "public, max-age=2419200, must-revalidate";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ~ ^/system/ {
add_header Cache-Control "public, max-age=2419200, immutable";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
try_files $uri @proxy;
}
location ^~ /api/v1/streaming {
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 $scheme;
proxy_set_header Proxy "";
proxy_pass http://streaming;
proxy_buffering off;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
tcp_nodelay on;
}
location @proxy {
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 $scheme;
proxy_set_header Proxy "";
proxy_pass_header Server;
proxy_pass http://backend;
proxy_buffering on;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache CACHE;
proxy_cache_valid 200 7d;
proxy_cache_valid 410 24h;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cached $upstream_cache_status;
tcp_nodelay on;
}
error_page 404 500 501 502 503 504 /500.html;
}
Activate your Nginx site configuration:
cd /etc/nginx/sites-enabled
sudo ln -s ../sites-available/example.com.conf
And finally restart the Nginx server:
sudo systemctl restart nginx
This configuration makes the assumption you are using Let's Encrypt as your TLS certificate provider.
If you are going to be using Let's Encrypt as your TLS certificate provider, see the
next sub-section. If not edit the ssl_certificate
and ssl_certificate_key
values
accordingly.
This section is only relevant if you are using Let's Encrypt as your TLS certificate provider.
Prerequisites: You need to have Certbot installed, follow these installation instructions.
Creating a Let's encrypt certificate easy. Just select the domain from the list after executing:
sudo certbot --nginx
When you are using the latest stable release of certbot your certificates will be renewed automatically.
- https://github.com/mastodon/mastodon
- Unmodified Nginx config example: https://github.com/mastodon/mastodon/blob/main/dist/nginx.conf
- https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Docker-Guide.md
- https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md