Run a flow from the CLI on your laptop, ship the editor as a container, spin up a local Kubernetes cluster, or stand up the full managed stack on GCP with one Terraform apply. This guide covers each path and how an integration's endpoints become public or stay private.
From smallest to largest. The runtime is a single Go binary; the editor and orchestrator are the platform around it.
Build the binary once, then run any config with it — no Go toolchain at run time, no cluster, no editor. Great for developing a flow.
task build # compiles ./bin/octo
./bin/octo --config samples/http-orders.yaml --watch
The editor image bundles the Next.js app and the octo binary, so the RUN feature works in-container.
# builds from the repo root (runtime + editor)
docker build -f editor/Dockerfile -t octo-editor .
docker run -p 3000:3000 octo-editor
A local Kubernetes cluster with the full stack (editor + orchestrator + Postgres) via DevSpace, with optional hot reload.
task cluster:deploy # bring up k3d + apply manifests
task cluster:dev # add hot reload
Terraform stands up a single-node k3s VM, DNS, TLS, and installs the Helm chart. Releases are a tagged Helm upgrade.
task state:bucket PROJECT=<project> # once
task infra:apply # VM, DNS, certs
task deploy TAG=v0.1.4 # Helm release
On a running Octo platform the editor and orchestrator are the control plane (backed by Postgres); deploying an integration turns its YAML into running pods. Four steps:
Author flows in the editor and save. The integration — a name + the definition YAML — is stored by the orchestrator in Postgres and reopens at /i/<id>.
Create cluster secrets (UPPER_SNAKE_CASE) in the editor. Values are written straight into one Kubernetes Secret (octo-secrets) and are never read back — the catalog keeps only names and timestamps.
In the deploy dialog choose replicas, an internal slug, and whether to expose it. Bind each declared env var to a literal value or a secret. HTTP_PORT / HTTP_HOST are managed for you.
The orchestrator renders Kubernetes objects and streams status back live. Scale by changing replicas; undeploy tears everything down.
For a deployment <id> with slug <slug>, the orchestrator applies:
| Object | Name | Why |
|---|---|---|
| ConfigMap | octo-dep-<id> | Holds your integration YAML, mounted read-only at /etc/octo/integrations — the runtime loads every file there. |
| Deployment | octo-dep-<id> | Runs the Octo runtime image; replicas → that many pods. Env is injected as literal values or secretKeyRefs into octo-secrets. |
| Service (ClusterIP) | octo-dep-<id> | Fronts this deployment's pods. |
| Service (ClusterIP) | octo-int-<slug> | A stable in-cluster address other flows call by name. |
| Ingress | octo-dep-<id> | Only when exposed externally: host <subdomain>.<base-domain>, TLS via cert-manager. |
env: entry is bound at deploy time to a literal value or a cluster secret (the secret reference wins). required vars must be bound. HTTP_PORT (which marks the integration as a network service) and HTTP_HOST (0.0.0.0) are supplied by the orchestrator and can't be set by hand. Deploys stream live status over server-sent events, so the editor shows pods going ready in real time.Deployed integrations are private by default — reachable only inside the cluster. Going public is an explicit opt-in per integration.
The orchestrator gives every deployment a ClusterIP Service, plus a stable per-integration Service that load-balances across its deployments:
http://octo-int-<slug>.octo-dev:8080
No load balancer, no public DNS — other in-cluster flows and services can reach it, the internet can't.
An integration becomes externally reachable when all of these hold:
HTTP_PORT env var (so it serves HTTP),BASE_DOMAIN configured.The orchestrator then creates a Traefik Ingress at <subdomain>.<base-domain> pointing at the deployment's Service.
0.0.0.0); you can't bind them yourself. Declaring HTTP_PORT in the integration's env: is what marks it as a network service eligible for exposure.cert-manager issues Let's Encrypt certificates. Two modes:
*.<domain> (and the apex), issued once via Cloud DNS and shared by the editor and every integration subdomain. Enable with wildcardTLS.enabled.letsencrypt-prod cluster issuer.DNS: an A record for the apex plus a wildcard A record point every <slug>.<domain> at the node's static IP → Traefik → the integration's Service.
SSO is opt-in (auth.oidc.enabled); local runs stay open. When enabled it gates the editor via Auth.js (the eetr OIDC provider):
/api calls get 401,AUTH_WRITE_ROLES (read from a configurable id-token claim),Register the redirect URI https://<domain>/api/auth/callback/eetr on the identity provider.
Keep platform credentials separate from the secrets your integrations consume.
The Postgres password, the Auth.js session secret, and the OIDC client secret are generated into the bucket-backed Terraform release state — not a separate secret manager — and the Helm chart materializes them as Kubernetes Secrets for the editor and orchestrator.
The secrets your flows use (API keys, DSNs) are cluster secrets you manage in the editor. Values are written into one Kubernetes Secret (octo-secrets) and never read back; a deployment's env binding references one by key. An integration declares the env it expects — required vars must be bound at deploy time:
env:
- { name: API_KEY, required: true }
- { name: HTTP_PORT, default: "8080" } # marks it a network service