Limited Time Offer: 40% off

Running ClickHouse in Docker

Get ClickHouse running in Docker in under 5 minutes.

Quick start

The fastest way to get ClickHouse running locally:

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  clickhouse/clickhouse-server

This pulls the latest ClickHouse image, starts a container in the background, and exposes two ports:

  • 8123: HTTP interface (used by most clients and web UIs)
  • 9000: Native TCP protocol (used by clickhouse-client and high-performance drivers)

Verify it's running:

BASH
curl http://localhost:8123/ping

You should see Ok. in the response. You can also run a quick query:

BASH
echo "SELECT version()" | curl http://localhost:8123/ --data-binary @-

Connecting with clickhouse-client

The ClickHouse Docker image ships with clickhouse-client built in. You can open an interactive session against your running container:

BASH
docker exec -it clickhouse clickhouse-client

This drops you into an interactive SQL shell. Try a few commands:

SQL
SELECT version();
SELECT now();
SHOW DATABASES;

To run a one-off query without entering the interactive shell:

BASH
docker exec clickhouse clickhouse-client --query "SELECT 1 + 1"

Pinning a version

Running clickhouse/clickhouse-server without a tag gives you latest, which can change unexpectedly. For anything beyond quick experiments, pin the version:

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  clickhouse/clickhouse-server:24.8

ClickHouse uses a year.month versioning scheme. The LTS (long-term support) releases are a safer choice for production. Check the ClickHouse releases page for current LTS versions.

Persistent storage with volumes

By default, all data lives inside the container. Stop and remove the container, and the data is gone. To keep your data around, mount a Docker volume.

Named volume (recommended)

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -v clickhouse-data:/var/lib/clickhouse \
  -v clickhouse-logs:/var/log/clickhouse-server \
  clickhouse/clickhouse-server:24.8

Named volumes are managed by Docker. They survive container removal and are easy to back up.

Bind mount (host directory)

If you want the data in a specific directory on your host machine:

BASH
mkdir -p ./ch-data ./ch-logs

docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -v $(pwd)/ch-data:/var/lib/clickhouse \
  -v $(pwd)/ch-logs:/var/log/clickhouse-server \
  clickhouse/clickhouse-server:24.8

Bind mounts give you direct access to the files, which can be useful for debugging or inspecting the storage layout.

Key directories inside the container

PathPurpose
/var/lib/clickhouseData files (tables, parts, metadata)
/var/log/clickhouse-serverServer logs
/etc/clickhouse-serverConfiguration files
/etc/clickhouse-clientClient configuration

Custom configuration

ClickHouse reads its configuration from XML files in /etc/clickhouse-server/. The main file is config.xml, with user-specific settings in users.xml. Rather than replacing these files entirely, ClickHouse supports override files in a config.d/ directory.

Adding a configuration override

Create a file called custom-config.xml on your host:

XML
<?xml version="1.0"?>
<clickhouse>
    <logger>
        <level>warning</level>
    </logger>
    <max_connections>256</max_connections>
    <mark_cache_size>10737418240</mark_cache_size>
</clickhouse>

Mount it into the container's config.d/ directory:

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -v clickhouse-data:/var/lib/clickhouse \
  -v $(pwd)/custom-config.xml:/etc/clickhouse-server/config.d/custom-config.xml \
  clickhouse/clickhouse-server:24.8

ClickHouse merges all files in config.d/ with the base config.xml at startup.

Custom user settings

Create a custom-users.xml file:

XML
<?xml version="1.0"?>
<clickhouse>
    <users>
        <default>
            <password>your_password_here</password>
            <networks>
                <ip>::/0</ip>
            </networks>
            <profile>default</profile>
            <quota>default</quota>
            <max_memory_usage>10000000000</max_memory_usage>
        </default>
    </users>
</clickhouse>

Mount it into users.d/:

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -v clickhouse-data:/var/lib/clickhouse \
  -v $(pwd)/custom-users.xml:/etc/clickhouse-server/users.d/custom-users.xml \
  clickhouse/clickhouse-server:24.8

This sets a password on the default user and limits memory usage per query to around 10 GB.

Environment variables

The ClickHouse Docker image supports several environment variables for common settings, so you don't always need config file overrides.

Setting a default user password

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -e CLICKHOUSE_PASSWORD=mysecretpassword \
  -v clickhouse-data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:24.8

Once set, all connections need to provide the password:

BASH
docker exec -it clickhouse clickhouse-client --password mysecretpassword

Or via HTTP:

BASH
curl "http://localhost:8123/?user=default&password=mysecretpassword" \
  --data-binary "SELECT 1"

Creating an additional user on startup

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -e CLICKHOUSE_USER=analyst \
  -e CLICKHOUSE_PASSWORD=analyst_password \
  -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \
  -v clickhouse-data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:24.8

The CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 variable enables SQL-based access management, which lets you create and manage users with SQL commands after startup.

Available environment variables

VariablePurpose
CLICKHOUSE_PASSWORDSet password for the default user
CLICKHOUSE_USERCreate an additional user
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENTEnable SQL-based user management (0 or 1)
CLICKHOUSE_DBCreate a database on startup
CLICKHOUSE_LOG_LEVELSet the log level (trace, debug, information, warning, error)

Running initialization scripts

The ClickHouse image runs any .sql or .sh scripts placed in /docker-entrypoint-initdb.d/ on first startup (when the data directory is empty). This is useful for creating tables and loading seed data.

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -v clickhouse-data:/var/lib/clickhouse \
  -v $(pwd)/init-scripts:/docker-entrypoint-initdb.d \
  clickhouse/clickhouse-server:24.8

A sample init script (init-scripts/001-create-tables.sql):

SQL
CREATE DATABASE IF NOT EXISTS analytics;

CREATE TABLE IF NOT EXISTS analytics.events (
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type String,
    properties String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, user_id, event_time);

Scripts run in alphabetical order, so prefixing with numbers (001, 002) gives you control over execution order.

Docker Compose: single node

For most development and many production workloads, a single ClickHouse node is enough. Docker Compose makes the setup reproducible and easy to share with your team.

Create a docker-compose.yml:

YAML
services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.8
    container_name: clickhouse
    ports:
      - "8123:8123"
      - "9000:9000"
    volumes:
      - clickhouse-data:/var/lib/clickhouse
      - clickhouse-logs:/var/log/clickhouse-server
      - ./config/custom-config.xml:/etc/clickhouse-server/config.d/custom-config.xml
      - ./config/custom-users.xml:/etc/clickhouse-server/users.d/custom-users.xml
      - ./init-scripts:/docker-entrypoint-initdb.d
    environment:
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-changeme}
      CLICKHOUSE_DB: analytics
      CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    restart: unless-stopped

volumes:
  clickhouse-data:
  clickhouse-logs:

Start it:

BASH
docker compose up -d

Check the logs:

BASH
docker compose logs -f clickhouse

Stop everything (data is preserved in volumes):

BASH
docker compose down

To also remove the data volumes:

BASH
docker compose down -v

Adding a health check

Add a health check so Docker can report whether ClickHouse is ready to accept queries:

YAML
services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.8
    container_name: clickhouse
    ports:
      - "8123:8123"
      - "9000:9000"
    volumes:
      - clickhouse-data:/var/lib/clickhouse
      - clickhouse-logs:/var/log/clickhouse-server
    environment:
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-changeme}
    healthcheck:
      test: ["CMD-SHELL", "clickhouse-client --query 'SELECT 1' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    restart: unless-stopped

volumes:
  clickhouse-data:
  clickhouse-logs:

Other services can use depends_on with a condition to wait for ClickHouse to be healthy before starting:

YAML
services:
  app:
    image: your-app:latest
    depends_on:
      clickhouse:
        condition: service_healthy

Resource limits

ClickHouse is designed to use all available resources. In a Docker environment, that can starve other containers. Set limits to keep things under control.

Memory and CPU limits in Docker

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  --memory=4g \
  --cpus=2 \
  -v clickhouse-data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:24.8

In Docker Compose:

YAML
services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.8
    deploy:
      resources:
        limits:
          memory: 4g
          cpus: "2.0"
        reservations:
          memory: 2g
          cpus: "1.0"

ClickHouse-level memory settings

Docker memory limits kill the container if exceeded. ClickHouse's own memory settings are more graceful, as they cancel individual queries instead of crashing the whole server.

Create a config override (config.d/memory.xml):

XML
<?xml version="1.0"?>
<clickhouse>
    <max_server_memory_usage_to_ram_ratio>0.8</max_server_memory_usage_to_ram_ratio>
</clickhouse>

And a user-level override (users.d/memory.xml):

XML
<?xml version="1.0"?>
<clickhouse>
    <profiles>
        <default>
            <max_memory_usage>3000000000</max_memory_usage>
            <max_memory_usage_for_user>6000000000</max_memory_usage_for_user>
        </default>
    </profiles>
</clickhouse>

This sets a per-query limit of 3 GB and a per-user limit of 6 GB. Queries that exceed these limits are cancelled with an error rather than consuming all memory.

File descriptor limits

ClickHouse opens many files, especially with MergeTree tables. The ulimits setting in Docker Compose (shown in earlier examples) is important. The default of 1024 is too low. Set it to at least 262144.

Docker Compose with ClickHouse Keeper

For replication and distributed setups, ClickHouse needs a coordination service. ClickHouse Keeper is the built-in replacement for ZooKeeper. Here's a setup with a single ClickHouse node and an embedded Keeper instance.

YAML
services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.8
    container_name: clickhouse
    ports:
      - "8123:8123"
      - "9000:9000"
    volumes:
      - clickhouse-data:/var/lib/clickhouse
      - clickhouse-logs:/var/log/clickhouse-server
      - ./config/keeper-config.xml:/etc/clickhouse-server/config.d/keeper-config.xml
      - ./config/macros.xml:/etc/clickhouse-server/config.d/macros.xml
    environment:
      CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-changeme}
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    healthcheck:
      test: ["CMD-SHELL", "clickhouse-client --query 'SELECT 1' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s
    restart: unless-stopped

volumes:
  clickhouse-data:
  clickhouse-logs:

The Keeper config (config/keeper-config.xml):

XML
<?xml version="1.0"?>
<clickhouse>
    <keeper_server>
        <tcp_port>9181</tcp_port>
        <server_id>1</server_id>
        <log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
        <snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
        <coordination_settings>
            <operation_timeout_ms>10000</operation_timeout_ms>
            <session_timeout_ms>30000</session_timeout_ms>
        </coordination_settings>
        <raft_configuration>
            <server>
                <id>1</id>
                <hostname>clickhouse</hostname>
                <port>9234</port>
            </server>
        </raft_configuration>
    </keeper_server>

    <zookeeper>
        <node>
            <host>localhost</host>
            <port>9181</port>
        </node>
    </zookeeper>
</clickhouse>

The macros config (config/macros.xml):

XML
<?xml version="1.0"?>
<clickhouse>
    <macros>
        <shard>01</shard>
        <replica>01</replica>
        <cluster>local_cluster</cluster>
    </macros>
</clickhouse>

With this in place, you can create ReplicatedMergeTree tables:

SQL
CREATE TABLE analytics.events_replicated (
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type String
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, user_id, event_time);

Multi-node cluster with Docker Compose

For testing sharding and replication locally, you can run multiple ClickHouse nodes. This setup creates two shards, each with two replicas, plus a three-node ClickHouse Keeper ensemble.

This is a substantial configuration. In production, you would typically run each node on separate machines. Here we use Docker Compose to simulate the cluster on a single host.

YAML
services:
  clickhouse-keeper-1:
    image: clickhouse/clickhouse-server:24.8
    container_name: keeper1
    volumes:
      - keeper1-data:/var/lib/clickhouse
      - ./config/keeper1.xml:/etc/clickhouse-server/config.d/keeper.xml
    ports:
      - "9181:9181"
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  clickhouse-keeper-2:
    image: clickhouse/clickhouse-server:24.8
    container_name: keeper2
    volumes:
      - keeper2-data:/var/lib/clickhouse
      - ./config/keeper2.xml:/etc/clickhouse-server/config.d/keeper.xml
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  clickhouse-keeper-3:
    image: clickhouse/clickhouse-server:24.8
    container_name: keeper3
    volumes:
      - keeper3-data:/var/lib/clickhouse
      - ./config/keeper3.xml:/etc/clickhouse-server/config.d/keeper.xml
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  clickhouse-01:
    image: clickhouse/clickhouse-server:24.8
    container_name: clickhouse-01
    ports:
      - "8123:8123"
      - "9000:9000"
    volumes:
      - ch01-data:/var/lib/clickhouse
      - ./config/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
      - ./config/macros-01.xml:/etc/clickhouse-server/config.d/macros.xml
    depends_on:
      - clickhouse-keeper-1
      - clickhouse-keeper-2
      - clickhouse-keeper-3
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  clickhouse-02:
    image: clickhouse/clickhouse-server:24.8
    container_name: clickhouse-02
    ports:
      - "8124:8123"
      - "9001:9000"
    volumes:
      - ch02-data:/var/lib/clickhouse
      - ./config/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
      - ./config/macros-02.xml:/etc/clickhouse-server/config.d/macros.xml
    depends_on:
      - clickhouse-keeper-1
      - clickhouse-keeper-2
      - clickhouse-keeper-3
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

volumes:
  keeper1-data:
  keeper2-data:
  keeper3-data:
  ch01-data:
  ch02-data:

The cluster config (config/cluster.xml) would define the shard and replica topology. The macros files for each node set their {shard} and {replica} values. This kind of setup is useful for testing distributed queries and understanding replication behavior before deploying to actual infrastructure.

Each Keeper node needs its own config file specifying its server_id and the full list of raft peers. For example, keeper1.xml:

XML
<?xml version="1.0"?>
<clickhouse>
    <keeper_server>
        <tcp_port>9181</tcp_port>
        <server_id>1</server_id>
        <log_storage_path>/var/lib/clickhouse/coordination/log</log_storage_path>
        <snapshot_storage_path>/var/lib/clickhouse/coordination/snapshots</snapshot_storage_path>
        <coordination_settings>
            <operation_timeout_ms>10000</operation_timeout_ms>
            <session_timeout_ms>30000</session_timeout_ms>
        </coordination_settings>
        <raft_configuration>
            <server>
                <id>1</id>
                <hostname>keeper1</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>2</id>
                <hostname>keeper2</hostname>
                <port>9234</port>
            </server>
            <server>
                <id>3</id>
                <hostname>keeper3</hostname>
                <port>9234</port>
            </server>
        </raft_configuration>
    </keeper_server>
</clickhouse>

For keeper2.xml and keeper3.xml, change only the server_id value (to 2 and 3 respectively). The raft_configuration block stays the same across all three.

The cluster config (config/cluster.xml) defines your shard and replica topology:

XML
<?xml version="1.0"?>
<clickhouse>
    <remote_servers>
        <my_cluster>
            <shard>
                <replica>
                    <host>clickhouse-01</host>
                    <port>9000</port>
                </replica>
            </shard>
            <shard>
                <replica>
                    <host>clickhouse-02</host>
                    <port>9000</port>
                </replica>
            </shard>
        </my_cluster>
    </remote_servers>

    <zookeeper>
        <node>
            <host>keeper1</host>
            <port>9181</port>
        </node>
        <node>
            <host>keeper2</host>
            <port>9181</port>
        </node>
        <node>
            <host>keeper3</host>
            <port>9181</port>
        </node>
    </zookeeper>
</clickhouse>

Each ClickHouse node gets its own macros file. For clickhouse-01 (config/macros-01.xml):

XML
<?xml version="1.0"?>
<clickhouse>
    <macros>
        <shard>01</shard>
        <replica>01</replica>
        <cluster>my_cluster</cluster>
    </macros>
</clickhouse>

For clickhouse-02 (config/macros-02.xml), change the shard to 02.

With this running, you can create distributed tables that span both shards:

SQL
-- Create local tables on each shard
CREATE TABLE analytics.events_local ON CLUSTER my_cluster (
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type String
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, user_id, event_time);

-- Create a distributed table that routes queries across shards
CREATE TABLE analytics.events ON CLUSTER my_cluster AS analytics.events_local
ENGINE = Distributed(my_cluster, analytics, events_local, rand());

Inserts to analytics.events get distributed across shards. Queries against it fan out to all shards and merge results. This is a good way to test distributed ClickHouse behavior locally before deploying to production infrastructure.

For more details on cluster configuration, refer to the ClickHouse documentation on cluster deployment.

Connecting with external clients

Once ClickHouse is running in Docker, you can connect from any compatible client.

clickhouse-client (installed on host)

If you have clickhouse-client installed on your host machine:

BASH
clickhouse-client --host localhost --port 9000

With a password:

BASH
clickhouse-client --host localhost --port 9000 --password mysecretpassword

HTTP interface with curl

The HTTP interface on port 8123 works for quick queries and scripting:

BASH
# Simple query
curl "http://localhost:8123/" --data-binary "SELECT database, name, engine FROM system.tables LIMIT 10"

# With authentication
curl "http://localhost:8123/?user=default&password=mysecretpassword" \
  --data-binary "SELECT 1"

# Output in JSON format
curl "http://localhost:8123/?default_format=JSON" \
  --data-binary "SELECT database, name, engine FROM system.tables LIMIT 5"

Python

Using the clickhouse-connect library:

PYTHON
import clickhouse_connect

client = clickhouse_connect.get_client(
    host='localhost',
    port=8123,
    username='default',
    password='mysecretpassword'
)

result = client.query('SELECT version()')
print(result.result_rows)

Install the library with:

BASH
pip install clickhouse-connect

Node.js

Using the official @clickhouse/client package:

JAVASCRIPT
const { createClient } = require('@clickhouse/client');

const client = createClient({
    url: 'http://localhost:8123',
    username: 'default',
    password: 'mysecretpassword',
});

async function main() {
    const result = await client.query({
        query: 'SELECT version()',
        format: 'JSONEachRow',
    });
    const data = await result.json();
    console.log(data);
}

main();

Install with:

BASH
npm install @clickhouse/client

Go

Using the official clickhouse-go driver:

GO
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/ClickHouse/clickhouse-go/v2"
)

func main() {
    conn, err := clickhouse.Open(&clickhouse.Options{
        Addr: []string{"localhost:9000"},
        Auth: clickhouse.Auth{
            Database: "default",
            Username: "default",
            Password: "mysecretpassword",
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    var version string
    err = conn.QueryRow(context.Background(), "SELECT version()").Scan(&version)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("ClickHouse version:", version)
}

Install with:

BASH
go get github.com/ClickHouse/clickhouse-go/v2

The Go driver uses the native TCP protocol (port 9000) by default, which is faster than the HTTP interface for bulk operations.

GUI clients

You can connect to your Dockerized ClickHouse with any GUI that supports the ClickHouse protocol or its HTTP interface. DB Pro is a good option for browsing your tables, running queries, and inspecting data across ClickHouse and other databases. Point it at localhost:8123 (HTTP) or localhost:9000 (native) and you're connected.

Networking considerations

Exposing ports selectively

By default, the port mappings shown above (-p 8123:8123) bind to all network interfaces, making ClickHouse accessible from any machine on your network. For development, bind to localhost only:

BASH
docker run -d \
  --name clickhouse \
  -p 127.0.0.1:8123:8123 \
  -p 127.0.0.1:9000:9000 \
  clickhouse/clickhouse-server:24.8

In Docker Compose:

YAML
ports:
  - "127.0.0.1:8123:8123"
  - "127.0.0.1:9000:9000"

Container-to-container communication

If other containers need to talk to ClickHouse, use a shared Docker network instead of exposing ports to the host:

YAML
services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.8
    networks:
      - backend

  app:
    image: your-app:latest
    networks:
      - backend
    environment:
      CLICKHOUSE_HOST: clickhouse
      CLICKHOUSE_PORT: 8123

networks:
  backend:

Containers on the same network can reach each other by service name. The app container connects to clickhouse:8123 without any port mapping to the host.

Common issues and troubleshooting

Container exits immediately

Check the logs:

BASH
docker logs clickhouse

Common causes:

  • Port already in use. Another process (or a previous ClickHouse container) is using port 8123 or 9000. Stop it first, or map to different host ports (-p 18123:8123).
  • Invalid configuration XML. A syntax error in a mounted config file will prevent startup. Validate your XML before mounting.
  • Permission issues on bind mounts. The ClickHouse process runs as user clickhouse (UID 101) inside the container. If you use bind mounts, the host directories need to be writable by that UID.

Fix permissions for bind mounts:

BASH
sudo chown -R 101:101 ./ch-data ./ch-logs

"Too many open files" errors

ClickHouse needs a high file descriptor limit. Add the ulimits setting to your Docker Compose file:

YAML
ulimits:
  nofile:
    soft: 262144
    hard: 262144

Or with docker run:

BASH
docker run -d \
  --name clickhouse \
  --ulimit nofile=262144:262144 \
  -p 8123:8123 \
  -p 9000:9000 \
  clickhouse/clickhouse-server:24.8

Slow queries eating all memory

If a query consumes too much memory and the container gets killed by Docker's OOM killer, set ClickHouse-level memory limits (as shown in the resource limits section). These are more graceful than Docker's hard memory cap because they cancel the offending query instead of killing the server.

Data disappears after restart

You're running without persistent volumes. Add a -v flag to your docker run command, or a volumes section to your Docker Compose file. See the persistent storage section above.

Cannot connect from host machine

Make sure you're using the right port. The HTTP interface is on 8123, and the native protocol is on 9000. If you set a password, you need to include it in your connection.

Test HTTP connectivity:

BASH
curl http://localhost:8123/ping

If that doesn't work, check that the container is running and the port mapping is correct:

BASH
docker ps --filter name=clickhouse

"Connection refused" when connecting from another container

If you're connecting from another container on the same Docker network, use the service name (e.g., clickhouse) as the hostname, not localhost. Localhost inside a container refers to that container, not the ClickHouse container.

YAML
# Correct: use service name
CLICKHOUSE_HOST: clickhouse

# Wrong: this refers to the app container itself
CLICKHOUSE_HOST: localhost

Queries are slow on macOS or Windows

Docker Desktop on macOS and Windows runs containers inside a lightweight VM. Disk I/O through bind mounts can be significantly slower compared to Linux. Named volumes perform better than bind mounts in this scenario because they live inside the VM's filesystem.

If you need bind mounts on macOS, Docker Desktop offers several mount consistency options. For read-heavy workloads, the cached option can help:

YAML
volumes:
  - ./ch-data:/var/lib/clickhouse:cached

For development workloads where peak performance isn't critical, this slowdown is tolerable. For benchmarking or performance testing, use a Linux host or named volumes.

Clock drift in containers

ClickHouse relies on accurate timestamps. If you notice time-related issues, make sure the container's clock is synced. On Docker Desktop (macOS/Windows), this is handled automatically. On Linux, the container uses the host's clock.

ClickHouse Keeper won't form a quorum

If you're running a multi-node setup and Keeper nodes can't connect to each other, verify that:

  1. All Keeper nodes are on the same Docker network.
  2. The hostnames in the raft configuration match the container names or service names.
  3. Port 9234 (the raft port) is accessible between containers (it doesn't need to be exposed to the host).

Check Keeper status from inside a container:

BASH
docker exec clickhouse clickhouse-client --query \
  "SELECT * FROM system.zookeeper WHERE path = '/'"

If this returns an error, Keeper isn't connected. Check the logs of each Keeper container for details.

Backup and restore

Quick backup with clickhouse-client

Create a backup of a specific table:

BASH
docker exec clickhouse clickhouse-client \
  --query "SELECT * FROM analytics.events FORMAT Native" > events_backup.native

Restore it:

BASH
cat events_backup.native | docker exec -i clickhouse clickhouse-client \
  --query "INSERT INTO analytics.events FORMAT Native"

Using the BACKUP and RESTORE commands

ClickHouse 22.8+ supports built-in BACKUP and RESTORE commands. With Docker volumes, you can back up to a local disk:

BASH
docker exec clickhouse clickhouse-client --query \
  "BACKUP TABLE analytics.events TO Disk('backups', 'events_backup.zip')"

For this to work, you need a disk configuration that points to a backup directory. Add a config file (config.d/backups.xml):

XML
<?xml version="1.0"?>
<clickhouse>
    <storage_configuration>
        <disks>
            <backups>
                <type>local</type>
                <path>/backups/</path>
            </backups>
        </disks>
    </storage_configuration>
    <backups>
        <allowed_disk>backups</allowed_disk>
        <allowed_path>/backups/</allowed_path>
    </backups>
</clickhouse>

Mount a host directory to /backups/ in your container:

BASH
docker run -d \
  --name clickhouse \
  -p 8123:8123 \
  -p 9000:9000 \
  -v clickhouse-data:/var/lib/clickhouse \
  -v $(pwd)/backups:/backups \
  -v $(pwd)/config/backups.xml:/etc/clickhouse-server/config.d/backups.xml \
  clickhouse/clickhouse-server:24.8

Upgrading ClickHouse

To upgrade your Dockerized ClickHouse to a newer version:

  1. Stop the current container:
BASH
docker compose down
  1. Update the image tag in your docker-compose.yml (e.g., from 24.8 to 24.12).

  2. Pull the new image and start:

BASH
docker compose pull
docker compose up -d

ClickHouse handles data format upgrades automatically on startup. It's still a good idea to back up your data before upgrading, especially for major version jumps.

Check the ClickHouse changelog for breaking changes before upgrading.

Production checklist

Running ClickHouse in Docker for production is common, but there are a few things to get right.

Storage. Use named volumes or bind mounts on fast storage (NVMe SSDs if possible). ClickHouse performance depends heavily on disk I/O. Avoid network-attached storage for the data directory if latency matters.

Memory. Set both Docker-level and ClickHouse-level memory limits. The ClickHouse limit should be lower than the Docker limit so ClickHouse can cancel queries gracefully before Docker kills the container.

File descriptors. Set ulimits for nofile to at least 262144. The default of 1024 will cause problems under load.

Logging. Mount the log directory and configure log rotation. ClickHouse can produce large log files under heavy query load.

Monitoring. ClickHouse exposes metrics through system.metrics, system.events, and system.asynchronous_metrics tables. You can also enable the Prometheus endpoint by adding this to your config:

XML
<?xml version="1.0"?>
<clickhouse>
    <prometheus>
        <endpoint>/metrics</endpoint>
        <port>9363</port>
        <metrics>true</metrics>
        <events>true</events>
        <asynchronous_metrics>true</asynchronous_metrics>
    </prometheus>
</clickhouse>

Expose port 9363 in your Docker Compose file and point Prometheus at it.

Security. Set passwords for all users. Bind ports to localhost or use Docker networks instead of exposing them to the host. Consider enabling TLS for the HTTP and native interfaces if connections cross network boundaries.

Backups. Schedule regular backups using the built-in BACKUP command or by snapshotting your Docker volumes. Test your restore process periodically.

Summary

The key commands to remember:

BASH
# Quick start
docker run -d --name clickhouse -p 8123:8123 -p 9000:9000 clickhouse/clickhouse-server:24.8

# With persistent storage and a password
docker run -d --name clickhouse \
  -p 8123:8123 -p 9000:9000 \
  -e CLICKHOUSE_PASSWORD=secret \
  -v clickhouse-data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:24.8

# Connect
docker exec -it clickhouse clickhouse-client

# Check health
curl http://localhost:8123/ping

For anything beyond a quick test, use Docker Compose with named volumes, health checks, resource limits, and proper configuration overrides. The examples in this guide are all copy-paste ready, so grab the one closest to your needs and adjust from there.