Quick Start
Get SOAT running locally with Docker Compose in under five minutes.
Prerequisites
- Docker 24+
- Docker Compose v2+
- curl and jq (for the API examples below)
1. Create a Docker Compose file
Create a new directory and save the following as docker-compose.yml:
services:
database:
image: pgvector/pgvector:0.8.2-pg18-trixie
environment:
POSTGRES_DB: soat
POSTGRES_USER: soat_user
POSTGRES_PASSWORD: soat_password
volumes:
- postgres_data:/var/lib/postgresql
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U soat_user -d soat']
interval: 10s
timeout: 5s
retries: 5
ollama:
image: ollama/ollama:latest
volumes:
- ollama_cache:/root/.ollama
entrypoint:
- /bin/sh
- -c
- 'ollama serve > /dev/null 2>&1 & sleep 5 && ollama pull qwen3-embedding:0.6b > /dev/null 2>&1 && ollama pull qwen2.5:0.5b > /dev/null 2>&1 && wait'
healthcheck:
test:
[
'CMD-SHELL',
'ollama list | grep qwen3-embedding && ollama list | grep qwen3.5',
]
interval: 10s
timeout: 30s
retries: 60
start_period: 60s
server:
image: ttoss/soat
depends_on:
database:
condition: service_healthy
ollama:
condition: service_healthy
ports:
- '5047:5047'
environment:
SOAT_ADMIN_USERNAME: admin
SOAT_ADMIN_PASSWORD: Admin1234!
DATABASE_HOST: database
DATABASE_PORT: '5432'
DATABASE_NAME: soat
DATABASE_USER: soat_user
DATABASE_PASSWORD: soat_password
SECRETS_ENCRYPTION_KEY: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
FILES_STORAGE_DIR: /data/files
OLLAMA_BASE_URL: http://ollama:11434
EMBEDDING_PROVIDER: ollama
EMBEDDING_MODEL: qwen3-embedding:0.6b
EMBEDDING_DIMENSIONS: '1024'
volumes:
- files_data:/data/files
volumes:
postgres_data:
ollama_cache:
files_data:
Change the secrets before going to production
Replace SOAT_ADMIN_PASSWORD and SECRETS_ENCRYPTION_KEY with strong values before exposing SOAT outside of localhost. See Advanced Configuration for details.
2. Start the stack
docker compose up -d
This starts three services:
| Service | Description |
|---|---|
database | PostgreSQL 18 with pgvector for relational and vector storage |
ollama | Local LLM runtime (downloads qwen3-embedding and qwen2.5 models) |
server | SOAT REST API + MCP server, exposed on port 5047 |
The first run pulls Docker images and downloads the Ollama models, which may take a few minutes. Wait until all services are healthy:
docker compose ps
All three services should show healthy or running.
3. Log in and obtain a token
TOKEN=$(curl -s -X POST http://localhost:5047/api/v1/users/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin1234!"}' | jq -r '.token')
echo "Token: ${TOKEN:0:40}..."
All subsequent requests use this JWT in the Authorization header.
4. Create your first project
Projects are the primary resource boundary in SOAT. Every document, file, secret, and agent belongs to a project.
PROJECT=$(curl -s -X POST http://localhost:5047/api/v1/projects \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"my-first-project"}' | jq)
echo "$PROJECT" | jq .
PROJECT_ID=$(echo "$PROJECT" | jq -r '.id')
5. Send your first chat message
First, register Ollama as an AI provider for the project:
AI_PROVIDER_ID=$(curl -s -X POST http://localhost:5047/api/v1/ai-providers \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"project_id\": \"$PROJECT_ID\",
\"name\": \"Local Ollama\",
\"provider\": \"ollama\",
\"base_url\": \"http://ollama:11434\",
\"default_model\": \"qwen2.5:0.5b\"
}" | jq -r '.id')
echo "AI Provider: $AI_PROVIDER_ID"
Then send a stateless completion — no chat resource required:
curl -s -X POST http://localhost:5047/api/v1/chats/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"ai_provider_id\": \"$AI_PROVIDER_ID\",
\"messages\": [
{ \"role\": \"system\", \"content\": \"You are a helpful assistant.\" },
{ \"role\": \"user\", \"content\": \"What is the color of the sky?\" }
]
}" | jq '.choices[0].message.content'
You should see a short answer from the configured model running locally via Ollama.
6. What's next?
If you continue with the CLI docs and tutorials, path parameters use resource-specific kebab-case flags such as --project-id, --agent-id, and --session-id rather than a generic --id.
| Goal | Where to go |
|---|---|
| Understand the permission model | IAM module |
| Browse the current CLI command surface | CLI Commands Reference |
| Connect an LLM provider (OpenAI, Anthropic, …) | AI Providers module |
| Save a reusable chat configuration | Chats module |
| Define and run an agent | Agents module |
| Interact via MCP | MCP docs |
| Use the TypeScript SDK | SDK docs |
| Full REST reference | API Reference |
| Tune environment variables | Advanced Configuration |