Skip to content

Providers and endpoints

Some tasks need to reach something alive while they run: a database for a migration, a SOCKS proxy into a private cluster, a throwaway Redis for an end-to-end test. The contract declares that need with a single type:

dependencies:
db:
type: endpoint/tcp

The worker receives an address through the variable the implementation maps — DB (host:port), plus DB_HOST and DB_PORT split for it — and nothing else. It cannot tell where the address came from, and that is the point: the task never changes between environments; only the wiring does.

# A routable address — production, a LAN service, anything already reachable.
db: "10.20.0.5:5432"
# THIS machine's own service — dev only. `localhost` inside a container is the
# container; `host:` rides the engine back to your machine.
db: host:5432
# A provider — a service container that lives and dies with the step.
db:
provider: techlite/cloud-sql-proxy@^1.0.0
inputs: { instance: "project:region:db" }
dependencies:
gcpCredential: ${env.OREN_GCP_CREDENTIAL}

Put the split in per-environment properties: the dev file wires host:5432, the CI file wires a provider, production wires the real address — and oren.yaml does not change a line.

A provider is a container bound to the step’s network, started before the worker and killed with it. Its open port is the readiness gate: the step only starts once the service accepts connections. Nothing survives the step — a throwaway database really is thrown away.

The reference can be:

  • published — provider: techlite/gke-iap-proxy@^1.0.0, resolved from the catalogue by oren install like any task;
  • a local document — provider: local/my-proxy@^1.0.0, read from .oren/registry/*.provider.yaml in the project;
  • inline — provider: { image: redis:7, port: 6379 }, identity by digest, for the case too small to deserve a document.

A provider declares its own dependencies — the credential the tunnel needs, which the task’s contract never mentions — and its inputs parameterize the path to the service (which bastion, which zone). Both use the same grammar a step uses.

Only endpoint dependencies are readable in expressions, because their value is the one thing the pipeline both needs and rightfully owns:

inputs:
databaseUrl: "postgres://app@${dependencies.db.host}:${dependencies.db.port}/app"

address, host and port re-read the wiring — with a literal you get what you wrote; with a provider, the address the runner assigned.

The consent gate prices the whole arrangement: the provider’s image (pinned to a digest by oren install, like the worker’s), the dependencies the provider itself consumes, and — for host: — the fact that the step reaches this machine’s services. Swapping any of it invalidates the grant, exactly as swapping the worker’s image does.

Terminal window
oren explain <pipeline> -o pipeline.html

draws the pipeline as cards — each step’s dependencies, inputs and outputs, the provider beside the step that consumes it, and the wires between them, with a padlock wherever a secret travels. oren trace draws the last run as a waterfall. See the CLI reference.