coolify/templates/compose/posthog.yaml

1737 lines
70 KiB
YAML

# documentation: https://posthog.com
# slogan: The single platform to analyze, test, observe, and deploy new features
# category: analytics
# tags: analytics, product, open-source, self-hosted, ab-testing, event-tracking
# logo: svgs/posthog.svg
# minversion: 4.0.0-beta.222
# port: 8000
services:
db:
image: postgres:15.12-alpine
environment:
- POSTGRES_USER=posthog
- POSTGRES_DB=posthog
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U posthog']
interval: 5s
timeout: 30s
retries: 30
start_period: 10s
volumes:
- posthog-postgres-data:/var/lib/postgresql/data
redis:
image: redis:7.2-alpine
command: redis-server --maxmemory-policy allkeys-lru --maxmemory 500mb
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 3s
timeout: 10s
retries: 10
volumes:
- posthog-redis-data:/data
zookeeper:
image: zookeeper:3.7.0
volumes:
- posthog-zookeeper-data:/data
- posthog-zookeeper-datalog:/datalog
- posthog-zookeeper-logs:/logs
clickhouse:
image: clickhouse/clickhouse-server:25.12.5.44
depends_on:
kafka:
condition: service_started
zookeeper:
condition: service_started
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
- KAFKA_HOSTS=kafka:9092
healthcheck:
test: ['CMD-SHELL', 'wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1']
interval: 5s
timeout: 10s
retries: 30
start_period: 30s
volumes:
- type: bind
source: ./clickhouse/config.xml
target: /etc/clickhouse-server/config.xml
read_only: true
content: |
<?xml version="1.0"?>
<!--
NOTE: User and query level settings are set up in "users.xml" file.
If you have accidentally specified user-level settings here, server won't start.
You can either move the settings to the right place inside "users.xml" file
or add <skip_check_for_incorrect_settings>1</skip_check_for_incorrect_settings> here.
-->
<clickhouse>
<logger>
<level>warning</level>
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
<size>1000M</size>
<count>10</count>
</logger>
<http_port>8123</http_port>
<mysql_port>9004</mysql_port>
<postgresql_port>9005</postgresql_port>
<https_port>8443</https_port>
<tcp_port_secure>9440</tcp_port_secure>
<interserver_http_port>9009</interserver_http_port>
<max_connections>4096</max_connections>
<keep_alive_timeout>3</keep_alive_timeout>
<openSSL>
<server>
<certificateFile>/etc/clickhouse-server/server.crt</certificateFile>
<privateKeyFile>/etc/clickhouse-server/server.key</privateKeyFile>
<dhParamsFile>/etc/clickhouse-server/dhparam.pem</dhParamsFile>
<verificationMode>none</verificationMode>
<loadDefaultCAFile>true</loadDefaultCAFile>
<cacheSessions>true</cacheSessions>
<disableProtocols>sslv2,sslv3</disableProtocols>
<preferServerCiphers>true</preferServerCiphers>
</server>
<client>
<loadDefaultCAFile>true</loadDefaultCAFile>
<cacheSessions>true</cacheSessions>
<disableProtocols>sslv2,sslv3</disableProtocols>
<preferServerCiphers>true</preferServerCiphers>
<invalidCertificateHandler>
<name>RejectCertificateHandler</name>
</invalidCertificateHandler>
</client>
</openSSL>
<!-- Maximum number of concurrent queries. -->
<max_concurrent_queries>200</max_concurrent_queries>
<!-- Maximum memory usage (resident set size) for server process.
Zero value or unset means default. Default is "max_server_memory_usage_to_ram_ratio" of available
physical RAM.
If the value is larger than "max_server_memory_usage_to_ram_ratio" of available physical RAM, it
will be cut down.
The constraint is checked on query execution time.
If a query tries to allocate memory and the current memory usage plus allocation is greater
than specified threshold, exception will be thrown.
It is not practical to set this constraint to small values like just a few gigabytes,
because memory allocator will keep this amount of memory in caches and the server will deny service
of queries.
-->
<max_server_memory_usage>0</max_server_memory_usage>
<!-- Maximum number of threads in the Global thread pool.
This will default to a maximum of 10000 threads if not specified.
This setting will be useful in scenarios where there are a large number
of distributed queries that are running concurrently but are idling most
of the time, in which case a higher number of threads might be required.
-->
<background_message_broker_schedule_pool_size>10</background_message_broker_schedule_pool_size>
<max_thread_pool_size>10000</max_thread_pool_size>
<!-- how many threads merge files, default is 16 -->
<background_pool_size>4</background_pool_size>
<!-- Number of workers to recycle connections in background (see also drain_timeout).
If the pool is full, connection will be drained synchronously. -->
<!-- <max_threads_for_connection_collector>10</max_threads_for_connection_collector> -->
<!-- On memory constrained environments you may have to set this to value larger than 1.
-->
<max_server_memory_usage_to_ram_ratio>0.9</max_server_memory_usage_to_ram_ratio>
<!-- Simple server-wide memory profiler. Collect a stack trace at every peak allocation step (in
bytes).
Data will be stored in system.trace_log table with query_id = empty string.
Zero means disabled.
-->
<total_memory_profiler_step>4194304</total_memory_profiler_step>
<!-- Collect random allocations and deallocations and write them into system.trace_log with
'MemorySample' trace_type.
The probability is for every alloc/free regardless to the size of the allocation.
Note that sampling happens only when the amount of untracked memory exceeds the untracked memory
limit,
which is 4 MiB by default but can be lowered if 'total_memory_profiler_step' is lowered.
You may want to set 'total_memory_profiler_step' to 1 for extra fine grained sampling.
-->
<total_memory_tracker_sample_probability>0</total_memory_tracker_sample_probability>
<!-- Set limit on number of open files (default: maximum). This setting makes sense on Mac OS X
because getrlimit() fails to retrieve
correct maximum value. -->
<!-- <max_open_files>262144</max_open_files> -->
<!-- Size of cache of uncompressed blocks of data, used in tables of MergeTree family.
In bytes. Cache is single for server. Memory is allocated only on demand.
Cache is used when 'use_uncompressed_cache' user setting turned on (off by default).
Uncompressed cache is advantageous only for very short queries and in rare cases.
Note: uncompressed cache can be pointless for lz4, because memory bandwidth
is slower than multi-core decompression on some server configurations.
Enabling it can sometimes paradoxically make queries slower.
-->
<uncompressed_cache_size>8589934592</uncompressed_cache_size>
<!-- Approximate size of mark cache, used in tables of MergeTree family.
In bytes. Cache is single for server. Memory is allocated only on demand.
You should not lower this value.
-->
<mark_cache_size>5368709120</mark_cache_size>
<mmap_cache_size>1000</mmap_cache_size>
<!-- Cache size in bytes for compiled expressions.-->
<compiled_expression_cache_size>134217728</compiled_expression_cache_size>
<!-- Cache size in elements for compiled expressions.-->
<compiled_expression_cache_elements_size>10000</compiled_expression_cache_elements_size>
<!-- Path to data directory, with trailing slash. -->
<path>/var/lib/clickhouse/</path>
<!-- Path to temporary data for processing hard queries. -->
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
<!-- Directory with user provided files that are accessible by 'file' table function. -->
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
<user_directories>
<users_xml>
<!-- Path to configuration file with predefined users. -->
<path>users.xml</path>
</users_xml>
<local_directory>
<!-- Path to folder where users created by SQL commands are stored. -->
<path>/var/lib/clickhouse/access/</path>
</local_directory>
</user_directories>
<default_profile>default</default_profile>
<custom_settings_prefixes></custom_settings_prefixes>
<default_database>default</default_database>
<mlock_executable>true</mlock_executable>
<!-- Reallocate memory for machine code ("text") using huge pages. Highly experimental. -->
<remap_executable>false</remap_executable>
<remote_url_allow_hosts>
<host_regexp>.*</host_regexp>
</remote_url_allow_hosts>
<zookeeper>
<node>
<host>zookeeper</host>
<port>2181</port>
</node>
</zookeeper>
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
<builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>
<!-- Maximum session timeout, in seconds. Default: 3600. -->
<max_session_timeout>3600</max_session_timeout>
<!-- Default session timeout, in seconds. Default: 60. -->
<default_session_timeout>60</default_session_timeout>
<!-- Query log. Used only for queries with setting log_queries = 1. -->
<query_log>
<!-- What table to insert data. If table is not exist, it will be created.
When query log structure is changed after system update,
then old table will be renamed and new table will be created automatically.
-->
<database>system</database>
<table>query_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<!-- Interval of flushing data. -->
<flush_interval_milliseconds>500</flush_interval_milliseconds>
<!-- Maximal size in lines for the logs. When non-flushed logs amount reaches max_size, logs dumped to the disk. -->
<max_size_rows>8388608</max_size_rows>
<!-- Pre-allocated size in lines for the logs. -->
<reserved_size_rows>8192</reserved_size_rows>
<!-- Lines amount threshold, reaching it launches flushing logs to the disk in background. -->
<buffer_size_rows_flush_threshold>524288</buffer_size_rows_flush_threshold>
<!-- Indication whether logs should be dumped to the disk in case of a crash -->
<flush_on_crash>true</flush_on_crash>
<!-- example of using a different storage policy for a system table -->
<!-- storage_policy>local_ssd</storage_policy -->
<ttl>event_date + INTERVAL 12 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</query_log>
<!-- Query thread log. Has information about all threads participated in query execution.
Used only for queries with setting log_query_threads = 1. -->
<query_thread_log>
<database>system</database>
<table>query_thread_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<max_size_rows>8388608</max_size_rows>
<reserved_size_rows>8192</reserved_size_rows>
<buffer_size_rows_flush_threshold>524288</buffer_size_rows_flush_threshold>
<flush_on_crash>false</flush_on_crash>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</query_thread_log>
<!-- Query views log. Has information about all dependent views associated with a query.
Used only for queries with setting log_query_views = 1. -->
<query_views_log>
<database>system</database>
<table>query_views_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</query_views_log>
<text_log>
<database>system</database>
<table>text_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<level>trace</level>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</text_log>
<!-- Metric log contains rows with current values of ProfileEvents, CurrentMetrics collected
with "collect_interval_milliseconds" interval. -->
<metric_log>
<database>system</database>
<table>metric_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<collect_interval_milliseconds>1000</collect_interval_milliseconds>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</metric_log>
<!--
Asynchronous metric log contains values of metrics from
system.asynchronous_metrics.
-->
<asynchronous_metric_log>
<database>system</database>
<table>asynchronous_metric_log</table>
<!--
Asynchronous metrics are updated once a minute, so there is
no need to flush more often.
-->
<flush_interval_milliseconds>7000</flush_interval_milliseconds>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</asynchronous_metric_log>
<!--
OpenTelemetry log contains OpenTelemetry trace spans.
-->
<opentelemetry_span_log>
<!--
The default table creation code is insufficient, this <engine> spec
is a workaround. There is no 'event_time' for this log, but two times,
start and finish. It is sorted by finish time, to avoid inserting
data too far away in the past (probably we can sometimes insert a span
that is seconds earlier than the last span in the table, due to a race
between several spans inserted in parallel). This gives the spans a
global order that we can use to e.g. retry insertion into some external
system.
-->
<engine>
engine MergeTree
partition by toYYYYMM(finish_date)
order by (finish_date, finish_time_us, trace_id)
</engine>
<database>system</database>
<table>opentelemetry_span_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</opentelemetry_span_log>
<!-- Crash log. Stores stack traces for fatal errors.
This table is normally empty. -->
<crash_log>
<database>system</database>
<table>crash_log</table>
<partition_by />
<flush_interval_milliseconds>1000</flush_interval_milliseconds>
<max_size_rows>1024</max_size_rows>
<reserved_size_rows>1024</reserved_size_rows>
<buffer_size_rows_flush_threshold>512</buffer_size_rows_flush_threshold>
<flush_on_crash>true</flush_on_crash>
</crash_log>
<!-- Session log. Stores user log in (successful or not) and log out events. -->
<session_log>
<database>system</database>
<table>session_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</session_log>
<backup_log>
<database>system</database>
<table>backup_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<ttl>event_date + INTERVAL 5 DAY</ttl>
<ttl_only_drop_parts>1</ttl_only_drop_parts>
</backup_log>
<!-- Custom TLD lists.
Format: <name>/path/to/file</name>
Changes will not be applied w/o server restart.
Path to the list is under top_level_domains_path (see above).
-->
<top_level_domains_lists>
<!--
<public_suffix_list>/path/to/public_suffix_list.dat</public_suffix_list>
-->
</top_level_domains_lists>
<!-- Configuration of external dictionaries. See:
https://clickhouse.com/docs/en/sql-reference/dictionaries/external-dictionaries/external-dicts
-->
<dictionaries_config>*_dictionary.xml</dictionaries_config>
<!-- Configuration of user defined executable functions -->
<user_defined_executable_functions_config>*_function.xml</user_defined_executable_functions_config>
<distributed_ddl>
<!-- Path in ZooKeeper to queue with DDL queries -->
<path>/clickhouse/task_queue/ddl</path>
</distributed_ddl>
<!-- Example of parameters for GraphiteMergeTree table engine -->
<graphite_rollup_example>
<pattern>
<regexp>click_cost</regexp>
<function>any</function>
<retention>
<age>0</age>
<precision>3600</precision>
</retention>
<retention>
<age>86400</age>
<precision>60</precision>
</retention>
</pattern>
<default>
<function>max</function>
<retention>
<age>0</age>
<precision>60</precision>
</retention>
<retention>
<age>3600</age>
<precision>300</precision>
</retention>
<retention>
<age>86400</age>
<precision>3600</precision>
</retention>
</default>
</graphite_rollup_example>
<!-- Directory in <clickhouse-path> containing schema files for various input formats.
The directory will be created if it doesn't exist.
-->
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
<!-- Default query masking rules, matching lines would be replaced with something else in the
logs
(both text logs and system.query_log).
name - name for the rule (optional)
regexp - RE2 compatible regular expression (mandatory)
replace - substitution string for sensitive data (optional, by default - six asterisks)
-->
<query_masking_rules>
<rule>
<name>hide encrypt/decrypt arguments</name>
<regexp>((?:aes_)?(?:encrypt|decrypt)(?:_mysql)?)\s*\(\s*(?:'(?:\\'|.)+'|.*?)\s*\)</regexp>
<!-- or more secure, but also more invasive:
(aes_\w+)\s*\(.*\)
-->
<replace>\1(???)</replace>
</rule>
</query_masking_rules>
<merge_tree>
<max_bytes_to_merge_at_max_space_in_pool>1073741824</max_bytes_to_merge_at_max_space_in_pool>
<number_of_free_entries_in_pool_to_execute_mutation>2</number_of_free_entries_in_pool_to_execute_mutation>
<number_of_free_entries_in_pool_to_execute_optimize_entire_partition>2</number_of_free_entries_in_pool_to_execute_optimize_entire_partition>
</merge_tree>
</clickhouse>
- type: bind
source: ./clickhouse/users.xml
target: /etc/clickhouse-server/users.xml
read_only: true
content: |
<?xml version="1.0"?>
<yandex>
<!-- See also the files in users.d directory where the settings can be overridden. -->
<!-- Profiles of settings. -->
<profiles>
<!-- Default settings. -->
<default>
<!-- Maximum memory usage for processing single query, in bytes. -->
<max_memory_usage>10000000000</max_memory_usage>
<!-- How to choose between replicas during distributed query processing.
random - choose random replica from set of replicas with minimum number of errors
nearest_hostname - from set of replicas with minimum number of errors, choose replica
with minimum number of different symbols between replica's hostname and local hostname
(Hamming distance).
in_order - first live replica is chosen in specified order.
first_or_random - if first replica one has higher number of errors, pick a random one from replicas
with minimum number of errors.
-->
<load_balancing>random</load_balancing>
<allow_nondeterministic_mutations>1</allow_nondeterministic_mutations>
<enable_analyzer>0</enable_analyzer>
<compatibility>25.6</compatibility>
<distributed_product_mode>global</distributed_product_mode>
<enable_parallel_replicas>0</enable_parallel_replicas>
<max_parallel_replicas>1</max_parallel_replicas>
<allow_experimental_window_functions>1</allow_experimental_window_functions>
<allow_suspicious_low_cardinality_types>1</allow_suspicious_low_cardinality_types>
<insert_distributed_sync>1</insert_distributed_sync>
<throw_on_max_partitions_per_insert_block>false</throw_on_max_partitions_per_insert_block>
<number_of_mutations_to_throw>1</number_of_mutations_to_throw>
</default>
<!-- Profile that allows only read queries. -->
<readonly>
<readonly>1</readonly>
</readonly>
<api>
<profile>default</profile>
</api>
<app>
<profile>default</profile>
</app>
</profiles>
<!-- Users and ACL. -->
<users>
<!-- If user name was not specified, 'default' user is used. -->
<default>
<!-- See also the files in users.d directory where the password can be overridden.
Password could be specified in plaintext or in SHA256 (in hex format).
If you want to specify password in plaintext (not recommended), place it in 'password' element.
Example: <password>qwerty</password>.
Password could be empty.
If you want to specify SHA256, place it in 'password_sha256_hex' element.
Example:
<password_sha256_hex>65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5</password_sha256_hex>
Restrictions of SHA256: impossibility to connect to ClickHouse using MySQL JS client (as of July
2019).
If you want to specify double SHA1, place it in 'password_double_sha1_hex' element.
Example:
<password_double_sha1_hex>e395796d6546b1b65db9d665cd43f0e858dd4303</password_double_sha1_hex>
If you want to specify a previously defined LDAP server (see 'ldap_servers' in the main config) for
authentication,
place its name in 'server' element inside 'ldap' element.
Example: <ldap><server>my_ldap_server</server></ldap>
If you want to authenticate the user via Kerberos (assuming Kerberos is enabled, see 'kerberos' in
the main config),
place 'kerberos' element instead of 'password' (and similar) elements.
The name part of the canonical principal name of the initiator must match the user name for
authentication to succeed.
You can also place 'realm' element inside 'kerberos' element to further restrict authentication to
only those requests
whose initiator's realm matches it.
Example: <kerberos />
Example: <kerberos><realm>EXAMPLE.COM</realm></kerberos>
How to generate decent password:
Execute: PASSWORD=$(base64 < /dev/urandom | head -c8); echo "$PASSWORD"; echo -n "$PASSWORD" |
sha256sum | tr -d '-'
In first line will be password and in second - corresponding SHA256.
How to generate double SHA1:
Execute: PASSWORD=$(base64 < /dev/urandom | head -c8); echo "$PASSWORD"; echo -n "$PASSWORD" |
sha1sum | tr -d '-' | xxd -r -p | sha1sum | tr -d '-'
In first line will be password and in second - corresponding double SHA1.
-->
<password></password>
<!-- List of networks with open access.
To open access from everywhere, specify:
<ip>::/0</ip>
To open access only from localhost, specify:
<ip>::1</ip>
<ip>127.0.0.1</ip>
Each element of list has one of the following forms:
<ip> IP-address or network mask. Examples: 213.180.204.3 or 10.0.0.1/8 or 10.0.0.1/255.255.255.0
2a02:6b8::3 or 2a02:6b8::3/64 or 2a02:6b8::3/ffff:ffff:ffff:ffff::.
<host> Hostname. Example: server01.yandex.ru.
To check access, DNS query is performed, and all received addresses compared to peer address.
<host_regexp> Regular expression for host names. Example, ^server\d\d-\d\d-\d\.yandex\.ru$
To check access, DNS PTR query is performed for peer address and then regexp is applied.
Then, for result of PTR query, another DNS query is performed and all received addresses compared
to peer address.
Strongly recommended that regexp is ends with $
All results of DNS requests are cached till server restart.
-->
<networks>
<ip>::/0</ip>
</networks>
<!-- Settings profile for user. -->
<profile>default</profile>
<!-- Quota for user. -->
<quota>default</quota>
<!-- User can create other users and grant rights to them. -->
<access_management>1</access_management>
<named_collection_control>1</named_collection_control>
<show_named_collections>1</show_named_collections>
<show_named_collections_secrets>1</show_named_collections_secrets>
</default>
<api>
<password>apipass</password>
<networks>
<ip>::/0</ip>
</networks>
<profile>api</profile>
<quota>default</quota>
</api>
<app>
<password>apppass</password>
<networks>
<ip>::/0</ip>
</networks>
<profile>app</profile>
<quota>default</quota>
</app>
</users>
<!-- Quotas. -->
<quotas>
<!-- Name of quota. -->
<default>
<!-- Limits for time interval. You could specify many intervals with different limits. -->
<interval>
<!-- Length of interval. -->
<duration>3600</duration>
<!-- No limits. Just calculate resource usage for time interval. -->
<queries>0</queries>
<errors>0</errors>
<result_rows>0</result_rows>
<read_rows>0</read_rows>
<execution_time>0</execution_time>
</interval>
</default>
</quotas>
</yandex>
- type: bind
source: ./clickhouse/config.d/default.xml
target: /etc/clickhouse-server/config.d/default.xml
read_only: true
content: |
<clickhouse>
<tcp_port>9000</tcp_port>
<remote_servers>
<posthog>
<shard>
<replica>
<host>clickhouse</host>
<port>9000</port>
</replica>
</shard>
</posthog>
<posthog_single_shard>
<shard>
<replica>
<host>clickhouse</host>
<port>9000</port>
</replica>
</shard>
</posthog_single_shard>
<posthog_migrations>
<shard>
<replica>
<host>clickhouse</host>
<port>9000</port>
</replica>
</shard>
</posthog_migrations>
<posthog_writable>
<shard>
<replica>
<host>clickhouse</host>
<port>9000</port>
</replica>
</shard>
</posthog_writable>
<posthog_primary_replica>
<shard>
<replica>
<host>clickhouse</host>
<port>9000</port>
</replica>
</shard>
</posthog_primary_replica>
</remote_servers>
<named_collections>
<msk_cluster>
<kafka_broker_list from_env="KAFKA_HOSTS"/>
</msk_cluster>
<warpstream_ingestion>
<kafka_broker_list from_env="KAFKA_HOSTS"/>
</warpstream_ingestion>
</named_collections>
<macros>
<shard>01</shard>
<replica>ch1</replica>
<hostClusterType>online</hostClusterType>
<hostClusterRole>data</hostClusterRole>
</macros>
</clickhouse>
- type: bind
source: ./clickhouse/user_defined_function.xml
target: /etc/clickhouse-server/user_defined_function.xml
read_only: true
content: |
<functions>
<function>
<type>executable_pool</type>
<name>aggregate_funnel</name>
<return_type>Array(Tuple(Int8, Nullable(String), Array(Float64), Array(Array(UUID)), UInt32))</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(Nullable(String))</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Int8)</type>
<name>optional_steps</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UUID, Nullable(String), Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel steps</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_cohort</name>
<return_type>Array(Tuple(Int8, UInt64, Array(Float64), Array(Array(UUID)), UInt32))</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(UInt64)</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Int8)</type>
<name>optional_steps</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UUID, UInt64, Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel steps</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_array</name>
<return_type>Array(Tuple(Int8, Array(String), Array(Float64), Array(Array(UUID)), UInt32))</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(Array(String))</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Int8)</type>
<name>optional_steps</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UUID, Array(String), Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel steps</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_test</name>
<return_type>String</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(Array(String))</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Int8)</type>
<name>optional_steps</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UUID, Nullable(String), Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel_test.py</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_trends</name>
<return_type>Array(Tuple(UInt64, Int8, Nullable(String), UUID))</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>from_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>to_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(Nullable(String))</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UInt64, UUID, Nullable(String), Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel trends</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_array_trends</name>
<!-- Return type for trends is a start interval time, a success flag (1 or -1), and a breakdown value -->
<return_type>Array(Tuple(UInt64, Int8, Array(String), UUID))</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>from_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>to_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(Array(String))</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UInt64, UUID, Array(String), Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel trends</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_cohort_trends</name>
<!-- Return type for trends is a start interval time, a success flag (1 or -1), and a breakdown value -->
<return_type>Array(Tuple(UInt64, Int8, UInt64, UUID))</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>from_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>to_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(UInt64)</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UInt64, UUID, UInt64, Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel trends</command>
<lifetime>600</lifetime>
</function>
<function>
<type>executable_pool</type>
<name>aggregate_funnel_array_trends_test</name>
<return_type>String</return_type>
<return_name>result</return_name>
<argument>
<type>UInt8</type>
<name>from_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>to_step</name>
</argument>
<argument>
<type>UInt8</type>
<name>num_steps</name>
</argument>
<argument>
<type>UInt64</type>
<name>conversion_window_limit</name>
</argument>
<argument>
<type>String</type>
<name>breakdown_attribution_type</name>
</argument>
<argument>
<type>String</type>
<name>funnel_order_type</name>
</argument>
<argument>
<type>Array(Array(String))</type>
<name>prop_vals</name>
</argument>
<argument>
<type>Array(Tuple(Nullable(Float64), UInt64, UUID, Array(String), Array(Int8)))</type>
<name>value</name>
</argument>
<format>JSONEachRow</format>
<command>aggregate_funnel_array_trends_test.py</command>
<lifetime>600</lifetime>
</function>
</functions>
- posthog-clickhouse-data:/var/lib/clickhouse
# Workaround for ClickHouse crash_log table not existing on fresh installs.
# See: https://github.com/PostHog/posthog/issues/40300
clickhouse-init:
image: alpine:3.19
entrypoint: /bin/sh
command:
- '-c'
- |
apk add --no-cache curl
until curl -sf http://clickhouse:8123/ping >/dev/null 2>&1; do sleep 2; done
curl -sf 'http://clickhouse:8123/' --data "CREATE TABLE IF NOT EXISTS system.crash_log (hostname LowCardinality(String) DEFAULT hostName(), event_date Date, event_time DateTime, timestamp_ns UInt64, signal Int32, thread_id UInt64, query_id String, trace Array(UInt64), trace_full Array(String), version String, revision UInt32, build_id String) ENGINE = MergeTree ORDER BY (event_date, event_time) TTL event_date + INTERVAL 30 DAY"
depends_on:
clickhouse:
condition: service_healthy
restart: 'no'
kafka:
image: docker.redpanda.com/redpandadata/redpanda:v25.1.9
command:
- redpanda
- start
- '--kafka-addr internal://0.0.0.0:9092'
- '--advertise-kafka-addr internal://kafka:9092'
- '--pandaproxy-addr internal://0.0.0.0:8082'
- '--advertise-pandaproxy-addr internal://kafka:8082'
- '--schema-registry-addr internal://0.0.0.0:8081'
- '--rpc-addr kafka:33145'
- '--advertise-rpc-addr kafka:33145'
- '--mode dev-container'
- '--smp 1'
- '--memory 1500M'
- '--reserve-memory 200M'
- '--overprovisioned'
- '--set redpanda.empty_seed_starts_cluster=false'
- '--seeds kafka:33145'
- '--set redpanda.auto_create_topics_enabled=true'
environment:
- ALLOW_PLAINTEXT_LISTENER=true
healthcheck:
test: curl -f http://localhost:9644/v1/status/ready || exit 1
interval: 5s
timeout: 10s
retries: 30
start_period: 30s
volumes:
- posthog-kafka-data:/var/lib/redpanda/data
kafka-init:
image: docker.redpanda.com/redpandadata/redpanda:v25.1.9
entrypoint: /bin/sh
command:
- '-c'
- |
TIMEOUT=120
ELAPSED=0
until rpk topic list --brokers kafka:9092 2>/dev/null; do
sleep 3
ELAPSED=$$((ELAPSED + 3))
if [ $$ELAPSED -ge $$TIMEOUT ]; then exit 1; fi
done
for topic in exceptions_ingestion clickhouse_events_json events_plugin_ingestion; do
rpk topic create "$$topic" --brokers kafka:9092 -p 1 -r 1 2>&1 || true
done
depends_on:
kafka:
condition: service_healthy
restart: 'no'
objectstorage:
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
environment:
- MINIO_ROOT_USER=${SERVICE_USER_MINIO}
- MINIO_ROOT_PASSWORD=${SERVICE_PASSWORD_MINIO}
entrypoint: sh
command: -c 'mkdir -p /data/posthog && minio server --address ":19000" --console-address ":19001" /data'
volumes:
- posthog-objectstorage:/data
seaweedfs:
image: chrislusf/seaweedfs:4.03
entrypoint:
- /bin/sh
- '-c'
- |
/usr/bin/weed "$$@" &
WEED_PID=$$!
while true; do
sleep 5
if echo "s3.bucket.list" | /usr/bin/weed shell -master=localhost:9333 2>&1 | grep -q "posthog"; then break; fi
echo "s3.bucket.create -name posthog" | /usr/bin/weed shell -master=localhost:9333 2>&1 || true
done
wait $$WEED_PID
- '--'
command:
- server
- '-s3'
- '-s3.port=8333'
- '-dir=/data'
volumes:
- posthog-seaweedfs:/data
healthcheck:
test: ['CMD', 'sh', '-c', "echo 's3.bucket.list' | /usr/bin/weed shell -master=localhost:9333 2>&1 | grep -q posthog"]
interval: 5s
timeout: 10s
retries: 30
start_period: 15s
temporal:
image: temporalio/auto-setup:1.20.0
environment:
- DB=postgresql
- DB_PORT=5432
- POSTGRES_USER=posthog
- POSTGRES_PWD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_SEEDS=db
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
- ENABLE_ES=false
healthcheck:
test: ['CMD-SHELL', 'nc -z $$(hostname) 7233']
interval: 5s
timeout: 5s
retries: 30
start_period: 300s
depends_on:
db:
condition: service_healthy
volumes:
- type: bind
source: ./temporal/dynamicconfig/development-sql.yaml
target: /etc/temporal/config/dynamicconfig/development-sql.yaml
read_only: true
content: |
limit.maxIDLength:
- value: 255
constraints: {}
system.forceSearchAttributesCacheRefreshOnRead:
- value: true
constraints: {}
temporal-admin-tools:
image: temporalio/admin-tools:1.20.0
environment:
- TEMPORAL_CLI_ADDRESS=temporal:7233
depends_on:
- temporal
temporal-ui:
image: temporalio/ui:2.31.2
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CORS_ORIGINS=http://localhost:3000
- TEMPORAL_CSRF_COOKIE_INSECURE=true
depends_on:
temporal:
condition: service_started
db:
condition: service_healthy
web:
image: posthog/posthog:latest
command:
- /bin/bash
- '-c'
- './bin/migrate && ./bin/docker-server'
environment:
- OTEL_SDK_DISABLED=true
- DISABLE_SECURE_SSL_REDIRECT=true
- IS_BEHIND_PROXY=true
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_DATABASE=posthog
- CLICKHOUSE_SECURE=false
- CLICKHOUSE_VERIFY=false
- CLICKHOUSE_API_USER=api
- CLICKHOUSE_API_PASSWORD=apipass
- CLICKHOUSE_APP_USER=app
- CLICKHOUSE_APP_PASSWORD=apppass
- KAFKA_HOSTS=kafka
- REDIS_URL=redis://redis:6379/
- PGHOST=db
- PGUSER=posthog
- PGPASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DEPLOYMENT=hobby
- CDP_API_URL=http://plugins:6738
- FLAGS_REDIS_ENABLED=false
- SITE_URL=${SERVICE_URL_PROXY}
- SECRET_KEY=${SERVICE_BASE64_64_SECRETKEY}
- ENCRYPTION_SALT_KEYS=${SERVICE_PASSWORD_ENCRYPTION}
- OBJECT_STORAGE_ACCESS_KEY_ID=${SERVICE_USER_MINIO}
- OBJECT_STORAGE_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_MINIO}
- OBJECT_STORAGE_ENDPOINT=http://objectstorage:19000
- OBJECT_STORAGE_PUBLIC_ENDPOINT=${SERVICE_URL_PROXY}
- SESSION_RECORDING_V2_S3_ENDPOINT=http://seaweedfs:8333
- SESSION_RECORDING_V2_S3_ACCESS_KEY_ID=any
- SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY=any
- OBJECT_STORAGE_ENABLED=true
- RECORDING_API_URL=http://plugins:6738
- LIVESTREAM_HOST=${SERVICE_URL_PROXY}/livestream
- USE_GRANIAN=true
- GRANIAN_WORKERS=2
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
clickhouse-init:
condition: service_completed_successfully
kafka:
condition: service_healthy
kafka-init:
condition: service_completed_successfully
objectstorage:
condition: service_started
seaweedfs:
condition: service_healthy
worker:
image: posthog/posthog:latest
command: ./bin/docker-worker-celery --with-scheduler
environment:
- OTEL_SDK_DISABLED=true
- DISABLE_SECURE_SSL_REDIRECT=true
- IS_BEHIND_PROXY=true
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_DATABASE=posthog
- CLICKHOUSE_SECURE=false
- CLICKHOUSE_VERIFY=false
- CLICKHOUSE_API_USER=api
- CLICKHOUSE_API_PASSWORD=apipass
- CLICKHOUSE_APP_USER=app
- CLICKHOUSE_APP_PASSWORD=apppass
- KAFKA_HOSTS=kafka
- REDIS_URL=redis://redis:6379/
- PGHOST=db
- PGUSER=posthog
- PGPASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DEPLOYMENT=hobby
- CDP_API_URL=http://plugins:6738
- FLAGS_REDIS_ENABLED=false
- SITE_URL=${SERVICE_URL_PROXY}
- SECRET_KEY=${SERVICE_BASE64_64_SECRETKEY}
- ENCRYPTION_SALT_KEYS=${SERVICE_PASSWORD_ENCRYPTION}
- OBJECT_STORAGE_ACCESS_KEY_ID=${SERVICE_USER_MINIO}
- OBJECT_STORAGE_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_MINIO}
- OBJECT_STORAGE_ENDPOINT=http://objectstorage:19000
- OBJECT_STORAGE_PUBLIC_ENDPOINT=${SERVICE_URL_PROXY}
- SESSION_RECORDING_V2_S3_ENDPOINT=http://seaweedfs:8333
- SESSION_RECORDING_V2_S3_ACCESS_KEY_ID=any
- SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY=any
- OBJECT_STORAGE_ENABLED=true
- RECORDING_API_URL=http://plugins:6738
- POSTHOG_SKIP_MIGRATION_CHECKS=1
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
kafka:
condition: service_healthy
web:
condition: service_started
plugins:
image: posthog/posthog-node:latest
command: node nodejs/dist/index.js
environment:
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- PERSONS_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- BEHAVIORAL_COHORTS_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- CYCLOTRON_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- KAFKA_HOSTS=kafka:9092
- REDIS_URL=redis://redis:6379/
- POSTHOG_REDIS_HOST=redis
- POSTHOG_REDIS_PORT=6379
- INGESTION_REDIS_HOST=redis
- INGESTION_REDIS_PORT=6379
- LOGS_REDIS_HOST=redis
- LOGS_REDIS_PORT=6379
- LOGS_REDIS_TLS=false
- CDP_REDIS_HOST=redis
- CDP_REDIS_PORT=6379
- SESSION_RECORDING_API_REDIS_HOST=redis
- SESSION_RECORDING_API_REDIS_PORT=6379
- COOKIELESS_REDIS_HOST=redis
- COOKIELESS_REDIS_PORT=6379
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_DATABASE=posthog
- CLICKHOUSE_SECURE=false
- CLICKHOUSE_VERIFY=false
- SITE_URL=${SERVICE_URL_PROXY}
- SECRET_KEY=${SERVICE_BASE64_64_SECRETKEY}
- ENCRYPTION_SALT_KEYS=${SERVICE_PASSWORD_ENCRYPTION}
- OBJECT_STORAGE_ACCESS_KEY_ID=${SERVICE_USER_MINIO}
- OBJECT_STORAGE_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_MINIO}
- SESSION_RECORDING_V2_S3_ACCESS_KEY_ID=any
- SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY=any
- SESSION_RECORDING_V2_S3_TIMEOUT_MS=120000
- OBJECT_STORAGE_ENDPOINT=http://objectstorage:19000
- OBJECT_STORAGE_PUBLIC_ENDPOINT=${SERVICE_URL_PROXY}
- SESSION_RECORDING_V2_S3_ENDPOINT=http://seaweedfs:8333
- OBJECT_STORAGE_ENABLED=true
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
kafka:
condition: service_healthy
objectstorage:
condition: service_started
seaweedfs:
condition: service_healthy
web:
condition: service_started
temporal-django-worker:
image: posthog/posthog:latest
command: ./bin/temporal-django-worker
environment:
- OTEL_SDK_DISABLED=true
- DISABLE_SECURE_SSL_REDIRECT=true
- IS_BEHIND_PROXY=true
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_DATABASE=posthog
- CLICKHOUSE_SECURE=false
- CLICKHOUSE_VERIFY=false
- CLICKHOUSE_API_USER=api
- CLICKHOUSE_API_PASSWORD=apipass
- CLICKHOUSE_APP_USER=app
- CLICKHOUSE_APP_PASSWORD=apppass
- KAFKA_HOSTS=kafka
- REDIS_URL=redis://redis:6379/
- PGHOST=db
- PGUSER=posthog
- PGPASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DEPLOYMENT=hobby
- CDP_API_URL=http://plugins:6738
- FLAGS_REDIS_ENABLED=false
- SITE_URL=${SERVICE_URL_PROXY}
- SECRET_KEY=${SERVICE_BASE64_64_SECRETKEY}
- ENCRYPTION_SALT_KEYS=${SERVICE_PASSWORD_ENCRYPTION}
- OBJECT_STORAGE_ACCESS_KEY_ID=${SERVICE_USER_MINIO}
- OBJECT_STORAGE_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_MINIO}
- OBJECT_STORAGE_ENDPOINT=http://objectstorage:19000
- OBJECT_STORAGE_PUBLIC_ENDPOINT=${SERVICE_URL_PROXY}
- SESSION_RECORDING_V2_S3_ENDPOINT=http://seaweedfs:8333
- SESSION_RECORDING_V2_S3_ACCESS_KEY_ID=any
- SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY=any
- OBJECT_STORAGE_ENABLED=true
- RECORDING_API_URL=http://plugins:6738
- TEMPORAL_HOST=temporal
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
kafka:
condition: service_healthy
objectstorage:
condition: service_started
seaweedfs:
condition: service_healthy
temporal:
condition: service_healthy
asyncmigrationscheck:
image: posthog/posthog:latest
command: python manage.py run_async_migrations --check
restart: 'no'
environment:
- OTEL_SDK_DISABLED=true
- DISABLE_SECURE_SSL_REDIRECT=true
- IS_BEHIND_PROXY=true
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- CLICKHOUSE_HOST=clickhouse
- CLICKHOUSE_DATABASE=posthog
- CLICKHOUSE_SECURE=false
- CLICKHOUSE_VERIFY=false
- CLICKHOUSE_API_USER=api
- CLICKHOUSE_API_PASSWORD=apipass
- CLICKHOUSE_APP_USER=app
- CLICKHOUSE_APP_PASSWORD=apppass
- KAFKA_HOSTS=kafka
- REDIS_URL=redis://redis:6379/
- PGHOST=db
- PGUSER=posthog
- PGPASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DEPLOYMENT=hobby
- CDP_API_URL=http://plugins:6738
- FLAGS_REDIS_ENABLED=false
- SITE_URL=${SERVICE_URL_PROXY}
- SECRET_KEY=${SERVICE_BASE64_64_SECRETKEY}
- ENCRYPTION_SALT_KEYS=${SERVICE_PASSWORD_ENCRYPTION}
- OBJECT_STORAGE_ACCESS_KEY_ID=${SERVICE_USER_MINIO}
- OBJECT_STORAGE_SECRET_ACCESS_KEY=${SERVICE_PASSWORD_MINIO}
- OBJECT_STORAGE_ENDPOINT=http://objectstorage:19000
- OBJECT_STORAGE_PUBLIC_ENDPOINT=${SERVICE_URL_PROXY}
- SESSION_RECORDING_V2_S3_ENDPOINT=http://seaweedfs:8333
- SESSION_RECORDING_V2_S3_ACCESS_KEY_ID=any
- SESSION_RECORDING_V2_S3_SECRET_ACCESS_KEY=any
- OBJECT_STORAGE_ENABLED=true
- RECORDING_API_URL=http://plugins:6738
- SKIP_ASYNC_MIGRATIONS_SETUP=0
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
clickhouse:
condition: service_healthy
kafka:
condition: service_healthy
web:
condition: service_started
capture:
image: ghcr.io/posthog/posthog/capture:master
environment:
- ADDRESS=0.0.0.0:3000
- KAFKA_TOPIC=events_plugin_ingestion
- KAFKA_HOSTS=kafka:9092
- REDIS_URL=redis://redis:6379/
- CAPTURE_MODE=events
- RUST_LOG=info,rdkafka=warn
depends_on:
kafka:
condition: service_healthy
redis:
condition: service_healthy
replay-capture:
image: ghcr.io/posthog/posthog/capture:master
environment:
- ADDRESS=0.0.0.0:3000
- KAFKA_TOPIC=session_recording_snapshot_item_events
- KAFKA_HOSTS=kafka:9092
- REDIS_URL=redis://redis:6379/
- CAPTURE_MODE=recordings
depends_on:
kafka:
condition: service_healthy
redis:
condition: service_healthy
livestream:
image: ghcr.io/posthog/posthog/livestream:master
environment:
- LIVESTREAM_JWT_SECRET=${SERVICE_BASE64_64_SECRETKEY}
- LIVESTREAM_MMDB_PATH=/share/GeoLite2-City.mmdb
- LIVESTREAM_KAFKA_BROKERS=kafka:9092
- LIVESTREAM_KAFKA_TOPIC=events_plugin_ingestion
- LIVESTREAM_KAFKA_GROUP_ID=livestream
- LIVESTREAM_REDIS_ADDRESS=redis:6379
depends_on:
geoip-init:
condition: service_completed_successfully
kafka:
condition: service_started
volumes:
- posthog-share:/share
- type: bind
source: ./livestream/configs.yml
target: /code/configs/configs.yml
read_only: true
content: |
debug: false
kafka:
brokers: 'kafka:9092'
topic: 'events_plugin_ingestion'
group_id: 'livestream'
security_protocol: 'PLAINTEXT'
session_recording_enabled: true
session_recording_security_protocol: 'PLAINTEXT'
redis:
address: 'redis:6379'
mmdb:
path: '/share/GeoLite2-City.mmdb'
feature-flags:
image: ghcr.io/posthog/posthog/feature-flags:master
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3001/_readiness']
interval: 5s
timeout: 5s
retries: 12
start_period: 10s
environment:
- WRITE_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- READ_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- PERSONS_WRITE_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- PERSONS_READ_DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- MAXMIND_DB_PATH=/share/GeoLite2-City.mmdb
- REDIS_URL=redis://redis:6379/
- ADDRESS=0.0.0.0:3001
- RUST_LOG=info
- COOKIELESS_REDIS_HOST=redis
- COOKIELESS_REDIS_PORT=6379
volumes:
- posthog-share:/share
depends_on:
geoip-init:
condition: service_completed_successfully
db:
condition: service_healthy
redis:
condition: service_healthy
property-defs-rs:
image: ghcr.io/posthog/posthog/property-defs-rs:master
environment:
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- KAFKA_HOSTS=kafka:9092
- SKIP_WRITES=false
- SKIP_READS=false
- FILTER_MODE=opt-out
depends_on:
kafka-init:
condition: service_completed_successfully
db:
condition: service_healthy
cyclotron-janitor:
image: ghcr.io/posthog/posthog/cyclotron-janitor:master
environment:
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- KAFKA_HOSTS=kafka:9092
- KAFKA_TOPIC=clickhouse_app_metrics2
depends_on:
db:
condition: service_healthy
kafka:
condition: service_started
geoip-init:
image: alpine:3.19
entrypoint: /bin/sh
command:
- '-c'
- |
if [ -f /share/GeoLite2-City.mmdb ]; then echo "GeoIP DB already exists"; exit 0; fi
apk add --no-cache curl brotli
curl -L 'https://mmdbcdn.posthog.net/' --http1.1 | brotli --decompress > /share/GeoLite2-City.mmdb
chmod 644 /share/GeoLite2-City.mmdb
restart: 'no'
volumes:
- posthog-share:/share
cymbal:
image: ghcr.io/posthog/posthog/cymbal:master
environment:
- KAFKA_HOSTS=kafka:9092
- KAFKA_CONSUMER_GROUP=cymbal
- KAFKA_CONSUMER_TOPIC=exceptions_ingestion
- OBJECT_STORAGE_BUCKET=posthog
- OBJECT_STORAGE_ACCESS_KEY_ID=any
- OBJECT_STORAGE_SECRET_ACCESS_KEY=any
- OBJECT_STORAGE_ENDPOINT=http://seaweedfs:8333
- OBJECT_STORAGE_FORCE_PATH_STYLE=true
- BIND_HOST=0.0.0.0
- BIND_PORT=3302
- DATABASE_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- PERSONS_URL=postgres://posthog:${SERVICE_PASSWORD_POSTGRES}@db:5432/posthog
- MAXMIND_DB_PATH=/share/GeoLite2-City.mmdb
- REDIS_URL=redis://redis:6379/
- ISSUE_BUCKETS_REDIS_URL=redis://redis:6379/
- RUST_LOG=info
volumes:
- posthog-share:/share
depends_on:
geoip-init:
condition: service_completed_successfully
kafka-init:
condition: service_completed_successfully
seaweedfs:
condition: service_healthy
db:
condition: service_healthy
redis:
condition: service_healthy
proxy:
image: caddy:2-alpine
command: caddy run -c /etc/caddy/Caddyfile
environment:
- SERVICE_URL_PROXY_8000
depends_on:
- web
- capture
- livestream
ports:
- '8000'
volumes:
- type: bind
source: ./caddy/Caddyfile
target: /etc/caddy/Caddyfile
content: |
:8000 {
@replay-capture {
path /s /s/ /s/*
}
@capture {
path /e /e/ /e/*
path /i/v0 /i/v0/ /i/v0/*
path /batch /batch/ /batch/*
path /capture /capture/ /capture/*
}
@livestream {
path /livestream /livestream/ /livestream/*
}
@flags {
path /flags /flags/ /flags/*
}
@webhooks {
path /public/webhooks /public/webhooks/ /public/webhooks/*
path /public/m/ /public/m/*
}
@objectstorage {
path /posthog /posthog/ /posthog/*
}
handle @livestream {
uri strip_prefix /livestream
reverse_proxy livestream:8080 {
flush_interval -1
}
}
handle @replay-capture {
reverse_proxy replay-capture:3000
}
handle @capture {
reverse_proxy capture:3000
}
handle @flags {
reverse_proxy feature-flags:3001
}
handle @webhooks {
reverse_proxy plugins:6738
}
handle @objectstorage {
reverse_proxy objectstorage:19000
}
handle {
reverse_proxy web:8000
}
}
- posthog-caddy-data:/data
- posthog-caddy-config:/config