Install
A working Flowdrome setup is one Nucleus (the control plane, with Studio embedded) plus one or more hosts (the machines that execute workflows).
The Nucleus ships with a host inside it, so installing the Nucleus alone gives you a complete, working Flowdrome — build a workflow, deploy it, call it. Add a separate host when you want workflows running on more machines than the one the Nucleus is on.
Default ports: 4800 for the Nucleus, 4820 for its built-in host, 4801 for a standalone host. First boot creates an Admin login of
admin/admin— change it immediately (the console prints one warning on the boot that seeds it; nothing nags after that, so do it now).
Versions
Releases are numbered Mark X Mod Y, written Mk X Mod Y. These docs describe Mk 2 Mod 9. Mod increments with every release; Mark changes rarely and deliberately.
You read the version of a running instance off the browser tab title: the Nucleus tab says
Flowdrome · Nucleus · Mk 2 Mod 9, a host dashboard says Flowdrome · Host · Mk 2 Mod 9. Studio is
embedded in the Nucleus, so it shares the Nucleus tab. That title is how you check what a running
instance is — there is no separate version panel.
The same version written as a dotted number is <mark>.<mod>.0, so Mk 2 Mod 9 is 2.9.0. That
form is what the .NET assemblies carry and what released artifacts are named with — Mk 2 Mod 9 is
2.9.0. There are no release candidates: a Mod is a release. Builds that are not tagged for
release are named with the git short commit instead, which is the <sha> you may see on artifacts
built from a checkout.
In a source checkout the two numbers live in exactly one file — version.json at the repo root,
{ "mark": 2, "mod": 9 }. The .NET build reads it through Directory.Build.props, the browser UI
reads it directly, and tests fail if any copy is edited independently.
Download
Every release publishes four artifacts on the latest release — the Nucleus and the Host, each as a Docker image and as a Proxmox LXC template:
| Docker | Proxmox LXC | |
|---|---|---|
| Nucleus (control plane, port 4800) | flowdrome-nucleus-docker_<ver>_amd64.tar.gz | flowdrome-nucleus-lxc_<ver>_amd64.tar.gz |
| Host (data plane, port 4801) | flowdrome-host-docker_<ver>_amd64.tar.gz | flowdrome-host-lxc_<ver>_amd64.tar.gz |
SHA256SUMS.txt is published alongside them — sha256sum -c SHA256SUMS.txt verifies a download.
One‑line install on Proxmox VE
If you run Proxmox VE, this is the easiest way in — no manual steps needed. Open the host shell
(Datacenter → your node → Shell) and paste one line. It builds a ready‑to‑run unprivileged LXC from
the latest release template (verified against SHA256SUMS.txt), asks a couple of questions or takes
sane defaults, and prints the URL when it’s up.
Install
Nucleus — control plane, port 4800 (first login admin / admin, change it immediately):
bash -c "$(curl -fsSL https://raw.githubusercontent.com/johnhowelljr/flowdrome-support/main/ct/flowdrome-nucleus.sh)"
Host — data plane, port 4801 (enrol it from the Nucleus Hosts tab):
bash -c "$(curl -fsSL https://raw.githubusercontent.com/johnhowelljr/flowdrome-support/main/ct/flowdrome-host.sh)"
Upgrade
Run the same command again and choose Update at the prompt. It pulls the latest release
template and upgrades the container in place — /data (host identity, deployed workflows, run history)
is never touched.
Prefer to do it by hand, or not on Proxmox? The Docker / LXC / ISO options below cover every other host.
Run the Nucleus
Option A — Docker
Download flowdrome-nucleus-docker_<ver>_amd64.tar.gz from the
latest release, load it, and run it:
docker load -i flowdrome-nucleus-docker_2.9.0_amd64.tar.gz
docker run -d --name flowdrome-nucleus --restart unless-stopped \
-p 4800:4800 -p 4820:4820 -v flowdrome-nucleus-data:/data flowdrome/nucleus:latest
The tarball carries two tags — flowdrome/nucleus:latest and the release’s own
flowdrome/nucleus:2.9.0 — so the plain :latest form above and a version-pinned docker run
both work, and docker images always shows which release a loaded image is.
-p 4820:4820 publishes the built-in host, which is what makes this single container a complete
install: without it you can still build and deploy, but the workflows you deploy are only callable
from inside the container. See the built-in host below.
First boot seeds admin / admin (override with -e FLOWDROME_BOOTSTRAP_USER /
-e FLOWDROME_BOOTSTRAP_PASSWORD). All state lives on the /data volume.
Option B — Proxmox LXC container
Download flowdrome-nucleus-lxc_<ver>_amd64.tar.gz, upload it to your Proxmox storage (web UI →
CT Templates, or drop it into /var/lib/vz/template/cache/), then:
pct create 200 local:vztmpl/flowdrome-nucleus-lxc_2.9.0_amd64.tar.gz \
--hostname flowdrome-nucleus --cores 2 --memory 2048 --swap 512 --rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp --unprivileged 1 --onboot 1 --start 1 \
--ssh-public-keys ~/.ssh/id_ed25519.pub
The container boots Debian 12 + systemd with the Nucleus supervised as flowdrome.service and all
state under /data. Nothing needs publishing — the built-in host is on the container’s own address
at 4820. Follow the log with pct exec 200 -- journalctl -u flowdrome -f.
To upgrade that container later, see Upgrade an LXC container in place.
Option C — from source
The Nucleus is a .NET service with the Studio UI embedded. From a checkout of the Flowdrome repository:
# prerequisites: Node 22+, Bun, and the .NET 10 SDK
npm install
npm run build
dotnet run --project core/Flowdrome.Control
Open http://localhost:4800, sign in as admin / admin, and change the password under
Admin → Users.
A fresh Nucleus starts with exactly three built-in service workflows, re-asserted from the
release on every boot: Authoring API and MCP Server (in the Authoring folder) and
Maintenance, the hourly disk sweep (in Maintenance). Everything else is yours to add. To
have something real to open before you build anything, seed the Node Demos folder — a
runnable example workflow for every node in the catalog, organized by category — from a repo
checkout: npm run seed:node-demos (or npm run seed:all to include the AI demo set) against
your running Nucleus.
Useful environment variables:
| Variable | Default | Meaning |
|---|---|---|
FLOWDROME_NUCLEUS_PORT | 4800 | Listen port |
FLOWDROME_NUCLEUS_PUBLIC_URL | – | The URL hosts and deployed apps use to dial home. Set this to a routable address (e.g. http://10.1.1.246:4800) when hosts are on other machines — it is baked into deployments. |
FLOWDROME_BOOTSTRAP_USER / FLOWDROME_BOOTSTRAP_PASSWORD | admin / admin | First-boot Admin account. The admin password default applies only while FLOWDROME_REQUIRE_AUTH is off — with it on and no password supplied, the Nucleus mints a strong random password and prints it once at boot |
FLOWDROME_REQUIRE_AUTH | 0 | 1 locks anonymous access out entirely. Without it, anonymous visitors get one built-in view-only principal. |
FLOWDROME_DATA | ./.flowdrome | Data directory (DuckDB stores, encrypted secrets) |
The built-in host
You do not have to add a host to deploy. The Nucleus supervises one of its own: it starts with the Nucleus, enrols itself over loopback, and appears in Hosts as Built-in host — no join token, no configuration. Deploy to it exactly as you would to any other host.
It listens on 4820 — the Nucleus’s port plus 20 — and binds the same interface the Nucleus does. In Docker that means publishing the port if you want to reach workflows deployed to it from outside the container:
docker run -d --name flowdrome-nucleus --restart unless-stopped \
-p 4800:4800 -p 4820:4820 -v flowdrome-nucleus-data:/data flowdrome/nucleus:latest
In an LXC container or on a bare install there is nothing to publish — it is reachable on the machine’s own address.
| Variable | Default | Meaning |
|---|---|---|
FLOWDROME_BUILTIN_HOST | 1 | 0 disables it — use only external hosts. |
FLOWDROME_BUILTIN_HOST_PORT | Nucleus port + 20 | Move it off 4820. |
FLOWDROME_BUILTIN_HOST_BIND | 0.0.0.0 | Pin the interface it listens on — the same one the Nucleus uses by default. |
FLOWDROME_BUILTIN_HOST_ADVERTISE | http://127.0.0.1:4820 | Pin the address it reports as its own. See below. |
FLOWDROME_BUILTIN_HOST_EXE | – | Path to the host executable. Only needed when it is not beside the Nucleus (packaged) or in the sibling project’s bin (a source checkout). |
Its data lives under the Nucleus’s own data directory (builtin-host/), so it is carried by the
same /data volume and survives upgrades with everything else.
The address its endpoints are shown at
The built-in host listens on every interface, so a workflow deployed to it is reachable at the
machine’s own address — http://10.1.1.5:4820/your-workflow/path — not only from the box itself.
What it advertises is loopback, deliberately: the Nucleus calls it from the same machine, and
127.0.0.1 is the most reliable address for that. The dashboard knows a loopback advertisement
means “the machine the Nucleus is on”, so wherever it shows you a URL — the Endpoints panel,
Open host, a deployment row — it substitutes the address you are already using to reach the
Nucleus, keeping the host’s port. Browse the Nucleus at http://10.1.1.5:4800 and its endpoints
read http://10.1.1.5:4820/…; browse it at localhost and they stay on loopback, which is correct
there.
Set FLOWDROME_BUILTIN_HOST_ADVERTISE when you would rather pin one fixed address — behind a proxy,
or when the machine has several addresses and you want a particular one. It takes a full URL, a bare
host, or host:port; a bare host keeps the port the host actually serves, so you cannot accidentally
advertise a port nothing is listening on.
-e FLOWDROME_BUILTIN_HOST_ADVERTISE=10.1.1.5 # -> http://10.1.1.5:4820
-e FLOWDROME_BUILTIN_HOST_ADVERTISE=https://flow.example.com:8443
In Docker, remember the container must publish the port for any of this to be reachable from
outside it (-p 4820:4820). An LXC container or a bare install needs nothing.
Why there is no per-trigger bind setting. A host has one listener, and every trigger deployed to it is served by that one listener — so “bind this webhook to localhost and that one to the LAN” cannot be honoured by a single host. The interface is a property of the host (
FLOWDROME_BUILTIN_HOST_BIND, orFLOWDROME_HOST_HOSTon a standalone host), and the advertised address is…_ADVERTISE. To serve some workflows on a different interface, deploy them to a different host.
Add a host
Add a host when you want workflows executing on a machine other than the one running the Nucleus — to put work near a database, to spread load, or to separate environments.
A host is a single self-contained agent. Pick the artifact that fits the machine; all four end at the same join flow (connect the host to your Nucleus with a join token).
Option A — Docker image
Download flowdrome-host-docker_<ver>_amd64.tar.gz from the
latest release — a slim .NET base,
non-root, with the engine worker embedded. Move the tarball to any server with Docker and load it:
docker load -i flowdrome-host-docker_2.9.0_amd64.tar.gz
docker run -d -p 4801:4801 -v flowdrome-data:/data flowdrome/host-agent:latest
As with the Nucleus image, the tarball holds the single tag flowdrome/host-agent:latest; the
version is in the filename.
The image carries everything a host needs to serve deployed workflows — the agent, the embedded engine, and the compiled worker. No Node, Bun, or .NET install on the server.
Option B — the live ISO appliance (Proxmox / any VM)
npm run iso:build
This builds dist/iso/flowdrome-host-live-<sha>.iso, a Debian-based live image. The ISO
is the operating system:
- Boot from it every time — there is no installer and no install step. Keep the ISO attached; a freshly booted VM is a host in seconds.
- Persistence by disk adoption. At boot the appliance looks for a filesystem labelled
FLOWDROME_DATAand mounts it as the host’s data directory — it never reformats it. If none exists, the first provably blank disk (no filesystem signatures, no partitions) is formatted once and labelledFLOWDROME_DATA. With no data disk at all, the host runs ephemeral and the console banner says so — add a blank virtual disk to keep state. - The OS layer is immutable across reboots. Anything outside the data disk — including a
changed root password — does not survive a reboot. Only
FLOWDROME_DATApersists. Upgrading a host = swapping the ISO and rebooting; the data disk carries the identity and state forward. - On Proxmox set the VM CPU type to
host: the host’s engine worker is compiled with Bun’s standard x64 build, which requires AVX2. A defaultkvm64-class vCPU kills it at launch (exit 132).
The console shows the host’s URL on boot (e.g. http://10.1.1.112:4801).
Option C — Proxmox LXC container
Download flowdrome-host-lxc_<ver>_amd64.tar.gz from the
latest release — this is the Host twin
of the Nucleus template above. Upload it to your Proxmox storage
(web UI → CT Templates, or drop it into /var/lib/vz/template/cache/), then:
pct create 210 local:vztmpl/flowdrome-host-lxc_2.9.0_amd64.tar.gz \
--hostname flowdrome-host --memory 1024 --cores 2 \
--mp0 local-lvm:8,mp=/data \
--net0 name=eth0,bridge=vmbr0,ip=dhcp --start 1
The container boots Debian 12 + systemd with the agent supervised as flowdrome.service and all
state under /data. The --mp0 line puts /data on its own volume — do this on day one:
it makes the data outlive any individual container. Unprivileged containers work; --password /
--ssh-public-keys behave like any stock Debian template. Because an LXC container uses the
node’s real CPU, the AVX2 concern from the VM path doesn’t apply — no CPU-type fiddling.
To upgrade that container later, see Upgrade an LXC container in place.
Option D — bare single file
pwsh scripts/publish-host.ps1 -Rid linux-x64 # or win-x64, osx-arm64, …
One self-contained executable (~75 MB on Linux) plus a dashboard/ directory beside it —
the publish script stages both, and the host resolves its UI from that sibling folder. Copy the
two together: the executable alone runs and serves workflows, but every dashboard page answers
503 DASHBOARD_MISSING. No .NET install needed on the target. Run it under your init system of
choice.
Connect the host to the Nucleus
In the Nucleus go to Hosts → Join token, generate one, and give it to the agent — via the
CLI (flowdrome-host --join <nucleus-url> --token <token>, which the dialog hands you as a
copyable one-liner), environment (FLOWDROME_JOIN_URL / FLOWDROME_JOIN_TOKEN), or the host GUI’s
Connect to Nucleus screen. Tokens are single-use and the value is shown only once — the Nucleus
keeps a hash. Once authorized, the host appears in the fleet view and can receive deployments.
Enroll it before you leave it. The Docker image, the CT template and the live ISO all bind
0.0.0.0, and until an agent is enrolled it acceptsPOST /api/enrollfrom anyone — the first-boot setup path, where the join token is the boundary. On an untrusted segment that window lets someone else’s Nucleus claim the box. PinFLOWDROME_HOST_TOKENto close it ahead of time. For what a running host exposes on that port, see What a host exposes on its port.
Upgrading
Your data is never in the image or the template. It lives in the /data volume (Docker) or the
/data directory (LXC), and every route below leaves it alone: the host identity, deployed
workflows, run history and the encrypted vault all survive. Upgrade the Nucleus first, then any
separate hosts.
Confirm what you are running afterwards from the browser tab title — it reads Mk X Mod Y. If the
tab still shows the old version after an upgrade that reported success, hard-reload it
(Ctrl+Shift+R): index.html is the one file with no hash in its
name, so a cached copy can outlive the release it came from.
Upgrade a Docker container
There is no in-place step for Docker, and none is wanted: replacing the container is the upgrade, because everything worth keeping is in the named volume. Load the new image, remove the old container, start a new one against the same volume.
docker load -i flowdrome-nucleus-docker_<new-version>_amd64.tar.gz
docker rm -f flowdrome-nucleus
docker run -d --name flowdrome-nucleus --restart unless-stopped \
-p 4800:4800 -p 4820:4820 -v flowdrome-nucleus-data:/data flowdrome/nucleus:latest
The -v flowdrome-nucleus-data:/data must name the same volume the old container used — that is
the whole upgrade. docker volume ls lists them if you are unsure, and docker inspect flowdrome-nucleus on the old container shows which one it had before you remove it.
Two things worth knowing:
- The tarball carries two tags —
flowdrome/nucleus:latestand the release’s ownflowdrome/nucleus:2.9.0— so bothdocker run flowdrome/nucleus:latestand the pinned versioned form work on a machine that only ever loaded the download. (Tarballs up to 2.8.0 carried only:latest; on those, re-tag after loading if you want the version visible.) - Back up the volume before a big jump:
docker run --rm -v flowdrome-nucleus-data:/data -v "$PWD":/backup alpine tar czf /backup/nucleus-data.tar.gz -C /data .
A separate Host container is the same shape with flowdrome-host-docker_<ver>_amd64.tar.gz,
flowdrome/host-agent:latest, port 4801 and its own volume.
Upgrade an LXC container in place
The CT template is also the upgrade artifact, and every container built from one — Nucleus or Host —
already has the upgrader inside it at /usr/local/bin/flowdrome-upgrade. Give it a newer template,
as a file or a URL:
# from the Proxmox host, no SSH into the container needed
pct push 200 flowdrome-nucleus-lxc_2.9.0_amd64.tar.gz /root/upgrade.tar.gz
pct exec 200 -- flowdrome-upgrade /root/upgrade.tar.gz
# or, if the template is reachable over HTTP
pct exec 200 -- flowdrome-upgrade https://your-server/flowdrome-nucleus-lxc_2.9.0_amd64.tar.gz
What it does, in order: unpack the new /app to a staging directory first (a truncated download
must not leave a half-replaced install), stop flowdrome.service, move the old /app aside to
/app.previous, move the new one into place, and start the service again. Then it checks
systemctl is-active: if the service came up it deletes /app.previous and installs this release’s
version of the upgrader over itself; if it did not, it leaves /app.previous in place, prints the
rollback command, and exits non-zero.
systemctl stop flowdrome; rm -rf /app; mv /app.previous /app; systemctl start flowdrome
Two properties worth knowing:
- It replaces
/app, it does not merge into it. Extracting over the top adds and overwrites but never removes, so every hashed UI asset from every past build would stay on disk — andindex.htmlis the one file with no hash in its name, so a browser holding a cached copy would go on loading a previous release’s bundle, which is still sitting there and still works. No error anywhere. Swapping the whole directory makes a stale cache 404 and reload, which is the failure you want. - It upgrades itself last, and only once the new release is proven to start. Without that, a container would keep running the upgrade logic it was created with for the rest of its life.
/data is never involved — host identity, deployed workflows, run history and the vault all survive.
It refuses any tarball that has no app/ inside, so pointing it at the wrong file is safe. Upgrade
the Nucleus first, then any separate hosts, and confirm what you are running from the browser tab
title (Mk 2 Mod 9).
If your container was created before Mk 2 Mod 7
The two properties above — replacing /app, and updating itself — arrived in Mk 2 Mod 7. The
upgrader lives at /usr/local/bin/flowdrome-upgrade, outside /app, so nothing that came before it
could ever replace it: a container runs the upgrade logic of the template it was created from
until something replaces it by hand. An older container therefore merges rather than replaces, once,
even while installing Mod 5 — and you will see the symptom, a browser tab still running the previous
release’s UI after an upgrade that reported success.
Do the first upgrade with the new upgrader taken from the template you are installing, and it fixes itself from then on:
# on the container
mkdir -p /tmp/up && cd /tmp/up
curl -fsSL -o t.tar.gz <url-of-the-new-template> # or copy the file in
tar -xzf t.tar.gz usr/local/bin/flowdrome-upgrade
chmod +x usr/local/bin/flowdrome-upgrade
./usr/local/bin/flowdrome-upgrade t.tar.gz
Afterwards /usr/local/bin/flowdrome-upgrade is the current one and plain
flowdrome-upgrade <file|url> is all you need. If you would rather not do this, a hard reload in the
browser (Ctrl+Shift+R) clears the stale-bundle symptom, and the next upgrade will be clean anyway.
If you would rather treat containers as cattle, the alternative is to create a new CT from the new
template and move the data volume across — possible when /data is its own mount point:
pct create 211 local:vztmpl/flowdrome-host-lxc_2.9.0_amd64.tar.gz --hostname flowdrome-host …
pct move-volume 210 mp0 --target-vmid 211 --target-volume mp0
pct destroy 210 && pct start 211
Back up & restore
Admin → Backup exports the entire Nucleus as one JSON file — every workflow with its full version history, folders, tags, variables, credentials, users, the host registry and settings — and restores it onto any Nucleus for a near-exact copy of the system. Secrets travel decrypted in the file (that’s what lets it restore onto a fresh install with a different data key; they’re re-encrypted on import), so guard the backup like the vault itself. Restore replaces everything except active logins — the admin doing it stays signed in.