The Personal Blog of Stephen Sekula

Some Tips on Setting Up a Mastodon Instance Using Docker and Apache

Mastodon has been around for a while, but garnered interest recently in the wake of Twitter’s leadership-driven meltdown. I tried setting up a Mastodon instance over a year ago but abandoned the effort due to time pressure. My interest in spinning up an instance was rekindled by recent events coupled with my long-standing interest in federated open social media. I encountered some pitfalls in doing this on my system, so I thought I would share the lessons here.

My system is probably not that different from other home Linux hobbyists. I have a “gateway” machine that provides firewall and other top-level network services. Inside the firewall is a private network of machines. One machine (henceforth referred to as “Web Server”) provides a number of web services and runs the main Apache server for my domains. Another machine is a media server (henceforth the “Media Server”) that isn’t doing any heavy lifting on the web side. Running Mastodon means, by default, running some core services on certain ports (like 3000 and 4000). However, at least one of those ports is already being used on the Web Server. I don’t like endlessly changing port settings to run various services, and I was afraid of overloading the Web Server with yet another social architecture.

I also like to contain installation of services in their own environments. I am a big fan of local Ruby or Python environments. If you screw something up you can nuke it and reinstall an environment and never mess up your core system. I also have been looking for an excuse to do more basic learning of managing containerized environments.

I decided to use this project to do a few things:

  • Distribute this social media platform onto a new machine, one that has not so far been used for this purpose (“Media Server”)
  • Configure the Web Server to redirect requests to the Media Server when someone wants to interact with the Mastodon instance
  • Use docker to deploy the Mastodon install, including the database, web component, etc.

Thankfully, there are some good instructions on precisely this approach ((https://gist.github.com/TrillCyborg/84939cd4013ace9960031b803a0590c4, https://howtoforge.com/how-to-install-mastodon-social-network-with-docker-on-ubuntu-1804/)). However, they skip over some key pieces. I will attempt to fill in the gaps. In particular:

  • Issues that need to be addressed when configuring the docker-compose process
  • Configuring Apache to point to the Mastodon instance

docker-compose: configuration considerations

First and foremost, it’s important to recognize a few traps in the default docker-compose.yml file. First, the existing instructions point out that you need to disable the following line for each of the web, sidekiq, and streaming applications configured in the docker-compose file

#build: .

Of course, if you intend to build your containers locally then you leave this alone. I wanted to pull standard containers from the web and use those. To do that, and to tie oneself to a specific version of Mastodon (v4.0.2 at the time of the writing of this post) to sync the git repository to the container, you need to amend the image settings for web, sidekiq, and streaming to use a version:

image: tootsuite/mastodon:v4.0.2

Then there are some specific network-related issues you need to deal with. The first is that the default settings in docker-compose.yml set the IP address of these components to be 127.0.0.1. If you leave this alone, these services will never allow connections that are not from this address (localhost and ONLY localhost). This will prevent Apache on the Web Server from successfully sending traffic to these services on the Media Server. If the Web Server occupies address 192.168.1.50 on your private network, then you need to edit the ports configuration to reflect this:

ports:
  - '191.168.1.50:4000:4000'

The ports are configured in the web and sidekiq applications. Finally, you need to specify DNS servers for the web application. If you do not do this, that application will NEVER be able to validate email domains for users that register … including for the administrative account! You must add a DNS configuration to the web application:

dns:
  - 8.8.8.8
  - 4.4.4.4

These are the Google DNS servers. I provide them only as an example. You can use what you like (your internet provider’s DNS, or OpenDNS, etc.). You absolutely also need to do this for the sidekiq and streamer sections as well or they will be unable to match domain names to IP addresses.

Let’s pull all of this together. Here is the example of my working web application configuration in docker-compose.yml:

  web:
    #build: .
    image: tootsuite/mastodon:v4.0.2
    restart: always
    env_file: .env.production
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
    dns:
      - 8.8.8.8
      - 4.4.4.4
    networks:
      - external_network
      - internal_network
    healthcheck:
      # prettier-ignore
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
    ports:
      - '192.168.1.50:3000:3000'
    depends_on:
      - db
      - redis
      # - es
    volumes:
      - ./public/system:/mastodon/public/system

Here is the streaming application configuration:

  streaming:
    #build: .
    image: tootsuite/mastodon:v4.0.2
    restart: always
    env_file: .env.production
    command: node ./streaming
    networks:
      - external_network
      - internal_network
    dns:
      - 8.8.8.8
      - 4.4.4.4
    healthcheck:
      # prettier-ignore
      test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
    ports:
      - '192.168.1.50:4000:4000'
    depends_on:
      - db
      - redis

Finally, here is the sidekiq configuration:

  sidekiq:
    #build: .
    image: tootsuite/mastodon:v4.0.2
    restart: always
    env_file: .env.production
    command: bundle exec sidekiq
    depends_on:
      - db
      - redis
    networks:
      - external_network
      - internal_network
    dns:
      - 8.8.8.8
      - 4.4.4.4
    volumes:
      - ./public/system:/mastodon/public/system
    healthcheck:
      test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]

Now you are ready to actually install the docker images:

docker-compose pull

and then run the command to configure your instance and generate the .env.production file contents:

docker-compose run --rm web bundle exec rake mastodon:setup

Apache Configuration

There is at least one example of an Apache2 configuration for a Mastodon instance out there. The official Mastodon documentation provides no examples (WTF?!) and only supports NGinx. As much as I respect NGinx, I have so many things configured in Apache I’ve never wanted to make the switch.

Anyway, here is an example of an Apache site configuration that is defined on the Web Server and directs requests to the Mastodon domain (mastodon.cooleysekula.net) to the Media Server:

<VirtualHost *:80>
   ServerName mastodon.domain.com
   Redirect permanent / https://mastodon.domain.com/
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName mastodon.domain.com
UseCanonicalName On
ServerAdmin email@domain.com
ProxyPreserveHost On
<Proxy *>
    Order deny,allow
    Require all granted
</Proxy>
<Location /api/v1/streaming>
    ProxyPass wss://192.168.1.50:4000/
    ProxyPassReverse wss://192.168.1.50:4000/
</Location>
<Location />
    ProxyPass http://192.168.1.50:3000/
    ProxyPassReverse http://192.168.1.50:3000/
</Location>
RequestHeader set X-Forwarded-Proto "https"
SSLEngine on
SSLProxyEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK
SSLHonorCipherOrder on
ErrorLog ${APACHE_LOG_DIR}/mastodon-error.log
CustomLog ${APACHE_LOG_DIR}/mastodon-access.log combined
RewriteEngine on
#Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateFile /etc/letsencrypt/live/mastodon.domain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mastodon.domain.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
</IfModule>

The Result

The result is my new Mastodon instance! I even had some time to run over to DALL-E and generate some nice art for the site: a prehistoric elephant and a hamster gazing at the moon in the style of anime.

You can follow me @steve@mastodon.cooleysekula.net