Service Discovery
Service discovery is how Octopus learns which backends exist and where they are. There are two complementary models, and you can use either or both:
Static upstreams — you declare backends and their instances by hand in configuration. Routes target an upstream by name.
Discovered upstreams — a discovery backend reports live instances, and FARP turns each service's published schema into routes automatically.
Static and discovered upstreams coexist. A typical deployment pins a few critical backends statically and lets discovery handle the rest.
Static upstreams01
A static upstream is a named cluster of instances you write out yourself. This is the most explicit model and needs no discovery infrastructure:
upstreams:
- name: user-service
lb_policy: round_robin
instances:
- id: user-1
host: 10.0.0.11
port: 8080
- id: user-2
host: 10.0.0.12
port: 8080
routes:
- path: /api/users/*
upstream: user-serviceStatic upstreams are ideal for fixed backends, external dependencies, or local development. See Upstreams and Routes for the full schema.
Discovered upstreams and FARP02
For dynamic environments — services that scale, appear, and disappear — declaring every instance by hand does not scale. FARP (the Forge API Gateway Registration Protocol) flips the model: services publish their own schema, a discovery backend reports their live instances, and Octopus generates the routes.
The discovery loop
Discover
A discovery backend reports the set of live service instances and their
addresses. FARP can source instances from mDNS/Bonjour (zero-config local
development), Consul, DNS (SRV/A records), or Kubernetes
(EndpointSlice-based Pod discovery). Backends are configured under
farp.discovery.backends. See Discovery backends.
Fetch schema
For each discovered service, Octopus retrieves its published schema (its FARP manifest). In the live discovery path services publish an OpenAPI document; the federated API surface additionally merges AsyncAPI and GraphQL schemas. See Schema federation.
Generate routes
The paths and methods from each schema become gateway routes pointing at the
discovered upstream. No hand-written routes: entries are needed for these
services.
Watch and reconcile
Octopus watches for changes and updates the router as services scale, appear, or disappear — without dropping existing connections. In Kubernetes, EndpointSlice events reflect Pod scale up/down that a Service-only watch would miss.
graph LR
A[Discovery backend] -->|live instances| B[FARP client]
B -->|fetch manifest / schema| C[Service]
C -->|OpenAPI / AsyncAPI / gRPC| B
B -->|generate routes| D[Router]
A -.->|scale / add / remove| B
B -.->|reconcile| DKubernetes discovery vs. the operator03
Two distinct mechanisms populate the gateway inside Kubernetes — keep them separate:
Discovery (a FARP
kubernetesbackend) populates upstream instances from thediscovery.k8s.io/v1EndpointSlice API. Endpointreadyconditions map to instance health. See Kubernetes discovery.The operator programs routes from Gateway API / Octopus CRDs. It is also the path on which load-balancing policy, health checks, and circuit breakers are wired into live behavior. See Operator.
Load-balancing policy, active health checks, and circuit breaking are wired on the Kubernetes/operator-driven path. For upstreams registered from a static config file, those fields are parsed and validated but not yet enforced — static balancing falls back to round-robin. See Upstreams.