Octov0.4.2
Platform

Deployments

Deploying integrations as Kubernetes workloads: rollouts and rollbacks.

Deploying an integration runs a version tag of it as its own Kubernetes workload: a Deployment of the generic octo-runtime image with the tag's frozen definition mounted in. One integration can be deployed many times — say, a staging and a production deployment on different tags — and each deployment has its own address, scale, and environment bindings.

An integration's deployments view on the platform

What a deploy creates

For each deployment the orchestrator creates resources named octo-dep-{deploymentId} in its namespace, labelled with the deployment and integration ids:

  • A ConfigMap holding the frozen definition as integration.yaml, mounted read-only at /etc/octo/integrations — the runtime image's default config directory.
  • A Deployment running octo-runtime at the requested replica count (minimum 1). The orchestrator injects the runtime-services environment (RUNTIME_SERVICES_MODULE=k8s, OCTO_DEPLOYMENT_ID, ORCHESTRATOR_URL, NATS_URL, the pod's name/namespace via the downward API, and the deployment's display name, tag, and snapshot id) plus your env bindings.
  • Services — only for networked integrations (see below): a per-deployment ClusterIP Service, and a stable internal Service octo-int-{slug} other integrations address.
  • An Ingress — only when the deployment is exposed externally.

A failed deploy is rolled back completely: partial cluster resources and the database row are removed, so nothing dangles.

Networked vs. bare workloads

An integration that declares HTTP_PORT (with a numeric default) in its env block has an HTTP source listening on that port. Only those deployments are networked: they get Services, a unique slug, an internal URL, and the option of external exposure. The orchestrator supplies HTTP_PORT (and HTTP_HOST=0.0.0.0, when declared) into the pod, so the listener binds all interfaces — these two variables are managed and cannot be overridden by bindings.

Anything else — a cron-driven job, a queue consumer — runs as a bare workload: pods only, no Service, no URL.

Internal address

Every networked deployment gets a slug, unique across all deployments: pick one in the deploy dialog or leave it empty to have the orchestrator allocate one from the integration name (orders, orders-001, ...). The slug names the stable internal Service, so other integrations in the cluster reach the deployment at a constant, replica-balanced address reported as the deployment's internalUrl:

http://octo-int-{slug}.{namespace}:{port}

External exposure

Toggling external exposure additionally creates a Traefik Ingress at https://{subdomain}.{baseDomain} — the subdomain defaults to the slug and must be unique across integrations. TLS comes from cert-manager: a per-host certificate issued via the configured ClusterIssuer (HTTP-01), or a shared pre-issued wildcard certificate when the chart's wildcardTLS is enabled. The public address is reported as externalUrl.

External endpoints require BASE_DOMAIN on the orchestrator (the Helm chart's orchestrator.baseDomain); without it an external deploy is rejected. See Deploying with Helm.

Environment bindings

The deploy dialog lists every env var the tag's definition declares. Each can be bound to a literal value or a cluster secret by name (injected via secretKeyRef, so the value never touches the database). Keys already supplied by the tag's frozen .env resources count as provided. A deploy that leaves a required variable unbound — by neither a binding nor a frozen .env resource — is rejected up front instead of letting the pod crash-loop. Full details: Resources and Secrets.

Status

Deployment status is computed live from the cluster through Kubernetes informers, so reads are cheap and updates are push-based:

  • pending — created, pods not ready yet.
  • running — at least one replica is ready.
  • failed — a pod is in a terminal or crash-looping state; the response carries the reason (e.g. an image or secret problem) and per-pod detail.

Each status change is published to NATS and streamed to the browser as SSE (see Event Bus), with a polling fallback when no broker is configured. Individual pod logs stream from GET /deployments/{id}/pods/{pod}/logs (with follow/tail); aggregated, searchable logs across deployments live in the logs view — see Monitoring.

Day-2 operations

OperationEndpointWhat it does
ScalePATCH /deployments/{id}Changes the replica count in place; the Service keeps balancing across the new set.
Roll out a new versionPOST /deployments/{id}/rolloutUpgrades the deployment to a different tag in place.
UndeployDELETE /deployments/{id}Removes the cluster resources and the record.

Rolling out a new version

A rollout ships a different tag's frozen definition to a live deployment as a rolling update: the orchestrator rewrites the ConfigMap, stamps a config-hash annotation on the pod template so Kubernetes rolls the pods, and records the new tag. The deployment keeps its identity — id, slug, internal and external URLs, replica count — and its env bindings (pass a replacement binding set to edit env during the rollout). Because the same mechanism accepts an older tag, rollback is a rollout to the previous tag.

Two guards apply, mirroring deploy: the target tag's required env vars must still be provided, and a tag that changes whether the integration is networked (adds or removes HTTP_PORT) is rejected — that would change the Service/Ingress topology, which a rolling update cannot express. Undeploy and redeploy instead.

Deploying "Current" from the editor first creates a tag of the working copy, then deploys that tag — so even ad-hoc deploys are pinned to an immutable version.

Undeploying

Undeploy deletes the Ingress, Deployment, Services, and ConfigMap, then the deployment record. The deployment's entries in the KV store are cleaned up best-effort — a cleanup failure never blocks the undeploy, and orphaned rows are harmless.

On this page