K3s Migration Roadmap¶
Borealis will migrate the Engine runtime from Docker Compose into single-node K3s through staged bridge rollout. The existing Engine deployment command shape stays intact while each workload moves only after explicit validation and one-way retirement planning for the old runtime equivalent.
Goal¶
Migrate Borealis Engine from Docker Compose into single-node K3s through staged bridge rollout. Preserve existing Engine.sh --network-mode <public|local> deploy <prod|dev> command shape and idempotent deployment behavior.
Principles¶
- Keep
Engine.sh deployidempotent: reconcile, do not recreate healthy K3s state. - Start with single-node non-HA K3s.
- Keep Compose Engine authoritative until each workload reaches explicit cutover.
- Introduce
borealis-operatoras only runtime K3s writer;Engine.shremains the deploy-time bootstrap/reconcile writer. - Keep
api-backend,job-scheduler, and migrated workloads Kubernetes-blind or operator-API-only. - Use allowlisted Borealis verbs, never raw YAML/kubectl from runtime services.
- Do not rotate secrets, delete PVCs, or teardown cluster during normal deploy.
- Use Longhorn as the Borealis K3s PVC/persistent volume backend for workloads that need durable container storage.
- Propagate the Engine host timezone into Borealis-managed Compose containers and K3s pods.
Stage 1: K3s Baseline¶
- Add K3s detection/install/reconcile phase inside existing
Engine.sh deploy.- Install K3s only when missing.
- Verify K3s service, kubeconfig, node readiness, container runtime.
- Disable bundled public ingress features until Borealis owns cutover path.
- Create
borealisnamespace. - Add managed labels/annotations for ownership and config hashes.
- Validation:
- Run deploy twice back to back and confirm no cluster teardown or pod churn.
- Confirm existing Compose Engine remains healthy.
- Confirm K3s API not publicly exposed.
Stage 2: borealis-operator Bridge¶
- Add
borealis-operatorworkload in K3s.- Replace future
site-worker-orchestratorrole. - Expose internal HMAC-authenticated Borealis API.
- Grant namespace-scoped RBAC only.
- Support read-only status verbs first.
- Replace future
- Allowed verbs:
-
GetClusterSummary -
ListWorkloads -
GetWorkloadStatus -
ListSiteWorkers
-
- Blocked verbs:
- raw YAML apply
- arbitrary pod spec
- arbitrary image/env/volume/service account
- hostPath or privileged pod creation outside fixed allowlist
- Validation:
- Compose
api-backendcan query operator status. - Operator rejects unauthenticated and unsupported requests.
- Operator RBAC cannot mutate resources outside Borealis namespace.
- Compose
Stage 3: Workload Rollout Interface¶
- Add restricted operator lifecycle verbs.
-
RolloutKnownWorkload(service_key, image_ref) -
RestartKnownWorkload(service_key) -
ScaleKnownWorkload(service_key, replicas) -
LaunchSiteWorker(site_id, worker_guid, image_ref, resource_profile) -
RetireSiteWorker(worker_guid, reason)
-
- Add image allowlist and digest/hash validation.
- Keep build work in
Engine.sh, not runtime services. - Validation:
- Known service rollout succeeds with valid image.
- Unknown service/image is rejected.
- Failed rollout leaves previous healthy workload serving.
Stage 4: First Low-Risk Workloads¶
- Move
webui-frontenddev workload into K3s bridge mode.- Preserve dev HMR path where practical.
- Keep Compose Traefik/API authoritative.
- Move
remote-desktop-guacdinto K3s bridge mode.- Keep ClusterIP-only exposure.
- Preserve existing VNC/Guacamole API behavior.
- Add scoped service-action bridge reconciliation.
- Reconcile K3s bridge workloads after
webui-frontendrebuilds. - Reconcile K3s bridge workloads after
remote-desktop-guacdrebuilds. - Keep Compose routing authoritative while refreshing K3s bridge images.
- Reconcile K3s bridge workloads after
- Fix DEV-mode Vite dependency cache mounts.
- Mount
node_modules/.viteas writable memory-backed cache for the transitional Compose WebUI path before Stage 6 retirement. - Mount
node_modules/.viteas writable memory-backed cache for K3s WebUI bridge pods. - Keep WebUI runtime source mounts read-only.
- Mount
- Fix DEV-mode WebUI runtime source staging.
- Sync staged WebUI source into
Engine/Services/webui-frontend/data/web-interface/on everydeploy dev. - Sync staged WebUI source during
--service webui-frontend rebuild dev. - Keep production deploys on seed-if-missing behavior unless
BOREALIS_REFRESH_WEBUI_RUNTIME_SOURCE=1.
- Sync staged WebUI source into
- Validation:
- WebUI dev path works.
- Guacd readiness passes.
- Existing Compose path remained available until Stage 6 WebUI cutover removed it.
- Scoped WebUI and guacd rebuilds refresh bridge workloads without full deploy.
- Unchanged bridge pod UIDs stay stable during scoped bridge reconciliation.
- DEV Vite dependency modules return JavaScript MIME through public Traefik after scoped WebUI rebuild.
- DEV bootstrap runtime no longer opens root
/socket.ioduring normal page load or operator-presence sync. -
/api/server/timezonesreaches authenticated Go backend route instead of returning an API 404. - DEV HMR source sync verified after next operator redeploy.
Stage 5: Site Worker Migration¶
- Move dynamic site workers from Docker launch to operator-managed K3s pods.
- Add
job-schedulerlifecycle mode switch for bridge validation before K3s site-worker ownership became default. - Route K3s site-worker launch/retire/list operations through
borealis-operatorallowlisted verbs. - Preserve one active worker per site in v1 through scheduler reconciliation.
- Preserve worker idle TTL and stale worker retirement in the K3s bridge path.
- Preserve non-root UID/GID, dropped capabilities, read-only root, memory-backed
/tmp, fixed resource requests/limits, and no ServiceAccount token. - Retire legacy Docker site-worker containers through
site-worker-orchestratorafter K3s worker listing succeeds. - Retire terminal K3s site-worker pods so failed pods do not block replacement workers.
- Restart K3s site-worker containers on nonzero exit while preserving idle TTL clean exits.
- Treat persistent transient heartbeat deadlocks as skipped heartbeat cycles instead of fatal worker exits.
- Retry site-worker database registration and heartbeat setup during transient PostgreSQL startup/unavailable windows.
- Preserve Agent Socket.IO registration after live redeploy validation.
- Add Agent stale connected Socket.IO detection so worker restarts do not leave Agents stuck on dead control channels.
- Name new K3s site-worker pods with deterministic sanitized site slugs for operator-readable Kubernetes and Server Info views.
- Enforce unique, bounded site-worker slugs on site create and rename so K3s worker names do not need UUID suffixes.
- Keep K3s worker GUIDs deterministic per site so Agent Socket.IO route URLs do not churn across redeploys.
- Add
- Replace Traefik route-file behavior with a controlled host-loopback bridge route while PostgreSQL stays localhost-only.
- Keep route files owned by
job-scheduler. - Keep worker listeners bound to
127.0.0.1inside host-network K3s pods. - Prune terminal route rows and orphaned
site-worker-*.ymlfiles during scheduler reconciliation. - Replace temporary host-loopback worker bridge with per-worker ClusterIP routing after API/PostgreSQL cutover.
-
borealis-operatorcreates one fixed-template ClusterIP Service per site worker. - K3s site-worker pods bind on pod networking instead of host loopback.
-
job-schedulerroute rows and Traefik route files point at the worker Service ClusterIP or DNS name. - Scheduler reconciliation retires old host-loopback worker pods so demand reconciliation can relaunch ClusterIP-backed workers.
-
- Keep route files owned by
- Validation:
- Focused operator/scheduler unit tests pass.
- Static
Engine.shand Python worker checks pass. - Agents connect to site worker through existing enrollment/token flow.
- Remote shell works through K3s site workers.
- File management works through K3s site workers.
- Process management works through K3s site workers.
- Service management works through K3s site workers.
- Connected Devices column reflects K3s worker registrations.
- Add Metrics Server-backed K3s site-worker CPU/RAM plumbing through
borealis-operator. -
/api/server/workersreturnsmetrics.k8s.ioCPU/RAM payloads for every active K3s site worker. - K3s resource metrics appear in Sites and Server Info.
- Stale workers retire cleanly.
- Redeploy validates K3s site workers survive PostgreSQL pod rollout/restart without disconnecting the fleet.
- Focused Go tests confirm new K3s site-worker pods are not host-networked and route through a per-worker ClusterIP Service.
- Live redeploy validates existing host-loopback site-worker pods retire and relaunch through ClusterIP Services without manual Agent service restart.
- Agent Socket.IO reconnect hardening is covered by Agent runtime tests/docs, and Device Inventory labels API-online/socket-missing devices as
Reconnecting.
Stage 6: Production WebUI Cutover¶
- Make K3s
webui-frontendauthoritative.- Add production
BOREALIS_WEBUI_TRAFFIC_OWNER=k3sroute owner that renders Compose Traefik's core WebUI upstream to the K3swebui-frontendClusterIP. - Route dev mode through the K3s
webui-frontendworkload as well. - Keep Compose Traefik as HTTP/HTTPS, certificate, and watched dynamic-route owner.
- Remove Compose
webui-frontendfromcompose.yaml. - Reject
BOREALIS_WEBUI_TRAFFIC_OWNER=docker-composeinEngine.sh. - Remove stale
borealis-engine-webui-frontendcontainer during deploy.
- Add production
- Validation:
- Public network mode serves WebUI through K3s route owner.
- Local network mode live validation is waived for this PR and remains tracked by Technical Debt #374 because active public-mode agents may reject the local CA trust plane during live testing.
- Static assets and SPA route handling work in public network mode.
- Guacamole dynamic Traefik route still works after WebUI cutover.
- Repeated deploys do not restart unchanged WebUI pod.
- Compose
webui-frontendcontainer is removed after a public-mode redeploy.
Stage 7: API Backend Cutover¶
- Move
api-backendinto K3s as one replica.- Add non-authoritative K3s
api-backendbridge Deployment. - Bind initial bridge API to host loopback port
5001while Compose API owned127.0.0.1:5000. - Mirror generated runtime env into
borealis-api-backend-runtime-env. - Disable API-owned background loops in the bridge pod with
BOREALIS_API_BACKGROUND_LOOPS=0before traffic cutover. - Use
Recreaterollout strategy for the host-network bridge so single-node K3s does not deadlock on fixed port5001. - Add one-shot K3s
api-backendshadow DB validator Job that targets imported K3s PostgreSQL data without moving public API traffic. - Make K3s
api-backendauthoritative for API, Socket.IO, and internal caller traffic on port5001, initially through host loopback. - Enable API-owned background loops in the K3s pod after Compose API retirement.
- Remove Compose
api-backendfromcompose.yamland retire staleborealis-engine-api-backendcontainers during deploy. - Route Traefik's
/apiand/socket.ioupstream to the configured K3s API target. - Recycle existing K3s site-worker pods once when
BOREALIS_INTERNAL_API_BASE_URLchanges from Compose API5000to K3s API5001. - Add a K3s
api-backendClusterIP Service and render non-API internal callers plus Traefik toapi-backend.borealis.svc.cluster.local:5001. - Move the authoritative K3s
api-backendpod off host networking; it listens on pod networking behind the ClusterIP Service while in-pod health/self-calls use pod loopback. - Reconcile K3s Traefik immediately after API rollout so public API and Socket.IO routes stop depending on host loopback.
- Preserve Aegis bootstrap/unlock behavior.
- Preserve internal API token behavior.
- Preserve logs/secrets/cache path contracts through fixed hostPath bridge mounts.
- Re-evaluate fixed hostPath bridge mounts for Longhorn PVC/Secret/ConfigMap mapping where durable pod-local storage is required.
- Remove broad
api-backendservice-root hostPath mounts from API, shadow DB validator, and PostgreSQL schema Job manifests; keep only reviewed subpath mounts or scratch roots. - Live redeploy validates
api-backendDeployment uses anemptyDirservice root with only API cache/config/logs/secrets hostPath subpaths. - Final API storage authority split remains tracked by Technical Debt #379 because v1 intentionally keeps API cache/config/logs/secrets host-visible for Backup/Restore, logs, Aegis/Engine secret files, and scheduler/site-worker read contracts.
- Remove broad
- Add non-authoritative K3s
- Validation:
- Bridge pod rollout passes.
- Pre-ClusterIP bridge
curl -fsS http://127.0.0.1:5001/healthpassed. -
Engine.sh --network-mode public|local --service api-backend shadow-db-validate prodvalidates Go API bootstrap state against K3s PostgreSQL shadow data. -
/healthpasses after traffic-owner deploy. - Public WebUI route returns HTTP 200 after K3s API cutover and Compose API retirement.
- Repeated public-mode deploy keeps K3s API, scheduler, and WebUI pods ready while Compose
api-backend,job-scheduler, andwebui-frontendcontainers remain absent. - Login, Aegis unlock, enrollment, heartbeat, and API routes pass.
- Remote shell, file management, process management, service management, registry, and remote desktop pass after API traffic-owner cutover.
- Backup export passes after API traffic-owner cutover.
- All agents appear connected to their site workers after API traffic-owner cutover.
- Realtime SSE works with one replica.
- Compose API removal plan is documented: K3s API owns traffic before stale Compose API container removal; Compose PostgreSQL remains DB owner until Stage 9.
- Live redeploy validates
api-backendService DNS routing, non-host-network pod state, Service health, and public API/Socket.IO routing after ClusterIP cutover.
Stage 8: Scheduler Cutover¶
- Move
job-schedulerinto K3s as one replica.- Add fixed K3s
job-schedulerDeployment withRecreaterollout strategy. - Remove Compose
job-schedulerservice and retire staleborealis-engine-job-schedulercontainers during deploy. - Use
borealis-operatorfor K3s workload/site-worker lifecycle where the scheduler can complete the queued action safely. - Preserve Postgres work leases and queue behavior through the existing scheduler manager and work-item tables.
- Keep scheduler Kubernetes-blind: no ServiceAccount token, no kubeconfig, no Docker socket.
- Keep temporary host-loopback access to K3s API and Compose PostgreSQL until PostgreSQL/API Service cutover cleanup.
- Move K3s
job-scheduleroff host networking and target internal API calls throughapi-backend.borealis.svc.cluster.local:5001.
- Add fixed K3s
- Validation:
- Scheduled job tick creates expected runs after live redeploy.
- Service actions route through operator for K3s-owned restart paths in focused Go tests.
- No duplicate scheduler loops in deployment model: Compose service removed, stale container retired before K3s rollout, and K3s Deployment uses
Recreate. - Live redeploy confirms exactly one K3s scheduler pod and no Compose
borealis-engine-job-schedulercontainer. - Scoped
job-schedulerrebuild applies the retired-orchestrator cleanup and fresh logs stay quiet for stalesite-worker-orchestratorsocket probes. - Live redeploy confirms
job-scheduleris not host-networked andBOREALIS_INTERNAL_API_BASE_URLuses the K3s API Service DNS.
Stage 9: PostgreSQL Cutover¶
- Move
postgres-dbinto K3s StatefulSet with one Longhorn-backed PVC.- Add
Engine.sh deployLonghorn reconcile phase before the first PVC-backed workload cutover. - Install/reconcile Longhorn from pinned manifest version
v1.12.0if it is enabled and not already present. - Reconcile Longhorn host iSCSI prerequisites without deleting existing Longhorn resources or PVCs.
- Keep K3s
local-pathdefault unchanged for now; future Borealis PVC manifests should requestBOREALIS_K3S_PVC_STORAGE_CLASSexplicitly. - Override Longhorn manifest default-StorageClass annotation so Longhorn stays explicit-use only.
- Add Borealis-owned single-node Longhorn StorageClass for fresh PVCs while preserving existing PostgreSQL PVC StorageClass on redeploy.
- Add non-authoritative K3s
postgres-dbshadow StatefulSet with one Longhorn-backed PVC. - Set PostgreSQL StatefulSet manifests to use the Longhorn StorageClass.
- Keep K3s PostgreSQL ClusterIP-only.
- Preserve profile-managed PostgreSQL startup settings in the K3s StatefulSet manifest.
- Use
PGDATA=/var/lib/postgresql/data/pgdataso Longhorn filesystem metadata does not blockinitdb. - Add retry-safe PostgreSQL PVC ownership init path with narrow init-container ownership capabilities.
- Add controlled final cutover path.
- Quiesce K3s API backend, K3s job-scheduler, and K3s site-worker writers before the final Compose snapshot.
- Import the final Compose PostgreSQL logical snapshot into K3s PostgreSQL before moving runtime DB URLs.
- Re-render runtime env so API backend, job-scheduler, and site-worker pods use
postgres-db.borealis.svc. - Run a K3s schema initializer Job after PostgreSQL becomes traffic owner.
- Retire stale Compose
borealis-engine-postgres-dbcontainers and removepostgres-dbfrom Compose.
- Preserve backup/export semantics.
- Keep Traefik ACME storage strict and Backup/Restore-readable while allowing Traefik restart/renewal.
- Add
postgres-db shadow-importvalidation path that restores a logical Compose DB snapshot into the K3s shadow DB without changing live traffic ownership. - Operator completed encrypted WebUI configuration export before cutover.
- No HA/replication in v1 StatefulSet.
- No automatic PVC deletion.
- Add
- Validation:
- Static
Engine.shsyntax passes after Longhorn reconcile addition. - First live deploy reconciles Longhorn once.
- Second live deploy after explicit-only StorageClass guard does not churn Longhorn pods.
- Longhorn system pods are ready and StorageClass is available.
- Live validation found upstream Longhorn default annotation drift; Engine reconcile now clears it after manifest apply.
- Longhorn volume attaches, mounts, detaches, and reattaches cleanly on the single-node Engine host.
- Fresh Longhorn StorageClass policy creates
borealis-longhornwith one replica without mutating existingpostgres-data-postgres-db-0. - Live deploy creates K3s
postgres-db-0andpostgres-data-postgres-db-0without changing Compose DB traffic owner. - K3s PostgreSQL readiness passes on the shadow StatefulSet.
- Logical backup/restore tested through
pg_dumpfrom Compose PostgreSQL andpg_restoreinto K3s shadow PostgreSQL. - Existing Engine data imports cleanly into K3s shadow PostgreSQL.
- Final cutover imports existing Engine data into authoritative K3s PostgreSQL.
- API backend, job-scheduler, and site-worker pods resolve
BOREALIS_DATABASE_URLtopostgres-db.borealis.svc. - K3s PostgreSQL PVC is bound to Longhorn.
- Second live redeploy keeps PostgreSQL on K3s and does not rerun Compose snapshot/import.
- Encrypted WebUI Backup/Restore analyze path is documented and covered by Go tests without mutating K3s PostgreSQL.
- Encrypted WebUI Backup/Restore Import live validation passed on a clean/disposable K3s target after rollback, redeploy, restore, Agent reconnect, site-worker reconnect, Remote Desktop smoke, and basic 7-Zip assembly execution on
LAB-OPERATOR-01. - DB profile settings preserved or documented.
- Static
Stage 10: WireGuard Cutover¶
- Move
wireguard-tunnelinto K3s pinned host-network pod.- Preserve
/dev/net/tun,NET_ADMIN,NET_RAW, control socket boundary. - Preserve firewall chains and peer
/32policy. - Route queued WireGuard reconcile/recover actions from K3s
job-schedulerthrough the mounted control socket instead of the Docker helper bridge.
- Preserve
- Validation:
- Existing agents reconnect.
- SQL worker audit shows K3s site-worker socket counts match online device counts after redeploy.
- WireGuard status shows fresh peer handshakes from the K3s host-network pod.
- Remote shell/desktop over tunnel works.
- API and K3s site-worker pods resolve and connect to
remote-desktop-guacd.borealis.svc.cluster.local:4822. - Operator browser smoke after guacd traffic-owner env propagation.
- WebUI login, Server Info, Device Inventory, Remote Desktop, Remote Shell, File Management, Process Management, and Service Management pass after WireGuard and guacd cutover.
- API and K3s site-worker pods resolve and connect to
- Focused scheduler tests confirm K3s WireGuard reconcile sends only predefined
reconcilecommand to the control socket and does not require the helper bridge. - Live Server Info
Recover Listeneraction validates queued scheduler-to-control-socket path after redeploy. - Quarantine/revocation live peer-removal validation is waived for this PR and tracked by Technical Debt #381; focused tests cover containment access denial and runtime cleanup calls.
- Existing agents reconnect.
Stage 11: Compose Retirement¶
- Remove Compose ownership only after all K3s-owned services are stable.
- Compose
webui-frontendretired. - Compose
api-backendretired. - Compose
job-schedulerretired. - Compose
postgres-dbretired. - Compose
wireguard-tunnelretired. - Compose
remote-desktop-guacdretired. - Move
traefik-edgeinto K3s as the public edge owner. - Preserve HTTP/HTTPS host-network ports, ACME/local CA state, and watched dynamic route files.
- Route
traefik-edge reloadthrough K3sjob-schedulerandborealis-operator, not a Docker helper. - Retire
site-worker-orchestratoras a long-running Compose service. - Leave
compose.yamlas an empty retired manifest. - Move Server Overview service rows for K3s-owned workloads off retired Compose container lookups and onto
borealis-operatorworkload status. - Expose WebUI restart as an operator-routed K3s action so simple WebUI pod restarts no longer need the helper bridge.
- Keep WebUI rebuilds CLI-only through
Engine.sh --service webui-frontend rebuild prod|dev; reject queued runtime WebUI rebuild helper actions. - Route K3s API, WebUI, PostgreSQL, and guacd restarts through
borealis-operatorwithout Docker-helper fallback. - Route K3s PostgreSQL restart service actions through
borealis-operatorinstead of the Docker helper bridge. - Route K3s WireGuard reconcile service actions through the scheduler-mounted control socket instead of the Docker helper bridge.
- Remove the
site-worker-orchestratorservice-action helper path after Traefik edge cutover. - Remove unused packaged
site-worker-orchestratorhealthcheck wrapper from the K3sjob-schedulerimage. - Remove remaining Compose bridge service rows after Traefik and orchestrator retirement.
- Stop Docker metadata reads for K3s site-worker rows once operator metrics are present.
- Retire Compose
docker-proxyafter K3s worker metrics and scheduler service snapshots became authoritative. - Remove Docker CLI, Docker Compose plugin, and retired
compose.yamlfrom the K3sjob-schedulerimage. - Make empty,
auto, and unknown site-worker lifecycle modes resolve to K3s instead of falling back to retired Docker helpers. - Reject retired
site-worker-orchestratorroles from the K3sjob-schedulerentrypoint. - Remove
BOREALIS_ALLOW_LEGACY_DOCKER_SITE_WORKERS; explicit Docker site-worker lifecycle modes now resolve to K3s. - Remove retired
site-worker-orchestratorsocket env generation and K3sjob-schedulerhostPath mount. - Reject retired
site-worker-orchestratorroles from theapi-backendcontainer entrypoint. - Remove retired
site-worker-orchestratorresource-limit env generation from Engine deploy env. - Remove retired Go
site_worker_orchestratorsource/tests and scheduler Docker lifecycle fallback paths. - Remove retired
site-worker-orchestratorimage alias compatibility logic from deploy manifest generation. - Remove unused Docker socket and service-action-helper env generation after Docker helper retirement.
- Remove unreachable Docker Compose service reconciliation branches from full Engine deploy.
- Remove retired Compose service-action rebuild fallback, old Compose schema initializer, and unused
docker compose uphelper. - Narrow remaining legacy Docker status code to PostgreSQL snapshot support and shared retired-container cleanup.
- Stop Engine deploy from installing/requiring Docker Compose plugin and retarget legacy dump import helper to K3s PostgreSQL.
- Clean stale post-retirement docs that still described Compose as deployment/runtime tooling.
- Rename operator RBAC from stale
readonlynaming to lifecycle-scoped controller naming and add old RBAC cleanup.
- Compose
- Keep migration and recovery docs until stable release.
- K3s Backup/Restore recovery notes document active K3s PostgreSQL targeting, safe Analyze validation, destructive Import behavior, and post-import redeploy expectations.
- K3s Backup/Restore docs include a clean/disposable restore-target validation runbook and post-import smoke checks.
- Update
Docs/Reference/Core Runtimes/Stack_Breakdown.md,engine-runtime.md,security-whitepaper.md, SBOM if dependencies changed. - Validation:
- Fresh install path works.
- Redeploy path works.
- Backup/restore path is validated for this PR with export/analyze/test coverage plus destructive clean-target Import live proof on a disposable DEV Engine.
- Backup export works against authoritative K3s API/PostgreSQL.
- Backup analyze path is documented and covered by focused Go tests without data mutation.
- Backup Import live proof on a clean K3s restore target passed after DEV rollback, redeploy, restore, restored Agent reconnect, site-worker reconnect, Remote Desktop, remote tooling, and assembly execution validation.
- Operator WebUI smoke passes after Compose retirement: login, Server Info, Device Inventory, Remote Desktop, Remote Shell, Files, Process Management, Service Management, Registry, and Backup Export.
- Narrow Engine tests pass.
- Compose policy confirms retired services stay out of
compose.yaml. - Live Docker check confirms retired Compose containers are absent.
-
docker compose config --servicesreturns no service names. - Server Overview unit tests confirm retired workloads render as K3s rows and legacy Compose snapshots are ignored.
- Server action tests confirm WebUI restart uses the operator path and WebUI rebuild is rejected from queued runtime helper paths.
- Scheduler tests confirm Traefik reload uses the operator path.
- Orchestrator tests confirm the helper path is retired.
-
Engine.sh --network-mode public --service webui-frontend restart prodrolls the K3s WebUI Deployment without restoring the retired Compose WebUI container. - Live deploy table reports K3s Traefik edge ready and Docker Compose retired.
- Live K3s rollout confirms
deployment/traefik-edgehealthy. - Live redeploy applies
borealis-operator-controllerRBAC and removes legacyborealis-operator-readonlyRole/RoleBinding. - Live Docker check confirms stale
borealis-engine-traefik-edgeandborealis-engine-site-worker-orchestratorcontainers are absent. - Docker cache corruption during required image restore is recoverable through builder-cache prune plus no-cache rebuild.
Open Risks¶
-
borealis-operatorRBAC must stay narrower than Docker socket power.- Live
kubectl auth can-iaudit confirms operator ServiceAccount is namespace-scoped, cannot mutate Secrets, cannot read Nodes, cannot readkube-systempods, and does not have wildcard Borealis namespace access. - Operator RBAC still grants namespace-wide pod and Service
create/deletefor dynamic site workers; accepted for this PR as narrower than Docker socket power, with narrower lifecycle primitive tracked by Technical Debt #380.
- Live
- Host networking must be minimized except WireGuard/edge needs.
- Live host-network audit identifies required v1 host-network pods:
traefik-edgefor TCP 80/443/health andwireguard-tunnelfor UDP 30000 plus/dev/net/tun. - Live host-network audit confirms
borealis-operator,postgres-db,remote-desktop-guacd,webui-frontend, andsite-worker-*are not host-networked. - Code and live redeploy move K3s
site-worker-*pods off host networking and onto per-worker ClusterIP Services. - Code moves K3s
api-backendandjob-scheduleroff host networking and routes API consumers through theapi-backendClusterIP Service DNS. - Live redeploy validates
api-backendandjob-schedulerare not host-networked,api-backendService endpoints are ready, Traefik routes API traffic through Service DNS, and scheduler/site-worker runtime env uses the Service URL.
- Live host-network audit identifies required v1 host-network pods:
- Runtime service actions must not become raw Kubernetes mutation API.
- Code audit confirms operator command surface is fixed to named verbs in
borealisOperatorAllowedVerbsandexecuteCommand; unsupported verbs returnunsupported Borealis operator verb. - Code audit confirms scheduler service actions route supported K3s restart/reload actions through
RestartKnownWorkloadand WireGuard reconcile through the fixed control-socket command, not raw YAML or kubectl. - Retired orchestrator source and Docker lifecycle fallback are removed; remaining references are fail-closed process-role guards, tests, retired-container cleanup, or PostgreSQL shadow-import migration support.
- Code audit confirms operator command surface is fixed to named verbs in
- K3s secrets must not replace Aegis security model.
- Security whitepaper states K3s Secret encryption at rest is enabled for Kubernetes objects while Aegis remains the Borealis model for protected operator, credential, token, and signing material.
- Backup/Restore code keeps
engine.aegis_cipher_stateinside the encrypted payload, derives backup keys from the Aegis Cipher, and clears the in-memory Aegis key after restore. - Continue treating generated K3s runtime-env Secrets as deployment plumbing, not durable protected-secret authority.
- Code audit confirms generated K3s runtime Secrets are rendered from deploy env and consumed through
envFrom.secretReffor pods/jobs; durable API secrets remain mounted Engine paths and Aegis/database-managed payloads.
- Code audit confirms generated K3s runtime Secrets are rendered from deploy env and consumed through
- Future PostgreSQL rollouts must avoid unnecessary site-worker churn and agent disconnects.
- Code audit confirms normal deploy only recycles K3s site-worker pods for API base URL changes, site-worker runtime Secret hash changes, or timezone mismatch; repeated unchanged deploys should not recycle workers.
- Code audit confirms PostgreSQL StatefulSet storage/profile hash changes do not directly recycle site workers unless shared runtime env values that workers consume actually change.
- Agents that already hold stale connected Socket.IO state may require Agent release rollout or manual service restart before the new reconnect logic is active.
- Current Agent code waits for site-worker registration acknowledgement, reconnects after 60 seconds of read-idle socket inactivity, and lets the watchdog restart stale socket states after 90 seconds. WebUI labels API-online/socket-missing devices as
Reconnectingwhile the management socket reattaches.
- Current Agent code waits for site-worker registration acknowledgement, reconnects after 60 seconds of read-idle socket inactivity, and lets the watchdog restart stale socket states after 90 seconds. WebUI labels API-online/socket-missing devices as
- Longhorn adds CSI/storage-manager dependencies that must be reconciled idempotently before PVC workloads depend on it.
- Live audit confirms Longhorn StorageClass exists as explicit-use only,
postgres-data-postgres-db-0isBoundto StorageClasslonghorn, and all Longhorn pods reportRunning. - Live audit found single-node Longhorn volume robustness is
degradedbecause upstream StorageClass provisionednumberOfReplicas: 3; tracked by Technical Debt #378. - Future fresh installs should use a Borealis-owned single-node Longhorn StorageClass or equivalent policy so v1 non-HA volumes do not start degraded.
- Existing 3-replica Longhorn PVCs are preserved during normal deploy and require an explicit manual migration if replica policy should change later.
- Live audit confirms Longhorn StorageClass exists as explicit-use only,
- Stateful data migration must have reversible checkpoints and no automatic Longhorn volume/PVC deletion during normal deploy.
- Code audit found no deploy-time PVC delete path for PostgreSQL or Longhorn volumes.
- Server-side dry-run confirms existing
longhornStorageClass replica parameters are immutable, so existing PVC replica-count repair needs a deliberate migration/rebuild workflow rather than normal deploy reconciliation.
- Full Engine WebUI unit lane needs K3s-compatible runtime test-cache staging; accepted as deferred for this PR and tracked by Technical Debt #377.
Detailed Codex Breakdown
Coordination surfaces¶
- Branch:
migrate-borealis-docker-compose-into-k3s-kubernetes-cluster. - Pull request:
Migrate Borealis Docker Compose into k3s Kubernetes Cluster. - PR body is the active milestone checklist. Update it after each completed stage or meaningful milestone.
- PR comments are the handoff log for staged work sessions. Add
Completed,Validation,Current risks, andNext stepafter each meaningful work session.
Related documentation¶
- Engine Deployment for current operator install and redeploy flow.
- Stack Breakdown for current K3s service boundaries and retired Compose state.
- Engine Runtime for Engine runtime paths and generated state.
- Security Whitepaper for Aegis, runtime trust, token, and network boundaries.
- Backup and Restore for stateful data checkpoints.
- Database Reference for PostgreSQL behavior and connection lifecycle.
Source map¶
- Engine deployment entrypoint:
Engine.sh. - Engine container source and Compose policy:
Data/Engine/Containers/. - Compose policy check:
Data/Engine/Containers/check-compose-policy.py. - Web UI source:
Data/Engine/Containers/webui-frontend/data/web-interface/src/. - API backend source:
Data/Engine/Containers/api-backend/data/. - Job scheduler source:
Data/Engine/Containers/job-scheduler/data/. - Current site worker orchestration code should be treated as bridge input until
borealis-operatorreplaces it.
Validation path¶
- Roadmap-only changes require documentation review, path review, and
git status --short --branch. - Stage 1 deploy script changes start with
bash -n Engine.sh. - Compose-sensitive changes include
python3 Data/Engine/Containers/check-compose-policy.py. - Runtime cutover stages require deploy twice back to back, cluster health checks, old-runtime retirement validation, and narrow Engine tests for touched backend areas.
- Stage 3 validation includes a no-op live rollout against the current
borealis-operatorimage, rejection checks for unknown services and unallowlisted mutable images, RBACcan-ichecks for allowed named workload patching and denied Secret/Node access, and unit coverage for failed rollout rollback. - Longhorn validation starts before the first PVC-backed workload cutover. Confirm StorageClass presence, Longhorn manager/CSI pod readiness, volume attachment, pod restart persistence, and no PVC/volume deletion during repeated Engine deploys.
Security constraints¶
borealis-operatoris the planned runtime K3s writer.Engine.shremains the deployment-time writer for cluster bootstrap and fixed operator manifests.- Runtime services call operator verbs, not Kubernetes APIs.
- Operator verbs must map to known Borealis workloads and fixed pod/service templates.
- No raw YAML apply, arbitrary pod spec, arbitrary image/env/volume/service account, or broad host access should be exposed through runtime APIs.
- K3s Secrets and RBAC help isolate runtime state but do not replace Aegis for protected Borealis secrets.
- Longhorn provides K3s persistent storage for workloads that need PVCs; it does not replace Borealis backup/restore, Aegis, or explicit data-migration checkpoints.