rdio.

Fly.io

Deploy the rdio API and web app to Fly.io.

The repository includes Fly configuration for the API and web apps:

  • apps/api/fly.toml
  • apps/web/fly.toml

This is the maintained example deployment path for the repository, not a requirement. rdio can run on other platforms that support the production requirements in Deployment.

The API app is the main radio services app. It bundles the API, Liquidsoap, and Icecast, and mounts persistent media storage at /media.

1. Install the Fly CLI

Install and sign in with flyctl:

fly auth login

2. Choose app names

Edit the app names in:

apps/api/fly.toml
apps/web/fly.toml

Use globally unique names. The examples use:

rdio-api
rdio-web

If you change the API app name, also update:

  • BROADCAST_HOST in apps/api/fly.toml
  • apps/web/nginx.conf proxy targets
  • BETTER_AUTH_URL and WEB_ORIGIN secrets

3. Create Postgres

Create a Fly Postgres database in the same region as the API app, then attach or copy its connection string into the API app as DATABASE_URL.

fly postgres create

Use the database URL that Fly gives you as the API app's DATABASE_URL secret.

4. Set up Cloudflare R2

Create an R2 bucket and API token before deploying. See Cloudflare R2.

5. Create API storage

Create the API app and its media volume:

fly apps create <api-app-name>
fly volumes create rdio_media --app <api-app-name> --region <region> --size 10

The API Fly config mounts this volume at /media.

6. Set API secrets

Set the required API secrets:

fly secrets set \
  DATABASE_URL="<postgres-url>" \
  BETTER_AUTH_SECRET="<strong-random-secret>" \
  BETTER_AUTH_URL="https://<api-app-name>.fly.dev" \
  WEB_ORIGIN="https://<web-app-name>.fly.dev" \
  ICECAST_SOURCE_PASSWORD="<source-password>" \
  R2_ACCOUNT_ID="<account-id>" \
  R2_ACCESS_KEY_ID="<access-key>" \
  R2_SECRET_ACCESS_KEY="<secret-key>" \
  R2_BUCKET="<bucket-name>" \
  R2_PUBLIC_URL="https://pub-example.r2.dev" \
  --app <api-app-name>

The API config sets BROADCAST_HOST for source clients. Make sure it points at the API app hostname.

7. Deploy the API

Deploy from the repository root so Docker can copy the workspace packages and Liquidsoap script:

pnpm deploy:api

Or manually:

fly deploy -c apps/api/fly.toml --local-only --depot=false

Use --local-only --depot=false if your Fly org does not have Depot builder access.

8. Run database migrations

After the first API deploy, run migrations against production Postgres:

DATABASE_URL="<postgres-url>" pnpm db:migrate

9. Add fallback audio

Liquidsoap needs fallback audio when nothing is scheduled.

Place a file at:

/media/fallback/v1-tone.mp3

Use fly ssh console --app <api-app-name> to upload or copy the file onto the mounted volume.

10. Deploy the web app

Update apps/web/nginx.conf if your API hostname differs from rdio-api.fly.dev, then deploy:

pnpm deploy:web

The web app expects API traffic at same-origin /api. Its nginx container proxies /api and /live.mp3 to the API app.

11. Finish setup

Open the web app:

https://<web-app-name>.fly.dev

The first browser to complete setup creates the station administrator. See First-time setup.

12. Verify the deployment

Check these endpoints:

CheckURL
API healthhttps://<api-app-name>.fly.dev/api/health
Streamhttps://<api-app-name>.fly.dev/live.mp3
Broadcast statushttps://<api-app-name>.fly.dev/api/broadcast/status

Confirm the web admin Settings page shows the correct stream URL.

Test harbor by connecting BUTT during a live broadcast slot. See Live broadcast.

Exposed ports

The API Fly config exposes:

PortPurpose
3001API (internal; public via Fly HTTP)
8001Icecast
8005Harbor plain (BUTT)
8443Harbor TLS (BUTT with SSL)

Troubleshooting

If deploy or broadcast fails, see Troubleshooting.

On this page