> ## Documentation Index
> Fetch the complete documentation index at: https://docs.slipway.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Service to service

> Let the services in a project reach each other by name, without hardcoding addresses.

Every service in a project shares one private network. A service reaches a sibling by its
bare service name, on the port that sibling declares.

```
http://api:8080
postgres:5432
redis:6379
```

Nothing outside the project can reach in, except through a service's public URL.

```mermaid theme={"system"}
flowchart LR
  net{{"Project network"}}
  web["web<br/>public port"]
  api["api<br/>no public port"]
  db["postgres"]

  web e1@-->|"http://api:8080"| api
  api e2@-->|"postgres:5432"| db
  net e3@-.->|"public URL"| web

  e1@{animation: slow}
  e2@{animation: slow}
  e3@{animation: slow}

  classDef choice fill:#0D857714,stroke:#0D8577,stroke-width:1.5px
  classDef result fill:#2DD4BF22,stroke:#0D8577,stroke-width:2px
  class net choice
  class web,api,db result
```

Because siblings resolve by name, **service names are unique within a project**. slipway
refuses to deploy a service whose name another source already publishes.

## Service references

Rather than hardcoding a name and port, reference the sibling and let slipway resolve it at
deploy time:

```yaml slipway.yaml theme={"system"}
services:
  - name: api
    runtime: docker
    env:
      DATABASE_URL: ${service.db.url}
      CACHE_URL: ${service.cache.url}
      FRONTEND: ${service.web.public_url}
```

| Field | Resolves to |
| - | - |
| `url` | A full connection URL, for engines that have one |
| `host` | The in-cluster hostname, which is the service name |
| `port` | The service's port |
| `public_url` | The public URL, for a service that has one |
| `external_url` | The external endpoint, when external access is on |
| `pooled_url` | The connection URL through the PgBouncer pool, when connection pooling is on |
| `user`, `password`, `database` | Managed-database credentials |
| `bucket`, `endpoint`, `access_key_id`, `secret_access_key` | Object storage |

> [!WARNING]
>
> Service references are substituted in `env` and `buildArgs` only. Anywhere else in the spec
> they are left as literal text, which usually shows up as an app receiving the string
> `${service.db.url}` rather than a URL.

An unresolvable reference fails the deploy rather than deploying with an empty value.

## Public and private services

A `web` service is reachable from the internet only if one of its ports is marked
`public: true`. A service with no public port is reachable by its siblings and nothing else,
which is the right shape for an internal API.

Publicness is not fixed at creation: the **Public exposure** section on the service's
Settings tab flips a service between public and private (and changes its port) with a
redeploy. See [changing exposure later](/services/overview#changing-exposure-later).

A `worker` service declares no ports at all, so nothing can reach it. It reaches out rather
than being called.

A `static` site has no container and therefore no in-cluster name. Siblings cannot call it
by name, though `${service.<site>.public_url}` resolves to its public URL.

## Waiting for a dependency

`dependsOn` holds a service back until the service it names is ready:

```yaml slipway.yaml theme={"system"}
services:
  - name: api
    runtime: docker
    dependsOn:
      - db

  - name: db
    image: postgres:18
```

The referenced service must exist, must not be the service itself, and must not be disabled,
`cron` or `static`. The graph must be acyclic; a cycle is reported as `a → b → a`.

`dependsOn` waits for readiness, not for correctness. A database that accepts connections
before its migrations have run still counts as ready.

## Previews

A [full-stack preview](/previews/pr-previews) copies every service, so siblings resolve
inside the preview's own namespace and nothing touches production.

A [single-service preview](/previews/service-previews) is the exception: sibling names
resolve to the base project's **production** services, so `http://postgres` in a preview
reaches the real database. That is what makes those previews zero-config, and it is why
preview values exist for anything that must not point at production.

> [!NOTE]
>
> In-cluster traffic from a preview cannot wake a sleeping or paused base sibling. Only a
> request arriving at the public URL wakes a service.

## Reaching other projects

Services in different projects are on different networks and cannot reach each other by
name. Use the target service's public URL, or put the services in the same project.

Co-location is what makes name resolution work, which is also why a project can hold
services from several repositories.

## Appendix: glossary

| Term | Meaning |
| - | - |
| Sibling | Another service in the same project. |
| Private network | The per-project network every one of its services shares. |
| Service reference | A `${service.X.field}` expression, resolved at deploy time in `env` and `buildArgs`. |
| Public port | A port marked `public: true`, which gives the service a hostname at the edge. At most one per service. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.