openvibers/openvibe.contracts Commits

ADR-032: containers for platform services (roadmap WS-N task 12). Platform services stay systemd units (dedicated users, hardening, slices, socket activation for zero-downtime restarts); ovhost keeps one deploy adapter with a reserved inventory runtime field; untrusted tenant code is the Stage C isolation ADR's. Revisit on a second host, a conflicting system dependency, Stage C, or a unit systemd cannot harden enough.

OP OpenVibers committed 26 Sept 2026 · 2a7f385aaa · 2 files, +65 −0


Changes

ddiff --git a/docs/adr/ADR-032-containers.md b/docs/adr/ADR-032-containers.md
nnew file mode 100644
iindex 00000000..d13dabc0
---- /dev/null
++++ b/docs/adr/ADR-032-containers.md
@@@ -0,0 +1,64 @@
++# ADR-032: Containers for platform services
++
++**Status:** Accepted 2026-09-26 (roadmap WS-N task 12). Revisit on any trigger below.
++
++## Context and current evidence
++
++- Every OpenVibe service runs as a systemd unit on one host, each as its own Unix user, deployed by
++ `ovhost deploy <service>` (OpenVibe.Host) or a per-repository `deploy.sh`. That is 25 services on 2026-09-26.
++- Deploys are checkout moves with dependency install, readiness polling and automatic rollback. Live
++ uses a release layout (`releases/<time>-<sha>`, `current` symlink) and **systemd socket activation**,
++ so a restart never refuses a connection (`127.0.0.1:3000` held by pid 1).
++- Units already carry hardening: `ProtectSystem`, `NoNewPrivileges`, `PrivateTmp` and `ReadWritePaths`
++ per data directory. `ovhost validate` checks each unit against the service's `lifecycle` declaration
++ (Contracts 0.55.0).
++- Backups, restore drills, the browser check and the alert relay are systemd timers that `ovhost` drives.
++- Stage C of Host (sandboxed **user** code) will need isolation stronger than a Unix user. It is not
++ started, and it has its own isolation ADR.
++- The host has 15 GB of memory and 11 GB available at peak; no service has asked for a system dependency
++ that conflicts with another's.
++
++## Decision
++
++- **Platform services stay systemd units.** There is no container runtime for first-party services. The
++ isolation they need comes from what systemd gives per unit: a dedicated user, a read-only system,
++ private /tmp, capability and syscall filters where a unit can take them, and slices for memory and
++ CPU limits.
++- **ovhost keeps one deploy adapter: systemd.** The inventory's service entry keeps `unit` and `socket`,
++ and a future `runtime` field is reserved: an absent value means `systemd`. A container adapter, if one
++ is ever added, is a second strategy behind the same deploy, readiness, rollback and lifecycle checks,
++ never a separate path.
++- **Tenant and user code (Stage C) is out of scope here.** Running untrusted code needs a sandbox
++ (gVisor, Firecracker or equivalent), with budgets, egress policy and kill/revoke. That is the Stage C
++ isolation ADR, and it may well use containers or micro-VMs.
++
++## Alternatives considered
++
++- **Docker (or Podman) Compose for every service.** Rejected for now:
++ - it rewrites deploy, rollback, backups and readiness for no measured gain;
++ - socket activation, which gives zero-downtime restarts, would need re-engineering;
++ - it adds a daemon (Docker) or a new unit model (Podman quadlets) to operate;
++ - image builds and registries add a supply chain and storage we do not need on one host.
++- **Kubernetes.** Rejected: one host, no scheduling problem to solve.
++- **Containers for some services only.** Rejected until a service needs it: two runtimes double the
++ operator's surface.
++
++## Revisit when any of these is measured
++
++- A second host joins, and services move between hosts: portability then pays for images.
++- A service needs a system dependency or runtime version that conflicts with another's on the host.
++- Stage C lands. Its sandbox runtime may be reused for platform workers that process untrusted media,
++ such as ffmpeg on uploads.
++- A unit cannot be hardened enough under systemd, for example it needs its own network namespace with
++ an egress policy that systemd's `IPAddressAllow`/`RestrictAddressFamilies` cannot express.
++
++## Consequences
++
++- Deploy tooling stays as it is (`ovhost deploy`, Live's release layout, socket activation).
++- New services ship a unit, a `lifecycle` block and `ReadWritePaths`, never a Dockerfile.
++- WS-N task 11 (`ovhost` absorbs the per-repository `deploy.sh` scripts) proceeds as systemd strategies.
++
++## Acceptance
++
++- `ovhost validate` passes for every service with `runtime` absent, which means systemd.
++- No first-party repository carries a Dockerfile or compose file that production uses.
ddiff --git a/docs/adr/README.md b/docs/adr/README.md
iindex 1da3aa0c..01467c53 100644
---- a/docs/adr/README.md
++++ b/docs/adr/README.md
@@@ -35,3 +35,4 @@ Platform-wide decisions from the OpenVibe development roadmap (section 10). Each
| [ADR-029](ADR-029-account-merge.md) | Merging two accounts: survivor keeps the subject, the other becomes an alias, services follow `network.subject.merged` | Accepted 2026-09-26; implementation pending |
| [ADR-030](ADR-030-follow-graph.md) | Follow graph: Network owns follows by subject, Live keeps a projection; expand/migrate/contract with reconciliation | Accepted 2026-09-26; migration pending |
| [ADR-031](ADR-031-s3-compatible-surface.md) | S3-compatible subset for Media: bucket = project namespace, object CRUD + ListObjectsV2 + presign, per-project keys | Accepted 2026-09-26; implementation pending |
++| [ADR-032](ADR-032-containers.md) | Containers: platform services stay systemd units (one adapter in ovhost, a reserved `runtime` field); tenant code isolation belongs to Stage C | Accepted 2026-09-26 |