Deployment (Docker)¶
SynthOrg runs as two Docker containers -- a Python backend API and an Nginx + React web dashboard. This guide covers production deployment, environment configuration, security hardening, and operations.
Architecture¶
graph LR
User["Browser"]
Web["web<br/><small>nginx:8080</small><br/><small>UID 101</small>"]
Backend["backend<br/><small>uvicorn:3001</small><br/><small>UID 65532</small>"]
Volume["synthorg-data<br/><small>SQLite + Memory</small>"]
User -->|":3000"| Web
Web -->|"/api/* proxy"| Backend
Web -->|"/api/v1/ws proxy"| Backend
Backend --> Volume
| Container | Image | Purpose |
|---|---|---|
| backend | ghcr.io/aureliolo/synthorg-backend |
Litestar API server (Chainguard distroless, non-root) |
| web | ghcr.io/aureliolo/synthorg-web |
Nginx + React 19 SPA (proxies API and WebSocket) |
Quick Deploy¶
See the Quickstart Tutorial for a complete walkthrough and the User Guide for all CLI commands.
Environment Variables¶
All environment variables are configured in docker/.env (copy from docker/.env.example):
Required¶
| Variable | Description |
|---|---|
SYNTHORG_JWT_SECRET |
JWT signing secret. Must be >= 32 characters of URL-safe base64. Never commit to version control. Generate: python -c "import secrets; print(secrets.token_urlsafe(48))" |
SYNTHORG_SETTINGS_KEY |
Fernet encryption key for sensitive settings at rest. Must be a valid Fernet key. Generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" |
Optional¶
| Variable | Default | Description |
|---|---|---|
SYNTHORG_DB_PATH |
/data/synthorg.db |
SQLite database path (inside container) |
SYNTHORG_MEMORY_DIR |
/data/memory |
Agent memory storage directory |
SYNTHORG_PERSISTENCE_BACKEND |
sqlite |
Persistence backend |
SYNTHORG_MEMORY_BACKEND |
mem0 |
Memory backend |
SYNTHORG_LOG_DIR |
/data/logs |
Log file directory |
SYNTHORG_LOG_LEVEL |
info |
Log level: debug, info, warning, error, critical |
BACKEND_PORT |
3001 |
Host port for the backend API |
WEB_PORT |
3000 |
Host port for the web dashboard |
MEM0_TELEMETRY |
false |
Mem0 telemetry (disable to reduce overhead) |
DOCKER_HOST |
(unset) | Docker socket for agent code execution sandbox (optional) |
First-Run Setup¶
After the containers are running, open http://localhost:3000. The setup wizard appears on a fresh install. See the User Guide for the full wizard walkthrough.
Container Details¶
Backend¶
- Base image: Chainguard Python distroless (no shell, continuously scanned)
- Build: 3-stage (builder -> setup -> runtime) for minimal attack surface
- User: UID 65532 (distroless non-root)
- Health check:
GET /api/v1/health(10s interval, 5s timeout, 3 retries, 30s start period) - Entry point:
uvicorn synthorg.api.app:create_app --factory --no-access-log
Web¶
- Base image:
nginxinc/nginx-unprivileged(Alpine-based) - User: UID 101 (nginx)
- Health check:
wget --spider http://127.0.0.1:8080/(10s interval, 3s timeout, 3 retries) - Routing: SPA routing (
try_files $uri /index.html), API proxy to backend, WebSocket proxy - Caching:
/index.htmlis no-cache;/assets/*is immutable with 1-year max-age (content-hashed filenames) - Static compression: pre-compressed
.gzfiles served viagzip_static on
Security Hardening¶
The Docker Compose configuration follows the CIS Docker Benchmark v1.6.0:
| Control | Setting | CIS Reference |
|---|---|---|
| No new privileges | security_opt: [no-new-privileges:true] |
5.3 |
| Drop all capabilities | cap_drop: [ALL] |
5.12 |
| Read-only root filesystem | read_only: true + tmpfs mounts |
5.25 |
| PID limits | 256 (backend), 64 (web) | 5.28 |
| Memory limits | 4G (backend), 256M (web) | -- |
| CPU limits | 2.0 (backend), 0.5 (web) | -- |
| Log rotation | json-file, 10MB max, 3 files | -- |
| Tmpfs security | noexec,nosuid,nodev on /tmp |
-- |
Security Headers (Nginx)¶
The web container sets the following response headers:
X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: geolocation=(), camera=(), microphone=()Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'Strict-Transport-Security: max-age=63072000(2 years)
Volumes & Data Persistence¶
The synthorg-data Docker volume persists all application data:
- SQLite database (
/data/synthorg.db) - Agent memory files (
/data/memory/) - Log files (
/data/logs/)
Backup¶
synthorg backup # create a backup
synthorg backup --list # list available backups
synthorg backup --restore # restore from backup
For manual Docker Compose deployments, back up the synthorg-data volume directly.
Wipe & Reset¶
Networking¶
Both containers run on the synthorg-net Docker network. The web container proxies API requests to the backend:
http://localhost:3000/api/*->http://backend:3001/api/*ws://localhost:3000/api/v1/ws->ws://backend:3001/api/v1/ws
Local LLM Providers¶
To use a local LLM like Ollama running on the host machine, configure the provider with host.docker.internal:
Image Verification¶
SynthOrg container images are signed with cosign keyless signatures and include SLSA Level 3 provenance attestations.
synthorg start and synthorg update automatically verify signatures before pulling images. If verification fails (e.g. in an air-gapped environment):
Updates¶
The CLI re-launches itself after binary replacement so the remaining steps use the new version. If the compose template has structural changes, the diff is shown for approval before applying.
Channels¶
| Channel | Description |
|---|---|
stable |
Stable releases only (default) |
dev |
Pre-release builds on every push to main |
synthorg config set channel dev # opt in to pre-release builds
synthorg config set channel stable # switch back to stable
Auto-Cleanup¶
Automatically remove old container images after updates (keeps current + previous version):
Production Checklist¶
Production readiness checklist
- Generate strong secrets for
SYNTHORG_JWT_SECRETandSYNTHORG_SETTINGS_KEY - Set
SYNTHORG_LOG_LEVELtowarningorinfo(notdebug) - Review and set appropriate
BACKEND_PORTandWEB_PORT - Configure budget limits to prevent runaway LLM costs
- Set autonomy level to
semiorsupervised(notfull) for production orgs - Enable security audit logging (
security.audit_enabled: true) - Set up backup schedule (
synthorg backup) - Place behind a reverse proxy with TLS termination
- Restrict Docker socket access if using the sandbox feature
- Monitor container health via
synthorg statusor Docker health checks
Troubleshooting¶
Health Check¶
synthorg doctor # run diagnostics
synthorg status # check container health
synthorg logs # view container logs
Common Issues¶
| Issue | Solution |
|---|---|
| Backend container keeps restarting | Check synthorg logs for startup errors. Verify SYNTHORG_JWT_SECRET and SYNTHORG_SETTINGS_KEY are set. |
| Dashboard shows "Connection refused" | Ensure the web container is healthy and WEB_PORT is not in use. |
| Image pull fails | Check network connectivity. If air-gapped, use --skip-verify. |
| "Port already in use" | Change BACKEND_PORT or WEB_PORT in docker/.env. |
| Ollama not connecting | Use http://host.docker.internal:11434 as the base URL. |
See Also¶
- Quickstart Tutorial -- get started in 5 minutes
- User Guide -- CLI commands and setup wizard
- Security -- security architecture reference
- Company Configuration -- full configuration reference