Skip to main content

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​

caution

These are the most common causes of "it's running but nothing works":

  • Tenant IDs must match. OKAREO_AIRGAP_TENANTS must contain identical tenant IDs in both the frontend and services containers.
  • Domain must match. OKAREO_APP_DOMAIN is set in both the UI and API containers and must be the user-facing UI URL.
  • Gateway must be browser-reachable. OKAREO_API_GATEWAY must 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​

VariableContainerDescriptionRequiredDefault
OKAREO_STANDALONE_AUTH_MODEfrontend, servicesAuth mode: static or ssoYesstatic
OKAREO_AIRGAP_TENANTSfrontendTenant config JSON (id + name + token)Yes--
OKAREO_AIRGAP_TENANTSservicesTenant config JSON (id + name, no tokens)Yes--
OKAREO_API_GATEWAYfrontendBrowser-reachable API URLYes--
OKAREO_APP_DOMAINfrontend, servicesUser-facing UI URLYes--
OKAREO_JWT_PRIVATE_KEYservicesBase64-encoded RSA private key for JWTsYes--
DATABASE_URLservicesPostgres connection stringYes--
AIRGAP_ONLYservicesMust be TRUE for standaloneYes--
EVAL_API_KEYservicesProvider API key for evaluationsYes--
EVAL_MODEL_NAMEservicesEvaluator model name (e.g. azure/<deployment>)Yes--
EMBEDDING_MODEL_NAMEservicesEmbedding model nameYes--
VLLM_API_KEYservicesvLLM API key (sk-none if no auth)Nosk-none
AIRGAP_NATS_URLservicesNATS connection URLNonats://localhost:4222
AIRGAP_WEBHOOK_NOTIFICATIONS_ENABLEDservicesEnable webhook notifications UINofalse
OKAREO_CUSTOM_ENDPOINT_SSL_VERIFYservicesVerify TLS for custom HTTP targetsNotrue
STDOUT_LOG_LEVELservicesLog level (10=DEBUG, 20=INFO, 30=WARNING)No30

Next step: Configure HTTPS / TLS Termination

Related: Model Configuration | Operations and Maintenance