Learn how to host your own Mastodon instance on AWS

December 16, 2022
 • 
20
 Min
an elephant with a tusk
Join our newsletter
Get noticed about our blog posts and other high quality content. No spam.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Mastodon development environments on Release and AWS

Twitter users are flying the coop in droves and many are turning to Mastodon, an open-source federated social network.

Unlike Twitter’s centralized architecture, a federated social network consists of thousands of individual social networks, called instances, each with its own users.

Federation is sometimes described as being similar to email in the sense that email users can send each other messages, even if they have different email hosts and use different domains.

Mastodon instances fetch content from other instances, which allows a user on one server to follow a user on a different server, thereby creating one big network.

To the DevOps-minded among us, the most compelling thing about the fastest-growing Twitter alternative is figuring out how to host and scale this exciting new platform.

At Release, we enable teams to create development, staging, and production environments for their apps. If you intend to add new features or otherwise modify your Mastodon instances, Release can provide the environment you need to move your project forward. Whether you want to just take Mastodon for a spin to see what it’s all about, or run a production Mastodon instance that can scale up as you need, using Release means you don’t have to trade off simplicity for power. You can easily collaborate with your colleagues, preview the latest features, and upgrade to the latest builds.

In this post, we’ll put Mastodon’s welcoming community and complex federation protocols aside so that we can focus on the nuts and bolts of hosting a Mastodon instance.

What makes a Mastodon instance

Mastodon Services Diagram

A Mastodon instance consists of seven services:

  1. A web server, which runs on Ruby on Rails.
  2. A streaming server that enables the instance’s local users to send and receive real-time updates to the server.
  3. Sidekiq, a job scheduler that runs background tasks, polls other instances’ web servers, and sends data to other instances’ web servers.
  4. A Redis server that stores jobs for Sidekiq and caches data such as feeds for local users.
  5. A PostgreSQL database that stores posts, user profiles, and instance settings.
  6. File storage to store and serve media such as images and videos.

An SMTP email server to send messages to local users’ email accounts.

How we’ll run a Mastodon instance on Release and AWS

Release creates Kubernetes clusters in your AWS account.

Although all seven of Mastodon’s services could be hosted on Kubernetes, we’ll use a few AWS-provided services to host supporting systems.

We’ll create containers to run Mastodon’s web server, streaming server, and Sidekiq in Kubernetes.

From AWS, we’ll use:

  • Amazon ElastiCache for Redis
  • Amazon RDS to host a PostgreSQL server
  • Amazon S3 as a file store
  • Amazon SES to send emails to local users

Prepare your Release account

Before we get started, you’ll need to create a Release account and upgrade to a paid account.

Next, verify your domain in Release, and create a cluster using the AWS integration.

Configuration details to save

In this guide, configuration details to note down will be pointed out with the heading: Configuration details to save.

While in Release, navigate to your cluster, and note down the following:

  1. The cluster context.
  2. Your cluster region.

Your node group.

Prepare your local machine

On your local system, you’ll need to install Git and Docker.

Keep a text editor handy to take notes. We’ll gather configuration details as we go along, and saving these in a central file will make setting up your environment much easier later.

Fork the Mastodon repository on GitHub

Fork the Mastodon repository from GitHub to your GitHub, Bitbucket, or GitLab account. We’ll use GitHub in this guide.

  1. Log into your GitHub account.
  2. Fork the Mastodon repository.

GitHub creates a public fork by default. Since Mastodon is AGPL-licensed, keeping your fork public is a good way to make sure you adhere to licensing requirements from the start.

Configuration details to save

Make a note of your repository name from GitHub.

Clone your Mastodon fork to your local machine

  1. On the main page of your repository on GitHub.com, click <>Code.
  2. Copy the URL for the repository.
  3. Open your terminal and navigate to where you want to clone your repository.
  4. Clone your repository using the URL you copied

git clone git@github.com:YOUR-USERNAME/mastodon.git

  1. Change your current working folder to the new repository and keep your terminal open to use again later.

cd mastodon

Generate Mastodon secret keys and VAPID keys

Mastodon needs three secret keys and one public key to run. We’ll generate these using the tootsuite/mastodon Docker image on our local machine.

  1. Set a local environment variable SECRET_KEY_BASE. In your terminal, run:

SECRET_KEY_BASE=$(docker run --rm -it tootsuite/mastodon:v4.0.2 bin/rake secret)

  1. Set a local environment variable OTP_SECRET, by running the following in your terminal:

OTP_SECRET=$(docker run --rm -it tootsuite/mastodon:v4.0.2 bin/rake secret)

  1. Generate VAPID keys, using the SECRET_KEY_BASE and OTP_SECRET as inputs, and print our new keys to the terminal.

docker run --rm \
   -e OTP_SECRET=$OTP_SECRET \
   -e SECRET_KEY_BASE=$SECRET_KEY_BASE \
   -it tootsuite/mastodon:v4.0.2 \
   bin/rake mastodon:webpush:generate_vapid_key && \
   echo "SECRET_KEY_BASE=$SECRET_KEY_BASE" && \
   echo "OTP_SECRET=$OTP_SECRET"

Configuration details to save

Copy and save VAPID_PRIVATE_KEY, VAPID_PUBLIC_KEY, SECRET_KEY_BASE, and OTP_SECRET values from the output, which should look something like this:


VAPID_PRIVATE_KEY=cDpok1oPz1u6jpP2fE_Vf2TWBy-VVHh0n3KqdCEz81A=
VAPID_PUBLIC_KEY=BAb1gkLWzalGfAZq_7IeGX19T1Rp4I5aIerN_sDfon5eenIEn9DAWU1LLpFSu6VjtnhJJilbZXLBBdUSa6DL74Y=
SECRET_KEY_BASE=bb0231c8e07870b70934a9487cd6e796bbd6f4fe086dfa9039be3743a96e18726ea168cde3cf3ea823c5214e09b8afb7696324eb024f4317cb2626e0545aac12
OTP_SECRET=19a4d2e15d4ffa63c5ad08d716f33b2296419a6529a49908bd4f879891710d1f9efc1007efc9c18e82aeb52cba763cbd89b030ed2069be8464d9f26d167ea102

AWS Step 1: Create a PostgreSQL database using Amazon RDS

Log in to your AWS account and navigate to RDS.

Make sure your AWS region is set to the same region as your Release cluster.

Create a new PostgreSQL 14 database:

  1. Click on create database.
RDS create database
RDS create database
  1. Select standard create.
  2. Select PostgreSQL as the engine type.
  3. Pick PostgreSQL 14.5-R1 as the engine version.
RDS create database steps 2 to 4
RDS create database steps 2 to 4
  1. Select Free tier if you’re only setting up a small/test instance.
  2. Set the DB instance identifier to mastodon-1. We’ll need to refer to this again later.
RDS create database steps 5 to 6
RDS create database steps 5 to 6
  1. To make sure you don’t use more space than needed, change the allocated storage to 20 GiB.
  2. Disable autoscaling.
  3. Under the connectivity section, select the virtual private cloud (VPC) that Release created for your cluster. This will enable services running in the cluster to reach your new database.
RDS create database steps 7 to 8
RDS create database steps 7 to 8
  1. Choose to allow an existing security group.
  2. Under Existing VPC security groups, pick the security group that starts with eks-cluster-sg- followed by your cluster context, then close the dropdown.
RDS create database steps 8 to 10
RDS create database steps 9 to 11
  1. Click Create database.
RDS create database steps 8 to 10
RDS create database step 12

Your database will take a few minutes to launch.

Configuration details to save

Once the database is ready, click on view connection details and note down the database endpoint and database password.

Screenshot of RDS PostgreSQL configuration details
RDS PostgreSQL configuration details

AWS Step 2: Create a Redis cache using Amazon ElastiCache

Before we create a Redis cache, we’ll need to get the VPC ID for our Release cluster (Amazon ElastiCache does not show the full VPC name when creating a Redis cluster).

In AWS, navigate to VPC, then note down the VPC ID for your Release cluster.

Screenshot of Amazon VPC
Amazon VPC

With the VPC ID at hand, we can create a Redis cluster. In your AWS account, navigate to ElastiCache.

  1. Click on create cluster.
  2. Select create Redis cluster.
Screenshot of ElastiCache
ElastiCache

This opens up the cluster settings page, where we’ll configure our Redis cluster.

Under cluster info, set a name for this Redis cluster, for example, mastodon-redis.

Screenshot of Redis configuration section
Redis configuration section

In the connectivity section:

  1. Set a name for the Redis cluster’s subnet, for example, mastodon-redis-subnet.
  2. Select your Release cluster’s VPC ID from the dropdown.
Screenshot of Redis connectivity section
Redis connectivity section

Click Next.

Under selected security groups, click Manage.

  1. Filter the security groups to find one that starts with eks-cluster-sg-.
  2. Select the security group that starts with eks-cluster-sg- followed by your Release cluster’s context.
  3. Click choose.
Screenshot of Redis advanced settings page

Screenshot of Redis advanced settings page

Scroll down to the bottom of the page and click next.

Scroll down the page to review your Redis settings, then click on create.

AWS will take a moment to create your Redis cluster.

Configuration details to save

Open the Redis cluster’s details and copy the Redis cluster’s endpoint (you can omit the port number).

Screenshot of Redis Cluster details
Redis Cluster details

AWS Step 3: Create an Amazon S3 bucket for user media

In your AWS account, navigate to S3.

Click on create bucket.

Enter a unique bucket name, for example, mastodon-media-example.

Uncheck Block all public access.

Check “I acknowledge that the current settings might result in this bucket and the objects within becoming public”.

Click create bucket.

AWS will take a moment to create your new S3 bucket.

AWS Step 4: Create an IAM user to write to S3

In your AWS account, navigate to Identity and Access Management (IAM).

Click on Users, then on Add users.

  1. Enter a username, for example, mastodon-s3-writer.
  2. Under Select AWS credential type, select only Access key - Programmatic access.
  3. Click Next: Permissions.

On the Set permissions page:

  1. Select Attach existing policies directly.
  2. Filter for s3 policies.
  3. Check AmazonS3FullAccess.
  4. Click Next: Tags.

Click Next: Review.

Finally, click Create user.

Configuration details to save

Note down the access key ID and secret access key. Keep in mind that you won’t be able to see this secret again, so you’ll need to save it now and keep it secure.

AWS Step 5: Create an Amazon SES identity

In AWS, navigate to Amazon Simple Email Service (SES).

Click Create identity.

  1. Select Email address as the identity type.
  2. Enter an email address where you can receive emails.
  3. Click Create identity.

You’ll need to log in to the inbox for the email address you entered and verify the SES sending identity by clicking a link. Look for an email with the subject “Amazon Web Services – Email Address Verification Request”.

Back in AWS, you should now see that the identity status has changed to verified. If not, reload the page.

Send a test email to see whether the sending identity works.

Click on SMTP settings in the left sidebar, then on Create SMTP credentials.

Click Create.

Configuration details to save

Toggle Show User SMTP Security Credentials, then copy and save the SMTP username and password. You won’t have access to the password again after this step, so keep it secure.

After saving the credentials, click Close.

Create an application in Release

Log in to your Release account, then click on create new app.

  1. Enter a name for your Mastodon app, for example, mastodon.
  2. Pick your forked repository.
  3. Click Next step.
Pick your repository in Release
Pick your repository in Release

Pick your services in Release

  1. Select Analyze the repository
  2. Select the branch you would like to run.
  3. Select the root docker-compose.yml file.
  4. Click Start Analysis.
Analyze your repository in Release
Analyze your repository in Release

Release will now find services in your docker-compose.yml file.

Pick only the following services:

  • sidekiq
  • streaming
  • web
Pick services in Release
Pick services in Release

Click Next Step.

Edit the generated template

Release generates a template from the services we selected in the previous step.

This template is a YAML file that Release uses to generate new environments. It specifies the services, ingress rules, and workflows required to set up your app.

Double-check the template context and domain

Make sure the context in your application template matches your Release cluster’s context and that the domain matches the verified domain you’d like to use for this application.

Replace hostnames with rules

Release automatically generates hostname templates for services that have node ports and adds them to a hostnames section in your Application Template.

On a Mastodon server, the public services web and streaming share a hostname but are served at different paths.

Release configures a Kubernetes ingress controller to route traffic to your applications, based on the hostnames or rules settings, but only one of these settings can be used per environment. We’ll use only rules.

Update the template to replace hostnames with the following rules:


rules:
- service: web
  hostnames:
  - mastodon-${env_id}.${domain}
  path: "/"
- service: streaming
  hostnames:
  - mastodon-${env_id}.${domain}
  path: "/api/v1/streaming"

The changes should look like this:


- hostnames:
-   streaming: streaming-mastodon-${env_id}.${domain}
-   web: web-mastodon-${env_id}.${domain}
+ rules:
+ - service: web
+   hostnames:
+   - mastodon-${env_id}.${domain}
+   path: "/"
+ - service: streaming
+   hostnames:
+   - mastodon-${env_id}.${domain}
+   path: "/api/v1/streaming"

This instructs Release to create two Nginx location blocks on the Kubernetes ingress controller for your application, to direct requests to either web or streaming, depending on the path in the request.

Update the readiness_probe for Sidekiq

In the sidekiq service, replace the readiness_probe with the following:


  readiness_probe:
    exec:
      command:
      - bash
      - "-c"
      - ps aux | grep '[s]idekiq\ 6' || false
    period_seconds: 30
    timeout_seconds: 30
    failure_threshold: 3
    initial_delay_seconds: 10

Update the readiness_probe for the webserver

In the web service, replace the readiness_probe with the following:


  readiness_probe:
    http_get:
      path: "/health"
      port: 3000
    initial_delay_seconds: 20
    period_seconds: 30
    timeout_seconds: 30
    failure_threshold: 3

Update the readiness_probe for the streaming server

In the streaming server, replace the readiness_probe with the following:


  readiness_probe:
    http_get:
      path: "/api/v1/streaming/health"
      port: 4000
    initial_delay_seconds: 20
    period_seconds: 30
    timeout_seconds: 30
    failure_threshold: 3

Add database migrations as jobs

Release can run jobs in your environments to handle once-off or repeat tasks. These can be triggered by steps in an environment’s workflows.

For example, when Release first creates an environment for Mastodon, a db:setup job needs to be run to create Mastodon’s database schema. Release calls this stage in the workflow setup.

Mastodon also needs to run database migrations during version upgrades, so that the database schema is updated along with the code. We can use Release’s patch stage for this job.

During version upgrades, Mastodon splits pre-deployment and post-deployment database migrations. We’ll add two migration jobs, dbmigratepre and dbmigrate.

Add the following jobs to your template:


jobs:
- name: dbmigratepre
  command:
  - "/usr/bin/tini"
  - "--"
  args:
  - bash
  - "-c"
  - rails db:version && export SKIP_POST_DEPLOYMENT_MIGRATIONS=true && rails db:migrate || rails db:setup
  from_services: web
- name: dbmigrate
  command:
  - "/usr/bin/tini"
  - "--"
  args:
  - bash
  - "-c"
  - rails db:version && rails db:migrate || rails db:setup
  from_services: web

Add an admin user as a job

After our services are live and the database is ready, we’ll use the Mastodon CLI to create an admin user.

Add the following job to the jobs you created earlier:


- name: mastodonuser
  command:
  - "/usr/bin/tini"
  - "--"
  args:
  - bash
  - "-c"
  - tootctl accounts create $MASTODON_OWNER_USERNAME --email $MASTODON_OWNER_EMAIL --role Owner
  from_services: web

Add jobs to workflows

Let’s update the workflows section to run these jobs during setup and patch stages.

Because the order in which our new jobs are run is important, we’ll update the parallelize sections in these stages to create new steps.

Replace the workflows section of the template with the following:


workflows:
- name: setup
  parallelize:
  - step: dbmigratepre
    tasks:
    - jobs.dbmigratepre
  - step: services
    tasks:
    - services.sidekiq
    - services.streaming
    - services.web
  - step: dbmigrate
    tasks:
    - jobs.dbmigrate
  - step: mastodonuser
    tasks:
    - jobs.mastodonuser
- name: patch
  parallelize:
  - step: dbmigratepre
    tasks:
    - jobs.dbmigratepre
  - step: services
    tasks:
    - services.sidekiq
    - services.streaming
    - services.web
  - step: dbmigrate
    tasks:
    - jobs.dbmigrate
- name: teardown
  parallelize:
  - step: remove_environment
    tasks:
    - release.remove_environment

Save your changes to the template

Click Next Step to save your template, and move on to your environment variables.

Edit the default environment variables

For this step, you’ll need to retrieve the following information that we saved previously:

Generated on your local machine using Docker

  • SECRET_KEY_BASE
  • OTP_SECRET
  • VAPID_PRIVATE_KEY
  • VAPID_PUBLIC_KEY

PostgreSQL endpoint and password from Amazon RDS

  • DB_HOST
  • DB_PASS

Redis cluster endpoint from Amazon ElastiCache

  • REDIS_HOST

SMTP credentials from Amazon Simple Email Service

  • SMTP_LOGIN
  • SMTP_PASSWORD
  • SMTP_SERVER: Get the SES endpoint for your region from AWS docs
  • SMTP_FROM_ADDRESS: This is the verified email address Mastodon will send emails from

S3 bucket details and IAM access keys

  • S3_BUCKET
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • S3_REGION

Mastodon user details

  • MASTODON_OWNER_USERNAME: Some names, such as admin, owner, and user, are reserved. Use something unique.
  • MASTODON_OWNER_EMAIL: Use an email address you can receive your sign-up email at. Keep in mind that Amazon SES can only send messages to verified email addresses while in sandbox mode.

In Release, paste the following environment variables file in the default environment variables YAML editor, then update any values that are empty with the values we saved previously.


---
defaults:
  - key: SINGLE_USER_MODE
    value: true
  - key: SECRET_KEY_BASE
    value: 
    secret: true
  - key: OTP_SECRET
    value: 
    secret: true
  - key: VAPID_PRIVATE_KEY
    value: 
    secret: true
  - key: VAPID_PUBLIC_KEY
    value: 
  - key: DB_HOST
    value: 
  - key: DB_PORT
    value: 5432
  - key: DB_USER
    value: postgres
  - key: DB_PASS
    value: 
    secret: true
  - key: REDIS_HOST
    value: 
  - key: REDIS_PORT
    value: 6379
  - key: SMTP_SERVER
    value: 
  - key: SMTP_PORT
    value: 587
  - key: SMTP_LOGIN
    value: 
  - key: SMTP_PASSWORD
    value: 
    secret: true
  - key: SMTP_AUTH_METHOD
    value: plain
  - key: SMTP_OPENSSL_VERIFY_MODE
    value: none
  - key: SMTP_FROM_ADDRESS
    value: 
  - key: S3_ENABLED
    value: true
  - key: S3_BUCKET
    value: mastodon-media-example
  - key: AWS_ACCESS_KEY_ID
    value: 
  - key: AWS_SECRET_ACCESS_KEY
    value: 
    secret: true
  - key: S3_REGION
    value: 
  - key: S3_PROTOCOL
    value: https
  # MASTODON_OWNER_USERNAME can't be admin, owner, user
  - key: MASTODON_OWNER_USERNAME
    value: 
  - key: MASTODON_OWNER_EMAIL
    value: 
services:
  sidekiq: []
  streaming: []
  web: []
mapping:
  LOCAL_DOMAIN: WEB_INGRESS_HOST
  S3_HOSTNAME: s3-${S3_REGION}.amazonaws.com
  DB_NAME: mastodon_prod_${RELEASE_RANDOMNESS}

Click Next Step to save your default environment variables.

Deploy the app

You can skip over the build arguments step because we’re not adding any build arguments to this application.

Screenshot of app deploy button
Deploy your app

Finally, click Deploy your app!

Follow the deployment

Release will now build Docker images for our services and deploy an environment.

Screenshot of deployment
Deployment progress

The deployment information page shows all of the steps involved, from building the Docker images, through database migrations, and finally adding your admin user.

Click the mastodonuser job step to reveal the output, and copy the password Mastodon generated for your user.

Screenshot of step showing user password
View user password

If you miss this password, you can use the Mastodon web interface to request a password reset.

Log in and start posting

With your Mastodon instance up and running, log in and start posting.

Screenshot of environment info page, showing the web URL
environment info page, showing the web URL

You can find the public URL for your instance by navigating to the Environment Info page in Release. Click the web URL in the right sidebar to visit your instance.

Screenshot of Mastodon
Mastodon in action

What to do next: create more environments

Release really shines when you create environments for different branches in your codebase.

Test this by creating a feature branch with a small change, and follow Release’s guide on creating a new environment for your new branch.

Further reading

Even if Mastodon doesn’t replace Twitter, it will likely only grow in popularity. If you plan to host a public instance for your organization, you may want to make sure you focus on two important aspects: moderation and scaling.

Running any community on the internet is notoriously hard to get right. Keeping users, the public, and your servers happy might seem impossible, but many have gone before us and shared their experiences.

Here’s a quick overview of recent writing on these topics:

  • Scaling up your server: The official Mastodon documentation on scaling your server, which might not be as suited to a Kubernetes-hosted instance.
  • Moderation actions: The official Mastodon documentation on available moderation tools. These are invaluable if you plan to allow public content on your instance.
  • Mastodon Server Covenant: A covenant which can also inform your moderation and hosting decisions.

Learn more about Release

Of course, you aren't limited to hosting Mastodon. If you want to learn more about how Release can help you host your own software or other third party packages, book a demo.

Sign up here

About Release

Release is the simplest way to spin up even the most complicated environments. We specialize in taking your complicated application and data and making reproducible environments on-demand.

Speed up time to production with Release

Get isolated, full-stack environments to test, stage, debug, and experiment with their code freely.

Get Started for Free
Release applications product
Release applications product
Release applications product

Release Your Ideas

Start today, or contact us with any questions.