ClickHouse
pg addon install clickhouse runs ClickHouse 26.8 LTS — a single-node columnar OLAP database — as a managed container from the official docker.io/clickhouse/clickhouse-server multi-arch image. Like VictoriaMetrics and InfluxDB, it is a top-level addon: it does not front a PostgreSQL instance.
ClickHouse speaks two protocols on two ports: HTTP (curl, JDBC/ODBC, many drivers) and the native TCP protocol (clickhouse-client). pgcli runs it with host networking, so both are plain host ports. Authentication is always enabled — the install creates an admin account and stores its password in pg.yaml.
The admin password is generated at install time and can be read back at any time with
pg addon password clickhouse [--name NAME]. The HTTP and native listeners require authentication on every request; there is no anonymous access.--listendefaults to127.0.0.1— pass0.0.0.0explicitly when other hosts must connect, and front it with nginx for TLS.
Parameters
| Flag | Default | Description |
|---|---|---|
--name |
clickhouse |
Instance name (map key; also the default subcommand name) |
--version |
26.8.36.3 |
Upstream image tag, pulled directly from docker.io/clickhouse/clickhouse-server |
--data-dir |
<base_dir>/addon/clickhouse/<name>/data |
Data directory bind-mounted at /var/lib/clickhouse; must be an absolute path |
--listen |
127.0.0.1 |
Bind address for the HTTP and native listeners. 0.0.0.0 exposes both on the network |
--http-port |
auto (base 8123) |
Host HTTP port; 0 = auto-assign from clickhouse_http_start_port |
--tcp-port |
auto (base 9000) |
Host native-protocol port; 0 = auto-assign from clickhouse_tcp_start_port (shared with the S3 API pool) |
--admin-user |
admin |
Admin account created by the image entrypoint |
--admin-password |
generated | Admin password. On --force the stored password is kept unless this is set |
--config-option K=V |
— | Extra server setting as a dotted XML path (logger.level=debug, max_server_memory_usage=2147483648); repeatable; replaces the stored override map |
--force |
false | Re-render the config and secret, then recreate the container; keeps data, the stored password and the stored overrides |
--image does not apply to ClickHouse — use --version.
The generated config only pins listeners, ports, logging and the backup disk; every other server setting keeps the upstream default. Overrides are stored in pg.yaml under addons.clickhouse.<name>.settings and rendered into the generated XML, so they survive restarts and container recreation.
Install
A second instance takes the next free HTTP/TCP ports from its pools (for example 8124/9001) and can use a custom data dir:
Re-running install without --force is a no-op that reports the current state. --force re-renders the config, recreates the container and keeps the data dir, the stored admin password and the stored settings; pass --config-option only when you want to replace the override map.
The image is pulled directly from Docker Hub; no pgcli-built image is involved. --version accepts any safe upstream tag, but the tag must exist in the registry — the pull fails otherwise. pgcli pins the current LTS line (26.8.36.3) by default.
Ports
| Endpoint | Default | Protocol | Pool |
|---|---|---|---|
| HTTP | 8123 |
HTTP (queries, /ping, many drivers) |
clickhouse_http_start_port, default 8123 |
| Native | 9000 |
ClickHouse native TCP (clickhouse-client) |
clickhouse_tcp_start_port, default 9000, shared with the S3 API pool |
Both pools share one “already assigned” set, so an explicit port in one pool cannot collide with an auto-assigned port in the other. --http-port and --tcp-port must differ, and every HTTP/TCP port must be unique across all ClickHouse instances. Ports already listening on the host are skipped during auto-assignment.
When --listen is a wildcard (0.0.0.0, ::), pgcli’s own client commands dial 127.0.0.1; the printed endpoints follow the same rule. A LAN --listen bind also keeps loopback enabled inside the container so the in-container clickhouse-client always works.
What pgcli creates
Everything for one instance lives under <base_dir>/addon/clickhouse/<name>/:
| Host path | Container path | Notes |
|---|---|---|
config.d/ |
/etc/clickhouse-server/config.d |
Read-only; replaces the image’s stock config.d (which would otherwise bind every interface). zz-pgcli.xml is the generated override |
data/ |
/var/lib/clickhouse |
Database data and system tables |
logs/ |
/var/log/clickhouse-server |
clickhouse-server.log and clickhouse-server.err.log (also visible via podman logs) |
backups/ |
/var/lib/clickhouse-backups |
The managed backups disk used by backup/restore |
secrets/admin_password |
/run/secrets/admin_password |
Mode 0600, read by the entrypoint via CLICKHOUSE_PASSWORD_FILE |
The generated zz-pgcli.xml sets:
listen_host(exactly what--listenchose) pluslisten_tryhttp_port/tcp_portlogger.level,logger.log,logger.errorlog,logger.console, rotation (size,count)- a local disk named
backupsat/var/lib/clickhouse-backups, allowed forBACKUP/RESTORE
The container runs with --network host, --restart unless-stopped, a 30-second stop timeout and nofile=262144. Data ownership inside the container is uid/gid 101 (the image drops privileges after its root entrypoint chowns the mounts).
Credentials
The image entrypoint creates admin (or the name given with --admin-user) with full privileges and access management enabled (CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1), so CREATE USER / GRANT issued by this CLI work. The password is stored in pg.yaml (mode 0644 by default — protect that file) and duplicated into the 0600 secret file mounted into the container.
Every pgcli command that talks to ClickHouse passes credentials as environment variables expanded inside the container; the password never appears on the podman command line or the host process list. SQL is supplied on stdin.
Connecting
Use the printed endpoints from any host tool:
clickhouse-client is not installed on the host by pgcli — use the addon’s own query/shell commands below, or point an external client at the native port. /ping answers Ok. without authentication and is what pgcli polls during start.
Shell and query
query runs clickhouse-client as the admin user and writes the result to stdout, so it can be piped or redirected. The statements are joined from the positional arguments; --file reads a file (mount-free: the file is read by the host and piped on stdin), and - reads from stdin. The client is invoked without --multiquery, so pass one statement per call.
The instance must be running for query, database, user, grant, backup and restore; shell opens regardless but prints a warning when the container is stopped.
Databases
create takes a bare engine name (default Atomic; engine parameters are not exposed). drop is destructive — it asks for confirmation unless --yes is given. database list shows name, engine and data_path from system.databases. db is an alias for database.
Users and privileges
Users are created with IDENTIFIED WITH sha256_password. --host-any is the default when no host flag is given; --host-local, --host-ip (IP or CIDR) and --host-name add HOST clauses. --readonly appends SETTINGS readonly = 1. A generated password is printed exactly once and is not stored in pg.yaml — save it. --if-not-exists makes user create idempotent.
--read is SELECT, --write is INSERT, --ddl expands to CREATE, ALTER, DROP, TRUNCATE, OPTIMIZE, --all grants ALL, and --privileges takes a comma-separated list (uppercased and de-duplicated with the flag selections). Without --database the grant targets * (all databases); --table requires an explicit database. --revoke emits REVOKE ... FROM. grant list runs SHOW GRANTS FOR.
All of these run as the instance’s admin account; the CLI never stores per-user passwords or credentials.
Configuration
Server settings are addressed by dotted XML paths; the leaf value is the text of that element in the generated zz-pgcli.xml.
config set KEY=VALUE ...accepts one or more pairs, validates them, rewrites the XML andpg.yaml, then restarts a running container so the change takes effect. Pass--no-restartto defer it to the next start.config unset KEY ...removes overrides (one or more) and follows the same restart rule. Unsetting an unknown key is an error.config getprints the effective value of a custom override or a managed setting;config listshows managed settings first, then the custom overrides sorted by key.config showrenders the same XML that is written toconfig.d/zz-pgcli.xml; it does not touch the file or the container.--config-optionat install time replaces the whole override map; omit it to keep the stored one.
logger.level is special-cased: it folds into the managed <logger> block instead of producing a second <level> element. Valid levels are trace, debug, information, warning, error, fatal and none.
pgcli-managed keys are rejected, because overriding them would duplicate generated XML or silently contradict the stored paths/ports: listen_host, listen_try, http_port, https_port, tcp_port, interserver_http_port, interserver_http_host, mysql_port, postgresql_port, path, tmp_path, user_files_path, format_schema_path, access_control_path, backups.*, user_directories, profiles, users, storage_configuration.disks, storage_configuration.policies, and every logger.* key except logger.level. Keys containing spaces or values containing control characters are rejected too.
Typical settings:
Server settings cannot be changed with a hot reload by this CLI; every effective change recreates or restarts the container (ClickHouse reads this generated file at startup).
pgcli validates only the key shape and the managed-key rules — the value is checked by ClickHouse itself at startup. A value it rejects (for example a background_pool_size below what the merge/mutation sanity check allows) aborts startup, and the container’s unless-stopped policy turns that into a restart loop. Recover without losing data:
Backup and restore
Backups use ClickHouse’s SQL BACKUP command against the local backups disk (/var/lib/clickhouse-backups). They run online and the instance must be running. Only the managed local disk is exposed — S3 destinations are not configured.
--databaseis required for both commands: it names the database recorded in the backup (there is no “all databases” form in v1).--outis relative to the backups dir unless absolute; absolute paths must stay inside that dir, and the file must end in.zip. The default name is<database>-YYYYMMDD-HHMMSS.zip(UTC).restoretakes a file inside the backups dir, plus the recorded database name. Restoring into a database that already exists fails (ClickHouse refuses to overwrite); use--as NEWto restore alongside it.- Restore itself is online, but it needs free space and a running instance;
backupslists.zipfiles sorted newest first.
The backups directory is a normal host directory — copy the .zip files off-host for real disaster recovery. There is no scheduled backup; wire pg addon clickhouse backup into your own timer if you need one.
Lifecycle
start re-renders the generated config and password file from pg.yaml before starting, so a stopped instance picks up config set --no-restart changes and a changed admin password. status reports running (ping ok) when the HTTP /ping endpoint answers.
Without --clean-data, remove leaves <instance dir> (data, config, logs, backups, secret) and a custom --data-dir on disk; reinstalling under the same name revives the data and reuses the kept admin password (pass --admin-password to override). --clean-data deletes both the instance dir and a custom data dir (files owned by uid 101 are removed through podman unshare), but never removes the pulled image.
Auto-start after reboot:
Boot start also re-renders the config and secret first, then starts the existing container. pg start starts every autostart-enabled ClickHouse instance before nginx, so an nginx backend pointing at ClickHouse’s HTTP port is up when the proxy starts.
Production notes
- Single node only. This addon has no replication, no sharding and no HA. ClickHouse ReplicatedMergeTree/Keeper topologies are out of scope; backups are the disaster-recovery path.
- No TLS listener. The HTTP and native ports are plaintext. To expose ClickHouse over the network with TLS, put the nginx addon in front of the HTTP port (or use an external proxy); do not expose the native port directly to untrusted networks.
- Loopback by default.
--listendefaults to127.0.0.1. Authentication is always on, but a network bind still widens the attack surface; use0.0.0.0only on trusted networks. - Password storage. The admin password lives in
pg.yaml(0644by default); protect that file and preferpg addon password clickhouseover copying it around. - Resource use. The container has no CPU/memory limit from pgcli; ClickHouse’s own memory budget and the recommended settings are covered in Memory limits below.
- Upgrades.
--force --version <new>pulls a new tag and recreates the container on the same data dir. ClickHouse supports forward upgrades only — never start an older server against data written by a newer version. Take a backup before upgrading. - Data ownership. Data files are owned by uid 101. Use
--clean-datarather than deleting the directory by hand; pgcli handles the permission fallback. - Platform. Linux only (host networking); the official image is multi-arch (amd64/arm64).
Memory limits
pgcli never passes --memory/--cpus to podman, so the container has no cgroup memory cap; ClickHouse enforces its own budget instead:
- Server-wide.
max_server_memory_usageis the process-wide limit in bytes. Its default0keeps ClickHouse’s ratio rule,max_server_memory_usage_to_ram_ratio(default0.9of host RAM). That is fine on a dedicated host and risky on a shared one. On a shared host set the absolute value explicitly and leave headroom for the OS page cache and other services; on a dedicated host 50–70% of RAM is a common starting point. - Concurrency.
max_concurrent_queriesandbackground_pool_sizeare also server-level settings and work withconfig set. The server-wide limit covers all concurrent queries together, so plan forpeak per-query memory × concurrencyinside that budget. - Query-level.
max_memory_usage,max_bytes_before_external_group_by,max_bytes_before_external_sortandmax_threadsare user/profile settings, not server XML, soconfig setdoes not apply them. Set them for one session withSETin aclickhouse-clientstarted frompg addon clickhouse shell(one-shotquerycalls do not share session state), or define them on a SQL-created user withCREATE USER ... SETTINGS .... - Spill instead of OOM.
max_bytes_before_external_group_by/max_bytes_before_external_sortlet heavy aggregations and sorts spill to disk above the threshold, trading latency for a lower memory ceiling.JOINis usually the biggest consumer — size for the right-hand table or avoid large joins.
Check the live value:
Example — cap the whole server at 8 GiB and allow 20 concurrent queries:
Values are bytes; config set restarts a running container unless --no-restart is passed. For reference, a 2 vCPU / ~2 GiB test VM handles small OLAP workloads with max_server_memory_usage=805306368 (768 MiB); JOIN-heavy or high-cardinality aggregation workloads over large data need proportionally more memory.
CLI flag reference
pg addon install clickhouse
| Flag | Default | Description |
|---|---|---|
--name NAME |
clickhouse |
Instance name (map key); [A-Za-z0-9][A-Za-z0-9_-]*, max 48 chars |
--version TAG |
26.8.36.3 |
Upstream image tag from docker.io/clickhouse/clickhouse-server |
--data-dir DIR |
<base_dir>/addon/clickhouse/<name>/data |
Absolute host data dir bind-mounted at /var/lib/clickhouse |
--listen ADDR |
127.0.0.1 |
Bind address for both listeners |
--http-port N |
auto (base 8123) |
HTTP host port; 0 = auto-assign |
--tcp-port N |
auto (base 9000) |
Native host port; 0 = auto-assign; must differ from --http-port |
--admin-user USER |
admin |
Admin account name; [A-Za-z0-9_.-]{1,64} |
--admin-password PW |
generated | Admin password; on --force the stored value is kept unless supplied |
--config-option K=V |
— | Dotted XML path setting; repeatable; replaces the stored override map |
--force |
false | Re-render config/secret and recreate the container, keeping data, password and settings |
pg addon clickhouse status / shell / query [SQL...]
| Argument/flag | Default | Description |
|---|---|---|
--name NAME |
clickhouse |
Instance to operate on |
SQL... |
— | query only: SQL joined from positional arguments |
--database DB |
— | query only: adds --database DB |
--file FILE |
— | query only: read SQL from a host file (- = stdin) |
- (argument) |
— | query only: read SQL from stdin |
pg addon clickhouse database create|list|drop
| Argument/flag | Default | Description |
|---|---|---|
NAME |
— | Database name (create/drop, positional) |
--engine NAME |
Atomic |
Bare engine name (no parameters) |
--comment TEXT |
— | Database comment |
--if-not-exists |
false | create only |
--if-exists |
false | drop only |
--yes |
false | drop only: skip the confirmation prompt |
db is an accepted alias for database.
pg addon clickhouse user create|list|drop
| Argument/flag | Default | Description |
|---|---|---|
NAME |
— | User name (create/drop, positional) |
--password PW |
generated | Password; generated and printed once when omitted |
--default-database DB |
— | DEFAULT DATABASE clause |
--host-any |
implied | Allow any host (applied when no host flag is given) |
--host-local |
false | LOCAL (loopback) only |
--host-ip IP|CIDR |
— | Repeatable IP host clause |
--host-name NAME |
— | Repeatable NAME host clause |
--readonly |
false | Add SETTINGS readonly = 1 |
--if-not-exists |
false | create only |
--if-exists |
false | drop only |
--yes |
false | drop only: skip the confirmation prompt |
pg addon clickhouse grant USER / grant list USER
| Argument/flag | Default | Description |
|---|---|---|
USER |
— | Target user (positional) |
--read |
false | Grant SELECT |
--write |
false | Grant INSERT |
--ddl |
false | Grant CREATE, ALTER, DROP, TRUNCATE, OPTIMIZE |
--all |
false | Grant ALL |
--privileges LIST |
— | Extra comma-separated privileges, merged with the flags above |
--database DB |
all (*) |
Scope to a database |
--table TABLE |
all tables | Scope to a table; requires --database |
--revoke |
false | Emit REVOKE ... FROM instead of GRANT ... TO |
--with-grant-option |
false | Append WITH GRANT OPTION (grant only) |
pg addon clickhouse backup / restore FILE / backups
| Argument/flag | Default | Description |
|---|---|---|
FILE |
— | restore only: .zip file inside the backups dir |
--database DB |
required | Database to back up / the database name recorded in the backup |
--out NAME |
<db>-<UTC timestamp>.zip |
backup only: output path inside the backups dir |
--as NEW |
— | restore only: restore under a new database name |
pg addon clickhouse config list|get|set|unset|show
| Argument/flag | Default | Description |
|---|---|---|
KEY=VALUE... |
— | set only: one or more dotted XML settings |
KEY... |
— | get/unset: one setting key (get takes exactly one) |
--name NAME |
clickhouse |
Instance to inspect or modify |
--no-restart |
false | set/unset only: apply on the next start instead of restarting now |
Lifecycle, credentials, logs, autostart
| Command | Flags | Description |
|---|---|---|
pg addon start clickhouse |
--name NAME |
Start (create if missing), wait for /ping |
pg addon stop clickhouse |
--name NAME |
Stop the container, keep data |
pg addon remove clickhouse |
--name NAME, --clean-data |
Remove container and config entry; --clean-data also deletes the instance dir and a custom data dir |
pg addon password clickhouse |
--name NAME |
Print the admin password |
pg logs addon clickhouse |
--name NAME, -n N (default 50), -f |
Container logs |
pg autostart enable|disable |
--clickhouse, --name NAME |
Toggle boot start for one instance |
Global flags
| Flag | Description |
|---|---|
-c, --config FILE |
Config file (default ~/.pgcli/pg.yaml) |
-o, --output FILE |
Output config path (default the same file) |
-i, --instance NAME |
PostgreSQL instance selector — not used by ClickHouse; use --name instead |