Files
insights/compose.base.yml
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

594 lines
20 KiB
YAML

#
# docker-compose base file used for local development, hobby deploys, and other compose use cases.
#
services:
proxy:
image: caddy
entrypoint: sh
restart: always
command: -c 'set -x && echo "$$CADDYFILE" > /etc/caddy/Caddyfile && echo "$$CADDY_EXTRA_CONFIG" >> /etc/caddy/Caddyfile && exec caddy run -c /etc/caddy/Caddyfile'
volumes:
- /root/.caddy
environment:
CADDY_TLS_BLOCK: ''
CADDY_HOST: 'http://localhost:8000'
CADDY_EXTRA_CONFIG: ''
CADDYFILE: |
{
${CADDY_TLS_BLOCK:-}
}
${CADDY_HOST:-http://localhost:8000} {
@replay-capture {
path /s
path /s/
path /s/*
}
@capture-ai {
path /i/v0/ai
path /i/v0/ai/
path /i/v0/ai/*
}
@capture {
path /e
path /e/
path /e/*
path /i/v0
path /i/v0/
path /i/v0/*
path /batch
path /batch/
path /batch/*
path /capture
path /capture/
path /capture/*
}
@capture-logs {
path /i/v1/logs
path /i/v1/logs/
path /i/v1/logs/*
}
@flags {
path /flags
path /flags/
path /flags/*
}
@webhooks {
path /public/webhooks
path /public/webhooks/
path /public/webhooks/*
path /public/m/
path /public/m/*
}
@livestream {
path /livestream
path /livestream/
path /livestream/*
}
handle @livestream {
uri strip_prefix /livestream
reverse_proxy livestream:8080 {
flush_interval -1
}
}
handle @capture-ai {
reverse_proxy capture-ai:3000
}
handle @capture {
reverse_proxy capture:3000
}
handle @replay-capture {
reverse_proxy replay-capture:3000
}
handle @capture-logs {
reverse_proxy capture-logs:4318
}
handle @flags {
reverse_proxy feature-flags:3001
}
handle @webhooks {
reverse_proxy plugins:6738
}
handle {
reverse_proxy web:8000
}
}
db:
image: ghcr.io/hanzoai/sql:latest
restart: on-failure
environment:
POSTGRES_USER: insights
POSTGRES_DB: insights
POSTGRES_PASSWORD: insights
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U insights']
interval: 5s
timeout: 30s
volumes:
- ./docker/postgres-init-scripts:/docker-entrypoint-initdb.d
kv:
image: ghcr.io/hanzoai/kv:latest
restart: on-failure
command: redis-server --maxmemory-policy allkeys-lru --maxmemory 200mb
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 3s
timeout: 10s
retries: 10
datastore:
#
# Note: please keep the default version in sync across
# `insights` and the `charts-datastore` repos
#
image: ${DATASTORE_SERVER_IMAGE:-ghcr.io/hanzoai/datastore:26.6.1.1}
restart: on-failure
environment:
DATASTORE_SKIP_USER_SETUP: 1
KAFKA_HOSTS: 'kafka:9092'
healthcheck:
test: ['CMD-SHELL', 'wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1'] # nosemgrep: trailofbits.generic.wget-unencrypted-url.wget-unencrypted-url
interval: 3s
timeout: 10s
retries: 10
kafka:
image: docker.redpanda.com/redpandadata/redpanda:latest
restart: on-failure
command:
- redpanda
- start
- --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092
- --advertise-kafka-addr internal://kafka:9092,external://localhost:19092
- --pandaproxy-addr internal://0.0.0.0:8082,external://0.0.0.0:18082
- --advertise-pandaproxy-addr internal://kafka:8082,external://localhost:18082
- --schema-registry-addr internal://0.0.0.0:8081,external://0.0.0.0:18081
- --rpc-addr kafka:33145
- --advertise-rpc-addr kafka:33145
- --mode dev-container
- --smp 2
- --memory 3G
- --reserve-memory 500M
- --overprovisioned
- --set redpanda.empty_seed_starts_cluster=false
- --seeds kafka:33145
- --set redpanda.auto_create_topics_enabled=true
volumes:
- redpanda-data:/var/lib/redpanda/data
environment:
ALLOW_PLAINTEXT_LISTENER: 'true'
healthcheck:
test: curl -f http://localhost:9644/v1/status/ready || exit 1
interval: 3s
timeout: 10s
retries: 10
kafka_ui:
image: provectuslabs/kafka-ui:latest
restart: on-failure
environment:
KAFKA_CLUSTERS_0_NAME: local
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://kafka:8081
DYNAMIC_CONFIG_ENABLED: 'true'
objectstorage:
image: ghcr.io/hanzoai/storage:v2026.2.0
restart: on-failure
environment:
MINIO_ROOT_USER: object_storage_root_user
MINIO_ROOT_PASSWORD: object_storage_root_password
entrypoint: sh
command: -c 'mkdir -p /data/insights /data/ducklake-dev /data/ai-blobs && minio server --address ":19000" --console-address ":19001" /data' # create the 'insights', 'ducklake-dev', and 'ai-blobs' buckets before starting the service
objectstorage-azure:
image: mcr.microsoft.com/azure-storage/azurite:3.35.0
restart: on-failure
profiles: ['batch-exports']
command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --location /data
ports:
- '10000:10000'
maildev:
image: maildev/maildev:2.0.5
restart: on-failure
healthcheck:
# nosemgrep: trailofbits.generic.wget-unencrypted-url.wget-unencrypted-url
test: wget -qO- http://127.0.0.1:1080/healthz || exit 1
interval: 3s
timeout: 1s
retries: 3
flower:
image: mher/flower:2.0.0
restart: on-failure
environment:
FLOWER_PORT: 5555
CELERY_BROKER_URL: redis://kv:6379
worker: &worker
command: ./bin/docker-worker-celery --with-scheduler
restart: on-failure
environment: &worker_env
OTEL_SDK_DISABLED: 'true'
DISABLE_SECURE_SSL_REDIRECT: 'true'
IS_BEHIND_PROXY: 'true'
DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
DATASTORE_HOST: 'datastore'
DATASTORE_DATABASE: 'insights'
DATASTORE_SECURE: 'false'
DATASTORE_VERIFY: 'false'
DATASTORE_API_USER: 'api'
DATASTORE_API_PASSWORD: 'apipass'
DATASTORE_APP_USER: 'app'
DATASTORE_APP_PASSWORD: 'apppass'
API_QUERIES_PER_TEAM: '{"1": 100}'
KAFKA_HOSTS: 'kafka'
REDIS_URL: 'redis://kv:6379/'
PGHOST: db
PGUSER: insights
PGPASSWORD: insights
DEPLOYMENT: hobby
CDP_API_URL: 'http://plugins:6738'
FLAGS_REDIS_ENABLED: false
web:
<<: *worker
command: ./bin/start-backend & ./bin/start-frontend
restart: on-failure
capture:
image: ghcr.io/hanzoai/insights-capture:latest
build:
context: rust/
args:
BIN: capture
restart: on-failure
environment:
ADDRESS: '0.0.0.0:3000'
KAFKA_TOPIC: 'events_plugin_ingestion'
KAFKA_HOSTS: 'kafka:9092'
REDIS_URL: 'redis://kv:6379/'
CAPTURE_MODE: events
RUST_LOG: 'info,rdkafka=warn'
replay-capture:
image: ghcr.io/hanzoai/insights-capture:latest
build:
context: rust/
args:
BIN: capture
restart: on-failure
environment:
ADDRESS: '0.0.0.0:3000'
KAFKA_TOPIC: 'session_recording_snapshot_item_events'
KAFKA_HOSTS: 'kafka:9092'
REDIS_URL: 'redis://kv:6379/'
CAPTURE_MODE: recordings
capture-ai:
image: ghcr.io/hanzoai/insights-capture:latest
build:
context: rust/
args:
BIN: capture
restart: on-failure
environment:
ADDRESS: '0.0.0.0:3000'
KAFKA_TOPIC: 'events_plugin_ingestion'
KAFKA_HOSTS: 'kafka:9092'
REDIS_URL: 'redis://kv:6379/'
CAPTURE_MODE: events
RUST_LOG: 'info,rdkafka=warn'
# AI endpoint S3 blob storage (MinIO)
AI_S3_BUCKET: ai-blobs
AI_S3_PREFIX: 'llma/'
AI_S3_ENDPOINT: 'http://objectstorage:19000'
AI_S3_REGION: us-east-1
AI_S3_ACCESS_KEY_ID: object_storage_root_user
AI_S3_SECRET_ACCESS_KEY: object_storage_root_password
capture-logs:
image: ghcr.io/hanzoai/insights-capture-logs:latest
build:
context: rust/
args:
BIN: capture-logs
restart: on-failure
environment:
BIND_HOST: '0.0.0.0'
BIND_PORT: '4318'
RUST_LOG: info,rdkafka=warn
RUST_BACKTRACE: '1'
KAFKA_HOSTS: kafka:9092
JWT_SECRET: '<randomly generated secret key>'
KAFKA_TOPIC: logs_ingestion
networks:
- otel_network
- default
property-defs-rs:
image: ghcr.io/hanzoai/insights-property-defs:latest
build:
context: rust/
args:
BIN: property-defs-rs
restart: on-failure
environment:
DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
KAFKA_HOSTS: 'kafka:9092'
SKIP_WRITES: 'false'
SKIP_READS: 'false'
FILTER_MODE: 'opt-out'
feature-flags:
image: ghcr.io/hanzoai/insights-feature-flags:latest
build:
context: rust/
args:
BIN: feature-flags
restart: on-failure
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3001/_readiness']
interval: 5s
timeout: 5s
retries: 12
start_period: 10s
volumes:
- ./share:/share
environment:
WRITE_DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
READ_DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
PERSONS_WRITE_DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
PERSONS_READ_DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
MAXMIND_DB_PATH: '/share/GeoLite2-City.mmdb'
# Shared Redis for non-critical path (analytics, billing, cookieless)
REDIS_URL: 'redis://kv:6379/'
# Optional: Use separate Redis URL for read replicas
# REDIS_READER_URL: 'redis://redis-replica:6379/'
# Optional: Increase Redis timeout (default is 100ms)
# REDIS_TIMEOUT_MS: 200
# Dedicated Redis database for critical path (team cache + flags cache)
# Hobby deployments start in Mode 1 (shared-only). Developers override in docker-compose.dev.yml for Mode 2.
# FLAGS_REDIS_URL: 'redis://kv:6379/1'
# Optional: Use separate Flags Redis URL for read replicas
# FLAGS_REDIS_READER_URL: 'redis://redis-replica:6379/1'
ADDRESS: '0.0.0.0:3001'
RUST_LOG: 'info'
COOKIELESS_REDIS_HOST: kv
COOKIELESS_REDIS_PORT: 6379
plugins:
command: node nodejs/dist/index.js
restart: on-failure
environment:
DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
PERSONS_DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
BEHAVIORAL_COHORTS_DATABASE_URL: 'postgres://insights:insights@db:5432/insights'
KAFKA_HOSTS: 'kafka:9092'
REDIS_URL: 'redis://kv:6379/'
DATASTORE_HOST: 'datastore'
DATASTORE_DATABASE: 'insights'
DATASTORE_SECURE: 'false'
DATASTORE_VERIFY: 'false'
COOKIELESS_REDIS_HOST: kv
COOKIELESS_REDIS_PORT: 6379
SESSION_RECORDING_API_REDIS_HOST: kv
SESSION_RECORDING_API_REDIS_PORT: 6379
livestream:
image: 'ghcr.io/hanzoai/insights-livestream:latest'
restart: on-failure
depends_on:
kafka:
condition: service_started
migrate:
<<: *worker
command: sh -c "
python manage.py migrate
&& python manage.py migrate_datastore
&& python manage.py run_async_migrations
"
restart: 'no'
deploy:
replicas: 0
asyncmigrationscheck:
<<: *worker
command: python manage.py run_async_migrations --check
restart: 'no'
deploy:
replicas: 0
environment:
<<: *worker_env
SKIP_ASYNC_MIGRATIONS_SETUP: 0
# Temporal containers
elasticsearch:
environment:
- cluster.routing.allocation.disk.threshold_enabled=true
- cluster.routing.allocation.disk.watermark.low=512mb
- cluster.routing.allocation.disk.watermark.high=256mb
- cluster.routing.allocation.disk.watermark.flood_stage=128mb
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms256m -Xmx256m
- xpack.security.enabled=false
image: elasticsearch:7.17.28
expose:
- 9200
volumes:
- /var/lib/elasticsearch/data
temporal:
restart: on-failure
healthcheck:
test: ['CMD-SHELL', 'nc -z $(hostname) 7233']
interval: 5s
timeout: 5s
retries: 30
start_period: 300s
environment:
- DB=postgresql
- DB_PORT=5432
- POSTGRES_USER=insights
- POSTGRES_PWD=insights
- POSTGRES_SEEDS=db
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
- ENABLE_ES=true
- ES_SEEDS=elasticsearch
- ES_VERSION=v7
image: temporalio/auto-setup:1.20.0
ports:
- 7233:7233
labels:
kompose.volume.type: configMap
volumes:
- ./docker/temporal/dynamicconfig:/etc/temporal/config/dynamicconfig
depends_on:
db:
condition: service_healthy
elasticsearch:
condition: service_started
temporal-admin-tools:
environment:
- TEMPORAL_CLI_ADDRESS=temporal:7233
image: temporalio/admin-tools:1.20.0
stdin_open: true
tty: true
temporal-ui:
depends_on:
- temporal
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CORS_ORIGINS=http://localhost:3000
- TEMPORAL_CSRF_COOKIE_INSECURE=true
- TEMPORAL_CODEC_ENDPOINT=http://localhost:8000
image: temporalio/ui:2.31.2
ports:
- 8081:8080
temporal-django-worker:
<<: *worker
command: ./bin/temporal-django-worker
restart: on-failure
environment:
<<: *worker_env
TEMPORAL_HOST: temporal
cyclotron-janitor:
image: ghcr.io/hanzoai/insights-cyclotron-janitor:latest
build:
context: rust/
args:
BIN: cyclotron-janitor
restart: on-failure
environment:
DATABASE_URL: 'postgres://insights:insights@db:5432/cyclotron'
KAFKA_HOSTS: 'kafka:9092'
KAFKA_TOPIC: 'datastore_app_metrics2'
depends_on:
db:
condition: service_healthy
kafka:
condition: service_started
otel-collector:
image: otel/opentelemetry-collector-contrib:0.142.0
container_name: otel-collector-local
command: [--config=/etc/otel-collector-config.yaml]
user: '0:0' # have to run as root to read the docker logs
volumes:
- ./otel-collector-config.dev.yaml:/etc/otel-collector-config.yaml
# mount docker containers dir so we can ship the docker logs to local insights
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- '4317:4317' # OTLP gRPC receiver (mapped to host)
- '4318:4318' # OTLP HTTP receiver (mapped to host)
- '13133:13133' # health_check extension
- '55679:55679' # zpages extension
depends_on:
- jaeger
networks:
- otel_network
echo_server:
image: docker.io/library/caddy:2
container_name: echo-server
restart: on-failure
ports:
- '18081:8081'
volumes:
- ./rust/docker/echo-server/Caddyfile:/etc/caddy/Caddyfile
jaeger:
image: jaegertracing/all-in-one:1.76.0
container_name: jaeger-local
ports:
- '16686:16686' # Jaeger UI
- '14268:14268' # Accepts jaeger.thrift directly from clients (optional for this flow)
- '14250:14250' # Accepts model.proto (optional for this flow)
networks:
- otel_network
localstack:
container_name: '${LOCALSTACK_DOCKER_NAME:-localstack-main}'
image: localstack/localstack:4.12.0
ports:
- '127.0.0.1:4566:4566' # LocalStack Gateway
- '127.0.0.1:4510-4559:4510-4559' # external services port range
environment:
- DEBUG=${DEBUG:-0}
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
seaweedfs:
container_name: '${SEAWEEDFS_DOCKER_NAME:-seaweedfs-main}'
image: chrislusf/seaweedfs:4.03
ports:
- '127.0.0.1:8333:8333' # S3 API
- '127.0.0.1:9333:9333' # Master server (for admin UI)
entrypoint:
- /bin/sh
- -c
- |
/usr/bin/weed "$$@" &
WEED_PID=$$!
echo "Waiting for SeaweedFS to initialize..."
while true; do
sleep 5
echo "Checking if insights bucket exists..."
if echo "s3.bucket.list" | /usr/bin/weed shell -main=localhost:9333 2>&1 | grep -q "insights"; then
echo "Bucket insights exists!"
break
fi
echo "Bucket not found, attempting to create..."
echo "s3.bucket.create -name insights" | /usr/bin/weed shell -main=localhost:9333 2>&1 || true
echo "Retrying in 5s..."
done
echo "SeaweedFS ready with insights bucket"
wait $$WEED_PID
- --
command: ['server', '-s3', '-s3.port=8333', '-dir=/data']
networks:
otel_network:
driver: bridge
volumes:
redpanda-data: