Skip to main content
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 and a single .env.production, all defined in docker-stack.yml.
For a scalable, highly-available deployment, deploy Latitude to a Kubernetes cluster using our Helm chart instead — see Cluster deployment.

Quick start

1

Get the deploy files

Create a folder mkdir -p latitude and download all files into it:
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:
chmod +x docker/init-db.sh docker/seaweedfs/init.sh
2

Create your environment file

The .env.example file documents the neccessary production variables, create a copy of it and modify it to your needs.
cp .env.example .env.production
3

Generate the secrets

Generate a unique value for each key and paste them into .env.production:
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!
4

Configure an email transport

Choose one of the email providers supported (Mailgun, Sendgrid, SMTP…) and configure it so login (and other) emails reach you.
5

Pull the images and start

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.
To pin a specific release instead of latest, set LAT_IMAGE_TAG=X.Y.Z in .env.production (image tags have no leading v).
6

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.

What gets deployed

ContainerImagePurpose
weblatitudedata/webThe Latitude UI
apilatitudedata/apiPublic API & MCP server
ingestlatitudedata/ingestOTLP trace ingestion
workerslatitudedata/workersBullMQ background jobs
workflowslatitudedata/workflowsTemporal workers
migrationslatitudedata/migrationsOne-shot Postgres & ClickHouse migrations
postgrespgvector/pgvectorPrimary store + pgvector
clickhouseclickhouse/clickhouse-serverSpan/telemetry OLAP store
redis, redis-bullmqredisCache + queues
temporaltemporalio/auto-setupWorkflow engine (Postgres-backed)
seaweedfschrislusf/seaweedfsS3-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 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:
CapabilityProvidersFeature
GenerationAmazon Bedrock (default), Anthropic, OpenAI, Google, or any OpenAI-compatible endpointFlaggers, evaluations, issue summarization, taxonomy naming, AI generation
EmbeddingsVoyage AI (default), OpenAI, Google, or any OpenAI-compatible endpointSemantic trace/issue search, search highlights, issue clustering
RerankingVoyage AI (default) or Amazon BedrockIssue-discovery candidate matching
Every provider and model is selectable per feature through environment variables — see the AI configuration reference.

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) 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.

Backups

Persisted state lives in the postgres_data, clickhouse_data, seaweedfs_data, and redis_bullmq_data volumes. Back them up regularly:
  • Postgresdocker 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 (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.
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: 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:
# 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:
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.
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.

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:
DependencyRepointNotes
PostgresLAT_DATABASE_*, LAT_ADMIN_DATABASE_*Needs the vector (pgvector) extension and a schema-create admin role
ClickHouseLAT_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:
TemporalLAT_TEMPORAL_*Pointing to Temporal Cloud is recommended
Object storageLAT_STORAGE_*Point at any S3-compatible store or the local filesystem
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.

Next steps

Configuration reference

The full Latitude configurable environment-variable reference for self-hosting.