A few years back I wrote a tutorial on deploying Node-RED to Heroku. It was one button, from joeartsea/node-red-heroku, and two passwords. Then Heroku ended its free dynos in November 2022, and that post turned into a warning label.
I still want a Node-RED instance in the cloud. The usual advice is to run Node-RED on a Raspberry Pi, but anything on a home network sits behind a router, which is awkward when an ESP32 in the field needs to hit an HTTP endpoint, or when a webhook needs somewhere that is always up. So I went looking for the next free home and landed on Render.
Render's free web service gives you 750 instance hours a month. A 31-day month is 744 hours, so a single Node-RED instance can run 24/7 on it. The catch is in the details, and there are two of them. This post is the tutorial for my setup, plus the reasons it is shaped the way it is.
The two problems with free hosting
The first problem is the disk. Render's free service has no persistent disk. Node-RED, by default, saves your flows to flows.json in its user directory. Every redeploy, and every restart, starts from a fresh copy of the repo, so your flows quietly disappear.
The second problem is sleep. A free Render service spins down after 15 minutes without incoming traffic. Render's own dashboard banner puts it plainly:
Your free instance will spin down with inactivity, which can delay requests by 50 seconds or more.
For a dashboard you open once a day, that is annoying. For an IoT backend that a device calls on a schedule, a 50-second cold start is a timeout.
I put together a repo, ashad-red, that fixes both: Node-RED 5 with the flows stored in a database, and a keep-alive that stops Render from putting it to sleep.
It isn't from scratch. ashad-red started in 2021 as my copy of joeartsea/node-red-heroku, the project behind that Heroku button. Its central idea, a custom storage module that keeps flows in Postgres instead of on disk, is still the core of this one. What's new on top is Node-RED 5, Turso support, the keep-alive and the Render Blueprint.
Turso or Neon
Node-RED's storage is pluggable. The storage API is a small set of methods (getFlows, saveFlows, getCredentials, getSettings, getSessions, the library pair, and their save counterparts), and you can point settings.js at your own module that implements them. ashad-red ships one that talks to either Turso or PostgreSQL, and picks based on which environment variable you set.
Both have a free tier that comfortably fits this job, so the choice comes down to what you already use.
| Turso (libSQL) | Neon (PostgreSQL) |
|---|---|---|
What it is | Hosted SQLite, queried over HTTPS | Serverless Postgres |
How the app connects | Stateless HTTPS requests | TCP connection pool |
When idle | Nothing to wake | Scales to zero, wakes on the next query |
Set in Render | | |
Local testing | | Needs a Postgres server |
Good if | You want the least moving parts | You already know Postgres, or want to query the data with normal SQL tools |
My own default is Turso, and the deciding axis was complexity. Node-RED's whole state for one instance is a handful of JSON blobs: the flows, the credentials, the user settings and the login sessions. Each one is a few kilobytes, written when you click Deploy or log in, and read once at startup. SQLite over HTTP fits that, and the same code runs against a local SQLite file on my laptop.
Neon is the better pick if Postgres is already your tool. It's plain Postgres, so you can open the tables in psql or any SQL client, back them up with pg_dump, and keep other app data in the same project. Its scale-to-zero suits Node-RED's access pattern well: the database is only touched on deploys and logins, so it spends most of its life asleep and barely uses compute. The cost is a short wake-up on the first query after a quiet spell, which you'll only notice as a slightly slower deploy or login.
The tutorial covers both. Pick one in step 1 and the rest is the same.
What you need
- A free Turso or Neon account.
- A free Render account.
- About ten minutes. The Render build takes a minute or two of that.
Step 1: Create the database
Option A: Turso
Sign in at app.turso.tech and click Create Database. Give it a name (I used nodered) and pick a location close to where Render will run your app.
Back on the Databases list, open the menu on your database's row. You need two things from it:
- Copy URL. It looks like
libsql://nodered-<your-org>.turso.io. - Create Token. Node-RED writes to the database every time you deploy a flow, so the token must allow writes.
Option B: Neon
Sign in at console.neon.tech and click New project. Give it a name (I used nodered again) and pick a region close to your Render service; I used AWS US East 2 (Ohio). The dialog also offers object storage, functions, an AI gateway and auth. Leave all of those off; only Postgres database is needed.
Neon reported the project was created in 598 ms, then offered a prompt for a coding agent. You don't need it. Click Go to project.
On the project's Branch overview page, click Connect in the top left.
Copy the connection string. It looks like this:
// Neon connection string
postgresql://<user>:<password>@ep-<name>.<region>.aws.neon.tech/neondb?sslmode=require
Copy the whole string, including the sslmode part.
Neon offers a direct and a pooled (-pooler) host. Either works here; ashad-red keeps its own small pool and doesn't use named prepared statements, which is the usual thing that breaks behind a pooler. If the string ends with &channel_binding=require, you can leave it in; the Postgres client ignores it.
You don't need to create any tables. On first start ashad-red runs CREATE TABLE IF NOT EXISTS for the three it uses.
Step 2: Deploy with the button
This is the part that replaces Heroku's button. The repo includes a render.yaml, which Render calls a Blueprint: it describes the service (free plan, npm install, npm start) so you don't click through settings by hand. The secret values are marked sync: false, so Render asks you for them during setup instead of keeping them in the repo. It also pins Node 24, because Node-RED 5 needs Node 22.9 or later and I didn't want to depend on whatever Render defaults to that month.
Click the button to start:
Render opens a "You are deploying from a Blueprint" page with the variables waiting for values.
Fill it in:
Field | Value |
|---|---|
Blueprint Name | Anything; I used |
| Turso only: the |
| Turso only: the token. Blank for Neon. |
| Neon only: the connection string. Blank for Turso. |
| The editor login you want |
| The editor password you want |
Click Deploy Blueprint. In my old Heroku post I had a whole section about the editor rejecting the password on first login and fixing it in Config Vars. That problem doesn't exist here, because the login is read straight from these environment variables on every start.
Step 3: Watch the build
Render runs npm install, uploads the build, then starts Node-RED and waits for the health check on / to return 200.
You will see npm audit report a few dozen vulnerabilities in the build log. Most of them come from old dependencies in the repo that predate this rewrite (an old firebase-admin, nano, redis and feedparser), not from Node-RED itself. They are on my list to clean out. The build still succeeds.
When it's live, the log ends with the service URL. My deploy took 1m 17s.
Step 4: Open your Node-RED
Visit https://<your-app>.onrender.com. Instead of the old static Heroku page, the home page is drawn as a small Node-RED flow: an inject node wired to the editor, the deploy button and the docs.
That status line is there for a reason. If you forget to set the username and password, it turns into a red dot that says the editor is open to anyone. A public Node-RED editor is a public remote code runner, so I wanted that to be impossible to miss.
Click Open the flow editor, and log in with the username and password from step 2.
Build a flow, click Deploy, then trigger a manual deploy or restart on Render. The flow comes back, because it was never on Render's disk.
How it stays awake
The keep-alive is the part I was least sure about, so here is exactly what it does.
Render sets an environment variable called RENDER_EXTERNAL_URL on every web service, holding its public address. ashad-red reads it at startup and, every 10 minutes, makes an HTTP request to that address. You'll see it in the log:
// Render log
Using turso storage
Keep-alive: pinging https://ashad-redxxxx.onrender.com every 10 min
With Neon the first line reads Using postgres storage instead.
Why would an app pinging itself count as traffic? Because the request doesn't go to localhost. It leaves the container, goes out to the public internet, and comes back in through Render's edge proxy like any other visitor. As far as I can tell, Render decides whether a service is idle from what arrives at that proxy, so from its point of view somebody visits every 10 minutes. Ten, not fifteen, so a slightly late ping still lands inside the window.
The ping hits the static home page, not the editor, so it's cheap and doesn't need a login. It also never touches the database. That matters with Neon: Render stays awake while Neon is still free to scale to zero, so you aren't paying compute hours just to keep the web server up.
Optional: a keep-alive that lives outside Render
I haven't watched it through a full billing month yet. If Render ever decides self-pings don't count, the Apps Script above is the fallback, and the 744-out-of-750 hours maths still holds.
What to know before you rely on it
A few things this setup does not do, so you aren't surprised later:
- Context is not saved. Values from
flow.set()andglobal.set()live in memory and are gone after a restart. If a device counter matters, write it to a database node. - Credentials are stored unencrypted in the database (
credentialSecret: false). Keep your Turso token or Neon connection string private; anyone with either can read them. - On Neon, the first database access after a quiet spell is slower while the compute wakes up. That only affects deploys, logins and settings saves, never your running flows, which don't read from the database.
- Some core nodes are disabled:
exec,file,watch,tcpandudp. There is no persistent disk to write files to, Render doesn't accept raw TCP or UDP from outside, andexecon a public server is a shell waiting to happen. You can turn them back on insettings.jsif you know why you want them. - The 750 hours are per workspace, not per service. A second always-on free service on the same Render account will run out partway through the month.
- Palette installs don't survive a rebuild. Node-RED reinstalls the modules your flows use on the next start, which makes that start slower. Add anything permanent to
package.json.
Where this leaves it
The Heroku version of this post was shorter because Heroku did more for you, until it didn't. This version has more moving parts, but each one is small and replaceable: a database you can switch between Turso and Neon by changing environment variables, a keep-alive you can turn off with KEEP_ALIVE=false, and a host you could leave for any Node.js platform with Node 22.9 or later.
If you deploy it and something breaks, open an issue on the repo.
Deploy Node-RED on Render.com free hosting.
Discussion 0 comments
Be kind. I read everything but might take a day or two to reply.