I have been coding on my own Mastodon instance since 2017, the main thing I’m doing is customizing the styling. For this, it is a good idea to have a local development instance because redeploying (and rebuilding the assets) takes too long for a simple change like a different border color for some elements. In this post, I will show how I set up Mastodon for development.

There are also [the official docs](https://docs.joinmastodon.org/development/overview/) where all this is covered

but I tend to customize things and have found some pitfalls that are not mentioned in the official docs.

The official docs regarding development seem to have been deleted. Here’s the last snapshot from archive.org.

Let’s get right to it.

Prerequisites

Debian/Ubuntu dependencies

On Debian/Ubuntu based systems, there are a few dependencies that have to be installed using the package manager:

sudo apt install -y imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev file git g++ libprotobuf-dev protobuf-compiler pkg-config gcc autoconf bison build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm5 libgdbm-dev libidn11-dev libicu-dev libjemalloc-dev

macOS Dependencies

The dependencies can installed using Homebrew:

brew install imagemagick libpq libxml2 libxslt protobuf autoconf bison gdbm ffmpeg icu4c libyaml libidn jemalloc

Errors when running bundle install

There might be errors when running bundle install for the first time on macOS, because pg and idn-ruby fail to build.

This can be fixed using the following commands to install them (change the version numbers accordingly):

  • pg
    • ` gem install pg – –with-pg-config=/opt/homebrew/Cellar/libpq//bin/pg_config`
  • libidn
    • gem install idn-ruby -- --with-idn-dir=/opt/homebrew/Cellar/libidn/<LIBIDN_VERSION>/

NodeJS

Mastodon needs NodeJS to run webpack and install (frontend) dependencies. I recommend using nvm to set up and manage NodeJS because it makes switching versions very easy.

After NodeJS is installed we need to install yarn (an alternative to npm). This can be done by running:

npm i -g yarn

If you didn’t use nvm to install NodeJS, you’ll probably run the command as root.

Mastodon Dependencies

To install the Mastodon NodeJS dependencies, run the following command from the Mastodon folder:

yarn install --pure-lockfile

Ruby

Mastodon is written in ruby so we’ll need it too. Luckily there is something similar to nvm that is able to manage ruby installations: rbenv.

After this tool is installed, you can run cat ./.ruby-version from the Mastodon folder to get the currently needed ruby version which can then be installed with rbenv.

If you’re truly lazy, you can also run this as a oneliner (again from the Mastodon folder):

rbenv install $(cat ./.ruby-version)

This will use the value from the .ruby-version file directly.

Bundler and Mastodon Dependencies

To be able to install Mastodon’s ruby dependencies, we first need to install bundler, rails and foreman. Those can be installed by running:

gem install rails bundler foreman

After bundler is installed, we can fetch and build the Mastodon dependencies:

bundle install --with development,test

The --with argument will also download development (and test) dependencies. Those are usually not needed when in production mode and thus not activated by default.

Docker

Mastodon runs on itself but needs a database and a redis instance to function properly. The official docs just state that you should simply install those services on your computer. However (by default) this leaves a database running on your system that you don’t need most of the time.

That’s why I’m using Docker to run the necessary services.

To install Docker you can simply follow the docs (Ubuntu) or download the app if you use macOS.

Postgres

After Docker is installed, we can start the database. But before that, we need to create a directory in which the data we save is persisted. This will allow us to keep all the data when the database is restarted.

Because I’m lazy, I just created a data folder inside the Mastodon directory and added it to the .gitignore (any file listed in there will be ignored by git). Now when I open a shell inside the Mastodon folder, I can start the database like this:

docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=changemepls --name=mastopg postgres:alpine

Here’s a quick explanation of what the arguments to:

  • run tells docker that we want to run a given image
  • -d starts the service “detached” (ie. in the background)
  • -p 5432:5432 sets the port we want to use
  • -e POSTGRES_PASSWORD=changemepls sets the POSTGRES_PASSWORD variable, this will be used by postgres itself
  • --name=mastopg sets the container’s name to mastopg. This way we can start/stop it by using docker {start,stop} mastodpg
  • postgres:alpine is the image we want to run, alpine is a very minimal Linux distribution

Now there should be a running postgres visible when you run docker ps

Redis

The command to run redis is a lot shorter because we don’t need persistence and/or a password:

docker run --name mastoredis -p 6379:6379 -d redis

Again, with the name set to mastoredis, we can start/stop the container by using docker {start,stop} mastoredis.
You can validate that redis is running by running docker ps again.

Configuring Mastodon

Now that the neccessary services are running, we need to tell Mastodon how to use them. This can be easily done by creating the file .env.development inside the Mastodon folder. This file provides location and authentication information to Mastodon, it needs to have the following content:

REDIS_HOST=localhost
REDIS_PORT=6379
DB_HOST=localhost
DB_USER=postgres
DB_NAME=postgres
DB_PASS=changemepls
DB_PORT=5432

Since development is the default environment when running Mastodon without setting RAILS_ENV, we don’t need to do anything else to get Mastodon to use that file.

Initialization

The first time we run Mastodon, we’ll also need to create the initial database schema. Once the postgres container is running, this can be done by running the following command from the Mastodon folder:

bundle exec rails db:setup

This will output a ot of text which consists of all the commands necessary to initialize the database.

If you’ve just updated mastodon and already have an initialized database you can also run bundle exec rails db:migrate to run only the difference between your setup and the currently necessary state.

Running Mastodon

Now that the initial setup is done, you can run Mastodon by opening a terminal in the Mastodon folder and running:

foreman start

This will start all Mastodon processes. You can now go to http://localhost:3000 and log in as admin@localhost:3000 with the password mastodonadmin.

Debugging Mastodon (web)

If you’re done setting up the first few users and start tinkering with stuff, you might want to set breakpoints to make debugging easier.

My solution to this is to use RubyMine. It works really well and does most things automatically.

To run all things appropriately, I usually comment out the first line in Procfile.dev (starting with web: env PORT=) so foreman start doesn’t start the web worker.

RubyMine (at least in my case) automatically detects a Development: mastodon run configuration that can be started in debug mode.
That way it’s even possible to set breakpoints (ie. evaluate expressions mid-execution) while rendering .haml templates. 😱

Useful stuff to know

  • When you register a new user, the registration mail will not be sent to the specified mail address. Instead the mail will be opened in a browser.
    • This usually does not work when no browser is set as default.
    • This means you can create accounts with invalid mail addresses just to test the design of the mails.
  • You should not interact with any fediverse services running in production.
    • Those usually only work with https endpoints and the origin server being localhost will eventually cause a connection attempt to the live server you were interacting with instead of your local machine
    • Fetching stuff (ie. pasting a remote profile link into the search bar to test the layout of remote profiles) should work (at least it does with my instance)
  • Webpack will fail with a very non-helpful error message when dependencies are missing. If you didn’t change anything and webpack fails, running yarn install again will probably solve the problem
    • Same goes for bundler.

I hope this post was helpful. If you run into any problems, feel free to contact me by Mail or Mastodon.