Mastodon on Storm Buckets
Mastodon can keep its media on object storage. Point it at a Storm Bucket and every attachment, avatar, header and cached remote file uploads over S3, while browsers load them from the bucket's public URL. Your server keeps the database and the web app.
The variable names below come from Mastodon's own storage code at v4.7.3.
What you need first
-
A running Mastodon server. Installing Mastodon is Mastodon's own guide.
-
A bucket for the media and nothing else. See Getting Started.
-
An access key for that bucket. See Managing Access Keys.
-
Static hosting enabled on the bucket. See Static hosting. Browsers read media from this URL.
-
A public host you will keep. Every post links its media by this host, so pick it once. A custom domain such as
files.example.sociallets you move storage later without breaking old posts.
Configure object storage
Mastodon reads its storage settings from .env.production, on Docker and bare metal alike. Type your bucket name and public host, then copy the block into .env.production.
S3_ENABLED=true
S3_BUCKET=your-bucket
AWS_ACCESS_KEY_ID=<your key id>
AWS_SECRET_ACCESS_KEY=<your secret>
S3_REGION=montreal-1
S3_ENDPOINT=https://montreal-1.buckets.stormdevelopments.ca
S3_HOSTNAME=montreal-1.buckets.stormdevelopments.ca
S3_ALIAS_HOST=your-bucket.stormsites.ca
# Garage has no per-object ACLs. Empty disables them.
S3_PERMISSION=
-
S3_HOSTNAME. Set it to the Storm endpoint host. Left unset, Mastodon
builds media URLs againstamazonaws.com. -
Leave
S3_OVERRIDE_PATH_STYLEunset. The name reads backwards. Mastodon
already uses path-style onceS3_ENDPOINTis set, andtrueswitches it to
virtual-host style, which Storm Buckets does not serve. -
S3_REGIONis your endpoint's name,montreal-1. The S3 signature
includes it, so a wrong value fails every upload. -
S3_PERMISSION=empty. Garage has no per-object ACLs, and Mastodon's
default ispublic-read. Empty skips the ACL calls.
Restart
Docker:
docker compose restart web sidekiq streaming
Bare metal (systemd):
sudo systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming
Post an image, then open the bucket's Files tab in your dashboard. The upload sits under media_attachments/. Open the image in a new browser tab: its address starts with your public host.
Move media you already have
Media uploaded before the switch is still in public/system/ on the server. The object keys in the bucket mirror that folder, so copy it across with rclone:
rclone copy public/system storm:your-bucket --progress
rclone check public/system storm:your-bucket
Run the copy once before you restart with the new settings, then again afterwards with --max-age 3h to catch anything uploaded in between.
Change your media host safely
Mastodon's web app caches its allowed media hosts in a service worker. When you move media to a new host, add it to EXTRA_MEDIA_HOSTS about a week before the switch, so returning visitors' browsers accept it.
EXTRA_MEDIA_HOSTS=files.example.social
Housekeeping
tootctl media remove and tootctl media remove-orphans work against the bucket and free capacity as they go. Skip --fix-permissions: it sets object ACLs, which Garage does not have.
What this does not cover
-
Suspended accounts. With
S3_PERMISSIONempty, Mastodon cannot flip a suspended user's media to private, so files stay reachable by URL until they are deleted. Mastodon's own docs name this trade for every ACL-less backend. -
Storage on Storm is single-zone. Keep a second copy of anything you cannot afford to lose, as the Terms of Service say.
Next steps
-
PeerTube for video on the same footing.
-
Custom Domains to serve media from a host you own.
-
Managing Access Keys to rotate the key Mastodon uploads with.