Canopy Forest

The execution layer behind Canopy Host.

Canopy Host decides what should run. Forest is what makes that actually happen, and keeps checking that it still holds - which workload generation you asked for, which one a Grove actually reports, and whether the two agree. Your application belongs to Canopy, not to one server.

You don't configure Forest directly. Canopy Host is the only interface you need - this page is for the deploy log, not the dashboard.

workload status
desiredgeneration 42
observedgeneration 42
phaseRunning · converged
healthpassing (not just an HTTP 200)
GrovesKubernetes-based execution pools, one physical machine or several
Desired vs. observedEvery workload tracks both, continuously
Build here, run thereBuild and runtime capacity can live on separate machines
Self-hostedCanopy Host's own production API runs through it

The shift

Applications are not servers.

The old model tied an environment to a specific machine: pick a server, put a container on it, and that pairing is what you operate. Forest replaces the machine with a claim: here is the workload you want running, and here is how much capacity it needs. Forest decides where that capacity actually comes from - and can change its mind later without you redefining the app.

Before

Environment
Server
Container

Now, with Forest

Environment
Desired workload
Execution capacity

A physical machine becomes an infrastructure detail Forest manages, not something your application model has to know about.

Build here. Run there.

A build can run on one Grove. The artifact it produces can run on another.

Build capacity and runtime capacity don't have to be the same machine. A build Grove checks out your source, runs the build, and produces a container image identified by its content digest - not a tag someone could quietly move underneath you. That exact image is promoted into Canopy's own registry, addressable from any Grove, and a runtime Grove elsewhere pulls those exact bytes. Nothing gets rebuilt in between, and nothing changes what actually ends up running.

Source A commit, pushed to a connected repo.
Build Grove A build-class Grove checks out, builds, and produces a container image.
Immutable artifact The image is content-addressed by digest - not a tag someone could quietly move.
Canopy registry The artifact is promoted into the platform registry, addressable from any Grove.
Runtime Grove A runtime-class Grove, possibly on a different physical machine, pulls the exact digest.
Running workload The same bytes that were built are the bytes that run. No rebuild in between.

A build Grove that can't produce a properly addressable artifact is never selected for a build in the first place - this is a fail-closed guarantee, not a best effort.

Desired state. Observed reality.

A 200 response isn't proof a deployment landed. Forest checks the thing itself.

Every deploy has a generation number. Canopy records which generation it asked a Grove to run. The Grove reports back which generation it actually observes, and whether that workload has converged and is healthy - not just reachable. A request that happens to succeed against whatever was running a moment ago is not the same thing as the new version actually being live, so Forest doesn't treat it as one.

workload.status
{
  "generation": { "desired": 42, "observed": 42 },
  "phase": "Running",
  "converged": true,
  "healthy": true
}

Isolation is part of execution

What actually separates one workload from another.

One namespace per project

Every project on a Grove gets its own Kubernetes namespace. Workloads from different projects never share one.

Default-deny network policy

Each namespace starts with same-namespace traffic, DNS, and the public internet allowed - nothing else. Reaching anything on the box’s own private network is an explicit, reviewed exception, not the default.

Non-root, capability-dropped containers

Every container runs as a non-root user, with every Linux capability dropped and privilege escalation disabled, under the kernel’s default seccomp profile.

Build and runtime are separate Groves

A build never runs on the same execution capacity that serves your traffic - build-class and runtime-class Groves are different machines.

Credentials as Kubernetes Secrets, scoped per workload

A workload only mounts the secrets it declares, not a shared pool.

Start small. Grow when needed.

A one-node Grove is a real topology, not a compromise.

A Grove is a Kubernetes cluster running on physical machines Canopy operates. It doesn't need to look like a miniature enterprise cluster to be a valid one - Canopy runs Groves both ways today, and the difference is a capacity decision, not a rewrite.

Today, in production

prod-runtime-1

classruntime
control plane1 node
workers1 node
topologysingle-node control plane, on purpose

A larger topology Forest already supports

A multi-node Grove

classruntime
control plane3 nodes
workers2+ nodes
topologycontrol-plane quorum, more scheduling room

Recovery is part of the design

Infrastructure should be reconstructible, not remembered.

A Grove is declared as code - the nodes, the network, the platform layer above it - and a workload's desired state is declared too. That combination means recovering from losing a Grove is about restoring execution capacity and letting declared state converge onto it again, rather than reconstructing a sequence of commands someone ran by hand on a box. We've rebuilt a Grove from scratch after losing it and had workloads reconverge. We have not yet run a full, deliberate rehearsal of losing the production Grove specifically - so this page says what has actually been tested, not what the design implies should work.

What this means today

  • Every Grove is defined in code, reviewable and reproducible - not a hand-configured box.
  • A workload's desired generation is recorded independently of any one Grove's state.
  • A non-production Grove has been rebuilt from a total loss and reconverged, for real.
  • A full destructive rehearsal of the production Grove hasn't happened yet - not claiming it as proven until it has.

Built from boring primitives

Forest doesn't replace Kubernetes. It gives Canopy an execution contract on top of it.

Every piece below is a real, widely-used infrastructure project, not something Canopy wrote from scratch. What Forest adds is the layer above them: the Workload model, the desired/observed reconciliation loop, and the parts of the previous sections on this page - not a replacement for any one of these.

KubernetesSchedules workloads onto nodes, restarts what crashes, and gives Forest a real API to reconcile against instead of a fleet of hand-managed hosts.
Talos LinuxThe operating system on every Grove node - no SSH, no package manager, configured entirely through a typed API. Less for anything to misconfigure by hand.
CiliumPod networking and the default-deny network policy every workload namespace gets - same-namespace traffic, DNS, and the public internet are allowed by default; everything else is an explicit exception.
Gateway API + Envoy GatewayRoutes traffic into a workload and terminates TLS, or passes it straight through when a workload wants to terminate its own.
cert-managerIssues and renews the certificates behind every domain Canopy Host provisions on a Grove.
CloudNativePGRuns the Postgres databases Forest manages for deployed workloads, as a real Kubernetes operator rather than a hand-run instance.
OpenTofuDeclares each Grove itself as code - the nodes, the network, the platform layer above - so a Grove can be planned, reviewed, and rebuilt, not just remembered.
libvirt / KVMThe virtualization layer underneath a Grove’s nodes, running on physical machines Canopy operates - not a public cloud API.

Canopy runs on Forest

This isn't a demo environment. It's where Canopy Host's own production API runs.

Canopy Host's own production API is deployed as a workload on Forest, on the same desired/observed model described above - not a separate, simplified internal setup. Most Canopy Host environments still run on the substrate Forest is replacing while that migration continues; Canopy's own production API is one of the environments already moved, and the corporate website you're reading right now is deployed through that same pipeline.

What this proves, and what it doesn't

  • Forest runs a real, continuously-deployed production workload today, not just test traffic.
  • This is internal usage - Canopy running its own infrastructure on Forest - not a customer count or a scale claim.
  • It's the same exercise a customer's own deploy goes through: desired state, an observed generation, a health check that isn't just a 200.

Good to know

What Forest doesn't claim.

No automatic multi-region failover today - one region, one physical machine per Grove, until a specific Grove says otherwise.

No autoscaling. Capacity changes because someone (or a policy Canopy writes deliberately later) changes it, not because Forest decided to on its own.

A workload with a persistent volume redeploys with a short gap while the old pod releases the volume before the new one attaches it - a deliberate tradeoff, not an oversight. A workload with no volume does not have this gap.

A single-node Grove is a real, supported topology, not a placeholder for a "real" multi-node one. Growing to multiple control-plane nodes is a capacity decision, not a rewrite - Canopy already runs one Grove each way.

Forest is not sold or operated separately from Canopy Host. There is no independent "Forest" plan or sign-up - it is how Canopy Host runs what you deploy.

Get started

You don't configure Forest. You deploy on Canopy Host, and Forest takes it from there.

Connect a repo, push, and Forest places and reconciles the rest.