FlowFlow

Architecture

You don't need to understand any of this to use Flow day to day, but it makes a lot of behavior click into place, why deploys work the way they do, why the terminal feature exists, why adding a node doesn't need you to touch that server directly. So here's how the pieces actually fit together.

Control plane

This is the Next.js app you install and the dashboard you're reading these docs from a link inside. It handles the UI, the API routes, receiving Git webhooks, and deciding what needs to happen next, "this service needs a new deployment," "this schedule is due to run." Postgres holds all of that state: your orgs, projects, services, deployments, encrypted secrets, the works. Redis backs job queues and log streaming so the control plane isn't blocking on slow work while someone's staring at a loading spinner.

Crucially, the control plane itself never touches Docker directly. It doesn't SSH into your servers to run build commands. It just decides what should happen and hands the job off.

Node agent

Every server Flow manages, a node, runs a small agent: a single static Go binary, no dependencies to install alongside it. The agent is the thing that actually talks to Docker on that machine. It picks up build and deploy jobs from the control plane, runs the build, starts or stops containers, and reports back what happened. It also phones home periodically with health data, memory, disk, load average, and a list of every container running on that node, which is what powers the Monitoring and Docker pages.

You never install this yourself. The server you install Flow on gets one automatically: the installer starts an agent container alongside the control plane, and it registers itself as a node the moment it boots, no separate step at all. For any other server, Flow connects to it over SSH one time and installs the agent for you, see Adding a node for exactly what that looks like. Either way, the agent runs independently of the control plane once it's up, so a control plane restart doesn't take your running bots and sites down with it.

The agent is also what makes the in-dashboard terminal feature work. When you open a terminal on a running service, your browser opens a WebSocket to the control plane, the control plane hands that off to the agent on the right node, and the agent attaches a real shell inside that service's container or process and pipes the bytes back and forth. It feels like SSH, without you ever needing SSH access to that box yourself.

Reverse proxy

Caddy runs on each node and is configured automatically as you attach domains to services. Set a domain on a service, Flow tells that node's proxy about it, and routing plus a TLS certificate follow without you writing any proxy config by hand. This is separate from however you choose to expose the Flow dashboard itself, see the domain section in Installation.

Three ways a service can run

Not everything is a Docker container. When you create a bot or site Service, you choose a runtime:

  • Docker: the default. Flow builds an image from your repo (using Nixpacks by default, so most Node, Python, and Go projects build without a Dockerfile at all, or your own Dockerfile if you'd rather control that yourself) and runs it as a container on the node.
  • Process: runs directly on the node with a start command you give it, no container involved. Useful if the runtime your bot needs is already installed on that server and you want to skip the extra Docker layer.
  • Static: builds once with a build command, and the output directory is served as plain files, meant for a site that doesn't need a server process at all.

Whichever runtime you pick, the deploy history, rollbacks, logs, and environment variables all work the same way, see Deploying.

The bot and site link

This is the part that's actually specific to Flow rather than general-purpose hosting panels. Every Project provisions a private Docker network automatically the moment you create it. Every Service inside that Project joins it, so a site can reach its bot at a stable internal address, and vice versa, without either one needing to know the other's public URL, or even having one. Environment variables work the same way at the Project level: mark one as shared, and every Service in that Project sees it, which is exactly how an addon like a shared Postgres database hands its connection string to everything that needs it. See Projects and services and Environment variables for what that looks like in practice.

What runs where, at a glance

  • Postgres and Redis: alongside the control plane, wherever you installed Flow.
  • The control plane itself: also wherever you installed Flow.
  • Every bot, site, and addon you deploy: on whichever node you chose when you created that Service, which can be that same server or a completely different one.
  • The reverse proxy and TLS certificates for your deployed services: on each node individually, next to whatever it's routing to.