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
8000and connect on their native ports5432/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 port3000in Klutch.sh. If you prefer a dedicated streaming endpoint, deploy a second app pointing to the same repo and set its internal port to4000. - 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.mdKeep 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:
bundle config set path vendor/bundlebundle installyarn install --frozen-lockfileRAILS_ENV=development bin/rails db:setupbin/rails s -p 3000 -b 0.0.0.0yarn start:streaming --port 4000 --host 0.0.0.0Optional helper start.sh to align with production entrypoints:
#!/usr/bin/env bashset -euo pipefailbundle exec rails db:migratebundle 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.0wait -nMake 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 buildENV 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/mastodonCOPY Gemfile Gemfile.lock package.json yarn.lock ./RUN bundle install --jobs 4 --retry 3RUN yarn install --frozen-lockfile
COPY . .RUN bundle exec rails assets:precompile
FROM ruby:3.2-bullseyeENV RAILS_ENV=production NODE_ENV=production PORT=3000 STREAMING_PORT=4000RUN 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/mastodonCOPY --from=build /opt/mastodon /opt/mastodon
EXPOSE 3000 4000CMD ["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:precompileto 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.shPORT=3000STREAMING_PORT=4000DATABASE_URL=postgres://<user>:<password>@<postgres-host>:5432/<db>REDIS_URL=redis://:<password>@<redis-host>:6379/0SECRET_KEY_BASE=<secure-random>OTP_SECRET=<secure-random>SMTP_SERVER=<smtp-host>andSMTP_PORT=<port>plusSMTP_LOGIN,SMTP_PASSWORD,SMTP_FROM_ADDRESSSINGLE_USER_MODE=true(optional)S3_ENABLED=trueand relatedS3_BUCKET,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,S3_REGION,S3_PROTOCOL=https,S3_HOSTNAME=<bucket-endpoint>if using object storageSTREAMING_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.2NIXPACKS_NODE_VERSION=18NIXPACKS_BUILD_CMD=bundle exec rails assets:precompileNIXPACKS_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)
- Push your repository—with the Dockerfile at the root—to GitHub.
- Open klutch.sh/app, create a project, and add an app.
- Select HTTP traffic and set the internal port to
3000. (If you deploy a second streaming app, set its internal port to4000.) - Add the environment variables above, including Postgres/Redis URLs, secrets, SMTP settings, and any S3 configuration.
- Attach persistent volumes for
/opt/mastodon/public/system,/opt/mastodon/log, and/opt/mastodon/tmpwith sizes that match your media and log retention needs. - 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):
curl -X GET "https://example-app.klutch.sh/api/v1/timelines/public?limit=5" \ -H "Accept: application/json"Create a toot (requires user token):
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.