Node-RED Context Stores: When to Leave Memory and Go to Disk
Default Node-RED context lives in memory and vanishes on restart. Learn how to evaluate file-based versus in-memory stores for stateful flows, with a counter example and verification steps.
14 Aug 2025, 01:45 UTC

The problem: your counter resets on every deploy
You build a Node-RED flow that counts events or limits request rates. It works until you redeploy or restart Node-RED, and the count drops to zero. The takeaway: Node-RED's default context store is volatile, and choosing a persistent store is a deliberate trade-off between write latency and durability.
Three scopes, one decision
Node-RED context comes in three scopes. Node context is private to one node. Flow context is shared across nodes on the same tab. Global context is shared across the entire instance. All three default to an in-memory store: fast, zero-configuration, and erased when the process exits. If your state is cosmetic, memory is fine. If it controls a real-world action, you need persistence.
A counter that remembers
Wire an Inject node into a Function node, then into a Debug node. The Function node reads the current value from global context, increments it, and writes it back.
// Function node: increment a global counter
let count = global.get("counter") || 0;
count += 1;
global.set("counter", count);
msg.payload = count;
return msg;
The || 0 handles the first run when the key does not exist. Swap global for flow and the value stays local to that tab. Call global.keys() to list stored keys. Deploy this and the Debug pane should show increasing integers. Restart Node-RED without changing the store, and the next message starts from 1 again.
Making it stick: the settings.js decision
Persistence is configured in settings.js inside your Node-RED user directory, commonly ~/.node-red/settings.js. You need write permission on that file and on the storage path. The file ships with a commented contextStorage section where you define a store object—typically type: "file"—and map the global scope to it.
Property names for scope assignment have varied across Node-RED versions, so treat the block below as a pattern, not a guaranteed copy-paste snippet. Match the structure to the commented examples in your own settings.js. To revert, restore the original commented block and restart; the in-memory store takes over immediately.
// settings.js — illustrative pattern; verify property names locally
contextStorage: {
default: {
type: "memory"
},
disk: {
type: "file",
path: "/var/lib/node-red/context", // placeholder: absolute path preferred
flushInterval: 30
}
}
// Then map the global scope to the "disk" store.
// The exact key for this mapping depends on your Node-RED version.
Restart Node-RED after saving. If the path is unwritable, the runtime logs a storage initialization error and may fall back to memory. Check startup logs before assuming persistence is active.
The trade-off: latency, memory, and races
Disk storage buys durability at the cost of I/O. A file store writes snapshots on an interval; a hard crash before the next flush can lose recent writes. Redis adds network latency but scales across instances and survives host reboots more cleanly.
Memory pressure is the other hidden cost. Every key in global context occupies heap for the life of the process. A flow that appends to an array without pruning will eventually slow the editor and runtime. Audit stored keys with global.keys() and delete stale entries.
Concurrency is a correctness risk. If two flows read, increment, and write the same key simultaneously, one update can be lost. Keep the read-modify-write sequence inside a single Function node, or serialize access with a lock pattern if multiple entry points are unavoidable.
How to verify it actually persisted
Verification is straightforward and worth doing after any storage change.
- Deploy the counter flow and confirm the Debug output is climbing.
- Stop Node-RED. From a terminal, press
Ctrl+C. As a system service, runsudo systemctl stop noderedand adjust the service name to match your installation. - Restart Node-RED from the same user directory so it loads the same
settings.jsand storage path. - Trigger the Inject node again. The counter should resume, not reset.
- Inspect the storage file. A file store should contain the last flushed value of
counterin JSON. - Check the Node-RED logs for storage-related errors during startup.
For a programmatic check, the Admin API exposes GET /context/global. This requires an authenticated admin session and the Admin API to be enabled. The returned value should match the Debug output.
File-based stores are not transactional. If the process is killed between the in-memory update and the disk flush, the counter can appear to jump backward on restart. If that gap matters, use Redis or a database-backed store.
Closing: match the store to the blast radius
Ask what happens when the value disappears. For dashboards and cosmetic counters, leave the default memory store alone. For rate limiting, billing thresholds, or safety interlocks, configure a persistent store and verify restart behavior before relying on it. Start with the file store for single-instance simplicity, back up the storage directory, and migrate to Redis when you need horizontal scale or tighter durability.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.