Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
467 changes: 251 additions & 216 deletions AGENTS.md

Large diffs are not rendered by default.

30 changes: 24 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,38 @@ FROM clickhouse/clickhouse-server:latest
# Install nginx and supervisor
RUN apt-get update && apt-get install -y nginx supervisor && rm -rf /var/lib/apt/lists/*

# Node runtime for the capture server (glibc binary copied from the official
# Debian-based node image; the capture scripts use only Node built-ins, so no
# node_modules are required). The clickhouse-server image is glibc-based too.
COPY --from=node:20-bookworm-slim /usr/local/bin/node /usr/local/bin/node

# clickhouse-client used by the capture proxy. The single `clickhouse` binary
# dispatches on argv[0], so a clickhouse-client symlink runs in client mode.
RUN ln -sf /usr/bin/clickhouse /usr/local/bin/clickhouse-client

# Copy built frontend
COPY --from=builder /app/dist /var/www/html

# Copy nginx config (proxies /clickhouse to ClickHouse)
COPY docker/nginx.conf /etc/nginx/nginx.conf
# Capture server + proxy harness (no dependencies beyond Node built-ins)
COPY scripts/native-proxy.mjs scripts/capture-middleware.mjs scripts/capture-server.mjs /app/scripts/

# Copy ClickHouse user config (read-only viewer user)
# nginx config template (rendered at start with the proxy user) + start scripts
COPY docker/nginx.conf.template /etc/nginx/nginx.conf.template
COPY docker/start-nginx.sh docker/start-capture.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/start-nginx.sh /usr/local/bin/start-capture.sh

# Copy ClickHouse user config (viewer = read-only, writer = read-write)
COPY docker/users.xml /etc/clickhouse-server/users.d/viewer.xml

# Copy supervisor config (runs both nginx and ClickHouse)
# Copy supervisor config (runs ClickHouse + capture server + nginx)
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Expose only web port (ClickHouse internal only)
# READONLY=1 (default) serves the read-only viewer user; set READONLY=0 for an
# internal deployment that should also be able to INSERT.
ENV READONLY=1

# Expose only web port (ClickHouse and capture server are internal only)
EXPOSE 80

# Start supervisor (manages nginx + ClickHouse)
# Start supervisor (manages ClickHouse + capture server + nginx)
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
14 changes: 11 additions & 3 deletions docker/nginx.conf → docker/nginx.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,23 @@ http {
try_files $uri $uri/ /index.html;
}

# Proxy /clickhouse to ClickHouse server
# Native TCP protocol capture: proxied to the local capture server,
# which drives clickhouse-client through the packet-capturing proxy.
location /capture {
proxy_pass http://127.0.0.1:8124;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}

# Proxy /clickhouse to ClickHouse server. __CH_PROXY_USER__ is rendered
# at container start from the READONLY env var (viewer or writer).
location /clickhouse {
rewrite ^/clickhouse(.*) $1 break;
proxy_pass http://127.0.0.1:8123;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# Use the read-only viewer user
proxy_set_header X-ClickHouse-User viewer;
proxy_set_header X-ClickHouse-User __CH_PROXY_USER__;
proxy_set_header X-ClickHouse-Key "";
}
}
Expand Down
31 changes: 31 additions & 0 deletions docker/start-capture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
# Start the native-protocol capture server. The ClickHouse user it connects as
# follows the READONLY toggle (same as the HTTP proxy):
#
# READONLY=1 (default) -> viewer (read-only; experimental settings come from
# the profile, not per-query, so they are
# not re-sent as client settings)
# READONLY=0 -> writer (can INSERT / DDL)
#
# Any of CH_USER / CH_PASSWORD / CH_NATIVE_HOST / CH_NATIVE_PORT can override.
set -e

if [ "${READONLY:-1}" = "0" ]; then
DEFAULT_USER=writer
: "${CAPTURE_EXPERIMENTAL_SETTINGS:=1}"
else
DEFAULT_USER=viewer
# A readonly user rejects per-query setting changes; rely on its profile.
: "${CAPTURE_EXPERIMENTAL_SETTINGS:=0}"
fi

export CH_USER="${CH_USER:-$DEFAULT_USER}"
export CH_PASSWORD="${CH_PASSWORD:-}"
export CH_NATIVE_HOST="${CH_NATIVE_HOST:-127.0.0.1}"
export CH_NATIVE_PORT="${CH_NATIVE_PORT:-9000}"
export CLICKHOUSE_CLIENT="${CLICKHOUSE_CLIENT:-clickhouse-client}"
export CAPTURE_EXPERIMENTAL_SETTINGS
export CAPTURE_BIND="${CAPTURE_BIND:-127.0.0.1}"
export CAPTURE_PORT="${CAPTURE_PORT:-8124}"

exec node /app/scripts/capture-server.mjs
18 changes: 18 additions & 0 deletions docker/start-nginx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh
# Render the nginx config, substituting the ClickHouse proxy user based on the
# READONLY toggle, then run nginx in the foreground.
#
# READONLY=1 (default) -> viewer (read-only)
# READONLY=0 -> writer (can INSERT / DDL)
set -e

if [ "${READONLY:-1}" = "0" ]; then
CH_PROXY_USER=writer
else
CH_PROXY_USER=viewer
fi

sed "s/__CH_PROXY_USER__/${CH_PROXY_USER}/g" \
/etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf

exec /usr/sbin/nginx -g "daemon off;"
11 changes: 10 additions & 1 deletion docker/supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:capture]
command=/usr/local/bin/start-capture.sh
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
command=/usr/local/bin/start-nginx.sh
autostart=true
autorestart=true
stdout_logfile=/dev/stdout
Expand Down
26 changes: 26 additions & 0 deletions docker/users.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<?xml version="1.0"?>
<!--
Two users back the explorer:
viewer - readonly (default). Used when READONLY != 0.
writer - read-write, can INSERT. Used when READONLY=0 (internal deployments).
The container's entrypoint scripts select which one the HTTP proxy and the
capture server connect as, based on the READONLY environment variable.
Both profiles enable the experimental types so format exploration works.
-->
<clickhouse>
<users>
<viewer>
Expand All @@ -10,6 +18,15 @@
<quota>default</quota>
<access_management>0</access_management>
</viewer>
<writer>
<password></password>
<networks>
<ip>::/0</ip>
</networks>
<profile>readwrite</profile>
<quota>default</quota>
<access_management>0</access_management>
</writer>
</users>
<profiles>
<readonly>
Expand All @@ -22,5 +39,14 @@
<allow_experimental_qbit_type>1</allow_experimental_qbit_type>
<allow_suspicious_low_cardinality_types>1</allow_suspicious_low_cardinality_types>
</readonly>
<readwrite>
<!-- readonly defaults to 0: SELECT, INSERT and DDL allowed -->
<allow_experimental_variant_type>1</allow_experimental_variant_type>
<allow_experimental_dynamic_type>1</allow_experimental_dynamic_type>
<allow_experimental_json_type>1</allow_experimental_json_type>
<allow_suspicious_variant_types>1</allow_suspicious_variant_types>
<allow_experimental_qbit_type>1</allow_experimental_qbit_type>
<allow_suspicious_low_cardinality_types>1</allow_suspicious_low_cardinality_types>
</readwrite>
</profiles>
</clickhouse>
Loading
Loading