Skip to content

Deploying Mirlo to a VPS ​

This guide covers deploying Mirlo to a clean Virtual Private Server (VPS). This page has the recommended quick install and the Stripe webhook setup. Related pages:

Warning: At the moment we don't make any guarantees about version, backwards compatability, or anything like that. You're in charge or your own instance, and while we'll respond to bugs or issues with the set up, we don't have the resources to help you debug your server setup.

Architecture Overview ​

Mirlo consists of several components. We use Docker to orchestrate these:

  • API Server (Node.js/Express) - REST API and frontend
  • Database (PostgreSQL) - User data, tracks, artists, etc.
  • Queue (Redis) - Job queue
  • File Storage (MinIO/Backblaze/Hetzner) - S3-compatible storage for media files
  • Background Worker (Node.js) - Async job processing (audio conversion, image optimization, email)

Prerequisites ​

System Requirements ​

  • OS: Ubuntu 22.04 LTS or similar (recommended)
  • RAM: Minimum 2GB (4GB+ recommended for production)
  • Storage: 20GB+ free space (more for media files). We recommend setting up with a backblaze account.
  • CPU: 1+ cores

Required Tools ​

  • Docker Engine 20.10+
  • Docker Compose 2.0+
  • Git

(nginx and certbot are also needed for public https domains, but the install script installs those on apt-based systems.)

Before anything else, point your domain's DNS A record at the server — the SSL certificate step validates the domain over HTTP, so it should resolve to this server before you install (see the DNS and Cloudflare notes in Hetzner Cloud with Docker).

On a fresh Ubuntu server, install Docker and git, then clone the repo to /opt/mirlo:

bash
# Installs Docker Engine including the Docker Compose v2 plugin
curl -fsSL https://get.docker.com | sh

apt-get install -y git
mkdir -p /opt/mirlo && cd /opt/mirlo
git clone https://github.com/funmusicplace/mirlo.git .

From there, scripts/install.sh does everything in the step-by-step guide (env generation, compose config, build + start, admin user, client registration, frontend build, and nginx + Let's Encrypt for public https domains) in one unattended run. Run it as root for public https domains — it installs nginx and certbot itself (via apt) if they're missing; on non-apt distros, install those two manually first:

bash
cd /opt/mirlo
bash scripts/install.sh

Or non-interactively:

bash
MIRLO_DOMAIN=https://yourdomain.com \
ADMIN_EMAIL=you@example.com ADMIN_PASSWORD=secure-password \
bash scripts/install.sh

It's safe to re-run: existing .env / compose config / admin user are reused or updated, so a failed step (e.g. certbot before DNS propagated) can be fixed and retried by running the script again.

Updating ​

To update an existing instance later:

bash
bash scripts/update.sh

This pulls latest, rebuilds the images, restarts, and rebuilds the frontend (migrations run automatically on API boot — no separate migration step). See Maintenance and Updates for the equivalent manual steps, plus logs, backups and restores.

Some updates also include data migrations, such as moving stored files to a new layout. The background worker runs any pending ones when it starts, so these need no extra step either. To check on them, look for dataMigrations: in the worker's logs, or run yarn data:migrate --status in the API container. A migration that can't finish yet (for example, because uploads are still processing) is retried the next time the worker starts. Updates that remove support for the old data say so in their release notes, and expect the migrations before them to have finished.

The step-by-step guide is what install.sh automates — useful if you want to understand or customize what it's doing, or if you're not using Docker/Ubuntu and need to adapt the steps.

Note: for now you'll still need to do the Stripe set up steps described in Set Up Stripe Connect and Register Stripe Webhooks below.

Set Up Stripe Connect ​

Mirlo takes payments through Stripe Connect rather than charging everything through one platform account. Each artist gets their own connected Stripe account, and purchases/subscriptions are charged directly against that account (Mirlo takes an application fee on top). The main reason: with a connected account, tax reporting (1099s, VAT, etc.) is Stripe's and the artist's responsibility on their own account, not something the instance host has to handle on the artists' behalf.

To enable this on a fresh Stripe account:

  1. In the Stripe Dashboard, go to Connect → Settings and enable Connect for your platform account (if it isn't already). Choose Standard accounts — that's the type Mirlo creates (src/routers/v1/users/{userId}/stripe/connect.ts); it lets each artist manage their own Stripe account directly, including their own tax forms and payout schedule.
  2. Get your platform's secret key from Developers → API keys and set in Admin Settings.
  3. That's it for the host side — there's no manual per-artist setup. Once Stripe is configured, each artist connects their own account from their Mirlo dashboard, which redirects them through Stripe's own hosted onboarding (GET /v1/users/:userId/stripe/connect → stripe.accountLinks.create) and back again when done.

Next, register the webhook endpoint that keeps Mirlo in sync with events on those connected accounts:

Register Stripe Webhooks ​

If you're using Stripe, Mirlo needs a webhook endpoint registered against your platform's Stripe account (not per connected artist account — this is a one-time setup for the whole instance), once your domain is live.

The easy way: in Admin Settings → Stripe Settings, save your secret key, then click Register webhook with Stripe. Mirlo creates the endpoint below and saves its signing secret; no restart needed. Mirlo updates the endpoint's events on every boot, so upgrades pick up newly handled events by themselves. If the endpoint was deleted in Stripe, click Sync webhook with Stripe to register a new one. Stripe keeps separate endpoints for test and live mode, so if you switch modes afterwards, set up the new mode's endpoint by hand as below.

To do it by hand instead, go to the Stripe Dashboard under Developers → Webhooks.

Create https://yourdomain.com/v1/webhooks/stripe/connect — all of Mirlo's payment activity happens on connected accounts, and this endpoint is what keeps subscriptions, purchases and payouts in sync. When creating it, set "Listen to events on Connected accounts" and select at least:

  • setup_intent.succeeded
  • invoice.paid
  • invoice.payment_failed
  • payment_intent.succeeded
  • payment_intent.payment_failed
  • customer.subscription.deleted
  • account.updated
  • terminal.reader.action_succeeded
  • terminal.reader.action_failed

The full list is the keys of stripeConnectEventHandlers in src/utils/stripe/webhooks.ts.

After creating the endpoint, Stripe reveals a signing secret (starts with whsec_). Paste it into Admin Settings, or set it in .env as STRIPE_WEBHOOK_CONNECT_SIGNING_SECRET and recreate the api container (docker compose up -d).

Production Best Practices ​

  1. Backups: Automate daily database backups to off-site storage
  2. Monitoring: Setup alerts for disk space, CPU, memory, and service health
  3. SSL/TLS: Always use HTTPS with valid certificates (Let's Encrypt recommended)
  4. Firewall: Restrict access to internal ports (MinIO, Redis, PostgreSQL)
  5. Updates: Keep OS and dependencies updated regularly
  6. Logging: Centralize logs using ELK, Syslog, or similar
  7. Rate Limiting: Configure Nginx rate limiting for API endpoints
  8. Secrets Management: Use OS-level secret management, never commit to git
  9. Testing: Test backups and disaster recovery procedures
  10. Documentation: Document your specific deployment configuration

Getting Help ​