Skip to content

Runner deployment (containers + Kubernetes)

@lumenflow/host’s deployment/ module (WU-3518, INIT-091 phase 3) completes the runner deployment matrix: alongside the existing native Windows/macOS/Linux install, the runner now packages as a container and as a Kubernetes workload. Both new modes keep the same runner CLI args as the native-OS modes, so lease renewal, expiry, stale-runner fencing, crash recovery, and the local outbox behave identically across all five deployment modes.

import { buildContainerRunnerDeploymentPlan } from '@lumenflow/host';

const plan = buildContainerRunnerDeploymentPlan({
  image: 'lumenflow/connected-runner:latest',
  runnerDir: '/var/lib/lumenflow-runner',
  sessionId: 'session-1',
  credentialSecretName: 'lumenflow-runner-credential',
});

plan.lifecycle.run; // "docker run -d --name lumenflow-connected-runner --restart on-failure -v ..."
plan.ports; // [] -- never publishes a port

The generated plan never publishes a port (ports: [], noInboundListener: true): the runner is outbound-only with no inbound listener. The bootstrap credential mounts read-only from an externally-managed secret; a separate, writable volume persists the runner state directory (bootstrap credential cache plus the local outbox) so both survive a container restart.

import { buildKubernetesRunnerDeploymentPlan } from '@lumenflow/host';

const plan = buildKubernetesRunnerDeploymentPlan({
  image: 'lumenflow/connected-runner:latest',
  runnerDir: '/var/lib/lumenflow-runner',
  sessionId: 'session-1',
  credentialSecretName: 'lumenflow-runner-credential',
  namespace: 'lumenflow',
});

plan.deployment.spec.replicas; // 1 -- single owner at a time
plan.networkPolicy.spec.policyTypes; // ['Egress'] -- Ingress is never a policy type

This mode emits a single-replica Deployment (no Service, no container ports field — outbound-only, no inbound listener), a PersistentVolumeClaim so the local outbox and credential cache survive a pod reschedule, and an egress-only NetworkPolicy whose policyTypes never includes Ingress — defense-in-depth at the network layer, on top of the missing Service/ports. The bootstrap credential mounts from a Secret volume; the kubelet refreshes it in place on rotation, so credential rotation takes effect without a pod restart.

A crash-and-reschedule re-establishes ownership through the same fencing-generation takeover every other deployment mode uses, rather than a second replica racing the first — replicas: 1 is deliberate, not a scaling limitation to be lifted later.

What stays identical across all five modes

Section titled “What stays identical across all five modes”

Container and Kubernetes deployment mirror the same runner CLI argument list as the native Windows/macOS/Linux install path — buildRunnerArgs() in packages/@lumenflow/host/src/deployment/shared.ts is a separate implementation whose doc comment notes it “Mirrors connected-compute/ service.ts’s runnerArgs()” — so:

  • Lease renewal, expiry, and stale-runner fencing behave identically.
  • Bootstrap credentials are short-lived and rotatable in every mode; revocation takes effect without a restart.
  • Crash recovery and the local outbox survive a container restart or pod reschedule without duplicating a consequential effect.
  • Proxy, custom CA, and controlled egress configuration are supported the same way in every mode.