Auto selection of HTTP port?

As mentioned in Formalising reusable functions with autocompletion, parameter hints, click-through-navigation - #16 by TotallyInformation

One thing that seems to be less than ideal, for anyone wanting to run more than 1 instance of Node-RED on the same device, is that you have to do something to change the Node-RED port number. This can be done in settings.js (after first run) or by setting a PORT environment variable prior to launch (which is a different process on Windows to Linux/Mac).

Now, node.js does have all the tools available to be able to work out a free port. However, from a user perspective, the only place you can put some extra tooling is in settings.js and that runs as a commonJS, synchronous module so it does add some complexity.

The real question I have is - is there any interest in having a built-in solution to automate discovery of a free port (starting with the default 1880 of course)?


For reference, here is an example of code you can add to settings.js to automatically set a viable port.

Add this to the top of your settings.js:

const { spawnSync } = require('child_process')

/** Find a free http port
 * Has to spawn a child process because it needs to run asynchronously to the main process
 * @param {number} startPort - the port to start searching from
 * @returns {number} - the free port
 */
function getAvailablePort(startPort = 1880) {
    const code = `
    const net = require('net')
    
    function isPortAvailable(port) {
      return new Promise((resolve) => {
        const server = net.createServer()
        server.once('error', () => resolve(0))
        server.listen(port, '0.0.0.0', () => {
            server.close(() => resolve(port))
        })
      })
    }

    (async () => {
      let port = ${Number(startPort)}
      while (!(await isPortAvailable(port))) { port++ }
      process.stdout.write(String(port))
    })()
    `

    // const output = execSync(`node -e "${code.replace(/\n/g, ' ')}"`, { encoding: 'utf8' })

    const output = spawnSync(
        process.execPath,
        ['-e', code]
    )

    return parseInt(output.output[1].toString(), 10)
}

Then replace the line that says uiPort: process.env.PORT || 1880, and replace with:

uiPort: process.env.PORT || getAvailablePort(1880),

On a small installation Mosquitto is often running on port 1883. There's scope for a race to claim that port?

I think that unlikely, especially if you make node-red auto-start dependent on MQTT start which is easy to do with systemd. But even without, you are far more likely to restart Node-RED than something like your MQTT broker.

In any case, it is a reasonable point and would easily be avoided by making the check skip well-known ports.

What I'm really interested in is whether people think this is a useful feature for Node-RED itself. I don't think it would be too hard to add to core if there were a call for it and that would be better than the work-around code in settings.js

Yes it is easy, but it does not do that by default.

I don't know much about this Julian so feel free to dismiss my opinions, not least because I have found it overly complicated to run multiple instances of Node-red on a single server, preferring to use a bunch of Pi Zero 2s.

Consider file-in and -out pathnames, served URLs, interaction with external servers such as databases, Mosquitto (should subscribing to foo/bar receive messages published by every instance?)

Isn't this kind of thing why people opt for Docker? I've briefly played but found that too complex too.

So anything that makes it easier is desirable but it does seem inevitable that two instances will interfere with each other.

There are several things that I'd personally improve :wink: And indeed do for my own implementations.

Never! Everyone has a view. Not all can be accommodated but all are interesting.

You see, I've long argued that's a shame. It is a direct result of the way Node-RED is presented. It isn't native to node.js apps at all. And that's the reason I created my alternate installer app. Which you can run using npx now by the way, you don't have to install anything. But the point being that by using a more "natural" node.js approach, mutli-instance installations, even with different versions of Node-RED are both trivial and highly robust.

I run a couple of "live" instances and often at least 2-3 dev instances. Of course, I do have a bit more memory to work with than might be typical for an average Pi. But with 8MB+ of RAM, you can easily run a couple of instances.

Not that it is "wrong" to run on separate Pi's either. I used to run 2 or 3 in parallel. But by the time I was running 3 and starting to run short of memory on them, I realised it wsa probably better to consolidate - if you have the hardware anyway. Probably not worth buying dedicated hardware unless you have other needs as well.

I think that, if you are already running several Docker services, adding another probably is your comfort zone. I always get tied in knots when I start to run multiple. I also think (though I've not extensively tested this theory) that it uses a fair bit more resources as well.

I've never really understood why people think that the Docker version of Node-RED is "easier" than a native install. For most people, probably the hardest part of a native install is installing node.js itself. And if you use Dave's script, that's a non-issue anyway.

For myself, I want to learn the components, how to manage them. It helps enormously if something goes wrong - which it inevitably will at some point. Because you understand the interactions.

We are, of course, talking personal/family installs here. Commercial should be very different.

Really not. node.js absolutely deals with this quite handily. Node-RED already uses a non-standard port (1880 instead of 80/443), creating another instance on another port really is trivial for node.js - just a bit of a pain for Node-RED configuration. Indeed, UIBUILDER for Node-RED can also create its own port should you want/need different web server settings than Node-RED's main port. Setting that up was also easy. Easy for users too since it is just 1 line in a uibuilder property inside settings.js.

Would automatically finding an open port be a valid option for a system running multiple node red instances? The port allocated for each one would depend on the order of startup, and if one was stopped and restarted then it might restart with a different port.

I have a use case (detailed here GitHub - golfvert/WIS2-GlobalBroker-Antiloop · GitHub) that requires running ~120 instances of the same flows on a server.
At the moment, all is packed into docker and by putting traefik as a proxy, I don't need a separate port for each instance. treafik route the request based on the name of the container.
I need access to the UI port for fetching metrics (through node-red-contrib-prometheus-exporter (node) - Node-RED) and sometimes putting some debug.

It is working fine. But, as the number of running instances continues to grow, running 120, 150, 200 instances of the same container puts some burden (mostly) on the memory of the server.

I have started working with AI on improving that.

I am currently working on two tracks:

  1. "translate" all Node Red code into Go. It took roughly half a day to come up with something 100% equivalent. And Go has the possibility to obtain a dynamic port, so that the metrics can be exposed the same way
  2. Install Node Red on the server. Symlink everything but settings.js for having my 120+ instances. I have a small "sidecar" python script that gives an available port on the host and through ansible I deploy the settings.js per instance with that dynamic port.

I appreciate my use case is quite specific... That being said, having the opportunity to set (and get) a dynamic port while starting NodeRed would be really useful for me.

Julian, in post 1, how would I know the uiPort allocated in Node Red ?

would not work for me, or rather, I wouldn't use that.
If this were the case, sequence of startup, whether native or docker, would mess with the configuration of services using the multiple node-reds configured this way.

If you're going to use multiple instances, just configure them right.

Or use traefik like greengolfer explains above.
We use an an environment variable (the container name) to address the nodered instance, which in this case makes sure you address the right instance (you can not have 2 docker containers withthe same name).

Of course, this would be something to be aware of. Fairly easily managed if you are aware of it though.

If one were stopped, then restarted, it is very likely that it would get the same port since that one would be free.

If you are juggling many up and down, that would be different. But not sure how likely a scenario that really is? Even when running multiple dev instances, I would usually only restart one down at a time. The selection of the port would ideally be early in the startup process. Even in my example code in settings.js, I can see that the settings are absorbed early in the start process so the check is executed early as well.

You would, of course, still have the option to fix the port the usual way, we wouldn't want to lose that feature for sure. But this is why I asked this as a question rather than a simple request since I'm not totally sure whether it is a good option to be built-in to core. I think it would be a nice feature but open to discussion.

You could certainly use my example code. You know the port because it is posted to the log on startup.

If you want the port inside Node-RED, well there are two ways.

If you are using a custom node, the node can obtain any Node-RED settings and the port is one of them of course.

The second method exposes the port as a global variable inside Node-RED. This is what I do anyway. To do it, you move the main content of the settings.js file from a direct export to a variable and then export the variable. This lets you further process the variable before exporting it. In this case, it lets you add a line like this:

/** Splitting the export this way allows us to dynamically override settings if we want to */
nrsettings.functionGlobalContext._port = nrsettings.uiPort

module.exports = nrsettings

Where nrsettings is the variable you've created.

Incidentally, this approach in settings.js has other potential advantages. For example, you can add a debugger statement after creating the variable such that starting node-red with an --inspect option will stop node-red's startup immediately after creating the variable and before the export so you can check the settings and step through the rest of the startup process. But the main benefit is being able to access the settings object and create new settings based on other existing ones, something you cannot do with the default settings file.


PS: A 3rd way would be to grab the port number before the settings as a variable and use that variable both in uiPort and later in functionGlobalContext.

Of course, for most things this is the right approach. But for rapid deployment, especially for that first run when you don't yet have a settings.js to be able to configure it and where you don't want to mess with environment variables (which are different between OS's), this is useful.

It is especially useful for firing up dev environments that you only occasionally use. For example, I have several "clean" dev environments that are occasionally used to make sure things work as expected before deployment for when creating new training video's. It is much easier not to have to worry about fixed port numbers for those, they vary in number and use anyway so trying to track the port numbers already used is just too much work. With this approach I never have to worry about port numbers, I just do npm start in the appropriate folder and look for the link in the startup messages.

This is a separate thing altogether. I use NGINX to eliminate port numbers for end users but you still have to know what the port is to be able to proxy it. Of course that has to be fixed. This proposal is a different use-case.

No, Traefik always connects to (for the docker instances) port 1880.
And when using container labels Traefik recognizes, no extra configuration is required for a new instances (traefik uses the docker socket, and scans labels of active containers, to know if it needs to and how to proxy.)

Outside the docker host, the containers are accessible through a fixed port, and the container name in the url. ( http://dockerhost:1880/instance1/ )
This is very easy when developing, and you need an extra instance. just spin up an instance with the right labels, and go.

In my non-docker deployment, as explained above, I am still using traefik so that external access is via a fixed URL: https://my_server/instance1...2.
However, unlike in the pure docker deployment, I cannot simply use the name instance1 / 2 to route the trafic. In the case, I create a dynamic file for traefik to consume. In there I need to say that intance1 is on port 1890 , and instance2 on port 2167. At time of the start on the node red instance1 I have a small wrapper that creates the traefik dynamic file with the newly attributed port.
So, knowing the port in Node Red would allow me to deploy without the wrapper. Like the go implementation.

You could easily achieve this by doing the port check up-front in settings.js and writing the result to a file that Traefik could use. I expect the same could be done with NGINX though I'm not sure how - possibly using the LUA extension. Perhaps I should revisit Traefik - I would be interested to know if it uses less or more resource than NGINX?

I don't use Docker for Node-RED nor for my proxy as there really is no need and it just adds extra complexity and resource overheads on my somewhat constrained 8GB RAM server.


Hmm, as I thought, NGINX tends to use fewer resources. Traefic is written in Go and has more capabilities that I don't really need (mostly the Docker integration).


Ha, I think ChatGPT is getting to know me rather too well! I asked it how I might go about doing dynamic configuration using NGINX and it gave me a solution not only referencing Node-RED but also referencing the free port finder I'd previously asked about.

I have done it.
I have embedded in the settings.js the piece of javascript that creates the traefik config. It pushed the file in traefik dynamic folder and done.
Access to node red running on a random port through a standard URL.

And I can't say if traefik is consuming more or less ressources. Until recently, I was only using it in a docker env and for that it is great. Using either Labels or dynamic files and pretty clever in rewriting/filtering/...

As I consider the switch to multiple instances out of docker, I don't want more change. I'll do with traefik.

I think a lot of people forget or never realise that settings.js is a full-fat node.js module and so can do all sorts of startup things. In mine, I create a pid file that makes it easier to kill/restart node-red. I also print out the startup memory use and key folder locations.