Sharing music with friends using fly.io, Azuracast, and Mopidy

Marty PennerMarty Penner
3 min read

TL;DR: Use Mopidy with Iris + Azuracast to live stream your music for cheap on fly.io. Here's the repo with my deployments. Feel free to fork and follow the instructions to have a self-hosted music and streaming service up and running quickly.

In my search for a simple self-hosted, highly available music service to play music for my friends, I've finally landed on navidrome for the player and azuracast for sharing the stream with friends. One of my goals is to be able to run roleplaying games for my friends wherever I am using a tablet or a low-spec laptop. So I need services that run somewhere in the cloud. By using navidrome, I can grab free apps like Substreamer to connect to my personal music library.

Substreamer is great because I can make select albums or playlists available offline, limiting my bandwidth usage.

If I'm on an iPad, I can stream from Substreamer to Azuracast using the IziCast app. Basically, IziCast provides a way of connecting to any Icecast server.

Tired of hearing the word "cast"? Same.

One downside of this approach is having to use an external audio loopback device to route my device audio. It's clunky and expensive. So I've opted to use Mopidy with the Iris frontend instead. In this case, Mopidy connects directly to Azuracast; no intermediary device or software required. Win!

I'm deploying all of this on the excellent fly.io platform. Their free tier is quite generous. Azuracast needs more resources than that unfortunately, so I'm using a beefier instance. In order to mitigate the expense, I've set up a GitHub action to scale down all instances every night at midnight. By adding workflow_dispatch to the action, I can trigger it manually. By adding an input dropdown allowing start and stop values, I can start or stop the instances at will.

In the future, I suspect fly.io machines will allow auto-scaling based on requests. Looking forward to that!

Since my music exists locally, I needed to tell fly.io to build locally only, then push the built images up. This seemed a lot more resilient than having it push up the docker context before building remotely. The downside is long upload times since the entire music library is being bsked in to the docker image. Bit of a tradeoff. I could probably set up syncthing, or curl a tar from an S3 bucket, but that seems less fun to me for now.

Re: azuracast, since it uses a few different internal paths for storage and fly.io does not currently allow multiple volume mounts, we resort to copying a copyright-free track to the station media directory every time the app boots up, i.e. after a new deploy. One caveat here is that we have to manually restart broadcasting after every new deploy or machine restart. Kind of a bummer, but not a huge deal.

restart broadcasting

Lastly, in order to avoid opening public ports, I'm running every container with a tailscale sidecar process. This allows only me to access Mopidy and Navidrome while locking it down for everyone else.

0
Subscribe to my newsletter

Read articles from Marty Penner directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Marty Penner
Marty Penner

Hey, I'm Marty! I help build great developer experiences, from documentation to dev tools. I have a lot of experience with modern monorepos and React. I built a VSCode extension (called "CSS to Go") in mostly Rust for autocompleting CSS classnames. I've worked on a task manager app (who hasn't these days?) that I'm aiming to be local-first, cloud-optional, and polished because it's fun. I love reading, drumming, some writing, and figuring hard problems out. I've worked with a lot of tech over the 10+ years I've been a developer, and I love the inflection point we are at in the industry. The future is bright! I am currently a Frontend Developer at Race Roster, based out of Ontario, Canada.