Ctrl K
ComunidadGuías…

docker-compose.dev.yml

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#
# docker-compose file used ONLY for local development.
# For more info, see:
# https://posthog.com/handbook/engineering/developing-locally
#
# PostHog has sunset support for self-hosted K8s deployments.
# See: https://posthog.com/blog/sunsetting-helm-support-posthog
#
# Profile guidance:
#   New optional services should declare `profiles:` directly on the service
#   here (see capture, plugins, ingestion-* for examples).
#   docker-compose.profiles.yml is a legacy overlay that retroactively gates
#   older services; do not add new entries there.
#

services:
    proxy:
        extends:
            file: docker-compose.base.yml
            service: proxy
        ports:
            - 127.0.0.1:8010:8000
        extra_hosts:
            - 'web:host-gateway'
            - 'plugins:host-gateway'
            - 'capture:host-gateway'
            - 'capture-ai:host-gateway'
            - 'capture-logs:host-gateway'
            - 'capture-apm-metrics:host-gateway'
            - 'replay-capture:host-gateway'
            - 'feature-flags:host-gateway'
            - 'hypercache-server:host-gateway'
            - 'push-api:host-gateway'
        environment:
            CADDYFILE: |
                {
                    servers {
                        trusted_proxies static ${CADDY_TRUSTED_PROXIES:-127.0.0.1/32 ::1/128}
                    }
                }
                ${CADDY_HOST:-http://localhost:8000} {
                    @replay-capture {
                        path /s
                        path /s/*
                    }

                    @capture-ai {
                        path /i/v0/ai
                        path /i/v0/ai/*
                        path /i/v1/ai/events
                        path /i/v1/ai/events/
                    }

                    @capture {
                        path /e
                        path /e/*
                        path /i/v0/*
                        path /i/v1/analytics/events
                        path /i/v1/analytics/events/
                        path /batch
                        path /batch*
                        path /capture
                        path /capture*
                    }

                    @capture-logs {
                        path /i/v1/logs
                        path /i/v1/logs/*
                        path /i/v1/traces
                    }

                    @capture-apm-metrics {
                        path /i/v1/metrics
                        path /i/v1/prometheus/write
                        path /i/v1/prometheus/write/*
                    }

                    @flags {
                        path /flags
                        path /flags*
                        # Local evaluation (flag definitions) — mirrors cloud Contour routing to Rust
                        path /api/feature_flag/local_evaluation
                        path /api/feature_flag/local_evaluation/
                        path /api/feature_flag/local_evaluation/*
                    }

                    @surveys {
                        path /surveys
                        path /surveys/
                        path /api/surveys
                        path /api/surveys/
                    }

                    @remote-config {
                        path /array/*
                    }

                    @webhooks {
                        path /public/webhooks
                        path /public/webhooks/*
                        path /public/m
                        path /public/m/*
                    }

                    @push-subscriptions {
                        path /api/push_subscriptions
                        path /api/push_subscriptions/
                    }

                    handle @capture-ai {
                        reverse_proxy capture-ai:3309
                    }

                    handle @capture {
                        reverse_proxy capture:3307
                    }

                    handle @capture-logs {
                        reverse_proxy capture-logs:4320
                    }

                    handle @capture-apm-metrics {
                        reverse_proxy capture-apm-metrics:4321
                    }

                    handle @replay-capture {
                        reverse_proxy replay-capture:3306
                    }

                    handle @flags {
                        reverse_proxy feature-flags:3001
                    }

                    handle @surveys {
                        reverse_proxy hypercache-server:3002
                    }

                    handle @remote-config {
                        reverse_proxy hypercache-server:3002
                    }

                    handle @webhooks {
                        reverse_proxy plugins:6738
                    }

                    handle @push-subscriptions {
                        reverse_proxy push-api:6750
                    }

                    handle {
                        reverse_proxy web:8000
                    }
                }

    capture:
        extends:
            file: docker-compose.base.yml
            service: capture
        image: ghcr.io/posthog/posthog/capture:${POSTHOG_CAPTURE_TAG:-master}
        restart: on-failure
        ports:
            - '3307:3000'
        depends_on:
            kafka:
                condition: service_healthy
            redis7:
                condition: service_healthy
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:3000/_liveness']
            interval: 5s
            timeout: 5s
            retries: 24
            start_period: 10s
        profiles:
            - capture

    capture-ai:
        extends:
            file: docker-compose.base.yml
            service: capture-ai
        image: ghcr.io/posthog/posthog/capture:${POSTHOG_CAPTURE_TAG:-master}
        restart: on-failure
        ports:
            - '3309:3000'
        depends_on:
            kafka:
                condition: service_healthy
            redis7:
                condition: service_healthy
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:3000/_liveness']
            interval: 5s
            timeout: 5s
            retries: 24
            start_period: 10s
        profiles:
            - ingestion

    plugins:
        extends:
            file: docker-compose.base.yml
            service: plugins
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6738:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
            PERSONS_DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_PERSONS_DB_NAME:-posthog}
            # The plugins container runs the CDP rerun worker (default capabilities), which
            # requires this. bin/start exports it for local dev; the value below is the default
            # for callers (like the E2E CI job) that launch this service without bin/start.
            CYCLOTRON_NODE_DATABASE_URL: ${CYCLOTRON_NODE_DATABASE_URL:-postgres://posthog:posthog@db:5432/cyclotron_node}
            CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-posthog}
            # CDP Redis defaults to 127.0.0.1 — must point to the Docker service
            CDP_REDIS_HOST: redis7
            CDP_VALKEY_HOST: valkey-cluster
            CDP_VALKEY_PORT: 6390
            # Encryption keys default is only set when NODE_ENV=dev, but the image uses production
            ENCRYPTION_SALT_KEYS: '00beef0000beef0000beef0000beef00'
            # PersonHog is required for CDP — point at the local gRPC router
            PERSONHOG_ADDR: 'personhog-router:50052'
            PERSONHOG_ENABLED: 'true'
        depends_on:
            kafka:
                condition: service_started
            redis7:
                condition: service_healthy
            valkey-cluster:
                condition: service_healthy
            db:
                condition: service_healthy
            personhog-router:
                condition: service_started
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    ingestion-general:
        extends:
            file: docker-compose.base.yml
            service: ingestion-general
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6739:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
            PERSONS_DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_PERSONS_DB_NAME:-posthog}
            CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-posthog}
            # The combined-mode AI consumer reads persons through PersonHog
            PERSONHOG_ADDR: 'personhog-router:50052'
            PERSONHOG_ENABLED: 'true'
        depends_on:
            kafka:
                condition: service_started
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
            objectstorage:
                # The combined AI consumer's blob store healthchecks the ai-blobs bucket at
                # startup, and SeaweedFS creates it asynchronously, so wait for the sentinel.
                condition: service_healthy
            personhog-router:
                condition: service_started
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    ingestion-sessionreplay:
        extends:
            file: docker-compose.base.yml
            service: ingestion-sessionreplay
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6740:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
            ENCRYPTION_SALT_KEYS: '00beef0000beef0000beef0000beef00'
        depends_on:
            kafka:
                condition: service_started
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    recording-api:
        extends:
            file: docker-compose.base.yml
            service: recording-api
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6741:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
            CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE:-posthog}
            ENCRYPTION_SALT_KEYS: '00beef0000beef0000beef0000beef00'
            # The image runs as production, so the in-code dev defaults do not apply. Set
            # RECORDING_API_JWT_SECRET to the key Django uses under DEBUG, so that local recording
            # calls authenticate. Keep these known dev values out of docker-compose.base.yml, which
            # hobby extends, so that a hobby install never runs with them.
            INTERNAL_API_SECRET: 'posthog123'
            RECORDING_API_JWT_SECRET: 'dev-recording-api-jwt-secret'
        depends_on:
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    ingestion-error-tracking:
        extends:
            file: docker-compose.base.yml
            service: ingestion-error-tracking
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6742:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
            PERSONHOG_ADDR: 'personhog-router:50052'
            PERSONHOG_ENABLED: 'true'
        depends_on:
            kafka:
                condition: service_started
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
            personhog-router:
                condition: service_started
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    ingestion-logs:
        extends:
            file: docker-compose.base.yml
            service: ingestion-logs
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6743:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
        depends_on:
            kafka:
                condition: service_started
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    ingestion-traces:
        extends:
            file: docker-compose.base.yml
            service: ingestion-traces
        image: ghcr.io/posthog/posthog-node:${POSTHOG_NODE_TAG:-master}
        ports:
            - '6744:6738'
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}
        depends_on:
            kafka:
                condition: service_started
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
        healthcheck:
            test: ['CMD', 'curl', '-f', 'http://localhost:6738/_ready']
            interval: 5s
            timeout: 5s
            retries: 30
            start_period: 15s
        profiles:
            - ingestion

    browserless:
        # Renders heatmap screenshots and image exports — there is no local-browser fallback.
        # Optional: docker compose -f docker-compose.dev.yml --profile browserless up -d browserless
        # See the HEATMAP_BROWSERLESS_* / BROWSERLESS_* entries in .env.example for the app-side wiring.
        image: ghcr.io/browserless/chromium:v2.51.2
        restart: on-failure
        ports:
            - '127.0.0.1:4445:3000'
        environment:
            TOKEN: dev
            LOG_LEVEL: warn
            DEBUG: '-*'
        profiles:
            - browserless

    db:
        extends:
            file: docker-compose.base.yml
            service: db
        ports:
            - '5432:5432'
        volumes:
            - postgres-15-data:/var/lib/postgresql/data
        # something in the django app when running in dev mode
        # (maybe only in Pycharm) keeps many idle transactions open
        # and eventually kills postgres, these settings aim to stop that happening.
        # They are only for DEV and should not be used in production.
        command: postgres -c max_connections=1000 -c idle_in_transaction_session_timeout=300000 -c max_prepared_transactions=10

    redis7:
        extends:
            file: docker-compose.base.yml
            service: redis7
        ports:
            - '6379:6379'

    valkey-cluster:
        image: valkey/valkey:8.1-alpine
        restart: on-failure
        ports:
            - '127.0.0.1:6390:6390'
        command:
            - /bin/sh
            - -ec
            - |
                # 6390 inside the container as well as on the host, so the port CLUSTER SLOTS
                # announces is right for both. It announces no address, which every cluster
                # client reads as "the one you reached me on", so a host client stays on
                # 127.0.0.1 and a container client stays on valkey-cluster. Announcing an
                # address instead would name one of those two and break the other.
                valkey-server --port 6390 --cluster-enabled yes --cluster-config-file /tmp/nodes.conf --cluster-node-timeout 5000 &
                pid=$$!
                until valkey-cli -p 6390 ping >/dev/null 2>&1; do sleep 0.1; done
                valkey-cli -p 6390 cluster addslots $$(seq 0 16383)
                wait $$pid
        healthcheck:
            test:
                [
                    'CMD-SHELL',
                    'valkey-cli -p 6390 cluster info | grep -q cluster_state:ok && test "$$(valkey-cli -p 6390 cluster info | sed -n "s/cluster_slots_assigned://p" | tr -d "\r")" = 16384',
                ]
            interval: 2s
            timeout: 3s
            retries: 15
            start_period: 5s

    redis-cluster:
        image: redis:7.2
        restart: on-failure
        ports:
            - '127.0.0.1:6399:6379'
        command: >
            bash -c "
            redis-server
            --port 6379
            --cluster-enabled yes
            --cluster-config-file /data/nodes.conf
            --cluster-node-timeout 5000
            --cluster-announce-ip 127.0.0.1
            --cluster-announce-port 6399
            --appendonly yes &
            sleep 1 &&
            redis-cli -p 6379 cluster addslots $$(seq 0 16383) &&
            tail -f /dev/null
            "
        volumes:
            - redis-cluster-data:/data
        healthcheck:
            test: ['CMD', 'redis-cli', '-p', '6379', 'cluster', 'info']
            interval: 2s
            timeout: 3s
            retries: 10

    flower:
        extends:
            file: docker-compose.base.yml
            service: flower
        ports:
            - '5555:5555'

    clickhouse:
        extends:
            file: docker-compose.base.yml
            service: clickhouse
        hostname: clickhouse
        # ClickHouse writes ~1 GB/hour in dev and json-file driver has no default rotation
        logging:
            driver: json-file
            options:
                max-size: '50m'
                max-file: '3'
        # Development performance optimizations
        mem_limit: 8g
        cpus: 2
        environment:
            - AWS_ACCESS_KEY_ID=object_storage_root_user
            - AWS_SECRET_ACCESS_KEY=object_storage_root_password
            - CLICKHOUSE_SKIP_USER_SETUP=1
        ports:
            - '8123:8123'
            - '8443:8443'
            - '9000:9000'
            - '9440:9440'
            - '9009:9009'
        volumes:
            - ./posthog/idl:/idl
            - ./docker/clickhouse/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
            - ./docker/clickhouse/config.xml:/etc/clickhouse-server/config.xml
            - ./docker/clickhouse/config.d/default.xml:/etc/clickhouse-server/config.d/default.xml
            - ./docker/clickhouse/config.d/dev-memory.xml:/etc/clickhouse-server/config.d/dev-memory.xml
            - ./docker/clickhouse/users-dev.xml:/etc/clickhouse-server/users.xml
            - ./docker/clickhouse/user_defined_function.xml:/etc/clickhouse-server/user_defined_function.xml
            - ./posthog/user_scripts:/var/lib/clickhouse/user_scripts
            - clickhouse-data:/var/lib/clickhouse
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        depends_on:
            - kafka
            - zookeeper

    zookeeper:
        extends:
            file: docker-compose.base.yml
            service: zookeeper
        ports:
            - '2181:2181'
        # With persistent volumes, snapshots and txn logs accumulate forever unless
        # autopurge is on (the image default disables it). Matches hobby.
        environment:
            ZOO_AUTOPURGE_PURGEINTERVAL: 1
            ZOO_AUTOPURGE_SNAPRETAINCOUNT: 3
        volumes:
            - zookeeper-datalog:/datalog
            - zookeeper-data:/data
            - zookeeper-logs:/logs

    kafka:
        extends:
            file: docker-compose.base.yml
            service: kafka
        ports:
            - '9092:9092'
        depends_on:
            - zookeeper

    kafka-init:
        image: docker.io/redpandadata/redpanda:v25.1.9
        entrypoint: /bin/sh
        restart: 'no'
        command:
            - -c
            - |
                set -x
                # Redpanda auto_create_topics_enabled=true only fires on producer-first
                # writes. Consumer-only services (e.g. embedding-worker) error on missing
                # topics, so we pre-create them here. Idempotent — re-running is safe.
                TIMEOUT=60
                ELAPSED=0
                until rpk topic list --brokers kafka:9092 2>/dev/null; do
                    echo "Waiting for Kafka broker... (elapsed: ${ELAPSED}s)"
                    sleep 2
                    ELAPSED=$((ELAPSED + 2))
                    if [ $ELAPSED -ge $TIMEOUT ]; then
                        echo "Timeout waiting for Kafka broker after ${TIMEOUT}s"
                        exit 1
                    fi
                done
                # The topic list is docker/kafka/topics.txt, mounted below. It is a
                # shared file rather than a literal here so a test can check it against the
                # Kafka tables in schema.py; that header explains what needs pre-creating.
                TOPICS=$$(grep -vE '^[[:space:]]*#|^[[:space:]]*$$' /topics.txt)
                EXISTING=$$(rpk topic list --brokers kafka:9092 | awk '{print $$1}')
                for topic in $$TOPICS; do
                    if rpk topic create "$$topic" --brokers kafka:9092 -p 1 -r 1 2>&1; then
                        echo "Topic $$topic created"
                    elif echo "$$EXISTING" | grep -qx "$$topic"; then
                        echo "Topic $$topic already exists"
                    else
                        echo "Failed to create topic $$topic"
                        exit 1
                    fi
                done
        volumes:
            - ./docker/kafka/topics.txt:/topics.txt:ro
        depends_on:
            kafka:
                condition: service_healthy

    kafka_ui:
        extends:
            file: docker-compose.base.yml
            service: kafka_ui
        ports:
            - '9093:8080'
        environment:
            SERVER_MAX_HTTP_REQUEST_HEADER_SIZE: 65536
        depends_on:
            - kafka

    objectstorage:
        extends:
            file: docker-compose.base.yml
            service: objectstorage
        ports:
            - '19000:19000'
            - '19001:19001'
        # Named volume from the SeaweedFS swap; the old MinIO volume is left in place
        # because SeaweedFS cannot read its on-disk format.
        volumes:
            - objectstorage-data:/data

    objectstorage-azure:
        extends:
            file: docker-compose.base.yml
            service: objectstorage-azure
        ports:
            - '10000:10000'

    maildev:
        extends:
            file: docker-compose.base.yml
            service: maildev
        ports:
            - '1080:1080'
            - '1025:1025'

    webhook-tester:
        image: tarampampam/webhook-tester:2.2.6
        restart: on-failure
        ports:
            - '2080:8080'
        environment:
            - AUTO_CREATE_SESSIONS=true

    personhog-replica:
        extends:
            file: docker-compose.base.yml
            service: personhog-replica
        environment:
            PRIMARY_DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_PERSONS_DB_NAME:-posthog}
        depends_on:
            db:
                condition: service_healthy
        profiles:
            - ingestion

    personhog-router:
        extends:
            file: docker-compose.base.yml
            service: personhog-router
        ports:
            - '50052:50052'
        depends_on:
            personhog-replica:
                condition: service_started
        profiles:
            - ingestion

    personhog-router-leader:
        extends:
            file: docker-compose.base.yml
            service: personhog-router-leader
        ports:
            - '50054:50054'
        depends_on:
            etcd:
                condition: service_healthy
            personhog-replica:
                condition: service_started
        profiles:
            - ingestion

    personhog-leader:
        extends:
            file: docker-compose.base.yml
            service: personhog-leader
        environment:
            FALLBACK_DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_PERSONS_DB_NAME:-posthog}
        depends_on:
            etcd:
                condition: service_healthy
            db:
                condition: service_healthy
            kafka:
                condition: service_started
            personhog-etcd-init:
                condition: service_completed_successfully
        profiles:
            - ingestion

    personhog-writer:
        extends:
            file: docker-compose.base.yml
            service: personhog-writer
        environment:
            DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_PERSONS_DB_NAME:-posthog}
        depends_on:
            etcd:
                condition: service_healthy
            db:
                condition: service_healthy
            kafka:
                condition: service_started
            personhog-etcd-init:
                condition: service_completed_successfully
        profiles:
            - ingestion

    personhog-identity:
        extends:
            file: docker-compose.base.yml
            service: personhog-identity
        environment:
            PRIMARY_DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_PERSONS_DB_NAME:-posthog}
        ports:
            - '50055:50055'
        depends_on:
            db:
                condition: service_healthy
            personhog-router-leader:
                condition: service_started
        profiles:
            - ingestion

    # Seeds the changelog config the personhog services read at boot;
    # idempotent, and the same key bin/mprocs.yaml writes for hogli runs.
    personhog-etcd-init:
        image: quay.io/coreos/etcd:v3.6.8
        restart: 'no'
        entrypoint:
            [
                'etcdctl',
                '--endpoints=http://etcd:2379',
                'put',
                '/personhog/config/changelog',
                '{"topic":"personhog_updates","partitions":4,"epoch":1,"writer_group":"personhog-writer","draining":[]}',
            ]
        depends_on:
            etcd:
                condition: service_healthy
        profiles:
            - ingestion

    feature-flags:
        extends:
            file: docker-compose.base.yml
            service: feature-flags
        depends_on:
            redis7:
                condition: service_healthy
            db:
                condition: service_healthy
        environment:
            FLAGS_REDIS_URL: 'redis://redis7:6379/1'
            # The /flags/definitions reader stays on shared Redis unless
            # FLAG_DEFINITIONS_DEDICATED_REDIS_ENABLED is set.
            REDIS_COMPRESSION_ENABLED: 'true'
            INTERNAL_REQUEST_TOKEN: 'dev-internal-token'
            # Answer /flags?v=4 with the v4 response, as production does
            FLAGS_V4_RESPONSE_ENABLED: 'true'

    usage-ingestion:
        extends:
            file: docker-compose.base.yml
            service: usage-ingestion
        ports:
            - '7143:7143'
            - '7144:7144'
        depends_on:
            db:
                condition: service_healthy
            kafka:
                condition: service_healthy
            valkey-cluster:
                condition: service_healthy
        environment:
            USAGE_INGESTION_REDIS_URL: 'redis://valkey-cluster:6390'
        # Keeps a bare `up` from building an image that is not published yet.
        profiles:
            - ingestion

    # Temporal containers
    elasticsearch:
        extends:
            file: docker-compose.base.yml
            service: elasticsearch
        expose:
            - 9200

    # OpenSearch (LLM trace reverse index). 9201 to avoid Elasticsearch's 9200.
    opensearch:
        extends:
            file: docker-compose.base.yml
            service: opensearch
        ports:
            - '127.0.0.1:9201:9200'
        profiles: [opensearch]
    opensearch-dashboards:
        extends:
            file: docker-compose.base.yml
            service: opensearch-dashboards
        ports:
            - '127.0.0.1:5601:5601'
        profiles: [opensearch]
    opensearch-init:
        image: curlimages/curl:8.10.1
        depends_on:
            opensearch:
                condition: service_healthy
        restart: 'no'
        volumes:
            - ./products/ai_observability/opensearch:/work:ro
        entrypoint: ['sh', '/work/init.sh']
        profiles: [opensearch]

    temporal:
        extends:
            file: docker-compose.base.yml
            service: temporal
        ports:
            - 7233:7233
        volumes:
            - ./docker/temporal/dynamicconfig:/etc/temporal/config/dynamicconfig

    temporal-admin-tools:
        extends:
            file: docker-compose.base.yml
            service: temporal-admin-tools
        depends_on:
            - temporal
    temporal-ui:
        extends:
            file: docker-compose.base.yml
            service: temporal-ui
        ports:
            - 8081:8080
        depends_on:
            temporal:
                condition: service_started
            db:
                condition: service_healthy

    otel-collector:
        extends:
            file: docker-compose.base.yml
            service: otel-collector
        container_name: otel-collector-local
        ports:
            - '4317:4317' # OTLP gRPC receiver
            - '4318:4318' # OTLP HTTP receiver
            - '13133:13133' # health_check extension
            - '55679:55679' # zpages extension
        volumes:
            - ./otel-collector-config.dev.yaml:/etc/otel-collector-config.yaml
        environment:
            # Used by otel-collector-config.dev.yaml to scope container log
            # collection to this compose project only.
            COMPOSE_PROJECT: posthog
        extra_hosts:
            - 'host.docker.internal:host-gateway'
        depends_on:
            - jaeger

    jaeger:
        extends:
            file: docker-compose.base.yml
            service: jaeger
        container_name: jaeger-local
        ports:
            - '16686:16686' # Jaeger UI
            - '14268:14268' # jaeger.thrift
            - '14250:14250' # model.proto

    dynamodb:
        extends:
            file: docker-compose.base.yml
            service: dynamodb
        profiles: [dynamodb]
        container_name: '${DYNAMODB_DOCKER_NAME:-dynamodb-main}'
        ports:
            # Not 8000 (the Django dev server) and not 18000, which the eval harness binds for its
            # live server, where sharing the port fails `hogli evals` on Linux with the stack up.
            - '127.0.0.1:18010:8000'

    seaweedfs:
        extends:
            file: docker-compose.base.yml
            service: seaweedfs
        container_name: '${SEAWEEDFS_DOCKER_NAME:-seaweedfs-main}'
        command: server -s3 -s3.port=8333 -filer -dir=/data
        ports:
            - '127.0.0.1:8333:8333' # S3 API
            - '127.0.0.1:9333:9333' # Master server
            - '127.0.0.1:8888:8888' # SeaweedFS Filer UI
        volumes:
            - 'seaweedfs-data:/data'

    duckgres:
        extends:
            file: docker-compose.base.yml
            service: duckgres
        profiles: ['duckgres']
        ports:
            - '15432:15432'

    etcd:
        image: quay.io/coreos/etcd:v3.6.8
        restart: on-failure
        environment:
            ETCD_NAME: etcd0
            ETCD_ADVERTISE_CLIENT_URLS: http://etcd:2379
            ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
        ports:
            - '127.0.0.1:2379:2379'
        healthcheck:
            test: ['CMD', 'etcdctl', 'endpoint', 'health']
            interval: 5s
            timeout: 5s
            retries: 10

    # A second, empty Kafka cluster for personhog's cross-cluster changelog
    # tests; only the PersonHog e2e gate CI job starts it.
    kafka-secondary:
        image: docker.io/redpandadata/redpanda:v25.1.9
        profiles: [personhog-secondary]
        restart: on-failure
        command:
            - redpanda
            - start
            - --kafka-addr 0.0.0.0:29092
            - --advertise-kafka-addr 127.0.0.1:29092
            - --mode dev-container
            - --smp 1
            - --memory 1G
            - --overprovisioned
            - --set redpanda.auto_create_topics_enabled=true
        ports:
            - '127.0.0.1:29092:29092'
        healthcheck:
            test: curl -f http://localhost:9644/v1/status/ready || exit 1
            interval: 3s
            timeout: 10s
            retries: 10

networks:
    otel_network:
        driver: bridge

volumes:
    postgres-15-data:
    redis-cluster-data:
    redpanda-data:
    seaweedfs-data:
    duckgres-data:
    objectstorage-data:
    clickhouse-data:
    zookeeper-data:
    zookeeper-datalog:
    zookeeper-logs: