An app that serves traffic says so in its package by declaring endpoints, and receives the names, certificates and addresses it needs as settings the platform fills in. This page is the reference for both. The rest of the package is in Authoring apps, and how those settings reach your program in App commands and values.

Declaring endpoints

An endpoint is one thing your app listens for connections on: where it listens, what it speaks there, how to tell when it is ready, and whether more than one node may answer for it. That is the whole contract a package carries. Who may reach it, and on which network, are decisions of the deployment, made in the request composer (see Project and organization exposure). Your package says what you serve; the deployment says who may reach it.

the app package (artifact.yaml)endpointpgport5432protocoltcpprobetcp connectserveprimarysays WHAT it serves+the request (one deployment)exposeorgservice namedbport5432says WHO may reach itone endpoint, two decisions, made in two places

endpoints is a map under config, keyed by endpoint name:

config:
  endpoints:
    pg:
      port: 5432
    metrics:
      port: 9187
      protocol: http
      probe:
        http: /metrics
FieldMeaning
portRequired. The listen port, 1–65535.
protocoltcp (the default), udp, http, or https.
probeOptional readiness check: one of tcp, http or command, plus interval and timeout.
serveprimary (the default) or spread: how many nodes answer for this endpoint.

http is a plaintext HTTP listener; https is the same with your app terminating TLS on the port itself. Declare https when your app serves TLS: the platform then knows the scheme rather than guessing it, and a plaintext http endpoint is refused public exposure (Public exposure).

Endpoint names are DNS labels — the db in db.shop.internal: lowercase letters, digits and dashes, 1–63 characters, no leading or trailing dash. Two endpoints may share a port number only on different transports: http and https count as tcp, while tcp and udp on one number coexist (QUIC beside TCP on 443). orc build checks all of this:

endpoint "Metrics" is not a DNS label (lowercase alphanumeric and '-', 1-63 characters, no leading or trailing '-')
endpoint "web" probe.http requires protocol "http", not "tcp"
endpoint "metrics" port 5432 duplicates endpoint "pg" on transport "tcp"

Serve mode: primary or spread

A pool can run one node or twenty. serve says how many of them may answer a request of a given endpoint — the one thing about spreading traffic the deployment cannot decide, because only the app knows whether a second node holds the same answer.

ModeWho answers
primary (default)The slot-1 node alone. While it is not ready, the endpoint answers nothing.
spreadEvery node that passes this endpoint's probe.

primary is the default on purpose: a package that says nothing has not said that a replica may serve a write. Declare spread when any node can serve any request of the endpoint — a stateless HTTP tier, a read-only mirror, a metrics port. One app may mix them: a database serves pg as primary and metrics as spread.

One rule catches people out. A pool has one set of names but many ports. So if any endpoint of the version serves primary, the pool's plain names answer the slot-1 node alone — its internal name, its service-name aliases and its public name. Slot names (db-1, db-2, …) still name their own nodes, SRV records still list every ready node, and each port still delivers as its own mode says. While slot 1 is unready, such a name answers empty rather than falling through to a sibling. See Networking.

Probes

A probe is a check ORC8R repeats to decide whether an endpoint is ready to serve traffic.

ProbePasses when
tcp: trueA connection to the port is accepted. The default for tcp, http and https.
http: /pathA GET answers 2xx or 3xx. On https it dials TLS without checking the certificate.
command: [...]The command exits 0.

interval and timeout are written <integer>(s|m|h); the defaults are 10s and 5s. An endpoint goes unready after 3 consecutive failures and recovers after 1 success. A udp endpoint has no default probe: without a command probe its readiness follows the app's. A probe command is an argv list like any other and takes the values of start, but a startup file is gone by the time it runs: read what you need at start, or probe over the port.

Bind [::], not loopback — and not one address family

Make your app listen on [::], the dual-stack wildcard. A probe dials every address the endpoint is served on: the node's own address, and for a publicly exposed endpoint the pool address public traffic arrives on. It never dials 127.0.0.1, because a passing probe has to mean that this node can take the traffic it will be sent. Two ways to get it wrong:

  • Loopback only. An app bound to 127.0.0.1 stays unready forever, and its name answers nothing.
  • One address family only. Public traffic arrives at your node addressed to the pool address, which is usually IPv6. 0.0.0.0 is an IPv4 wildcard; an app bound there has no socket for it, and every public connection is reset.

Where a runtime cannot serve both families on one socket (Python's http.server), open one listener per family. Privacy is the deployment's job, not your listen address's. Probe failures name the address that failed:

connect to 2001:db8::5 port 5432 failed: Connection refused
http probe /healthz on 100.64.0.3 returned 503

A refusal on the pool address while the node's own address answers is the one-family mistake above.

Values the platform fills in

Some settings are not typed by a person — a peer's name, a trust bundle, a certificate. Declare them in config.params with an x-source, and the platform fills them in on the node:

params:
  type: object
  properties:
    writer:
      type: string
      x-source: {kind: peers.first}
    tls_cert:
      type: string
      contentMediaType: application/x-pem-file
      x-source: {kind: tls.cert, params: {purpose: server_tls}}
    tls_key:
      type: string
      sensitive: true
      contentMediaType: application/x-pem-file
      x-source: {kind: tls.key, params: {pair: tls_cert}}

They reach your commands like any other setting: --writer=${writer}, --tls-cert-file=${tls_cert_file}. These settings are locked by default: the request form does not show them, and a project or organization can unlock one on its Apps page.

kindparamsWhat arrives
pool.slot—The node's slot, a decimal string; 1 is the writer by convention.
pool.name—The name of the node's pool.
peers.firstpool, projectThe slot-1 node's DNS name.
peers.gossippool, projectThe pool's plain name, answering per its serve modes.
peers.anypool, projectThe plain name too; where it spreads, answers rotate.
peers.allpool, projectOne slot name per allocated slot, newline-separated.
endpoint.nameendpointYour endpoint's canonical name: public where exposed publicly.
endpoint.portendpointThe port it actually listens on, after the deployment's override.
endpoint.urlendpoint{scheme}://{name}, plus :{port} unless it is the scheme's default.
ca.bundlescope: project or orgThe trust chain, PEM.
tls.certpurpose, optional sansA signed certificate and its chain, PEM.
tls.keypair: the tls.cert settingThe matching private key, PEM. Must be marked sensitive.

Give PEM values and peers.all a contentMediaType (application/x-pem-file, text/plain), so they arrive as a file and you name them ${name_file}. A tls.key setting must be sensitive, or the package is refused.

peers.* hand you names, not addresses. Nodes are replaced and slots are reused, and the next DNS answer absorbs that without restarting your app. Resolve them at connect time; do not parse, cache or split them.

A name that answers nothing is the fence, not an error. peers.first is the slot-1 name whether or not a healthy slot-1 node exists; while none does, it answers empty and your connection fails. Retry; do not fall back to another name.

endpoint.* is your own address, for an absolute link, an OAuth callback or a root_url. endpoint names which of your endpoints; omit it only when the app declares exactly one. Naming one the app does not declare fails the deploy with a message naming the fix.

Certificates renew by restarting

There is no reload channel. Each time the agent brings the node in line with its assignment, it arms a timer at two thirds of the shortest-lived certificate's validity, with jitter. When it fires, fresh material is minted and the app restarts (stop reason restart). So start fast, hold no unflushed state, and read your certificate at startup rather than watching the file.

For purpose: server_tls you may assert your pool's plain name, your slot name and the endpoint's service names; request anything else with sans, and a request outside that list gets no certificate. A publicly exposed endpoint gets a certificate from a public authority instead, shared by the pool's nodes; on a pool with several public services, name the one this binding is for in sans. On a public pool every name the platform hands your app is a public one, so names keep matching certificates. See Public exposure.

Waiting for dependencies

A pool comes up in parallel, and nothing orders apps across pools for you. Wait in your own start script, with a bounded retry around the name rather than a sleep:

i=0
while ! pg_isready -h "$writer" -q; do  # $writer came from --writer=${writer}
  i=$((i + 1))
  [ "$i" -lt 120 ] || { echo "writer $writer never became ready" >&2; exit 1; }
  sleep 2
done
exec /usr/sbin/my-service --upstream="$writer"

The loop resolves the name on every try, so a replacement writer is picked up, and the failure is bounded and loud, so a broken deployment fails instead of hanging in starting.

A complete example

A single-writer database that serves postgres and metrics, wires itself up from platform-filled values, and branches on its slot:

artifactType: application/vnd.orc8r.app.v2
annotations:
  org.opencontainers.image.title: examplepg
  org.opencontainers.image.description: Single-writer database, slot 1 is the writer
config:
  params:
    type: object
    properties:
      slot: {type: string, x-source: {kind: pool.slot}}
      writer: {type: string, x-source: {kind: peers.first}}
  endpoints:
    pg:
      port: 5432
      probe: {tcp: true, interval: 5s}
    metrics:
      port: 9187
      protocol: http
      serve: spread
      probe: {http: /metrics}
  install:
    command: ["./install-examplepg.sh"]
  start:
    command: ["./start-examplepg.sh", "--slot=${slot}", "--writer=${writer}"]
files:
  - install-examplepg.sh
  - start-examplepg.sh
platforms:
  - {os: linux, arch: amd64}

start-examplepg.sh branches on --slot: slot 1 initialises and serves, any other seeds from --writer and follows it. The platform is told nothing about roles, and does not need to be.