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 where all this is covered but I tend to customize things and have found some pitfalls that are not mentioned in the official docs.

Let's get right to it.

Prerequisites

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 verisons 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 neccessary 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 db 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 -v "$(pwd)/db:/docker-entrypoint-initdb.d" -p 5432:5432 -e POSTGRES_PASSWORD=changemepls --name=mastopg postgres:alpine

Here's a quick explaination 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)
  • -v "$(pwd)/db:/docker-entrypoint-initdb.d" creates a volume (ie. tells Docker to save anything inside the container at the path /docker-entrypoint-initdb.d into $(pwd)/db. The $(pwd) (sub-)command prints out the current directory.
  • -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 anyhting else to get Mastodon to use that file.

Initialization

The first time we run Mastodon, we'll also need to create the initial databse 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 neccessary 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 neccessary 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.

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 adresses 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.