Install Mastodon in Kubernetes
Mastodon is an open-source, federated (i.e., decentralized) social network, inspired by Twitter's "microblogging" format, and used by upwards of 4.4M early-adopters, to share links, pictures, video and text.
Why would I run my own instance?
That's a good question. After all, there are all sorts of public instances available, with a range of themes and communities. You may want to run your own instance because you like the tech, because you just think it's cool
You may also have realized that since Mastodon is federated, users on your instance can follow, toot, and interact with users on any other instance!
If you're not into that much effort / pain, you're welcome to join our instance
- A Kubernetes cluster (not running Kubernetes? Use the Docker Swarm recipe instead)
- Flux deployment process bootstrapped
- An Ingress controller to route incoming traffic to services
- Persistent storage to store persistent stuff
- External DNS to create an DNS entry
The Mastodon project doesn't currently publish a versioned helm chart - there's just a helm chart stored in the repository (I plan to submit a PR to address this). For now, we use a GitRepository instead of a HelmRepository as the source of a HelmRelease.
apiVersion: source.toolkit.fluxcd.io/v1beta2 kind: GitRepository metadata: name: mastodon namespace: flux-system spec: interval: 1h0s ref: branch: main url: https://github.com/funkypenguin/mastodon # (1)!
- I'm using my own fork because I've been working on improvements to the upstream chart, but
https://github.com/mastodon/mastodonwould work too.
We need a namespace to deploy our HelmRelease and associated ConfigMaps into. Per the flux design, I create this example yaml in my flux repo at
apiVersion: v1 kind: Namespace metadata: name: mastodon
Now that the "global" elements of this deployment (just the GitRepository in this case) have been defined, we do some "flux-ception", and go one layer deeper, adding another Kustomization, telling flux to deploy any YAMLs found in the repo at
/mastodon. I create this example Kustomization in my flux repo:
apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: name: mastodon namespace: flux-system spec: interval: 15m path: mastodon prune: true # remove any elements later removed from the above path timeout: 2m # if not set, this defaults to interval duration, which is 1h sourceRef: kind: GitRepository name: flux-system healthChecks: - apiVersion: apps/v1 kind: Deployment name: mastodon-web namespace: mastodon - apiVersion: apps/v1 kind: Deployment name: mastodon-streaming namespace: mastodon - apiVersion: apps/v1 kind: Deployment name: mastodon-sidekiq namespace: mastodon
Now we're into the mastodon-specific YAMLs. First, we create a ConfigMap, containing the entire contents of the helm chart's values.yaml. Paste the values into a
values.yaml key as illustrated below, indented 4 spaces (since they're "encapsulated" within the ConfigMap YAML). I create this example yaml in my flux repo:
apiVersion: v1 kind: ConfigMap metadata: name: mastodon-helm-chart-value-overrides namespace: mastodon data: values.yaml: |- # (1)! # <upstream values go here>
- Paste in the contents of the upstream
values.yamlhere, intended 4 spaces, and then change the values you need as illustrated below.
Values I change from the default are:
spec: values: mastodon: createAdmin: enabled: true username: funkypenguin email: email@example.com local_domain: so.fnky.nz s3: enabled: true access_key: "<redacted>" access_secret: "<redacted>" bucket: "so-fnky-nz" endpoint: https://s3.us-west-000.backblazeb2.com hostname: s3.us-west-000.backblazeb2.com secrets: secret_key_base: "<redacted>" otp_secret: "<redacted>" vapid: private_key: "<redacted>" public_key: "<redacted>" smtp: domain: mg.funkypenguin.co.nz enable_starttls_auto: true from_address: firstname.lastname@example.org login: email@example.com openssl_verify_mode: peer password: <redacted> port: 587 reply_to: firstname.lastname@example.org server: smtp.mailgun.org tls: false ingress: enabled: true annotations: kubernetes.io/ingress.class: traefik nginx.ingress.kubernetes.io/proxy-body-size: 10m hosts: - host: so.fnky.nz paths: - path: '/' postgresql: auth: postgresPassword: "<redacted>" username: postgres password: "<redacted>" primary: persistence: size: 1Gi redis: password: "<redacted>" master: persistence: size: 1Gi architecture: standalone
Finally, having set the scene above, we define the HelmRelease which will actually deploy the mastodon into the cluster. I save this in my flux repo:
apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: name: mastodon namespace: mastodon spec: chart: spec: chart: ./charts/mastodon sourceRef: kind: GitRepository name: mastodon namespace: flux-system interval: 15m timeout: 5m releaseName: mastodon valuesFrom: - kind: ConfigMap name: mastodon-helm-chart-value-overrides valuesKey: values.yaml # (1)!
- This is the default, but best to be explicit for clarity
Commit the changes to your flux repository, and either wait for the reconciliation interval, or force a reconcilliation1 using
flux reconcile source git flux-system. You should see the kustomization appear...
~ ❯ flux get kustomizations | grep mastodon mastodon main/d34779f False True Applied revision: main/d34779f ~ ❯
The helmrelease should be reconciled...
~ ❯ flux get helmreleases -n mastodon NAME REVISION SUSPENDED READY MESSAGE mastodon 1.2.2-pre-02 False True Release reconciliation succeeded ~ ❯
And you should have happy Mastodon pods:
~ ❯ k get pods -n mastodon NAME READY STATUS RESTARTS AGE mastodon-media-remove-27663840-l2xvt 0/1 Completed 0 22h mastodon-postgresql-0 1/1 Running 0 5d20h mastodon-redis-master-0 1/1 Running 0 5d20h mastodon-sidekiq-5ffd544f98-k86qp 1/1 Running 0 5d20h mastodon-streaming-676fdcf75-hz52z 1/1 Running 0 5d20h mastodon-web-597cf7c8d5-2hzkl 1/1 Running 4 5d20h ~ ❯
... and finally check that the ingress was created as desired:
~ ❯ k get ingress -n mastodon NAME CLASS HOSTS ADDRESS PORTS AGE mastodon <none> so.fnky.nz 80, 443 8d ~ ❯
Now hit the URL you defined in your config, and you should see your beautiful new Mastodon instance! Login with your configured credentials, navigate to Preferences, and have fun tweaking and tooting away!
What's my Mastodon admin password?
The admin username may be output by the post-install hook job which creates it, but I didn't notice this at the time I deployed mine. Since I had a working SMTP setup however, I just used the "forgot password" feature to perform a password reset, which feels more secure anyway.
Once you're done, "toot" me up by mentioning email@example.com in a toot!
If your instance feels lonely, try using some relays to bring in the federated firehose!
What have we achieved? We now have a fully-swarmed Mastodon instance, ready to federate with the world!
- Mastodon configured, running, and ready to toot!
Chef's notes 📓
There is also a 3rd option, using the Flux webhook receiver to trigger a reconcilliation - to be covered in a future recipe! ↩
Tip your waiter (sponsor) 👏
Did you receive excellent service? Want to compliment the chef? (..and support development of current and future recipes!) Sponsor me on Github / Ko-Fi / Patreon, or see the contribute page for more (free or paid) ways to say thank you! 👏
Employ your chef (engage) 🤝
Is this too much of a geeky PITA? Do you just want results, stat? I do this for a living - I'm a full-time Kubernetes contractor, providing consulting and engineering expertise to businesses needing short-term, short-notice support in the cloud-native space, including AWS/Azure/GKE, Kubernetes, CI/CD and automation.
Learn more about working with me here.
Flirt with waiter (subscribe) 💌
Want to know now when this recipe gets updated, or when future recipes are added? Subscribe to the RSS feed, or leave your email address below, and we'll keep you updated.