Every endpoint a pool serves is reachable from inside its own project the moment the app is running. You configure nothing to get that. This page is about using those names from your nodes, and about the one setting that widens a service to the rest of your organization.

Project scope is the isolation default. On a provider that isolates networking, a project's nodes share one private network that nothing outside the project can reach into, so a project-scoped endpoint answers only from within that network. The org and public scopes are the controlled widenings of that default; this page covers project scope and org.

Names you get for free

For the pool db in the project whose code is shop, serving postgres on an endpoint called pg, these names answer with no configuration at all:

NameAnswers with
db.shop.internalthe pool's plain name: the slot-1 node alone when any endpoint serves primary (the default), every online node when they all serve spread
db-1.shop.internalthe node holding slot 1, and nothing else
db-2.shop.internalslot 2, and so on
_pg._tcp.db.shop.internalthe pg endpoint's port, with healthy nodes as targets and slot 1 first

A slot is a node's stable identity inside its pool, and slot 1 is the writer by convention; Networking overview has the rest. The last row is the SRV form: it carries the endpoint's port alongside its targets, which is the one place an endpoint's own name appears in DNS.

What the plain name answers is the app's decision, not the deployment's: each endpoint declares a serve mode in its package, and the plain name follows the strictest one across the version (Authoring apps). The slot names and the SRV form are the same under either mode.

Nodes carry shop.internal as a DNS search domain, so from a node in the same project the short form works:

$ psql -h db -U postgres
$ curl http://storefront:8080/healthz
$ dig +short db.shop.internal
10.42.7.14
10.42.7.15

Address answers follow node liveness; SRV answers follow endpoint health. A slot name whose node is offline returns an empty answer — never a different node, and the plain name of a primary pool is fenced the same way. That is the property everything single-writer depends on, so do not paper over it with a retry that falls back to another name.

The answers come from a resolver inside the agent on the node itself, from state the server pushes to it. Two things follow: resolution keeps working while the server is unreachable, and answers are fresh within seconds of a membership change. Plain-name answers carry a TTL of at most 5 seconds; slot, SRV and negative answers 1 second.

What the agent changes on your node

The agent installs split DNS: only names ending in the internal suffix are sent to its own resolver, and everything else is looked up exactly as it was before. Your default resolvers, your corporate DNS, and every public name are untouched — the agent's resolver refuses to answer anything outside the suffix, and nothing outside the suffix is sent to it. On Linux this hangs off a network interface the agent owns under systemd-resolved; on Windows it is an NRPT rule, which is the Windows mechanism for routing one suffix to one resolver. It is undone when the node leaves the pool or the agent stops, and leftovers from a killed run are cleaned up before anything is installed.

If you route the suffix yourself, you can switch the operating-system configuration off and keep the resolver.

Naming rules

  • Project codes and pool names are DNS labels: lowercase letters, digits and dashes, not starting or ending with a dash, at most 63 characters.
  • A pool name may not end in -<digits> — that shape is reserved for slot names. The rejection suggests the fix: pg-15 becomes pg15. api-v2 is fine, because v2 is not all digits.

Sharing a service with the rest of the organization

Setting an endpoint's scope to Organization makes it resolvable and reachable from every project of the same organization, under the same names, with the same behaviour. Nothing changes for consumers in the owning project.

Expose: Project (the default)organization acmeproject shopdbpg 5432project analyticsyour appblockedExpose: Organizationorganization acmeproject shopdbpg 5432project analyticsyour appdb.shop.internal

Setting it

Exposure is part of a request, so you set it where you request or reconfigure the pool.

  1. Open the request composer and add the app to the pool as usual.
  2. In the app's Exposure section, each endpoint the package declares gets its own row, separated by a hairline — there is no surrounding card, and an endpoint the package does not declare cannot be exposed.
  3. On the endpoint's row, set its visibility to Organization (and adjust its address if you like — below).
  4. Submit the request.

Each row has two parts:

  • Address — one field shaped like the URL you are building, {scheme} name {domain} :{port}, that scrolls sideways rather than wrapping. Only the name in the middle is editable; it defaults to the pool's name and becomes the service's own name (an alias, described below). Everything around it is read-only and derived. The scheme comes from the endpoint's declared protocol — http:// for http, https:// for https, tcp:/udp: otherwise — and follows the package, not the scope: an http app reads http:// whether it is shared with the project or the whole internet. The domain suffix is the only part that follows the scope — .{project}.internal at project and organization scope, the organization's public base domain at public scope. The :port is the package's declared port.
  • Visibility — a segmented control: Project or Organization, plus Public when the zone has a public address configured (a public base domain and at least one admitting address plan). A helper line beneath it states in plain language who can reach the endpoint and updates with your choice — project: only this project's nodes; organization: any node in the org; public: anyone on the public internet.

If you are driving the API, or reading the composer's YAML preview, the same thing looks like this:

apps:
  - name: postgres
    params:
      shared_buffers: 512MB
    endpoints:
      pg:
        expose: org

Controls left at their default write nothing into the document. That is what keeps an unchanged copy of a request byte-identical to the request it was copied from, and therefore a genuine no-op.

What organization scope actually gives you

  • It is ambient. Every node of every project in the organization can dial the endpoint with no configuration on the consuming side, including projects created after the exposure. From a terminal on any node in the organization you can run psql -h db.shop.internal as soon as the exposure exists.
  • Consumers write the full name. The search-domain shortcut is not extended across projects, so from project analytics you write db.shop.internal in full.
  • Only what you exposed is reachable. Other endpoints of the same pool, other nodes of shop's network, and other ports stay blocked. The consuming side is permitted to the exposing network's subnet on that protocol and port; the exposing side enforces the exact node addresses. Default-deny is preserved in both directions.
  • Only what you exposed resolves. From another project, an unexposed sibling pool's name answers NOT_FOUND. Org names resolve through the server and are cached for their TTL, so a name you have already used keeps answering during a server outage while one you have never used does not.
  • TLS works across projects. The leaf your app got for db.shop.internal chains to your organization root, and every project's ca.bundle contains it — a client in analytics validates it without you copying anything around.

If the pool runs on a provider that does not isolate networking, org scope gates resolution only — reachability is up to the network those nodes already sit on.

Service names and port overrides

Two optional decorations travel with the exposure.

Service name gives the service a name of its own, independent of the pool it happens to run on. It is an alias: with a service name of maindb, maindb.shop.internal answers exactly like db.shop.internal, slot forms included. It survives a pool rename, and it follows the service if you move it to a different pool — the new request simply carries the same name. Service names share the same space of names as pools within a project, so a service name cannot collide with a pool name there.

Port overrides the listen port for this deployment. It is not a translation — nothing rewrites ports on the internal path — so you must also point the app at the same port, usually with one of its own parameters. Probes, SRV records and policy all follow the override. It is also the escape hatch when two apps in one pool want the same port: a pool's (transport, port) space must be collision-free, and the request is rejected at submission if it is not.

Changing and revoking

Exposure is versioned pool configuration, not a runtime switch. Changing it is a reconfiguration like any other: the pool rolls to the new request version and nodes come up carrying the new state.

When an exposure ends — you set the scope back to Project, or the pool goes away — the routes, the return routes and the permits are cleaned up across the organization in lockstep, and hosts return to exactly their pre-exposure state. New connections from other projects fail as soon as the permits are gone; nothing preserves flows across a revocation, so treat it as a hard cut-off rather than a drain.