← All posts

Your Fleet Is a Live Database. Ask It Something.

osquery turns every host into SQL tables. osctrl makes the whole fleet reachable at once. FloMorphic turns a question into a validated issue — and Venapce is where you see it.

A host is a database. It always was — it just never had a query interface.

Which processes are listening on which ports? What packages are installed, at what version? Is there a Docker socket, and which containers publish which ports? What does the INPUT chain of the firewall actually say? Every one of those is a fact sitting on the machine right now. The problem was never that the data didn't exist. The problem was that getting at it meant SSH, a shell script, and a spreadsheet — or a monitoring agent that had already decided which ten facts you were allowed to care about.

osquery fixed the first half of that, years ago. It exposes the operating system as SQL tables — processes, listening_ports, deb_packages, rpm_packages, docker_containers, docker_container_ports, iptables, file, users, crontab, kernel_modules, hundreds more — and you ask with SELECT. osctrl fixes the second half: it's the open-source fleet manager that enrolls hosts, tags them, dispatches a query to every node matching a tag, and collects the rows back. Together they turn a fleet of machines into one queryable, live dataset.

That's the resource Venapce is built on. And the point of this post is what you do with a live dataset that you cannot do with a pile of alerts: you investigate it, you validate what you find, and you only raise the issues that survive.


The resource: any fact, any host, now

Venapce reaches the fleet through its osquery sense. Run your own osctrl, or register a space at osctrl.inflowenger.com from the Settings menu — either way, Venapce connects to the hub and your enrolled nodes appear. From that moment, the FloMorphic instance that ships with Venapce has a plugin node with an action called osquery.queryByTags: give it a SQL statement and a list of tags, and it returns rows from every node that carries those tags.

-- every Linux node tagged "linuxmint", one row each
SELECT name AS os_name, version AS os_version, platform, arch FROM os_version
-- what's listening, and which process owns it
SELECT l.protocol, l.address, l.port, p.name AS process, p.cmdline
FROM listening_ports l LEFT JOIN processes p ON l.pid = p.pid
WHERE l.port > 0
-- the firewall's actual opinion
SELECT chain, policy, target, protocol, dst_port
FROM iptables WHERE filter_name = 'filter'

This is why I keep calling it a monitoring and live data resource rather than a monitoring tool. A tool ships with its findings baked in. A resource lets you ask a new question at 3 p.m. that nobody thought of at 9 a.m. — and get an answer from the machines as they are right now, not as they were in last night's inventory sync.

But a resource has a cost: raw rows are not findings. 0.0.0.0:5432 in listening_ports is a fact. "Postgres is reachable from the internet on this host" is a judgment, and it needs the bind address, the firewall policy, the ACCEPT rules, and whether Docker published that port — four sources, correlated. That correlation is exactly the work FloMorphic does, and it's the whole reason Venapce keeps two tables instead of one.

  • Stage is intake and audit trail. Everything the fleet says lands here — including every retry, every timeout, every "probe returned zero rows." Nothing is dropped because it was inconvenient.
  • Issues is the validated signal. A row gets here only after a flow correlated the evidence and decided it holds.

Between them sits a graph you can read. Let me show you one.


The walkthrough: from a prompt to a validated audit

I didn't draw this flow. FloMorphic exposes itself as an MCP server, so I opened Claude Desktop with FloMorphic connected and described what I wanted:

Using the installed Venapce plugin, create a process flow that discovers all Linux nodes, checks whether Docker is installed on each, identifies all exposed ports, verifies which are reachable from outside the host, and produces a consolidated report. Include retries and timeout handling. If any step fails or returns incomplete results, do not stop — record it with a Venapce stage upsert carrying workflow, node, failed step, error, timestamp and status. Track every unreachable node, missing Docker, and inaccessible port check the same way. Raise issues for real findings.

What came back is a workflow called Linux fleet Docker and port exposure audit v2, using exactly three Venapce actions: osquery.queryByTags, db.stages.upsert, db.issues.upsert. Here is its shape:

Start → Init run context → Retry budget left?
                              │ attempt
                              ▼
                  1. Discover Linux nodes  (osquery: os_version, tags default+linuxmint)
                              │
                  Discovery returned nodes?
                    │ retry                          │ ok — fan out, in parallel
                    ▼                                ▼
     Build retry diagnostic            2.  Check Docker installed
     Stage: discovery retry            4.  Enumerate open ports
     Back off 1 minute                 4b. Docker published ports
     Increment attempt ──┐             5.  Read firewall rules
                         │                           │
     (back to the gate) ◄┘             Wait for all probes
                                                     ▼
      exhausted path:                  3+6. Correlate, triage failures
     Build abort diagnostic                          │
     Stage: discovery aborted          Stage upsert per failure / warning
     Issue: discovery aborted                        │
     Final summary (aborted)           7. Consolidated report
                                                     │
                                       Anything to raise?
                                    issues_found │        │ all_clear
                                                 ▼        ▼
                              Issue upsert per finding   Closeout, nothing to raise
                              Closeout with issues

Three things about this graph are worth slowing down on, because they are the difference between monitoring and validation.

1. Discovery is a loop with a memory

The first Venapce call asks every tagged node for its os_version row. If the hub returns nothing — no check-in, a timeout, an error — the flow doesn't fail and it doesn't silently continue with an empty fleet. It builds a diagnostic (workflow, step: discover_linux_nodes, attempt, error, timestamp, status: retrying), writes it to Stage with disposition retry, waits a minute, increments the counter, and goes back through the gate. Three misses and it takes the abort path: one Stage row with disposition failure, one issue with severity high, and a final summary that says so in plain text.

Notice what that means for the audit trail. A run that failed twice and succeeded on the third try leaves two Retry n/3 rows in Stage. The recovery is recorded as carefully as the success.

2. Evidence comes from several tables, not one

Once nodes are found, four probes run in parallel against the whole fleet, and a Wait for all probes node holds until every branch has settled.

Is Docker installed? Not one signal — four, in one query:

SELECT 'binary'  AS kind, path AS detail, ''  AS extra FROM file
  WHERE path IN ('/usr/bin/docker','/usr/bin/dockerd','/usr/local/bin/docker',
                 '/usr/local/bin/dockerd','/snap/bin/docker')
UNION ALL SELECT 'socket',  path, ''                FROM file WHERE path = '/var/run/docker.sock'
UNION ALL SELECT 'process', name, CAST(pid AS TEXT) FROM processes
  WHERE name IN ('dockerd','containerd','docker-proxy')
UNION ALL SELECT 'package', name, version           FROM deb_packages
  WHERE name LIKE 'docker%' OR name LIKE 'containerd%'
UNION ALL SELECT 'package', name, version           FROM rpm_packages
  WHERE name LIKE 'docker%' OR name LIKE 'containerd%'

Any hit means installed. A process hit means running. The evidence list travels with the node into the issue, so when you open it you see why the flow said yes.

Which ports are open comes from listening_ports joined to processes. Which of those belong to a container comes from docker_containers joined to docker_container_ports. What the firewall allows comes from iptables.

3. Reachability is a verdict, computed — and honest about uncertainty

This is the node the whole flow exists for: 3+6. Correlate, triage failures. It's one JavaScript node, and it reads like an analyst's checklist.

First it grades every probe. A null response or an error field is a failure. Zero rows from a hard probe — discovery, Docker, listening ports — is a warning: the step ran but the result is incomplete. Zero rows from a soft probe — Docker published ports, firewall — is fine; a host with no containers and no iptables rules is a legitimate state.

Then, per node, per open port, it decides:

EvidenceVerdict
Bound to 127.0.0.1 / ::1no (loopback only)
Bound to 0.0.0.0 / ::, firewall data unavailableundetermined (no firewall data)
Public bind, INPUT policy DROP/REJECT, port not in any ACCEPT ruleno (blocked by firewall)
Public bind, otherwiseyes — externally reachable

If a port's number matches a Docker published host port, the container name is attached. If any verdict is undetermined, the node's status becomes partial and a verify_external_reachability warning is recorded — the flow refuses to guess, and it refuses to stay quiet about not knowing.

Then it decides what to write. One Stage record per failure and per warning. One summary Stage record for the run, carrying the row count of every probe. And an issue only for nodes with a reason: Docker not installed, N externally reachable ports, or incomplete data — severity high if anything is reachable, medium if data was incomplete, low otherwise. If any step failed outright, one more run-level issue points at the Stage rows.

The remaining nodes just execute that plan: a db.stages.upsert that iterates the stage records, a consolidated text report, a gate — anything to raise? — and a db.issues.upsert that iterates the issue records. Every row carries source: linux-docker-port-audit-v2 and the tag linux-audit-v2, so it can be filtered, charted, and traced back to the run that produced it.


What lands in Venapce

Run it, and switch to the panel.

Stage now has rows with dispositions retry, warning, failure, summary — the health of the run itself, as data. If the firewall probe came back empty on some host, there's a row saying so, with the timestamp and the step name. You can see the flow's uncertainty; it isn't hidden inside a green checkmark.

Issues has one row per node that earned it: linux-docker-port-audit-v2: web-03, severity, a one-line summary — docker installed and running; 6 open ports, 2 externally reachable — and a data payload with every port, its bind address, its owning process, its container if any, and its verdict.

And then the part I care about most: the dashboard builder. Because everything the flow wrote is rows in Postgres, every question about it is a chart:

  • Issues by severity where source = 'linux-docker-port-audit-v2'
  • Nodes with Docker installed vs. not
  • Externally reachable ports per node — the exposure ranking
  • Stage rows by disposition — how healthy the audit was, not just the fleet

None of these views existed in Venapce yesterday. No one added them to the product. A flow wrote rows; I asked questions of the rows.


Why this is the point, not a demo trick

Look at what made the result trustworthy, and notice none of it is specific to Docker or ports:

  • Every fact came live from the host, through osquery, at run time. Not from an inventory that was correct last Tuesday.
  • Every judgment was correlated from multiple tables, and the correlation is code you can open and read.
  • Every failure became a record, not a gap. Retries, timeouts, empty results, unknowns — all in Stage, all queryable.
  • Every finding carried its evidence, so the issue explains itself.
  • The flow said "undetermined" when it was undetermined. A monitoring tool that can't say I don't know is a monitoring tool you eventually stop believing.

That is what I mean by a strong validation, and it's the property Venapce gets from being built on FloMorphic rather than around a rule engine. The flow is the feature. Change the prompt and Venapce grows a new capability without a release:

  • Which hosts have SSH keys in authorized_keys that aren't in our approved set?
  • Which nodes have a user with UID 0 that isn't root?
  • Which kernel modules are loaded that weren't there last week?
  • Which hosts have unattended-upgrades disabled and a deb_packages row older than the last CVE?
  • Which cron entries call something under /tmp?

Each of those is an osquery table, a correlation, a verdict, and two upserts. Each becomes rows. Each becomes a chart. The fleet was always a database. Now there's something that knows how to ask it a careful question — and how to tell you when it couldn't get a clean answer.

Vein, the agents across your compute. Synapse, the signal when something is wrong. And in between, a flow you can read.