Render
Deploy the console on Render with the console image and Render Postgres 18: one-click Deploy to Render, manual creation in the Dashboard, and the Render CLI equivalents.
Requirements
| Item | Requirement |
|---|---|
| Render | An account that can use paid plans. Free web services spin down after 15 minutes without traffic and Free Postgres expires 30 days after creation; neither can be used |
| Console image | ghcr.io/marvinli001/edgeweir:<YYYYMMDD>-<commit>, public; Render runs linux/amd64, which the image includes. Tag rules: Versions, upgrades and rollback |
| PostgreSQL | Render Postgres, PostgreSQL 18 |
| Nodes | edgeweir-node 0.2.0 or later: Render does not forward TCP, so nodes reach the node channel through the WebSocket entry |
| Master key | Generated by the Blueprint, or with openssl rand -base64 32; keep it outside Render, apart from database backups |
| Local commands | curl; openssl for manual creation; the Render CLI is optional |
Topology
| Render resource | Target | Carries |
|---|---|---|
Web service edgeweir, source the console image, plan 0.5c-512mb, 1 instance | — | ROLE=all (image default): web UI, API, node channel, pg-boss worker |
<name>.onrender.com or a custom domain, HTTPS terminated by Render | Container port 3000 (PORT=3000) | Browsers, /api/v1, /install.sh, /healthz; the node channel's WebSocket entry /node-channel |
Render Postgres edgeweir-db, PostgreSQL 18, plan 0.1c-256mb, private network only | Internal connection string, DATABASE_URL | Database |
Render forwards one HTTP port per web service and terminates TLS at its load balancer. The node channel's 8443 is not exposed: nodes enroll with wss://<name>.onrender.com, and the node channel's TLS runs inside the WebSocket, terminated by the console with mTLS; see The node channel's WebSocket entry.
One-click creation
render.yaml at the repository root is the Blueprint:
| Resource | Settings |
|---|---|
Web service edgeweir | runtime: image, ghcr.io/marvinli001/edgeweir:latest; plan: 0.5c-512mb; healthCheckPath: /healthz |
| Variables | PORT=3000; EDGEWEIR_MASTER_KEY generated by Render (a base64-encoded random 256-bit value); DATABASE_URL from the internal connection string of edgeweir-db; EDGEWEIR_PUBLIC_URL from the service's own RENDER_EXTERNAL_URL; EDGEWEIR_NODE_API_WEBSOCKET=true |
Database edgeweir-db | PostgreSQL 18, database and user edgeweir, plan: 0.1c-256mb, ipAllowList: [] (private network only) |
Both resources are created in Render's default region, oregon, which cannot be changed afterwards; for another region use manual creation.
- Click Deploy to Render at the top of this page (
https://render.com/deploy?repo=https://github.com/marvinli001/edgeweir) and sign in to Render. - Enter a Blueprint name, check that
edgeweirandedgeweir-dbwill be created, and click Deploy Blueprint. - Turn off automatic sync: on the Blueprint's Settings page, set Auto Sync to No. Otherwise every change to this repository's
render.yamlsyncs to your deployment and overwrites the same settings changed in the Dashboard (image, variables). - Save the master key: on the
edgeweirservice's Environment page, copy the value ofEDGEWEIR_MASTER_KEYand keep it offline, apart from database backups; see Backup and recovery. - Pin the version, then initialize.
Manual creation
Equivalent to one-click creation, with a region of your choice.
-
Generate the master key:
umask 077 openssl rand -base64 32 > edgeweir-master-key -
Database: Dashboard + New → Postgres. Name
edgeweir-db, PostgreSQL Version 18, pick the region and plan, click Create Database.- Copy the internal connection string (Internal) from the Connect menu at the top right of the database page.
- A new database accepts password connections from any address (
0.0.0.0/0): in the Networking section of the database's Info page, remove that entry and keep the private network only, likeipAllowList: []in one-click creation.
-
Console: + New → Web Service, under Source Code pick Existing Image, enter
ghcr.io/marvinli001/edgeweir:20260929-a1b2c3d(replace with the target tag) as Image URL, click Connect. -
Name
edgeweir, Region the same as the database, plan0.5c-512mbor larger. -
Advanced:
- environment variables below;
- Health Check Path
/healthz.
PORT=3000 DATABASE_URL=<internal connection string> EDGEWEIR_MASTER_KEY=<contents of edgeweir-master-key> EDGEWEIR_PUBLIC_URL=https://edgeweir.onrender.com EDGEWEIR_NODE_API_WEBSOCKET=trueEDGEWEIR_PUBLIC_URLfollows the service name; when the name is taken, Render assigns another subdomain. Look up the actual address on the service page after creation, correct it on Environment, and choose Save and deploy. -
Click Create Web Service.
Pin the version
One-click creation runs the latest image. Once the first deploy succeeds, pin the tag it runs:
-
Read the running version:
curl -fsS https://<name>.onrender.com/healthzThe
versionfield is the tag, e.g.20260929-a1b2c3d. -
In the Deploy section of the
edgeweirservice's Settings page, change the image URL toghcr.io/marvinli001/edgeweir:<that tag>and save.
Initialization
An uninitialized console logs the same setup token on every start.
edgeweirservice Logs, search forfirst-run setup.- The line's
setupTokenfield is the setup token and itsurlfield the setup wizard (<EDGEWEIR_PUBLIC_URL>/setup). - Open the wizard and enter the setup token; see Quick start.
Variables
| Variable | Value | Notes |
|---|---|---|
PORT | 3000 | Render's default port is 10000; set 3000, as the image's health check expects. |
DATABASE_URL | Internal connection string of edgeweir-db | Required. Private network, same region. |
EDGEWEIR_MASTER_KEY | Generated by the Blueprint, or the output of openssl rand -base64 32 | Required. |
EDGEWEIR_PUBLIC_URL | https://<name>.onrender.com | Required. The Blueprint takes RENDER_EXTERNAL_URL (never a custom domain); with a custom domain, set the literal value. |
EDGEWEIR_NODE_API_WEBSOCKET | true | The default node channel URL is wss://<host of EDGEWEIR_PUBLIC_URL>, and the WebSocket entry is always open. |
EDGEWEIR_NODE_API_URL | Unset | When set, it replaces the default of the previous row; it must be a wss:// URL. |
EDGEWEIR_TRUSTED_PROXIES | Empty | See Limitations. |
EDGEWEIR_VERSION | Unset | The running version built into the image; the image tag decides it. |
BETTER_AUTH_SECRET | Unset | Keep the original value when migrating from a deployment that set it. |
ROLE, HOST, and NODE_API_PORT keep the image defaults all, 0.0.0.0, and 8443; only the WebSocket entry connects to 8443, inside the container. All variables: Environment variables.
Verification
| Check | Where or command | Expected |
|---|---|---|
| Deploy | edgeweir service Deploys | The current deploy is live |
| Web and API | curl -fsS https://<name>.onrender.com/healthz | {"status":"ok","version":"20260929-a1b2c3d"} |
| WebSocket entry | curl -s -o /dev/null -w '%{http_code}\n' https://<name>.onrender.com/node-channel | 426 |
| Node channel URL | "Node channel" in System settings | wss://<name>.onrender.com, connection check "Reachable" |
| Node enrollment | Clusters and nodes → Add node | --server wss://<name>.onrender.com; run it on the node as in Adding nodes |
Custom domain
Do this before enrolling nodes; enrolled nodes connect to the address they enrolled with, see Node channel URL and certificate.
-
edgeweirservice Settings → Custom Domains → + Add Custom Domain, enterconsole.example.com; add theCNAMEthe page shows to DNS and click Verify. -
Change on Environment, choose Save and deploy:
EDGEWEIR_PUBLIC_URL=https://console.example.comThe default node channel URL becomes
wss://console.example.com. The Blueprint's Auto Sync must be No, or the next sync setsEDGEWEIR_PUBLIC_URLback toRENDER_EXTERNAL_URL. -
Verify: run the commands of Verification with the new domain.
With nodes already enrolled through wss://<name>.onrender.com, save wss://console.example.com under "Node channel" in System settings instead of changing environment variables: the old name stays in the node channel certificate. Keep the onrender.com subdomain on (do not set Render Subdomain to Disabled).
Upgrade
-
Back up the database: database page Recovery → Create export, download the export; paid plans also have Point-in-Time Recovery. See Backup and recovery.
-
In the Deploy section of the
edgeweirservice's Settings page, change the image URL to the new tag and save; on the Deploys page, Manual Deploy → Deploy latest reference. -
Verify:
curl -fsS https://<name>.onrender.com/healthzExpected:
versionis the new tag.
Migrations, signature verification, and rollback: Versions, upgrades and rollback.
Render CLI
| Task | Command |
|---|---|
| Validate the Blueprint | render blueprints validate render.yaml |
| Read the setup token | render logs --resources <service ID> --output json | grep setupToken |
| Upgrade | render services update <service ID> --image ghcr.io/marvinli001/edgeweir:<new tag>, then render deploys create <service ID> --wait |
Limitations
| Item | Behavior | Impact |
|---|---|---|
| Node channel | A web service forwards one HTTP port and no TCP | Nodes connect through the WebSocket entry only, with edgeweir-node 0.2.0 or later |
| WebSocket connections | Render sets no maximum duration; they close when the instance is replaced (deploys, restarts, platform maintenance) | Nodes reconnect; meanwhile they serve with their last good configuration |
| Deploy switchover | The new instance gets traffic once healthy; after 60 seconds the old one receives SIGTERM and is killed after the shutdown delay (30 seconds by default) | Old and new versions run side by side for a short while; node connections on the old instance drop and reconnect to the new one |
| Client IP | Requests reach the container through Cloudflare and Render's load balancer; the source ranges the load balancer connects from are not published | Leave EDGEWEIR_TRUSTED_PROXIES empty; audit IPs, sign-in rate limiting, and the nodes' "connection source address" use the load balancer's address; see Trusted proxies and client IP |
| Blueprint sync | With Auto Sync on Yes, changes to render.yaml sync to the resources and overwrite conflicting Dashboard settings; a deleted Blueprint-managed resource is recreated by the next sync | Set Auto Sync to No after creation |
| Region | Cannot be changed after creation; the web service and the database must share it to use the private network | One-click creation uses oregon |
| Database access | ipAllowList: [] allows the private network only | A local pg_dump needs an allowed source address added on the database page first; or use the exports on the Recovery page |
| Logs | Kept per workspace plan (7 days on Hobby) | The setup token is logged again on every start |
/downloads/* | The container has no downloads mirror; EDGEWEIR_DOWNLOADS_DIR is unset | 404; install.sh downloads from GitHub, see Adding nodes |