← Back to Integrations with 3rd parties

DigitalOcean Apps Platform

Deploy rtcstats-server on DigitalOcean Apps Platform in two stages: boot it, then make it keep your data.

You have a WebRTC app sending data to rtcstats-js. Now you need rtcstats-server somewhere your clients can actually reach. Not your laptop. Not a Docker container on a colleague's machine. A real URL with HTTPS that stays up.

DigitalOcean Apps Platform is one of the simplest paths to get there. It picks up your repo from GitHub, runs the buildpack, and gives you a *.ondigitalocean.app hostname. This guide walks the deploy in two stages: first it boots, then it does something useful. We won't pretend the first version is production-ready. It isn't. We'll be honest about what's missing as we go.

Before you start

You need:

  • A DigitalOcean account
  • A GitHub account (Apps Platform pulls from GitHub by default)

That's it. No Docker installed locally. No Kubernetes. No Terraform.

Stage 1: get it running

Stage 1 is the smallest version of "running." You set one environment variable. The buildpack does the rest. The result is a server that boots and accepts connections - but isn't actually useful yet. We'll fix that in Stage 2.

Step 1: choose your source

In DigitalOcean, click Create App and pick Git repository.

  • Git provider: GitHub
  • Repository: rtcstats/rtcstats
  • Branch: main
  • Source directory: leave empty. The build command runs from the repo root and targets the workspace.
  • Autodeploy: OFF. This is a server you deploy and forget. You don't want a git push to main to silently redeploy production.

Click Next.

DigitalOcean Apps Platform: choose source step with rtcstats/rtcstats repo and main branch selected
DigitalOcean Apps Platform: choose source step with rtcstats/rtcstats repo and main branch selected

Step 2: configure the service

  • Resource type: Web Service
  • Detected runtime: Node.js (the buildpack auto-detects)
  • Size: start with the smallest tier. 1 GB RAM, 1 shared vCPU. You can scale up later from real load, not from anxiety.
  • Build strategy: Buildpack
  • Build command: leave empty for now. You'll add one later if you enable GeoIP enrichment.
  • Run command: npm start --workspace=packages/rtcstats-server
  • Public HTTP port: 8080

The 8080 default comes from config/default.yaml:7 (httpPort: 8080). Match it on both sides or both will be wrong in different ways.

The workspace flag in the run command is what lets you start the server from the monorepo root.

DigitalOcean Apps Platform: configure app review screen showing Web Service, 1 GB RAM size, Buildpack strategy, and run command
DigitalOcean Apps Platform: configure app review screen showing Web Service, 1 GB RAM size, Buildpack strategy, and run command

Step 3: add a health check

  • Type: HTTP
  • Port: 8080
  • Path: /healthcheck

rtcstats-server exposes this endpoint at packages/rtcstats-server/rtcstats-server.js:94. It returns 200, no auth required. Use HTTP, not the default TCP check. TCP only confirms the port is open. HTTP confirms the app is actually serving.

Step 4: add one environment variable

App-level environment variables, one row:

Key Value Scope Encrypt
NODE_ENV not-yet-production Run and build time No

That's the whole environment for Stage 1. Pick any value that reminds you this deploy isn't real yet. We use not-yet-production because it shows up in screenshots and reminds future-you what state this app is in.

DigitalOcean Apps Platform: app-level environment variables panel with NODE_ENV set
DigitalOcean Apps Platform: app-level environment variables panel with NODE_ENV set

Click Create Resource and let it deploy. You'll get a URL ending in .ondigitalocean.app. Hit https://your-app.ondigitalocean.app/healthcheck. You should see a blank 200.

That's Stage 1. The server is up. It's also, right now:

  • Not enriching anything with GeoIP (no MaxMind data)
  • Stores dump files on the servers hard disk.
  • Not authenticating any clients (anyone with the URL can post data)

We're going to configure persistence in Stage 2. GeoIP enrichment is covered in How to enrich rtcstats-server with GeoIP data. Auth is covered at the end of this page - and it's the most important one before you point real traffic at this.

Stage 2: make it keep your data

Gathering dumps from your clients and storing them in object storage is what rtcstats-server is built around. Stage 2 wires up the storage half of that - a bucket the server can write each finished dump into, and credentials for the upload.

DigitalOcean Spaces is the natural fit here: it's S3-compatible, lives in the same dashboard as your app, and the AWS S3 SDK that rtcstats-server uses talks to it without modification. Pointing the server at a Space is a few minutes of clicking and one environment variable.

Step 1: create a Space

In DigitalOcean, go to Spaces Object StorageCreate a Spaces Bucket.

  • Region: pick one close to your app. The region slug becomes part of the endpoint hostname (e.g. nyc3nyc3.digitaloceanspaces.com). Latency doesn't really matter though.
  • Enable CDN: off. rtcstats dumps are write-once, read-rarely. CDN buys you nothing here and costs extra.
  • File listing: Restrict. The bucket should not be world-readable.
  • Name: something you'll recognise, e.g. rtcstats-dumps-prod. The name is global across DigitalOcean Spaces, so generic names are taken.

Note the region slug and bucket name. You'll need both in Step 3.

Step 2: create a Spaces access key

Spaces uses its own access keys, separate from your DigitalOcean API token.

Go to APISpaces KeysGenerate New Key. Scope it to the bucket you just created, with Read/Write access. Give it a name like rtcstats-server-prod so you can revoke it later without guessing.

DigitalOcean shows the secret key exactly once. Copy both the access key ID and the secret key now.

Step 3: configure rtcstats-server

rtcstats-server reads storage settings from the storage.s3 block in config/default.yaml. Override them at runtime via the NODE_CONFIG environment variable - it's inline JSON consumed by the node-config package at boot, and YAML in the repo maps one-for-one to keys here.

Replace the Stage 1 NODE_CONFIG (if you set one) with this. Mark it Encrypt: Yes - it carries the secret key.

Key Value Scope Encrypt
NODE_CONFIG see below Run time Yes
{
  "storage": {
    "s3": {
      "credentials": {
        "accessKeyId": "DO00EXAMPLEKEYID",
        "secretAccessKey": "exampleSecretKeyReplaceMe"
      },
      "region": "us-east-1",
      "endpoint": "https://nyc3.digitaloceanspaces.com",
      "bucket": "rtcstats-dumps-prod"
    }
  }
}

Two things worth calling out:

  • region is us-east-1 regardless of where your Space actually lives. Spaces ignores the region for routing - the endpoint does that work - but the AWS SDK refuses to sign requests without one, and us-east-1 is the safe default.
  • endpoint is https://<region-slug>.digitaloceanspaces.com, not the per-bucket URL DigitalOcean shows you. The SDK appends the bucket name itself.

forcePathStyle is left at the default (false). Spaces supports virtual-hosted style; only flip it on for backends that require path-style (Supabase Storage is one).

DigitalOcean Apps Platform: app-level environment variables panel after Stage 2 changes, with NODE_CONFIG filled in
DigitalOcean Apps Platform: app-level environment variables panel after Stage 2 changes, with NODE_CONFIG filled in

Step 4: verify dumps land in the Space

Save and redeploy. Connect a client over the websocket (the default transport) and let it disconnect. Watch the Runtime Logs tab for a line like:

Connection with uuid <uuid> disconnected, starting to process data

Note the UUID. Then open your Space in the DigitalOcean console - you should see an object whose key matches it. If the bucket stays empty, the most likely culprits are:

  • Wrong endpoint region slug (nyc3 vs fra1 vs sfo3).
  • Access key not scoped to this bucket, or scoped read-only.
  • bucket name typo - rtcstats-server logs an S3 error in the runtime logs when this happens.

Before this sees real traffic: lock down auth

Everything above gets you a working rtcstats-server you can poke at. You should make it more robust for production, partially by adding auth. This also doubles as our identifier mechanism.

The configuration in this guide does not set authorization.jwtSecret. From config/default.yaml:

JWT secret key to use for authorizing clients. If not set, no authorization is performed.

In plain English: anyone who finds your *.ondigitalocean.app URL can post data to it. That's fine for a deploy you're poking at. It's not fine the moment real WebRTC traffic starts flowing.

For production, generate a JWT and authorize clients with it. The full setup is documented in the How to authenticate clients with rtcstats-server guide and also think about identifying users and sessions.

Set authorization.jwtSecret in NODE_CONFIG, sign tokens for your clients, and configure rtcstats-js to send them. That's the single change that turns this from "it deploys" into "it deploys safely."

What's next

Was this page helpful?