Skip to main content
If you prefer to run the components without Docker, the complete manual setup steps are in CONTRIBUTING.md. The same environment variables documented in Configuration apply.

Running each component

The signaling server and the web client are two long-lived processes, so each needs its own terminal (or a supervisor, as below). Run each block from the repository root. The signaling server needs Node.js 18 or later. It calls the built-in fetch, which arrived in 18, and its own dependencies declare the same floor. package.json has no engines field, so nothing stops you starting it on something older; it fails at runtime instead. CI runs it on Node 20 and the published image is built on Node 24, so anything in that range is well trodden.
Terminal
Terminal
Terminal
The client requires a production build (pnpm build then pnpm start) to serve static files. Use pnpm dev only during development, as it enables Next.js hot-reloading and is not suitable for production.

Environment variables

Copy and configure the environment files before starting:
Terminal
Set at minimum:
  • In server/.env: CLIENT_URL=http://localhost:3000
  • In client/.env.local: NEXT_PUBLIC_SOCKET_URL=http://localhost:3001
  • In client/.env.local: NEXT_PUBLIC_SITE_URL, set to your own public address
That third one matters more here than it does under Docker, and it is easy to miss. The published image omits canonical links, Open Graph tags, and the sitemap when no site URL is set, rather than claiming somebody else’s domain. A build you run yourself does not do that. The omit behavior is switched on by a flag that only the official Dockerfile sets, so a plain pnpm build with NEXT_PUBLIC_SITE_URL unset falls back to https://www.floe.one: your instance would serve canonical tags, share previews, and a sitemap advertising floe.one instead of itself. Set it to your own origin, or accept that those tags point somewhere else. It is inlined at build time, so changing it later means another pnpm build. NEXT_PUBLIC_SOCKET_URL is inlined when the client is built, so on this path changing it later needs another pnpm build. To change the address without rebuilding, leave it unset and set the unprefixed SOCKET_URL in the environment of the process that runs pnpm start instead; the browser then reads it per request from /api/config. See How the client finds the server. Leave both empty when the client and the signaling server share one origin behind a reverse proxy. Also set NODE_ENV=production in server/.env. Docker sets it for you; a direct Node run does not. Floe registers its own final error handler that returns a generic JSON error at every setting, so this is not a stack-trace leak. Set it anyway: production is what both the published image and server/.env.example use, so a direct Node run matches what is tested.

Updating an existing deployment

The Docker instructions in Operations assume there is no repository to keep in sync. Running directly with Node means there is, so pull, reinstall, then restart:
Terminal
Then restart both processes with whatever supervises them (pm2 restart <name>, systemctl restart <unit>, or your own runner). pnpm start serves whatever pnpm build last produced, so skipping the client rebuild leaves the old client running with nothing to indicate it is stale.
Reinstall before you restart, and do not skip it. A dependency update that only moves a lockfile changes no source file, so a restart on its own re-executes the same node_modules and the update silently does not take effect. The process comes back healthy and the old version is still running.
Confirm the restart landed by checking that uptime reset:
Terminal
To confirm a specific dependency actually moved, read the installed version rather than trusting the restart:
Terminal