Service discovery
Every service in a project can reach the others by name — no hardcoded IPs,
no service registry to wire up. When you deploy a web service and a db
service into the same project, web connects to db over the project's private
network just by using its name.
The names
A service called db answers to all of these from any sibling in the same
project:
db— the bare service name (the Docker Compose style)db.internaldb.bootload.internaldb.<projectID>.internal— fully qualified, if you want to be explicit
So a web app talks to its database with a connection string like
postgres://user:pass@db.internal:5432/app — and it keeps working when the
database is rescheduled to a new host, because the name follows the service, not
the machine.
Internal-only services
A service does not need a public route to be reachable. Deploy a database, a cache, or a background worker with no domain attached, and it stays off the public internet — but its siblings still find it by name. Only the service that actually serves users (your frontend) needs an HTTPS route; everything behind it can stay internal.
You can see a service's internal address any time on its detail page in the dashboard, under the status card.
External names still work
Discovery only intercepts your own service names. Everything else —
api.stripe.com, one.one.one.one, your own external APIs — resolves normally
through upstream DNS. You get internal discovery and the open internet at once.
Good to know
- Names resolve the moment a replica is healthy, and a short DNS TTL means a rescheduled replica's new address propagates quickly.
- Discovery is scoped to a single project — services in different projects can't resolve each other by name (that isolation is the point).
- An unknown name inside the project returns
NXDOMAIN, so typos fail fast instead of silently reaching the wrong place.