# Architecture

> The services in the Compose stack, how traffic reaches them and how the backend routes requests by hostname.

Source: https://docs.grovs.io/docs/self-hosting/get-started/architecture

## Request flow

Everything public enters through the proxy on ports 80 and 443. The databases are reachable only inside the Compose network.

```mermaid
flowchart TD
  U[Apps, browsers, SDKs]:::evt --> P[Caddy proxy]:::proc
  P -->|dashboard host| D[Dashboard]:::proc
  P -->|api, sdk, link hosts| W[Rails web]:::proc
  D -->|API calls| P
  W --> PG[(PostgreSQL)]:::done
  W --> R[(Redis)]:::done
  W --> CH[(ClickHouse)]:::done
  W --> S[(Uploads volume)]:::done
  R -->|jobs and events| K[Sidekiq workers]:::proc
  K --> PG
  K --> CH
```

Behind your own proxy (Coolify, Dokploy and other platforms) the `proxy` service is left out and your proxy routes the same hosts to `dashboard:3000` and `web:3000`.

## Services

| Service      | Role                                                   | Port (internal)        | Volume                       | Scaling                         |
| ------------ | ------------------------------------------------------ | ---------------------- | ---------------------------- | ------------------------------- |
| `proxy`      | Caddy with automatic TLS. `standalone` profile only.   | `80`, `443`, published | `caddy_data`, `caddy_config` | One per server                  |
| `postgres`   | PostgreSQL 16. The system of record.                   | `5432`                 | `pg_data`                    | Larger server                   |
| `redis`      | Redis 7. Queues, caches, dedup. AOF, no eviction.      | `6379`                 | `redis_data`                 | Larger server                   |
| `clickhouse` | ClickHouse 25.3. Events and analytics.                 | `8123`                 | `clickhouse_data`            | Larger server                   |
| `migrate`    | Migrates both databases, seeds the OAuth app and admin | None                   | `storage`                    | Runs on every start, then exits |
| `web`        | Rails (Puma): API, redirects, SDK, uploads             | `3000`                 | `storage`                    | `WEB_CONCURRENCY`               |
| `worker-1`   | Sidekiq: scheduler, events, batches                    | None                   | `storage`                    | **Never.** Exactly one replica. |
| `worker-2`   | Sidekiq: maintenance and device updates                | None                   | `storage`                    | Larger server                   |
| `dashboard`  | Next.js dashboard, configured from `.env`              | `3000`                 | None                         | Stateless                       |

Start order is fixed: the three databases become healthy, `migrate` exits with code 0, `web` becomes healthy, then the workers and dashboard start. [Sizing](https://docs.grovs.io/docs/self-hosting/operate/sizing) covers the process settings, and [Monitoring](https://docs.grovs.io/docs/self-hosting/operate/monitoring#containers) lists each healthcheck.

Uploaded images and exports live in the `storage` volume and are served through the API host. To use S3 or an S3-compatible store instead, set `ACTIVE_STORAGE_SERVICE=amazon` and the `AWS_S3_*` variables. Railway, Render, Easypanel and CapRover use object storage so the API and workers share the same uploads.

## Hostname routing

The backend routes by hostname. Fixed hosts live under your **app domain** (`SERVER_HOST`). Links live under your **links domain** (`DOMAIN_LIVE`) and **test links domain** (`DOMAIN_TEST`). These default to the app domain and `test.<links domain>`.

| Host                    | Serves                                                                              |
| ----------------------- | ----------------------------------------------------------------------------------- |
| `dashboard.`            | Dashboard UI                                                                        |
| `api.`                  | Dashboard API, uploaded assets, health check                                        |
| `sdk.`                  | Mobile, web and server SDK endpoints. This is the SDK `baseURL`.                    |
| `preview.`              | Link preview pages                                                                  |
| `mcp.`                  | MCP (AI tool) OAuth and API                                                         |
| `go.`                   | Reserved for the quick-link helper, which is not enabled on self-hosted deployments |
| `*.<links domain>`      | Per-project production link subdomains, for example `a1b2c3d4.acme.link`            |
| `*.<test links domain>` | Per-project test link subdomains, for example `a1b2c3d4.test.acme.link`             |

[DNS](https://docs.grovs.io/docs/self-hosting/get-started/deployment#dns) lists the records each host needs.
