HappierDocs

Self-hosting

Running your own Happier relay — how to deploy it, how to configure who may sign in, and what the server keeps.

This section is about running your own Happier relay (the sync backend that clients and machines connect to). Everything here is optional: Happier Cloud exists so you do not have to.

Your clients and machines then point at your URL — say https://api.example.com — instead of the Cloud one. Nothing else about how you use Happier changes.

Pick how to run it

If you are unsure: Docker if you already run a supervisor, the self-host runtime if you want a managed service on a plain host, and repo-local if you are changing the server itself.

Configure it

  • Environment variables — the complete server runtime surface. The canonical template is apps/server/.env.example.
  • API rate limits — every limited endpoint, its default, and the two variables that change it.

Decide who may sign in

This is the part that takes the longest and is the easiest to get subtly wrong, so it has a page per provider rather than one long one.

  • Auth overview — the policy model, and how the pieces below combine.
  • GitHub — OAuth for signup and connect, plus allowlists.
  • OIDC — Okta, Auth0, Entra ID, Keycloak and anything else generic.
  • mTLS — client certificates from your MDM, auto-provisioning, and mapping one person's devices to one identity.
  • Custom providers — adding your own without forking the core auth logic.

Decide what the server keeps

Understand what you are running

  • How Happier fits together — the three moving parts, and what that means for a proxy in front of them.
  • Protocol — the event contract between clients, the relay and your machines.

Flavors

Both flavors run the same API and the same logic. The difference is only which storage backends they use.

  • Full — external Postgres (default) or MySQL 8.0.16+, with S3 or a compatible store (default; HAPPIER_FILES_BACKEND=local switches to disk). Redis is optional and only needed for multi-replica realtime.
  • Light — SQLite (default) or embedded Postgres (PGlite) on disk, with local file storage served by the server at GET /files/*.

Use full for a shared server; use light for the easiest single node.

With SQLite and no DATABASE_URL, the server requires HAPPIER_SERVER_LIGHT_DATA_DIR and refuses to start without it.

Roles and scaling

One process is the simple case. To split it, set SERVER_ROLE:

SERVER_ROLEWhat the process does
unset (default all)API, realtime and background work together
apiAPI and realtime only — this is what clients connect to
workerbackground work only, no HTTP listener

Two things about that table are worth knowing before you rely on it:

  • An unrecognized value is not an error. Anything other than api or worker — including a typo — silently resolves to all. A misspelled SERVER_ROLE=wroker gives you a second full server, not a worker.
  • worker requires the Redis adapter. The process throws at startup unless REDIS_URL is set and HAPPIER_SOCKET_ADAPTER=redis-streams, because a worker publishes to rooms it has no direct sockets for.

The converse also holds: once the Redis adapter is on, durable machine-presence writes move to a worker. An all-api deployment with Redis enabled will log that it needs one, and presence will not be written durably until you run one.

For multi-replica API deployments, enable Redis fanout and configure sticky sessions at your load balancer. Docker has a working example.

Reverse proxy basics

In production, run the API behind a proxy that:

  • terminates TLS,
  • supports WebSocket upgrades — Socket.IO realtime lives at /v1/updates,
  • uses timeouts long enough for a long-lived connection,
  • and keeps secrets in your platform's env or secret manager rather than in git.

If you serve /files/* from the server (light flavor), allow those routes too.

Broken /v1/updates upgrades are the single most common self-hosting failure, and they do not look like a networking problem: machines register fine and then sit there offline, reported as machine.none_online. See Environment variables → Networking.

The local stack CLI can also bootstrap a remote host over SSH, which is often faster than doing it by hand:

On this page