Installation
This page covers the step-by-step deployment of a self-hosted XTM One instance using Docker Compose. Before starting, read the Overview to understand the architecture and the infrastructure requirements.
Get the deployment files
Check Releases for the latest version tag.
Option A — clone a release (builds images from source):
git clone --branch <version-tag> --depth 1 https://github.com/XTM-One-Platform/xtm-one.git && cd xtm-one
Option B — use pre-built images (no build required):
VERSION="<version-tag>"
mkdir xtm-one && cd xtm-one
curl -fSLO "https://raw.githubusercontent.com/XTM-One-Platform/xtm-one/${VERSION}/docker-compose.yml"
curl -fSLO "https://raw.githubusercontent.com/XTM-One-Platform/xtm-one/${VERSION}/.env.sample"
Then create an override to pull images instead of building:
# docker-compose.override.yml
services:
platform:
image: xtmone/platform:<version-tag>
build: !reset null
worker:
image: xtmone/worker:<version-tag>
build: !reset null
Configure environment
cp .env.sample .env
chmod 600 .env
touch .gitignore
grep -qxF ".env" .gitignore || echo ".env" >> .gitignore
Edit .env and fill in the required values. At minimum you must set:
SECRET_KEY— generate withpython -c "import secrets; print(secrets.token_urlsafe(48))"ADMIN_EMAILandADMIN_PASSWORDBASE_URLandFRONTEND_URL— the public URL of your instanceDB_PASSWORD— password for the bundled PostgreSQLS3_ACCESS_KEYandS3_SECRET_KEY— credentials for the bundled MinIO
The complete list of available parameters is documented in the Configuration section.
Start
The platform waits for PostgreSQL, Redis, and MinIO health checks to pass, then automatically runs migrations, creates the admin user, and seeds default templates.
Verify
docker compose ps # all services should show "healthy"
curl localhost:4000/api/health/ready # 200 = fully ready
Configure the platform
- Open your
BASE_URLin a browser. - Sign in with
ADMIN_EMAIL/ADMIN_PASSWORD. - Go to
Settings→AI Models— add at least one LLM provider. - Configure authentication if needed — see Authentication.
Network requirements
Inbound
| Port | Purpose |
|---|---|
| 4000/tcp | Platform HTTP + WebSocket |
Reverse proxy
The platform serves plain HTTP. For production, terminate TLS with any reverse proxy (Nginx, Caddy, Traefik, cloud load balancer) and forward to port 4000. Your proxy configuration must handle:
| Requirement | Why | Nginx example |
|---|---|---|
| Forward client IP | Needed for rate limiting and audit logs | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; |
| Forward protocol | OAuth callbacks need the real scheme | proxy_set_header X-Forwarded-Proto $scheme; |
| Allow large request bodies | Knowledge base uploads can reach 50 MB | client_max_body_size 50m; |
| WebSocket upgrade | Required for chat streaming | proxy_set_header Upgrade $http_upgrade; |
| Generous read timeout | LLM calls can take 60s+ | proxy_read_timeout 300s; |
Internal (between containers)
| From | To | Port |
|---|---|---|
| Platform, Worker | PostgreSQL | 5432 |
| Platform, Worker | Redis | 6379 |
| Platform, Worker | MinIO/S3 | 9000 |
Air-gapped deployments
Air-gapped deployments work if you use a local AI provider (Ollama or any OpenAI-compatible endpoint on your network) and the built-in MinIO for storage. To get Docker images into an air-gapped network:
# On a machine with internet access — export the images (pre-built images setup from Option B):
docker compose pull
docker save xtmone/platform:<version-tag> xtmone/worker:<version-tag> pgvector/pgvector:pg17 redis:8-alpine minio/minio:latest > xtm-one-images.tar
# Transfer xtm-one-images.tar to the air-gapped host, then load:
docker load < xtm-one-images.tar
Health checks
| Endpoint | Use for | Returns 200 when |
|---|---|---|
GET /api/health |
Liveness probe | HTTP server is running |
GET /api/health/ready |
Readiness probe | Database connected, migrations and seeding complete |
The Docker Compose healthcheck uses /api/health/ready so dependent services wait for full startup.
Scaling workers
The default docker-compose.yml sets container_name: worker, which limits you to one instance. To run multiple workers:
- Remove (or comment out) the
container_name: workerline. - Scale:
Each worker creates its own connection pools. Recalculate the PostgreSQL connection budget accordingly.
Next step
Configure the platform parameters in the Configuration section, and read the Upgrade procedure before your first version bump.