Deploy Standalone with Docker
This guide deploys Okareo Standalone on a single host using explicit docker run commands. Each container is started individually with the correct environment variables, network, and volume mounts.
1) Get Okareo container images
Path A: Pull from Docker Hub
docker login -u okareoartifacts -p <YOUR_ACCESS_TOKEN>
docker pull okareoartifacts/appfront:<version>
docker pull okareoartifacts/appservices:<version>
Replace <YOUR_ACCESS_TOKEN> with the read-only access token provided by Okareo. Use versioned tags only (no latest).
Path B: Load from .tar files (air-gapped)
docker load -i <frontend-tar-file>
docker load -i <services-tar-file>
Image names in the tar files already match the Docker Hub registry (okareoartifacts). Confirm the images loaded:
docker images | grep okareoartifacts
2) Create installation directory
sudo mkdir -p /opt/okareo
sudo chown "$USER":"$USER" /opt/okareo
cd /opt/okareo
Place your model.json in this directory. See Model Configuration for the full field reference.
3) Create bridge network
docker network create okareo
All containers join this network so they can reach each other by container name.
4) Start each container
PostgreSQL
docker run -d \
--name okareo_db \
--network okareo \
--restart always \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=okareo_db \
-e POSTGRES_LOG_STATEMENTS=all \
-e POSTGRES_LOG_DESTINATION=stderr \
-v okareo-db-data:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16.1 \
postgres -c max_connections=600
The named volume okareo-db-data persists data across container restarts and recreation. For production, consider using an external managed PostgreSQL and updating DATABASE_URL accordingly.
Redis
docker run -d \
--name okareo_redis \
--network okareo \
--restart always \
-p 6379:6379 \
redis:7.2-bookworm
NATS
docker run -d \
--name nats \
--network okareo \
--restart always \
-p 4222:4222 \
nats:latest
Okareo Services (backend API)
docker run -d \
--name okareo_services \
--network okareo \
--restart always \
-e OKAREO_STANDALONE_AUTH_MODE=static \
-e DATABASE_URL=postgresql+psycopg2://postgres:password@okareo_db:5432/okareo_db \
-e DB_USER=postgres \
-e DB_PASSWORD=password \
-e DB_NAME=okareo_db \
-e 'OKAREO_AIRGAP_TENANTS=[{"id":"<TENANT_ID>","name":"<DISPLAY_NAME>"}]' \
-e TENANT_DB=postgresql+psycopg2://postgres:password@okareo_db:5432/okareo_db \
-e SAVE_INFO_TO_LOCAL=true \
-e OKAREO_JWT_PRIVATE_KEY=<OKAREO_JWT_PRIVATE_KEY> \
-e OKAREO_APP_DOMAIN=<OKAREO_UI_URL> \
-e AIRGAP_ONLY=TRUE \
-e EVAL_API_KEY=<EVAL_API_KEY> \
-e EVAL_MODEL_NAME=azure/<your-evaluator-deployment-name> \
-e EMBEDDING_MODEL_NAME=azure/<your-embedding-deployment-name> \
-e VLLM_API_KEY=sk-none \
-e AIRGAP_NATS_URL=nats://nats:4222 \
-e AIRGAP_WEBHOOK_NOTIFICATIONS_ENABLED=false \
-e TASK_LISTENER_ENABLED=true \
-e TRACE_THREAD_POOL_SIZE=4 \
-e STDOUT_LOG_LEVEL=30 \
-e FILE_LOG_LEVEL=30 \
-e DISABLE_TELEMETRY=TRUE \
-v /opt/okareo/model.json:/app/model.json:ro \
-p 8000:8000 \
okareoartifacts/appservices:<version> \
bash -c "alembic upgrade head && python3 ./.init/add_tenant_info.py && uvicorn app.main:app --host 0.0.0.0 --port 8000 --timeout-keep-alive 3600 --ws-ping-interval 300 --ws-ping-timeout 300 --workers 9"
The container runs database migrations (alembic upgrade head) on startup before launching the API server.
Generate the JWT private key with:
openssl genrsa 2048 | openssl base64 -A
Replace all <PLACEHOLDER> values with your environment-specific settings. See the placeholder reference for descriptions of each value.
AIRGAP_NATS_URL uses the container name nats as the hostname because all containers share the okareo bridge network.
Okareo UI (frontend)
docker run -d \
--name okareo_ui \
--network okareo \
--restart always \
-e OKAREO_STANDALONE_AUTH_MODE=static \
-e 'OKAREO_AIRGAP_TENANTS=[{"id":"<TENANT_ID>","name":"<DISPLAY_NAME>","token":"<JWT>"}]' \
-e OKAREO_API_GATEWAY=<OKAREO_API_URL> \
-e OKAREO_APP_DOMAIN=<OKAREO_UI_URL> \
-p 3000:3000 \
okareoartifacts/appfront:<version> \
node server.js
5) Must-match constraints
These are the most common causes of "it's running but nothing works":
- Tenant IDs must match.
OKAREO_AIRGAP_TENANTSmust contain identical tenant IDs in both the frontend and services containers. - Domain must match.
OKAREO_APP_DOMAINis set in both the UI and API containers and must be the user-facing UI URL. - Gateway must be browser-reachable.
OKAREO_API_GATEWAYmust be reachable from the user's browser, not just from inside Docker.
6) Configure model providers
Before running evaluations, configure your model providers in model.json. See Model Configuration for the full field reference, provider examples, and recommended defaults.
7) Verify the installation
Set up verification URLs:
export OKAREO_UI_URL="https://<UI_HOST>:3000"
export OKAREO_API_URL="https://<API_HOST>:8000"
export OKAREO_API_KEY="<OKAREO_AIRGAP_TOKEN>"
Health check
The health endpoint requires a POST request and no authentication:
curl -sS -X POST "${OKAREO_API_URL}/v0/health"
Expected response: {"status":"I am healthy"}
UI access
Open ${OKAREO_UI_URL} in a browser. The login page should load without repeated API errors in the browser console.
API smoke test
curl -sS -X POST "${OKAREO_API_URL}/v0/register_model" \
-H "api-key: ${OKAREO_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"name":"My Test Model"}'
curl -sS -X GET "${OKAREO_API_URL}/v0/models_under_test" \
-H "api-key: ${OKAREO_API_KEY}"
8) Troubleshooting
Check container state:
docker ps --filter "network=okareo" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
View logs:
docker logs --tail=200 okareo_services
docker logs --tail=200 okareo_ui
docker logs --tail=200 okareo_db
Ports already in use
Symptoms: Container fails to start with bind: address already in use.
Fix: Identify the conflicting process, free the port, or change the host port mapping in the docker run command. Default ports: 3000 (UI), 8000 (API), 5432 (Postgres), 6379 (Redis), 4222 (NATS).
UI loads but cannot call the API
Symptoms: UI renders but shows errors, empty screens, or failing network calls.
Likely cause: OKAREO_API_GATEWAY points to a URL not reachable from user browsers.
Fix: Set OKAREO_API_GATEWAY to the API URL reachable from user machines, then recreate the frontend container. Verify the API is reachable:
curl -sS -X POST "<OKAREO_API_URL>/v0/health"
401 Unauthorized or tenant mismatch
Symptoms: API calls return 401. UI shows tenant-related errors.
Fix: Confirm that tenant IDs in OKAREO_AIRGAP_TENANTS match between frontend and services containers. The JWT token in the frontend tenant config must belong to a configured tenant. Restart both containers after making changes.
model.json not mounted or not readable
Symptoms: Services logs mention missing /app/model.json.
Fix: Verify the file on the host:
ls -l /opt/okareo/model.json
Verify inside the container:
docker exec -it okareo_services sh -lc 'ls -l /app/model.json && head -n 5 /app/model.json'
Services container cannot reach model endpoint
Symptoms: Evaluations fail with timeouts or connection refused in logs.
Fix: Test connectivity from inside the container:
docker exec -it okareo_services sh -lc 'curl -sS http://{MODEL_HOST}:{PORT}/v1/models | head'
If using host.docker.internal on Linux and it does not resolve, use a routable host IP instead.
Custom HTTP target with self-signed certificate
Symptoms: SSL verification errors such as CERTIFICATE_VERIFY_FAILED.
Recommended fix: Mount your internal CA into the services container. See Operations and Maintenance for details.
Escape hatch: Set OKAREO_CUSTOM_ENDPOINT_SSL_VERIFY=false on the services container. This disables TLS certificate validation for Custom HTTP Endpoint targets only. All other Okareo outbound HTTPS connections retain full verification.
Database migrations failing
Symptoms: Services logs show migration errors or database connection failures.
Fix: Confirm the database container is running and healthy:
docker logs --tail=200 okareo_db
Verify that DATABASE_URL matches the Postgres credentials. If intentionally resetting, stop and remove all containers and the okareo-db-data volume before restarting.
9) Environment variable reference
| Variable | Container | Description | Required | Default |
|---|---|---|---|---|
OKAREO_STANDALONE_AUTH_MODE | frontend, services | Auth mode: static or sso | Yes | static |
OKAREO_AIRGAP_TENANTS | frontend | Tenant config JSON (id + name + token) | Yes | -- |
OKAREO_AIRGAP_TENANTS | services | Tenant config JSON (id + name, no tokens) | Yes | -- |
OKAREO_API_GATEWAY | frontend | Browser-reachable API URL | Yes | -- |
OKAREO_APP_DOMAIN | frontend, services | User-facing UI URL | Yes | -- |
OKAREO_JWT_PRIVATE_KEY | services | Base64-encoded RSA private key for JWTs | Yes | -- |
DATABASE_URL | services | Postgres connection string | Yes | -- |
AIRGAP_ONLY | services | Must be TRUE for standalone | Yes | -- |
EVAL_API_KEY | services | Provider API key for evaluations | Yes | -- |
EVAL_MODEL_NAME | services | Evaluator model name (e.g. azure/<deployment>) | Yes | -- |
EMBEDDING_MODEL_NAME | services | Embedding model name | Yes | -- |
VLLM_API_KEY | services | vLLM API key (sk-none if no auth) | No | sk-none |
AIRGAP_NATS_URL | services | NATS connection URL | No | nats://localhost:4222 |
AIRGAP_WEBHOOK_NOTIFICATIONS_ENABLED | services | Enable webhook notifications UI | No | false |
OKAREO_CUSTOM_ENDPOINT_SSL_VERIFY | services | Verify TLS for custom HTTP targets | No | true |
STDOUT_LOG_LEVEL | services | Log level (10=DEBUG, 20=INFO, 30=WARNING) | No | 30 |
Next step: Configure HTTPS / TLS Termination
Related: Model Configuration | Operations and Maintenance