Common problems and their solutions. If your issue isn't listed here, open an issue on GitHub.
Check the logs:
docker compose logs backendCommon causes:
| Error message | Fix |
|---|---|
could not connect to server: Connection refused |
METADATA_DB_HOST is wrong or the DB container isn't ready yet. Wait 10s and retry. |
FATAL: password authentication failed |
Wrong METADATA_DB_PASSWORD in .env |
alembic.ini not found |
Run from the repo root, not a subdirectory |
Port 8000 already in use |
Change BACKEND_PORT=8001 in .env |
The backend container is starting slowly. Run docker compose ps — if the backend shows starting, wait 15 seconds and retry. If it shows exited, check docker compose logs backend.
The backend is trying to connect to your data warehouse (not the metadata DB). Check:
WAREHOUSE_HOST,WAREHOUSE_PORT,WAREHOUSE_USER,WAREHOUSE_PASSWORD,WAREHOUSE_DBin.env- Network connectivity: can the Docker container reach your warehouse?
- For cloud warehouses (BigQuery, Snowflake), check that firewall/VPC rules allow the container's IP.
For BigQuery — ensure GOOGLE_APPLICATION_CREDENTIALS points to a valid service account JSON inside the container:
# docker-compose.yml (add to backend service)
volumes:
- ./my-service-account.json:/app/sa.json
environment:
GOOGLE_APPLICATION_CREDENTIALS: /app/sa.jsonFor Snowflake — the WAREHOUSE_ACCOUNT env var must be in account.region.cloud format (e.g. xy12345.us-east-1.aws).
ObservaKit uses PyMySQL for MySQL. Install it:
pip install 'observakit[mysql]'
# or inside the container:
docker compose exec backend pip install pymysqlRedshift requires SSL by default on port 5439. Ensure your connector config doesn't disable SSL. The redshift-connector library handles this automatically — make sure you're using it:
pip install 'observakit[redshift]'Your timestamp_column values might be stored in local time instead of UTC, causing ObservaKit to compute a large lag. Check:
SELECT MAX(updated_at), NOW() AT TIME ZONE 'UTC' FROM your_table;If the difference doesn't match the expected lag, your warehouse stores timestamps in a different timezone. Either:
- Convert in the warehouse (
updated_at AT TIME ZONE 'UTC') - Or set
TZ=UTCin your warehouse session
Your table uses a different name. Common alternatives: modified_at, last_modified, _updated_at, ts. Update timestamp_column in kit.yml.
ObservaKit requires at least 3 historical data points before it will fire a volume anomaly (MIN_HISTORY_FOR_ANOMALY = 3). If you've just set it up, wait for 3 scheduled runs, or manually call POST /checks/volume three times.
Your anomaly_threshold is too low. The default is 0.3 (±30% deviation). If your table has natural daily variation larger than 30%, increase it:
volume:
tables:
- table: public.orders
anomaly_threshold: 0.5 # ±50%Soda Core is not installed in the backend container. Install it:
docker compose exec backend pip install soda-core-postgres
# or for other warehouses:
# pip install soda-core-bigquery
# pip install soda-core-snowflake
# pip install soda-core-mysqlOr add it to backend/requirements.txt and rebuild:
docker compose build backend && docker compose up -d backendSome versions of Soda Core don't support --json-output or output to stderr instead of stdout. Try upgrading:
pip install --upgrade soda-core-postgresIf the issue persists, use custom SQL checks instead of Soda:
quality:
custom_sql:
- name: "No null order IDs"
query: "SELECT COUNT(*) FROM orders WHERE order_id IS NULL"
assert: "result == 0"
table: ordersThis usually means the information_schema query is returning column types in a non-deterministic format (e.g. character varying vs varchar). This is a known PostgreSQL behaviour.
Workaround: ObservaKit normalises type strings, but if you see persistent false positives, open an issue with your PostgreSQL version and the column types affected.
Schema drift detects added and removed columns. A rename looks like one removal + one addition. This is intentional — ObservaKit cannot infer intent from information_schema alone. If you rename a column, you'll see two drift events: one removed and one added.
Set distribution.enabled: true in config/kit.yml.
Your drift_threshold is too low for the natural variation in your column. Increase it:
distribution:
tables:
- table: public.orders
drift_threshold: 0.20 # 20% shift (was 10%)Make sure you have at least one .yml file in config/contracts/. Copy the example:
cp config/contracts/example_orders.yml config/contracts/my_table.ymlAssertions are evaluated with result as the variable. Make sure your SQL returns a single scalar value:
rules:
- name: "Max lag"
sql: "SELECT COUNT(*) FROM orders WHERE created_at > NOW()"
assert: "result == 0" # ✅ correct
# assert: "COUNT(*) == 0" # ❌ wrong — use 'result', not the column name- Check
SLACK_WEBHOOK_URLis set and starts withhttps://hooks.slack.com/ - Test the webhook directly:
curl -X POST $SLACK_WEBHOOK_URL -d '{"text":"test from ObservaKit"}'
- Check the backend logs:
docker compose logs backend | grep -i slack
- Check
PAGERDUTY_ROUTING_KEYis set correctly in.env. - Check the
AlertLogtable in your metadata DB for details on each dispatch failure:SELECT sent_at, success, message FROM alert_logs ORDER BY sent_at DESC LIMIT 10;
- PagerDuty Events API v2 returns descriptive error messages in the
messagefield if a routing key or payload is invalid.
ObservaKit has a 60-minute deduplication window per table + alert type. If you're still seeing duplicates, check:
- The
alert_logstable in your metadata DB. - Whether you have multiple routing rules matching the same alert.
Use the suppression API to mute alerts:
curl -X POST http://localhost:8000/suppress \
-H "X-API-Key: $OBSERVAKIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"table_name": "public.orders", "suppress_hours": 4, "reason": "Planned migration"}'
# Or use the CLI:
observakit suppress orders 4hData warehouses occasionally reset connections during maintenance or high load.
Fix:
ObservaKit (v0.1.10+) includes an automatic retry engine via the @resilient_query decorator. This provides:
- 3 attempts per query.
- Exponential backoff (2, 4, 8 seconds).
- Automatic reconnection on stale sessions.
If you are still seeing connection issues, check if your warehouse has a narrow rate-limit or if the ObservaKit host's IP is being throttled by a firewall.
The GET /status endpoint queries several tables. For large metadata DBs, add indexes:
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_freshness_checked_at
ON freshness_records(checked_at);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_volume_recorded_at
ON volume_records(recorded_at);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_check_results_executed_at
ON check_results(executed_at);For very large tables, use a sample instead of a full scan. You can work around this with a custom SQL check that queries a TABLESAMPLE or a materialized view with pre-computed statistics.
- GitHub Issues: https://github.com/willowvibe/ObservaKit/issues
- Check the FAQ for common questions
- Run
make logsto see live backend output