# Projects

###### A project is a YAML file under `~/.localcan/projects/` that groups related services and the URLs they should be reachable at.

A **service** is one local server you want to expose: for example your app on `localhost:3000`, an API on `localhost:4000`, or a database on `localhost:5432`. A project can hold one service or many. Each service has one or more **endpoints**, which are URLs that route requests back to it: typically a local `.local` domain plus a Public URL.

The filename (without `.yml`) is the project ID. A minimal project looks like this:

```yaml title="~/.localcan/projects/my-app.yml"
name: My App
icon: 🚀

services:
  web:
    target: http://localhost:3000
    endpoints:
      - provider: localcan
        url: dev.example.com       # Public URL (tunnel)
      - url: my-app.local          # local .local domain
```

After editing, run `localcan reload` to pick up the change.

> [!TIP]
> `localcan reload` is non-disruptive. Tunnels for projects you didn't touch keep their connections.

#### Project keys

| Key | Type | Notes |
| --- | --- | --- |
| `name` | string | Display name shown in the desktop app and `localcan projects ls`. Defaults to the project ID. |
| `icon` | string | Optional emoji or image path shown next to the name in the desktop app. Relative image paths resolve from the project YAML file. |
| `services` | map | Required. Keyed by service ID. See below. |

#### Project icons

Use an emoji for the simplest icon:

```yaml
icon: 🚀
```

Or choose an image in the desktop app. LocalCan copies it into `~/.localcan/assets/icons/` and writes a relative path:

```yaml
icon: ../assets/icons/my-app-a1b2c3d4.svg
```

Supported image formats: SVG, PNG, JPG, WebP, GIF, and AVIF.

#### Services

If your app has a frontend on `localhost:3000`, an API on `localhost:4000`, and a database on `localhost:5432`, that's three services in the same project, each pointing at one of those local addresses:

```yaml
services:
  web:                            # the service ID — keep it short and lowercase
    target: http://localhost:3000
  api:
    name: REST API                # optional friendly name shown in the desktop app
    target: https://localhost:4000
  db:
    target: tcp://localhost:5432
```

The map key (`web`, `api`, `db`) is the **service ID**. Pick something descriptive. It shows up in `localcan services ls` and in daemon logs.

| Key | Type | Notes |
| --- | --- | --- |
| `name` | string | Optional display name. Defaults to the service ID. |
| `target` | string | **Required.** Where requests are forwarded. Must be `http://`, `https://`, or `tcp://`. |
| `endpoints` | list | URLs the service is exposed at. See below. |

The target scheme decides the kind of traffic the endpoint can carry:

- `http://` and `https://`: HTTP traffic. The proxy terminates TLS for HTTPS endpoints and forwards plaintext to `http://` targets, or speaks TLS to `https://` targets even when the cert is self-signed.
- `tcp://`: raw TCP. Only works with Public URLs, since the local proxy is HTTP-only.

#### Endpoints

Each endpoint is one URL pointing at the same service. You'll usually have two per service: a `.local` domain for working on your own machine and a Public URL for sharing.

```yaml
endpoints:
  - url: my-app.local                    # local .local domain
  - provider: localcan                   # Public URL (random subdomain)
  - provider: localcan
    url: my-app-prod-12.localcan.dev     # Public URL on a pinned subdomain
```

| Key | Type | Default | Notes |
| --- | --- | --- | --- |
| `provider` | string | omitted (local) | `localcan` for a Public URL. Omit for a local `.local` domain. |
| `url` | string | auto-generated for tunnels | Required for local endpoints, optional for tunnels (LocalCan picks a random subdomain when omitted). |
| `scheme` | string | `http` | `http` or `https`. Controls which proxy port the local domain is served from. Tunnels are always HTTPS. |
| `protocol` | string | inferred | `http` or `tcp`. Inferred from the service target when omitted. |
| `enabled` | bool | `true` | Disable an endpoint without removing it. |
| `proxy` | bool | `true` for HTTP, `false` for TCP | When `false`, requests bypass the LocalCan proxy. Not allowed on tunnel endpoints. |
| `inspect` | bool | `true` | Set `false` to skip traffic inspection for this endpoint. |
| `headers` | list | — | Header rewrite rules. See [Headers](/docs/configuration/headers). |
| `basic_auth` | list | — | Username/password challenges. See [Basic auth](/docs/configuration/basic-auth). |
| `policy` | string or map | — | Access control policy for a Public URL: a team policy name, or an inline policy map. Public URL endpoints only. See [Access control](/docs/configuration/access-control). |

> [!NOTE]
> Endpoint URLs must be unique across a project. Two services can't both claim `myapp.local`. If you duplicate one, `localcan reload` reports the conflict and refuses to load that project.

#### Bigger example

```yaml title="~/.localcan/projects/checkout.yml"
name: Checkout
icon: 💳

services:
  web:
    name: Storefront
    target: http://localhost:3000
    endpoints:
      - url: checkout.local
      - provider: localcan
        url: checkout-staging-42.localcan.dev
        headers:
          - "Host: checkout.example.com"
        basic_auth:
          - "preview:demo123"

  api:
    target: http://localhost:4000
    endpoints:
      - url: api.checkout.local
      - provider: localcan
        policy: client-review        # team Access control policy

  db:
    target: tcp://localhost:5432
    endpoints:
      - provider: localcan
```

Three services, six endpoints: one of them rewrites the Host header, one is password-protected, one carries a team [Access control](/docs/public-urls/access-control) policy, and one tunnels a Postgres database over raw TCP.

