Overview
Understand the pieces rdio needs before choosing a hosting platform.
rdio is easiest to deploy when you treat it as three concerns:
- a web admin app
- an API that owns station state and proxies the public stream
- persistent radio data and audio services
The current production model bundles the API, Liquidsoap, and Icecast in one API container. The web app is deployed separately and talks to the API through /api.
rdio is not tied to a single hosting provider. The repository includes a Fly.io guide because that is the first maintained deployment path, but any platform can work if it can run the required services and expose the right ports.
What must persist
Production deployments need durable storage for station data and uploaded media.
API volume (/media)
Mount persistent storage at /media on the API container. rdio stores schedule state, playout pointers, fallback audio, and related radio files under this path.
Object storage (R2)
Uploaded media files live in Cloudflare R2, not on the API volume. Configure R2 before expecting media uploads to work. See Cloudflare R2.
Postgres
Auth, members, and application data live in Postgres.
Required services
A production deployment needs:
- Postgres for auth and application data
- the API container (API + Liquidsoap + Icecast)
- the web container
- persistent storage mounted into the API container at
/media - Cloudflare R2 for media uploads
- public HTTP access to the web app
- public HTTP access to the API stream proxy at
/live.mp3 - reachable harbor ports for live source clients (8005 plain, 8443 TLS)
Required secrets
Set these for the API. See Environment variables for the full reference.
| Secret | Purpose |
|---|---|
DATABASE_URL | Postgres connection string |
BETTER_AUTH_SECRET | Signs Better Auth cookies and tokens |
BETTER_AUTH_URL | Public API origin |
WEB_ORIGIN | Allowed web admin origins (comma-separated) |
ICECAST_SOURCE_PASSWORD | Password Liquidsoap uses to publish to Icecast and harbor |
R2_ACCOUNT_ID | Cloudflare account ID |
R2_ACCESS_KEY_ID | R2 API token access key |
R2_SECRET_ACCESS_KEY | R2 API token secret |
R2_BUCKET | R2 bucket name |
R2_PUBLIC_URL | Public base URL for media objects |
For the web app, keep VITE_API_BASE_URL=/api when the web server proxies same-origin API traffic.
Update apps/web/nginx.conf to point at your API hostname if it differs from the default.
Hosting options
Choose hosting based on the parts you need to run.
API and radio services
The API container also runs Liquidsoap and Icecast. Host it somewhere that can:
- run the API Dockerfile or an equivalent container image
- attach persistent storage at
/media - connect to Postgres
- expose public HTTP for
/apiand/live.mp3 - expose harbor ports for live source clients, especially 8005 and optionally 8443
- store API secrets securely
Examples include Fly.io, Render, Railway, DigitalOcean App Platform, AWS ECS, or a VPS that runs Docker.
Web admin
The web app can be deployed with the API or hosted separately. It only needs to serve the built frontend and reach the API. Static or container-friendly hosts such as Vercel, Netlify, Cloudflare Pages, Fly.io, Render, or a regular web server can work.
Media storage
Uploaded media currently uses Cloudflare R2. Keep the Cloudflare R2 setup even if the API and web app run somewhere else.
Deployment checklist
Before the first public launch:
- create the production database
- generate a strong
BETTER_AUTH_SECRET - configure
WEB_ORIGINwith your web admin URL - set up Cloudflare R2 and add
R2_*secrets - mount persistent storage at
/media - place fallback audio at
/media/fallback/v1-tone.mp3 - run database migrations
- verify
GET /api/health - verify the public stream at
/live.mp3 - verify harbor responds on port 8005
- complete first-admin setup in the web UI
Next steps
Use the maintained Fly.io guide if you want to follow the repository's existing deployment config:
If something fails after deploy, see Troubleshooting.