Skip to content

Deploying a Mastodon App

Introduction

Mastodon is a federated social networking platform built with Ruby on Rails, Sidekiq workers, and a Node.js streaming service. Deploying Mastodon with a Dockerfile on Klutch.sh gives you reproducible builds, managed secrets, and persistent storage for media uploads—all configurable from klutch.sh/app. This guide covers installation, repository prep, a production-ready Dockerfile, deployment steps, Nixpacks overrides, and production checks.


Prerequisites

  • A Klutch.sh account (sign up)
  • A GitHub repository containing your Mastodon source and Dockerfile (GitHub is the only supported git source)
  • External PostgreSQL and Redis instances (deploy as separate Klutch.sh TCP apps on port 8000 and connect on their native ports 5432/6379)
  • Optional object storage (S3-compatible) for media; otherwise use a persistent volume
  • Domain ready to point to your Mastodon instance

For onboarding, see the Quick Start.


Architecture and ports

  • Web UI/API runs on internal port 3000 (Rails + Puma) and should use HTTP traffic.
  • The streaming service uses internal port 4000 (Node). Keep both ports open inside the container; expose port 3000 in Klutch.sh. If you prefer a dedicated streaming endpoint, deploy a second app pointing to the same repo and set its internal port to 4000.
  • Sidekiq runs alongside to process background jobs.
  • Persistent storage is required for uploads and recommended for logs.

Repository layout

mastodon/
├── Dockerfile # Must be at repo root for auto-detection
├── Gemfile / Gemfile.lock
├── package.json / yarn.lock
├── config/
├── app/
├── public/system/ # Media uploads (mount as volume)
├── .env.production.example # Template only; no secrets
└── README.md

Keep secrets out of Git; store them in Klutch.sh environment variables.


Installation (local) and starter commands

Install dependencies and validate locally before pushing to GitHub:

Terminal window
bundle config set path vendor/bundle
bundle install
yarn install --frozen-lockfile
RAILS_ENV=development bin/rails db:setup
bin/rails s -p 3000 -b 0.0.0.0
yarn start:streaming --port 4000 --host 0.0.0.0

Optional helper start.sh to align with production entrypoints:

#!/usr/bin/env bash
set -euo pipefail
bundle exec rails db:migrate
bundle exec sidekiq -q default -q mailers -q pull -q push -q ingress -q scheduler -e production &
bundle exec rails s -p "${PORT:-3000}" -b 0.0.0.0 &
yarn run start:streaming --port "${STREAMING_PORT:-4000}" --host 0.0.0.0
wait -n

Make it executable with chmod +x start.sh.


Dockerfile for Mastodon (production-ready)

Place this Dockerfile at the repo root; Klutch.sh auto-detects it (no Docker selection in the UI):

FROM ruby:3.2-bullseye AS build
ENV RAILS_ENV=production NODE_ENV=production BIND=0.0.0.0 \
BUNDLER_WITHOUT="development test"
RUN apt-get update && apt-get install -y --no-install-recommends \
curl gnupg build-essential libpq-dev libidn11-dev libicu-dev libyaml-dev \
libreadline-dev ffmpeg imagemagick libvips redis-tools file git \
nodejs npm && rm -rf /var/lib/apt/lists/*
RUN npm install -g yarn
WORKDIR /opt/mastodon
COPY Gemfile Gemfile.lock package.json yarn.lock ./
RUN bundle install --jobs 4 --retry 3
RUN yarn install --frozen-lockfile
COPY . .
RUN bundle exec rails assets:precompile
FROM ruby:3.2-bullseye
ENV RAILS_ENV=production NODE_ENV=production PORT=3000 STREAMING_PORT=4000
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg imagemagick libpq5 libidn11 libicu67 libyaml-0-2 libvips redis-tools \
nodejs npm && rm -rf /var/lib/apt/lists/*
RUN npm install -g yarn
WORKDIR /opt/mastodon
COPY --from=build /opt/mastodon /opt/mastodon
EXPOSE 3000 4000
CMD ["bash", "-lc", "bundle exec rails db:migrate && bundle exec sidekiq -q default -q mailers -q pull -q push -q ingress -q scheduler -e production & bundle exec rails s -p ${PORT:-3000} -b 0.0.0.0 & yarn run start:streaming --port ${STREAMING_PORT:-4000} --host 0.0.0.0; wait -n"]

Notes:

  • If you use an external S3 bucket for media, keep the volume mounts for logs and tmp but you can skip persisting public/system.
  • Adjust bundle exec rails assets:precompile to add extra JS/CSS flags if required.
  • To slim the image, add rm -rf tmp/cache log/* after asset compilation.

Environment variables (Klutch.sh)

Set these in Klutch.sh before deploying:

  • LOCAL_DOMAIN=example-app.klutch.sh
  • PORT=3000
  • STREAMING_PORT=4000
  • DATABASE_URL=postgres://<user>:<password>@<postgres-host>:5432/<db>
  • REDIS_URL=redis://:<password>@<redis-host>:6379/0
  • SECRET_KEY_BASE=<secure-random>
  • OTP_SECRET=<secure-random>
  • SMTP_SERVER=<smtp-host> and SMTP_PORT=<port> plus SMTP_LOGIN, SMTP_PASSWORD, SMTP_FROM_ADDRESS
  • SINGLE_USER_MODE=true (optional)
  • S3_ENABLED=true and related S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_REGION, S3_PROTOCOL=https, S3_HOSTNAME=<bucket-endpoint> if using object storage
  • STREAMING_API_BASE_URL=https://example-app.klutch.sh
  • Any additional Mastodon feature flags you require

If you deploy without the Dockerfile and need Nixpacks overrides:

  • NIXPACKS_RUBY_VERSION=3.2.2
  • NIXPACKS_NODE_VERSION=18
  • NIXPACKS_BUILD_CMD=bundle exec rails assets:precompile
  • NIXPACKS_START_CMD=bash -lc 'bundle exec rails db:migrate && bundle exec sidekiq -q default -q mailers -q pull -q push -q ingress -q scheduler -e production & bundle exec rails s -p 3000 -b 0.0.0.0 & yarn run start:streaming --port 4000 --host 0.0.0.0; wait -n'

Attach persistent volumes

In Klutch.sh storage settings, add mount paths and sizes (no names required):

  • /opt/mastodon/public/system — media uploads and attachments.
  • /opt/mastodon/log — application logs.
  • /opt/mastodon/tmp — cache/tmp data for Sidekiq and Rails.

Ensure the directories are writable within the container.


Deploy Mastodon on Klutch.sh (Dockerfile workflow)

  1. Push your repository—with the Dockerfile at the root—to GitHub.
  2. Open klutch.sh/app, create a project, and add an app.
  3. Select HTTP traffic and set the internal port to 3000. (If you deploy a second streaming app, set its internal port to 4000.)
  4. Add the environment variables above, including Postgres/Redis URLs, secrets, SMTP settings, and any S3 configuration.
  5. Attach persistent volumes for /opt/mastodon/public/system, /opt/mastodon/log, and /opt/mastodon/tmp with sizes that match your media and log retention needs.
  6. Deploy. Your Mastodon instance will be reachable at https://example-app.klutch.sh; attach a custom domain if desired.

Sample API usage

Fetch the public timeline (JSON):

Terminal window
curl -X GET "https://example-app.klutch.sh/api/v1/timelines/public?limit=5" \
-H "Accept: application/json"

Create a toot (requires user token):

Terminal window
curl -X POST "https://example-app.klutch.sh/api/v1/statuses" \
-H "Authorization: Bearer <user_token>" \
-d "status=Hello from Mastodon on Klutch.sh!"

Health checks and production tips

  • Add a health endpoint and probe it: curl -I https://example-app.klutch.sh/health.
  • Enforce HTTPS at the edge; forward traffic internally to port 3000.
  • Keep Gemfile.lock and yarn.lock pinned; upgrade Ruby/Node deliberately.
  • Monitor Sidekiq queues and database connections; scale background workers if jobs back up.
  • Rotate secrets regularly and store them only in Klutch.sh.
  • If using S3, enable lifecycle policies to control media retention.

Mastodon on Klutch.sh combines reproducible Docker builds with managed secrets, persistent storage for media, and flexible HTTP/TCP routing. With the Dockerfile at the repo root, ports set to 3000/4000, and Postgres/Redis configured, you can deliver a federated social experience without extra YAML or workflow overhead.