# Internal networking
URL: https://synquic.tech/docs/guides/networking.md

How your apps and services reach each other privately by name, how each project gets its own network, and how to connect a database from one project to an app in another.

import { Step, Steps } from 'fumadocs-ui/components/steps';

Most apps talk to something else: a backend to its database, a worker to a queue, a website to an API. On
Synq that traffic stays **inside the server**. It never goes through the internet, never needs a public
port, and never needs a domain. Each project gets a private network, every container in it gets a name, and
the other containers reach it by that name.

This page explains the names, the per-project boundary, and how to cross it on purpose with an **Internal
connection**.

<Callout title="What you need first">
- A project deployed with Docker to one of your own servers. (Apps that run directly on the host, without
  Docker, share the host's network instead and have no private names.)
- For a connection between two projects: both projects on the **same server**.
</Callout>

## One private network per project

When a project deploys, Synq creates a private Docker bridge network for it, named `synq-<project-slug>`.
Every container of the project joins it: each service of a compose or monorepo project, and the app
container of a single-app project.

- Containers on the same network reach each other directly on their **container ports**. A database on port
  `5432` is reachable on `5432`; you don't publish anything.
- Containers of **other projects are not on this network**, so they can't reach it, even on the same
  server. Two projects can both have a service called `db` without any clash.
- Nothing here is exposed to the internet. Public traffic only arrives through a
  [domain](/docs/guides/custom-domains) or a port you publish on purpose (see
  [Public ports](/docs/guides/ports)).

## The names other containers use

| What | Name on the network | Example |
| --- | --- | --- |
| A service in a compose or monorepo project | The service name | `db`, `redis`, `api` |
| The app of a single-app project | The project slug, lowercased, with anything that isn't a letter, digit or dash turned into a dash | `my-shop` |
| An extra name you choose | Your **Network alias** (a service) or **Hostname alias** (a single app) | `postgres`, `backend` |

Put the name and the container port together to get the address: `db:5432`, `http://api:3000`,
`redis://redis:6379`.

### See a service's internal address

<Steps>

<Step>

### Open the service

Open the project, go to **Apps & Services** and click the service.

</Step>

<Step>

### Read the Private network card

On the service's **Overview** tab, the **Private network** card shows its **Internal address** (for
example `db:5432`) with a copy button. Under **Runtime details** you also see the current container IP;
don't use that, it changes on every restart.

</Step>

</Steps>

A single app answers to its project slug on its port, for example `http://my-shop:3000` for a project with
the slug `my-shop` listening on `3000`. An app installed from the **Apps** catalog also shows its connection
details on the project's **Overview** tab.

### Give a service an extra name

Sometimes the code expects a particular host name, like `postgres` instead of `db`.

- **A service:** open the service, go to **Settings**, and in the **Networking** section fill in **Network
  alias**. Press **Save changes**.
- **A single app:** open the project's **Advanced** tab, expand **Internal hostname**, fill in **Hostname
  alias** and press **Save**.

The extra name works **alongside** the default one; both resolve. Use letters, digits and dashes (other
characters become dashes, and names are cut at 48 characters). Synq refuses a name another container of the
same project already answers to. The new name applies when the container is created, so **redeploy** after
saving.

<Callout title="Coming from a platform that used project_service host names?" type="info">
Some platforms give every service a host name like `<project>_<service>` (for example `shop_db`). Synq does
not create that name, and an alias can't contain an underscore (it becomes a dash). Change the host in your
connection string to the service name (`db`), or set a **Network alias** such as `shop-db` and use that.
</Callout>

## Connection strings for databases

Inside the same project, point your app at the database service by name. Typical values:

```bash
# PostgreSQL service named "db"
DATABASE_URL=postgresql://app:your-password@db:5432/app

# MySQL or MariaDB service named "mysql"
DATABASE_URL=mysql://app:your-password@mysql:3306/app

# MongoDB service named "mongo"
MONGODB_URI=mongodb://root:your-password@mongo:27017/?authSource=admin

# Redis service named "redis"
REDIS_URL=redis://:your-password@redis:6379
```

The user, password and database name are whatever the database container was started with (its
`POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` style variables). For a database service, its
**Overview** tab has a **Public access** card whose **Internal** row shows the address and, when Synq can read
the credentials, a ready-made connection URL with the password hidden. Copy it and put the real password in
yourself.

Store the finished URL as a variable, marked secret, in the app's
[environment](/docs/guides/environment-variables).

<Callout title="Builds can't reach the database" type="warn">
The private network exists for **running** containers. An image build does not join it, so a build step that
opens a database connection (for example a framework that queries data while it builds) fails with
`getaddrinfo ENOTFOUND db`. Move the query to request time, or give the build a database address it can
reach.
</Callout>

## Connect a service from another project (Internal connections)

Projects are private to each other by default. To let an app in project **B** use a database that lives in
project **A**, create a **connection**. Synq then puts the value (for example a `DATABASE_URL`) into project
B's environment and joins B's containers to a private network with that one service, so the traffic stays on
the server.

### Create the connection

You can start from either side.

<Tabs items={['From the database (project A)', 'From the app (project B)']}>

<Tab value="From the database (project A)">

1. Open project A, go to **Apps & Services** and open the database service.
2. Press **Use in a project** in the service header.
3. Under **Project**, choose project B.
4. Under **Values to inject**, tick what you want (for a database, usually its connection URL). Each value
   has a box with the variable name it will get, for example `DATABASE_URL`; type in it to rename the
   variable. **Advanced** hides the name boxes and shows each name as a chip you can copy.
5. Keep **Internal (recommended)** selected.
6. Press **Connect**.

</Tab>

<Tab value="From the app (project B)">

1. Open project B, go to **Apps & Services** and press **Add**.
2. In **Add an app or service**, switch to **Connect existing**.
3. Under **Source project or app**, pick project A (or the service in it).
4. Tick the values, check their variable names, keep **Internal (recommended)**, and press **Connect**.

</Tab>

</Tabs>

An app installed from the **Apps** catalog has **Use in a project** on its connection card, on the project's
**Overview** tab. A plain single-app project can be picked as a source with **Connect existing**; the value
it offers is its internal address.

### What happens next

A toast confirms the connection and mentions a redeploy. The new variable and the network join take effect
when project B's containers are created, and Synq takes care of that redeploy for you:

- If project B already has a live deployment, Synq starts a new deployment of project B straight away.
- If project B was never deployed, the connection is picked up on its first deploy.

Once project B is running with the connection:

- Project B's **Overview** tab lists the link under **Connected services**.
- In project B's **Environment** section, the **Edit** window shows the variable with a **Linked** badge. It
  can't be edited there.
- The source service in project A lists project B under **Used by**.

### Internal or Public

| Mode | What your app gets | When it works |
| --- | --- | --- |
| **Internal (recommended)** | A private address on the server. For a service it looks like `postgresql://user:password@shared-svc-1a2b3c:5432/app`; for a single app it is its internal address, like `http://my-shop:3000`. | Both projects on the same server, both deployed with Docker, and the source service deployed and running. No public port is needed. |
| **Public** | The published `host:port` or the public URL. | Anywhere, including another server. The database port has to be published (see [Public access for a database](/docs/guides/ports#public-access-for-a-database)). |

Values that aren't addresses (a password, an API key, a bucket name) are injected as they are in both modes.

### Remove a connection

In project B, open **Overview**, find the link under **Connected services** and press **Remove** (or open
**Environment**, press **Edit**, click the **Linked** variable and press **Remove connection**). The injected
variable is deleted with the link, project B's containers leave the private network immediately, and if
project B has a live deployment Synq redeploys it without the variable.

### Limits

- **Same server only.** Internal mode needs both projects on the same server. For projects on different
  servers, use **Public**.
- **Docker only.** The consuming project must run in Docker containers, not directly on the host.
- **Deploy and start the source first.** A service that has never been deployed or isn't running can't be
  connected privately.
- **One owner per variable name.** If project B already has a variable with the same name that you set by
  hand, Synq refuses the connection rather than overwrite it. Remove your own variable or choose another name.
- **Deleting the source.** If you delete project A, the delete dialog lists the connected projects. They keep
  running but lose the connection; redeploy them afterwards.

## Troubleshooting

<Callout title="getaddrinfo ENOTFOUND db (or EAI_AGAIN)" type="error">
The name doesn't resolve from where the code runs. Check that the service name is spelled exactly as it
appears in **Apps & Services**, that both containers belong to the same project (or are connected), and that
this isn't an image build step. After adding an alias, redeploy. A new connection redeploys a project that
is already live for you; check that this deployment finished.
</Callout>

<Callout title="Connection refused" type="error">
The name resolves but nothing listens. Use the **container** port (for example `5432`), not a published host
port, and check the service is **Running** on its **Overview** tab.
</Callout>

<Callout title="Internal mode needs both projects on the same server" type="warn">
The two projects deploy to different servers. Choose **Public** instead, and publish the database port with
its **Public access** switch.
</Callout>

## Related guides

<Cards>
  <Card title="Public ports" href="/docs/guides/ports" description="Open a TCP or UDP port, or expose a database to the internet." />
  <Card title="Environment variables" href="/docs/guides/environment-variables" description="Store connection strings and keep them masked." />
  <Card title="Multi-service apps" href="/docs/guides/compose-multi-service" description="Run a web app, worker and database together as one project." />
</Cards>
