diff --git a/src/pages/docs/cookbook/self-hosting/docker-compose-quickstart.mdx b/src/pages/docs/cookbook/self-hosting/docker-compose-quickstart.mdx
index 58c7fb85..74ee32c7 100644
--- a/src/pages/docs/cookbook/self-hosting/docker-compose-quickstart.mdx
+++ b/src/pages/docs/cookbook/self-hosting/docker-compose-quickstart.mdx
@@ -1,16 +1,51 @@
---
title: "Deploy the Full Open-Source AI Stack Locally With Docker Compose in 5 Minutes"
-description: "Clone the Future AGI repo, configure .env, run `docker compose up`, and start sending traces. Five commands to a complete self-hosted stack on your laptop."
+slug: "docker-compose-quickstart"
+description: "Clone the FutureAGI repo, configure .env, run `docker compose up`, and start sending traces. Five commands to a complete self-hosted stack on your laptop."
+date: "2026-05-21"
+author: "futureagi-engineering"
+products:
+ - "traceAI"
+ - "Agent Command Center"
+ - "fi.evals"
+frameworks:
+ - "Docker"
+ - "Docker Compose"
+difficulty: "beginner"
+time-to-complete: "5 minutes"
+tags:
+ - "self-hosting"
+ - "deployment"
+ - "docker"
+og-image: "/images/cookbooks/docker-compose-quickstart/og.webp"
+canonical: "https://docs.futureagi.com/docs/cookbook/self-hosting/docker-compose-quickstart"
+last-tested-date: "2026-05-21"
+last-tested-with:
+ python: "3.11"
+ docker-engine: "24.0+"
+ docker-compose: "v2.20+"
+code-repo-url: "https://github.com/future-agi/future-agi"
+page-type: "cookbook"
---
-
-Five commands and one `.env` edit gets you a complete self-hosted Future AGI stack running locally: frontend, backend, gateway, Postgres, ClickHouse, Redis, MinIO, Temporal, and PeerDB CDC. All 21 containers, no external dependencies. Your traces, datasets, and evals stay on your machine.
-
-
| Time | Difficulty |
|------|-----------|
| 5 min hands-on (10 to 15 min for first image build) | Beginner |
+
+Run the full FutureAGI platform (21 containers: dashboard, backend, gateway, Postgres, ClickHouse, Temporal, PeerDB) on your laptop with one `docker compose up`. Point your existing SDKs at `localhost:8000` instead of the cloud API. Every trace, dataset, eval, and model call stays on your machine. Five commands from clone to a verified trace in the dashboard.
+
+
+## What you'll build
+
+A fully local FutureAGI deployment running in Docker, with a smoke-test script proving traces flow end-to-end. By the end you will have:
+
+- The frontend, backend, gateway, Postgres, ClickHouse, Temporal, and PeerDB running on your machine (21 containers total).
+- Four required secrets generated and pinned in `.env`, plus optional provider keys (OpenAI, Anthropic) so the gateway can route real model calls.
+- A first user created via the frontend, or via the Django shell if you skip Mailgun.
+- One trace ingested from a local Python script, replicated through PeerDB into ClickHouse, and rendered in the dashboard at `http://localhost:3000`.
+- A short list of `docker compose` operations (logs, exec, down, down -v) you'll reuse daily.
+
- Docker Engine 24.0+ and Docker Compose v2.20+ (`docker --version`, `docker compose version`)
- 8+ GB RAM and 64+ GB disk allocated to Docker (Docker Desktop defaults of 2 to 4 GB will OOM-kill ClickHouse)
@@ -18,22 +53,65 @@ Five commands and one `.env` edit gets you a complete self-hosted Future AGI sta
- Python 3.11
-## Tutorial
+## Verify your environment
+
+Before you spend 10-15 minutes on the first image build, run this one-liner. It exits non-zero if any prerequisite is missing, which catches the most common reason the stack fails to start: low Docker memory.
+
+```bash
+docker --version && docker compose version && \
+docker info --format '{{.MemTotal}}' | \
+ awk '{ gb = $1/1024/1024/1024; print "Docker RAM: " gb " GB"; if (gb < 8) { print "WARN: increase Docker memory to 8+ GB before continuing"; exit 1 } }'
+```
+
+Expected output:
+
+```text
+Docker version 24.0.7, build afdd53b
+Docker Compose version v2.21.0
+Docker RAM: 12 GB
+```
+
+If you see `WARN: increase Docker memory to 8+ GB`, open Docker Desktop → Settings → Resources and bump the memory before continuing. Everything else fails downstream from this.
+
+## Why self-host
+
+Self-host when your traces contain PII, PHI, or proprietary IP that can't leave your network for compliance, or when you want every span, eval, and model call to round-trip on `localhost` instead of the public internet. The five steps below deploy the full FAGI platform on your laptop in one `docker compose up`.
+
+## The self-hosted stack
+
+One Docker Compose file orchestrates 21 containers across four layers: apps (frontend, backend, gateway), databases (Postgres, ClickHouse, Redis, MinIO), a workflow engine (Temporal), and a replication stack (PeerDB) that copies Postgres to ClickHouse for fast analytics. Point your code at `http://localhost:8000` and every trace, dataset, eval, and model call stays in the local Docker network.
+
+localhost:8000"] --> apps["FAGI apps
(frontend, backend, gateway)"]
+ apps --> pg[(Postgres)]
+ pg -.PeerDB CDC.-> ch[(ClickHouse)]
+ ch -.analytics reads.-> apps
+`} />
+
+The five steps below verify your prerequisites, clone the repo, set the four required secrets, start the stack, create your first user, and send a test trace to verify every layer is wired.
+There's no separate `pip install` or `npm install`. The repo *is* the install. Cloning gets you the Compose file, the application source, and the four Dockerfiles needed to build each service.
+
```bash
git clone https://github.com/future-agi/future-agi.git
cd future-agi
```
-The OSS build uses `futureagi/Dockerfile.oss` (Python 3.11 base) and builds locally, so there's nothing to pre-pull. First-build downloads about 6 GB of layers; subsequent boots reuse the cache.
+Expected output:
+
+```text
+Cloning into 'future-agi'...
+```
+
+The OSS build uses `futureagi/Dockerfile.oss` (Python 3.11 base) and builds locally, so there's nothing to pre-pull. First build downloads about 6 GB of layers; subsequent boots reuse the cache.
-Copy the template and rotate the four `CHANGEME` placeholders.
+Four secrets are baked into running services at boot (the Django session signer, the Postgres user password, the MinIO root password, and the gateway-to-backend internal key). They have to be set before `docker compose up` or services will refuse to start. Provider and Mailgun keys are optional but useful to add now so you don't have to restart later.
```bash
cp .env.example .env
@@ -73,17 +151,38 @@ See [Environment Variables](/docs/self-hosting/environment) for the full list of
+`docker compose up -d` builds any missing images, then starts all 21 containers in dependency order (data stores first, then workflow, then app services that depend on them). The whole stack is up when the backend logs `Application startup complete`. Detached mode (`-d`) means your shell returns immediately so you can tail one service's logs without losing the full output.
+
```bash
docker compose up -d
docker compose ps --format "{{.Names}} {{.Status}}"
```
-`-d` runs detached. The `--format` flag prints one line per service so you can scan health quickly without horizontal-scrolling the default table. The stack is ready when the backend logs `Application startup complete`:
+Expected output (abbreviated):
+
+```text
+frontend Up 12 seconds
+backend Up 10 seconds (healthy)
+worker Up 10 seconds
+gateway Up 8 seconds
+postgres Up 15 seconds (healthy)
+clickhouse Up 14 seconds (healthy)
+redis Up 15 seconds (healthy)
+...
+```
+
+All services should show `Up`. Watch the backend until startup completes:
```bash
docker compose logs -f backend
```
+Wait for this line before continuing:
+
+```text
+INFO: Application startup complete.
+```
+
What you just started:
| Layer | Services |
@@ -101,7 +200,7 @@ First boot builds from source. Subsequent `docker compose up` calls reuse the ca
-Three URLs are now live on your machine:
+Three URLs are live now because the stack is three apps in one (the FutureAGI frontend, the FutureAGI backend, and the PeerDB admin UI for inspecting CDC replication). For day-to-day use you only need the first one. The second is the API your SDKs will call. The third is for verifying replication is healthy if you ever debug analytics issues.
| Service | URL | Notes |
|---------|-----|-------|
@@ -132,7 +231,9 @@ u.save()
-Point the FutureAGI instrumentation SDK at your local backend with the `FI_BASE_URL` env var. Anything else is identical to the cloud setup.
+Sending a trace is the smoke test for the whole deployment. A single request exercises every layer: the SDK posts spans (structured event records that make up a trace) over HTTP to the backend, the backend writes to Postgres, PeerDB replicates to ClickHouse, the frontend reads from ClickHouse, and the gateway proxies the OpenAI call so the cost shows up in your local cost tracking. If the trace appears in the dashboard, every component is wired correctly.
+
+Point the FutureAGI instrumentation SDK at your local backend with the `FI_BASE_URL` env var. Everything else is identical to the cloud setup.
```bash
pip install fi-instrumentation-otel traceai-openai openai
@@ -143,8 +244,10 @@ import os
from fi_instrumentation import register, FITracer
from fi_instrumentation.fi_types import ProjectType
from traceai_openai import OpenAIInstrumentor
+from openai import OpenAI
-os.environ["FI_BASE_URL"] = "http://localhost:8000" # SDK sends spans to /tracer/v1/traces on this host
+# Point the SDK at the local backend instead of the cloud API
+os.environ["FI_BASE_URL"] = "http://localhost:8000"
trace_provider = register(
project_type=ProjectType.OBSERVE,
@@ -153,30 +256,63 @@ trace_provider = register(
OpenAIInstrumentor().instrument(tracer_provider=trace_provider)
tracer = FITracer(trace_provider.get_tracer("local-stack-smoke-test"))
-from openai import OpenAI
client = OpenAI()
-@tracer.agent(name="hello_agent")
-def hello_agent(q: str) -> str:
- r = client.chat.completions.create(
+@tracer.agent(name="support_agent")
+def support_agent(question: str) -> str:
+ completion = client.chat.completions.create(
model="gpt-4o-mini",
- messages=[{"role": "user", "content": q}],
+ messages=[{"role": "user", "content": question}],
)
- return r.choices[0].message.content
+ return completion.choices[0].message.content
+
+answer = support_agent("What is the refund policy for cancelled orders?")
+print(answer)
-print(hello_agent("Say hi to my self-hosted Future AGI stack."))
+# Flush before exit so the trace reaches the backend
trace_provider.force_flush()
```
-Open **Tracing -> local-stack-smoke-test** in the dashboard. You should see one parent span (`hello_agent`) with the OpenAI call nested underneath. If the trace shows up, every layer of the stack is wired correctly: backend ingestion, ClickHouse via PeerDB, frontend rendering, gateway routing.
+Expected output:
+
+```text
+Our refund policy for cancelled orders...
+```
+
+The exact answer varies, but any non-error response means the gateway routed to OpenAI successfully. Open **Tracing → local-stack-smoke-test** in the dashboard at `http://localhost:3000`. Verify:
+
+- One parent span named `support_agent`
+- The OpenAI call nested underneath with model, tokens, and latency
+- If both appear, every layer is wired: backend ingestion, Postgres, PeerDB replication to ClickHouse, and the frontend
+
+
+
+## Troubleshooting
+
+| Symptom | Likely cause | Fix | Verify |
+|---|---|---|---|
+| ClickHouse or backend exits with `OOMKilled` | Docker Desktop RAM too low (default is 2-4 GB) | Increase Docker memory to 8+ GB in Docker Desktop → Settings → Resources | `docker compose ps` shows ClickHouse `Up (healthy)` after a restart |
+| `backend` logs show `django.db.utils.OperationalError: could not connect to server` | Postgres hasn't finished initializing yet | Wait 15-30 seconds and restart the backend: `docker compose restart backend` | Backend logs show `Application startup complete.` |
+| Trace sent but nothing appears in the dashboard | PeerDB replication hasn't caught up, or `FI_BASE_URL` not set | Check `docker compose logs peerdb-server` for errors. Confirm `FI_BASE_URL=http://localhost:8000` is exported | Re-run the smoke-test script; trace appears in **Tracing → local-stack-smoke-test** within 10s |
+| `docker compose up` fails with `port is already allocated` | Another service (or a previous run) is using the same port | Run `docker compose down` first, or check `lsof -i :3000` / `lsof -i :8000` and kill the conflicting process | `docker compose up -d` returns without error and all 21 containers show `Up` |
+| Frontend loads but shows a blank page or 502 | Backend not healthy yet, or frontend can't reach it | Run `docker compose ps`; backend should show `healthy`. Check `docker compose logs frontend` for connection errors | `curl http://localhost:8000/health/` returns 200 |
+| `code-executor` fails on macOS or Cloud Run | Needs `privileged: true`, which Docker Desktop supports but serverless runtimes don't | Only supported on Docker Desktop (macOS/Linux/WSL2). Not compatible with ECS Fargate or Cloud Run | `docker compose ps code-executor` shows `Up`, not `Restarting` |
+
+## What you built
+
-You're running 21 containers, ingested a trace through the same code path the cloud uses, and rendered it in a dashboard at http://localhost:3000. Every byte stayed on your machine.
+21 containers running locally, a trace ingested through the same code path as the cloud, and a dashboard at `http://localhost:3000` rendering it. Every byte stayed on your machine.
+- Full FutureAGI platform (tracing, evals, gateway, prompt management) on infrastructure you control
+- SDK pointed at `localhost:8000`; switch back to `api.futureagi.com` by removing `FI_BASE_URL`
+- PeerDB replication keeping ClickHouse in sync with Postgres for fast analytics queries
+- Direct code access to every service for debugging or extending platform behavior
+
## Common operations
```bash
@@ -193,16 +329,13 @@ docker compose down
docker compose down -v
```
-## Explore further
+## Next steps
+
+Once the stack runs locally, the next operational moves before this becomes anything more than a laptop demo:
+
+1. **Pin image tags before going to production.** Replace `image: future-agi/backend:latest` with explicit versions in your `docker-compose.yml` so a build cache invalidation doesn't change behavior under you.
+2. **Move ClickHouse and Postgres to durable storage.** Bind them to managed volumes (or external hosts) so `docker compose down -v` can't wipe your traces. The fast-iteration ergonomics of the laptop setup become a production hazard otherwise.
+3. **Put a reverse proxy with TLS in front.** Terminate HTTPS at nginx or Caddy and route `frontend:3000` and `backend:8000` behind one host so cookies, CSRF, and SDK base URLs all use a single canonical domain.
+4. **Set up Postgres backups.** PeerDB rebuilds ClickHouse from Postgres, so Postgres is the single source of truth that needs a real backup policy (pg_dump cron at minimum; managed Postgres in prod).
-
-
- Full deployment modes (full stack, dev overlay, frontend-only)
-
-
- Every secret, port, and runtime flag the stack reads
-
-
- Reverse proxy, HTTPS, secret rotation before exposing the stack
-
-
+Reference: [Docker Compose deployment modes](/docs/self-hosting/docker-compose) for the full set of overlays (dev, frontend-only), and [Production hardening](/docs/self-hosting/production) for the full HTTPS + secret rotation checklist.