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

# Single-host

> Run a production-grade Latitude instance on a single machine with Docker Compose or Docker Swarm.

Single-host runs the **entire Latitude product on one machine**: all five application services, a one-shot migrations job, and bundled infrastructure (Postgres, ClickHouse, Redis, Temporal, SeaweedFS). It uses the published [Docker images](https://hub.docker.com/u/latitudedata) and a single `.env.production`, all defined in [`docker-stack.yml`](https://github.com/latitude-dev/latitude-llm/blob/development/docker-stack.yml).

<Note>
  For a scalable, highly-available deployment, deploy Latitude to a Kubernetes cluster using our Helm chart instead — see [Cluster](/deployment/cluster) deployment.
</Note>

## Quick start

<Steps>
  <Step title="Get the deploy files">
    Create a folder `mkdir -p latitude` and download all files into it:

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    base=https://raw.githubusercontent.com/latitude-dev/latitude-llm/development
    curl -fsSL --create-dirs "$base/docker-stack.yml"              -o docker-stack.yml
    curl -fsSL --create-dirs "$base/.env.example"                  -o .env.example
    curl -fsSL --create-dirs "$base/docker/init-db.sh"             -o docker/init-db.sh
    curl -fsSL --create-dirs "$base/docker/seaweedfs/init.sh"      -o docker/seaweedfs/init.sh
    curl -fsSL --create-dirs "$base/docker/clickhouse/storage.xml" -o docker/clickhouse/storage.xml
    ```

    Give permissions to the initialization scripts for postgres and seaweedfs:

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    chmod +x docker/init-db.sh docker/seaweedfs/init.sh
    ```
  </Step>

  <Step title="Create your environment file">
    The `.env.example` file documents the neccessary production variables, create a copy of it and modify it to your needs.

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    cp .env.example .env.production
    ```
  </Step>

  <Step title="Generate the secrets">
    Generate a unique value for each key and paste them into `.env.production`:

    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    echo "LAT_MASTER_ENCRYPTION_KEY=$(openssl rand -hex 32)"
    echo "LAT_BETTER_AUTH_SECRET=$(openssl rand -hex 32)"
    ```

    Moreover, remember to change the default infrastructure passwords (`POSTGRES_PASSWORD_*`, `CLICKHOUSE_PASSWORD`...) too!
  </Step>

  <Step title="Configure an email transport">
    Choose one of the email providers supported (Mailgun, Sendgrid, SMTP...) and configure it so login (and other) emails reach you.
  </Step>

  <Step title="Pull the images and start">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    docker compose --env-file .env.production -f docker-stack.yml up -d
    ```

    The `migrations` container runs first and the application services wait for it to finish.

    <Tip>
      To pin a specific release instead of `latest`, set `LAT_IMAGE_TAG=X.Y.Z` in `.env.production` (image tags have no leading `v`).
    </Tip>
  </Step>

  <Step title="Create the first account">
    Open the web UI (at your `LAT_WEB_URL`) and register. Latitude sends a magic link through the email transport you configured; click it to finish signing in and create your organization.
  </Step>
</Steps>

## What gets deployed

| Container | Image | Purpose |
| - | - | - |
| `web` | `latitudedata/web` | The Latitude UI |
| `api` | `latitudedata/api` | Public API & MCP server |
| `ingest` | `latitudedata/ingest` | OTLP trace ingestion |
| `workers` | `latitudedata/workers` | BullMQ background jobs |
| `workflows` | `latitudedata/workflows` | Temporal workers |
| `migrations` | `latitudedata/migrations` | One-shot Postgres & ClickHouse migrations |
| `postgres` | `pgvector/pgvector` | Primary store + pgvector |
| `clickhouse` | `clickhouse/clickhouse-server` | Span/telemetry OLAP store |
| `redis`, `redis-bullmq` | `redis` | Cache + queues |
| `temporal` | `temporalio/auto-setup` | Workflow engine (Postgres-backed) |
| `seaweedfs` | `chrislusf/seaweedfs` | S3-compatible object store |

## Secrets

All configuration, secrets included, lives in `.env.production`. Keep `.env.production` out of version control and back it up with your other operational secrets.

See the [secrets reference](/deployment/configuration#secrets) for what each application secret does.

## AI features

The stack boots and core observability (ingest + trace viewing) works **without API keys**. AI-dependent features stay disabled until you add credentials for the providers you configure:

| Capability | Providers | Feature |
| - | - | - |
| Generation | Amazon Bedrock (default), Anthropic, OpenAI, Google, or any OpenAI-compatible endpoint | Flaggers, evaluations, signal summarization, taxonomy naming, AI generation |
| Embeddings | Voyage AI (default), OpenAI, Google, or any OpenAI-compatible endpoint | Semantic trace/signal search, search highlights, signal clustering |
| Reranking | Voyage AI (default) or Amazon Bedrock | Signal-discovery candidate matching |

Every provider and model is selectable per feature through environment variables — see the [AI configuration reference](/deployment/configuration#ai).

## Scaling

`docker-stack.yml` also runs under **Docker Swarm** (`docker swarm init`, then `docker stack deploy -c docker-stack.yml latitude`), which is the better fit for scaling. The stateless application services — `web`, `api`, `ingest`, `workers`, `workflows` — scale horizontally: raise their `deploy.replicas` in the file (or `docker service scale latitude_api=3`), and Swarm load-balances across replicas, including across extra machines joined with `docker swarm join`.

The bundled stateful services (Postgres, ClickHouse, Redis, Temporal, SeaweedFS) stay single-instance — for HA, point them at managed/external instances ([Bring your own infrastructure](#bring-your-own-infrastructure)) or move to the Cluster deployment. On plain Compose (one host), the services with no published ports — `workers` and `workflows` — can still scale directly: `docker compose -f docker-stack.yml up -d --scale workers=3`.

## Retention

A self-hosted deployment keeps telemetry for `LAT_TELEMETRY_RETENTION_DAYS` (default `3650`, ten years). With `LAT_BILLING_ENABLED` off, no ingest request is ever rejected for billing and no plan shortens retention. Usage is still counted per organization, so the credit figures on the billing page stay populated; nothing enforces them.

Retention is stamped on each row as it is ingested, so lowering or raising the value applies to new data only. To widen the window on data you have already ingested, update the stamp in ClickHouse:

```sql theme={"theme":{"light":"github-light","dark":"github-dark"}}
ALTER TABLE spans UPDATE retention_days = 3650 WHERE retention_days < 3650;
ALTER TABLE traces UPDATE retention_days = 3650 WHERE retention_days < 3650;
ALTER TABLE sessions UPDATE retention_days = 3650 WHERE retention_days < 3650;
ALTER TABLE trace_search_documents UPDATE retention_days = 3650 WHERE retention_days < 3650;
ALTER TABLE trace_message_occurrences UPDATE retention_days = 3650 WHERE retention_days < 3650;
```

ClickHouse deletes a row `retention_days + 30` days after its start time, so the extra month is your window to notice and correct a retention setting before anything is removed.

## Backups

Persisted state lives in the `postgres_data`, `clickhouse_data`, `seaweedfs_data`, and `redis_bullmq_data` volumes. Back them up regularly:

* **Postgres** — `docker compose -f docker-stack.yml exec postgres pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"` (the source of truth for projects, users, and metadata).
* **ClickHouse** — back up `clickhouse_data` or use [ClickHouse `BACKUP`](https://clickhouse.com/docs/en/operations/backup) (span/telemetry data).
* **Object store** — back up `seaweedfs_data` (or rely on your managed S3's durability).

## Upgrading

Pin a specific `X.Y.Z` tag for Latitude's images in production so upgrades are deliberate. For easiness, you can bump `LAT_IMAGE_TAG` to a new release (or use :latest) in `.env.production`. Then pull the new images and restart the services.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
docker compose --env-file .env.production -f docker-stack.yml pull
docker compose --env-file .env.production -f docker-stack.yml up -d
```

The `migrations` container runs automatically on every `up`, applying any new Postgres and ClickHouse migrations before the application services start.

## Health and observability

Every service exposes an HTTP health endpoint, wired as the container healthchecks in `docker-stack.yml` (`/health`; the web app uses `/api/health`; `workers`/`workflows` listen on internal ports 9090/9091). Check them with `docker compose -f docker-stack.yml ps` — every service should report `healthy`. OpenTelemetry export is available through the `LAT_OBSERVABILITY_*` variables in `.env.production`.

## TLS and reverse proxy

`docker-stack.yml` publishes `web` (3000), `api` (3001), and `ingest` (3002) on the host over plain HTTP. For anything internet-facing, put a TLS-terminating reverse proxy (Caddy, nginx, Traefik) in front, route your domain(s) to those ports, and set the `LAT_*_URL` / `*_ORIGINS` values to the public `https://` URLs.

## Custom domain

Latitude exposes three public surfaces, each typically on its own hostname behind your [reverse proxy](#tls-and-reverse-proxy): the **web** UI, the **API** (which also hosts the **MCP server**), and **ingest** (OTLP traces). `.env.example` ships these pointing at `localhost`; for a real domain you must update the public URLs **and** the origin allowlists, so that browsers, the API, and MCP clients all agree on where each service lives.

In `.env.production`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
# Public URLs — what users, SDKs, and MCP clients connect to
# (https, terminated by your reverse proxy)
LAT_WEB_URL=https://app.your-domain.com
LAT_API_URL=https://api.your-domain.com
LAT_INGEST_URL=https://ingest.your-domain.com

# Origin allowlists — must contain your web origin, or browser calls to the API
# are rejected and sign-in fails. Comma-separate to allow several origins.
LAT_TRUSTED_ORIGINS=https://app.your-domain.com
LAT_CORS_ALLOWED_ORIGINS=https://app.your-domain.com
```

Then restart so the services pick up the new values:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
docker compose --env-file .env.production -f docker-stack.yml up -d
```

The hostnames are yours to pick — three subdomains (above), three separate domains, or one domain with path routing — as long as each `LAT_*_URL` exactly matches (scheme included) the URL your reverse proxy serves, and the proxy forwards each host to the matching service port. Leave the `LAT_*_PORT` values as their defaults (`3000` / `3001` / `3002`): they're the ports each service listens on internally, not part of the public hostname.

<Note>
  **MCP clients** connect to `${LAT_API_URL}/v1/mcp` but sign in via OAuth against the **web** origin, so both `LAT_WEB_URL` and `LAT_API_URL` must be correct.
</Note>

## Bring your own infrastructure

Each infra service in `docker-stack.yml` is a clearly-marked, independently removable block. To use an existing or managed datastore, comment out its block and point the matching `LAT_*` value at your instance:

| Dependency | Repoint | Notes |
| - | - | - |
| Postgres | `LAT_DATABASE_*`, `LAT_ADMIN_DATABASE_*` | Needs the `vector` (pgvector) extension and a schema-create admin role |
| ClickHouse | `LAT_CLICKHOUSE_*` | Dedicated `LAT_CLICKHOUSE_DB` database |
| Redis (cache) | `LAT_REDIS_*` | Latitude namespaces all keys under `latitude:` |
| Redis (queue) | `LAT_BULLMQ_*` | Latitude namespaces all keys under `latitude:` |
| Temporal | `LAT_TEMPORAL_*` | Pointing to Temporal Cloud is recommended |
| Object storage | `LAT_STORAGE_*` | Point at any S3-compatible store or the local filesystem |

<Warning>
  If you remove a bundled service that another service lists under `depends_on` (`postgres`/`clickhouse` for `migrations`, `postgres` for `temporal`), delete that reference too, or Compose will refuse to start.
</Warning>

## Next steps

<Card title="Configuration reference" icon="settings" href="/deployment/configuration">
  The full Latitude configurable environment-variable reference for self-hosting.
</Card>


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