ClickHouse en Docker: Cómo dejé de preocuparme y lancé análisis en 2 minutos
Por qué Docker — y todo lo demás viene después
Recuerdo la primera vez que configuré ClickHouse en producción. Me llevó cuatro horas configurar permisos, límites, editar configuraciones manualmente y reiniciar systemd. Un mes después, se unió un nuevo desarrollador e intentamos replicar el entorno en su máquina — los mismos problemas de siempre.
Docker lo solucionó todo. Ahora tengo una sola carpeta con docker-compose.yml que llevo entre proyectos. Levanto un clúster de análisis en un minuto, y cuando necesito eliminarlo — docker-compose down -v y queda limpio. Sin desorden en el sistema.
A continuación, tres escenarios listos que uso en proyectos reales (desde un proyecto paralelo de startup hasta análisis de apuestas). Todas las configuraciones están probadas en Docker Engine 24+.
Escenario 1. Inicio rápido: Un comando para probar una hipótesis
Para desarrollo local y prototipado rápido, una sola línea basta. Pero no solo docker run clickhouse/clickhouse-server — agreguemos lo que hace útil a ClickHouse: almacenamiento persistente y mapeo de puertos.
docker run -d \
--name clickhouse-dev \
--restart unless-stopped \
-p 8123:8123 \
-p 9000:9000 \
-v clickhouse-data:/var/lib/clickhouse \
-v clickhouse-logs:/var/log/clickhouse-server \
-e CLICKHOUSE_DB=analytics \
-e CLICKHOUSE_USER=developer \
-e CLICKHOUSE_PASSWORD=devpass123 \
-e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \
clickhouse/clickhouse-server:latest
Lo importante aquí:
-v clickhouse-data— un volumen con nombre, no un bind mount. La diferencia: los volúmenes son gestionados por Docker, no se pierden al reiniciar y rinden mejor en macOS (importante si usas un MacBook — los bind mounts son lentos por la sincronización).CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1— habilita el control de acceso. Sin esta variable, el usuariodeveloperse crea pero no puede crear nuevas cuentas. Lo aprendimos por las malas: en producción, tuvimos que meternos al contenedor y editarusers.xml.- Puertos 9000 (protocolo nativo) y 8123 (HTTP) — siempre abro ambos porque la mitad de los clientes (DBeaver, TablePlus) funcionan sobre HTTP, mientras que las aplicaciones usan el driver nativo.
Verifica que funciona:
# Interfaz HTTP — la prueba más simple
curl "http://localhost:8123/?query=SELECT+version()"
# Salida: 24.8.2.3
Escenario 2. Docker Compose para desarrollo (Nodo único, Healthcheck)
Cuando el proyecto se vuelve un poco más complejo, cambio inmediatamente a docker-compose.yml. Este archivo lo uso en mis laptops y servidores de desarrollo:
version: '3.8'
services:
clickhouse:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-dev
hostname: clickhouse
ports:
- "8123:8123"
- "9000:9000"
- "9009:9009"
volumes:
- clickhouse-data:/var/lib/clickhouse
- clickhouse-logs:/var/log/clickhouse-server
- ./config/config.d:/etc/clickhouse-server/config.d
- ./config/users.d:/etc/clickhouse-server/users.d
environment:
CLICKHOUSE_DB: betting_analytics
CLICKHOUSE_USER: analyst
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-analyst123}
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
ulimits:
nofile:
soft: 262144
hard: 262144
nproc:
soft: 32768
hard: 32768
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8123/ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
networks:
- analytics-net
networks:
analytics-net:
driver: bridge
volumes:
clickhouse-data:
clickhouse-logs:
Por qué agregué ulimits: En producción, ClickHouse consume hasta 262144 descriptores de archivo abiertos. Sin esto, bajo carga pesada fallará con Too many open files. Una vez perdí tres horas en esto cuando el servidor empezó a fallar después de 2 millones de filas.
Healthcheck mediante /ping: ClickHouse tiene un endpoint /ping incorporado (devuelve "Ok." si está vivo). Esto es mejor que verificar con SELECT 1 porque no requiere autenticación y no escribe en los logs.
Variable por defecto: ${CLICKHOUSE_PASSWORD:-analyst123} — si no se establece en .env, la contraseña será analyst123. No olvides el archivo .env para secretos reales.
Escenario 3. Configuración similar a producción: ClickHouse + Zookeeper para replicación
La replicación de tablas en ClickHouse requiere ZooKeeper (o ClickHouse Keeper, pero empiezo con el clásico). Uso este compose para probar tolerancia a fallos:
version: '3.8'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
container_name: zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
volumes:
- zookeeper-data:/var/lib/zookeeper
networks:
- ch-cluster
clickhouse-1:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-1
hostname: clickhouse-1
ports:
- "8123:8123"
- "9000:9000"
volumes:
- ch1-data:/var/lib/clickhouse
- ./config/replicated.xml:/etc/clickhouse-server/config.d/replicated.xml
environment:
CLICKHOUSE_DB: bets
CLICKHOUSE_USER: replicator
CLICKHOUSE_PASSWORD: rep_pass
CLICKHOUSE_SHARD: 1
CLICKHOUSE_REPLICA: 1
depends_on:
- zookeeper
ulimits:
nofile:
soft: 262144
hard: 262144
networks:
- ch-cluster
clickhouse-2:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-2
hostname: clickhouse-2
ports:
- "8124:8123" # segunda instancia en un puerto diferente
- "9001:9000"
volumes:
- ch2-data:/var/lib/clickhouse
- ./config/replicated.xml:/etc/clickhouse-server/config.d/replicated.xml
environment:
CLICKHOUSE_DB: bets
CLICKHOUSE_USER: replicator
CLICKHOUSE_PASSWORD: rep_pass
CLICKHOUSE_SHARD: 1
CLICKHOUSE_REPLICA: 2
depends_on:
- zookeeper
ulimits:
nofile:
soft: 262144
hard: 262144
networks:
- ch-cluster
networks:
ch-cluster:
driver: bridge
volumes:
zookeeper-data:
ch1-data:
ch2-data:
Y aquí está el contenido de config/replicated.xml (montado en ambos contenedores):
<clickhouse>
<zookeeper>
<node>
<host>zookeeper</host>
<port>2181</port>
</node>
</zookeeper>
<remote_servers>
<replicated_cluster>
<shard>
<replica>
<host>clickhouse-1</host>
<port>9000</port>
</replica>
<replica>
<host>clickhouse-2</host>
<port>9000</port>
</replica>
</shard>
</replicated_cluster>
</remote_servers>
<macros>
<shard>1</shard>
<replica>${CLICKHOUSE_REPLICA}</replica>
</macros>
</clickhouse>
Cuándo es realmente necesario: En un proyecto de casino en producción, perdimos datos porque manteníamos todo en un solo nodo. Después de eso, siempre levanto al menos dos contenedores replicados para pruebas. La diferencia de precio son dos contenedores en lugar de uno, pero dormir bien no tiene precio.
Escenario 4. Configuración completa de apuestas: ClickHouse + Kafka + Redis
Para análisis de apuestas en tiempo real, necesito streaming (Kafka) y caché (Redis). Uso este compose para depuración local del pipeline:
version: '3.8'
services:
zookeeper-kafka:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- "2181:2181"
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper-kafka
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper-kafka:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
ports:
- "9092:9092"
redis:
image: redis:7-alpine
container_name: redis-cache
ports:
- "6379:6379"
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-cachepass}
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
clickhouse:
image: clickhouse/clickhouse-server:latest
container_name: clickhouse-betting
ports:
- "8123:8123"
- "9000:9000"
volumes:
- clickhouse-betting-data:/var/lib/clickhouse
- ./clickhouse-kafka.xml:/etc/clickhouse-server/config.d/kafka.xml
environment:
CLICKHOUSE_DB: betting
CLICKHOUSE_USER: streamer
CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PW:-stream123}
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
ulimits:
nofile:
soft: 262144
hard: 262144
depends_on:
- kafka
- redis
kafka-connector:
image: clickhouse/clickhouse-kafka-connect:latest
container_name: kafka-connector
depends_on:
- kafka
- clickhouse
environment:
CONNECT_BOOTSTRAP_SERVERS: kafka:9092
CONNECT_GROUP_ID: clickhouse-group
CONNECT_CONFIG_STORAGE_TOPIC: connect-configs
CONNECT_OFFSET_STORAGE_TOPIC: connect-offsets
CONNECT_STATUS_STORAGE_TOPIC: connect-status
CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter
CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter
ports:
- "8083:8083"
volumes:
redis-data:
clickhouse-betting-data:
Cómo usar esto en código de aplicación:
# Ejemplo en Python: leer una apuesta desde Redis (caché), escribir en ClickHouse
import redis
from kafka import KafkaProducer
import json
r = redis.Redis(host='localhost', port=6379, password='cachepass', decode_responses=True)
producer = KafkaProducer(bootstrap_servers='localhost:9092', value_serializer=lambda v: json.dumps(v).encode())
# Verificación de duplicados (detección de fraude)
bet_id = "bet_12345"
if r.setnx(bet_id, "processed"):
bet_event = {"user_id": 101, "amount": 500, "odds": 2.1}
producer.send('bets-stream', bet_event)
else:
print(f"Apuesta duplicada {bet_id} bloqueada")
Cómo montar tu propio config.xml sin romper todo
Un error que cometí cinco veces: montar un config.xml completo, solo para descubrir que una nueva versión de ClickHouse agregó secciones obligatorias. El contenedor fallaba con Config has no <logger>.
El enfoque correcto: colocar solo sobrescrituras en config.d/. Aquí hay una estructura que funciona:
docker-clickhouse/
├── docker-compose.yml
├── .env
├── config/
│ ├── config.d/
│ │ ├── memory.xml
│ │ ├── networks.xml
│ │ └── query-log.xml
│ └── users.d/
│ └── profiles.xml
Ejemplo config/config.d/memory.xml:
<clickhouse>
<max_server_memory_usage>0.75</max_server_memory_usage>
<max_memory_usage_for_all_queries>0</max_memory_usage_for_all_queries>
<background_pool_size>16</background_pool_size>
</clickhouse>
Ejemplo config/users.d/profiles.xml:
<clickhouse>
<profiles>
<default>
<max_memory_usage>10000000000</max_memory_usage>
<timeout_before_checking_execution_speed>0</timeout_before_checking_execution_speed>
</default>
<analyst>
<readonly>1</readonly>
<max_execution_time>300</max_execution_time>
</analyst>
</profiles>
</clickhouse>
Trabajar con clickhouse-client dentro de un contenedor
Saltar a un contenedor para consultas rápidas es normal. Pero no mediante docker exec -it bash — hazlo directamente:
# Ejecutar una consulta
docker exec -it clickhouse-dev clickhouse-client --query "SELECT count() FROM system.tables"
# Modo interactivo
docker exec -it clickhouse-dev clickhouse-client
# Con contraseña
docker exec -it clickhouse-dev clickhouse-client --password devpass123
Mi truco: Agrega un alias a ~/.bashrc:
alias ch-cli='docker exec -it clickhouse-dev clickhouse-client'
Después de eso, solo escribe ch-cli y trabaja como si fuera una base de datos local.
Variables de entorno: lo que realmente funciona
La imagen oficial no soporta todas las variables prometidas en foros. Aquí están las probadas:
| Variable | Propósito | Ejemplo |
|---|---|---|
CLICKHOUSE_DB |
Nombre de base de datos por defecto | analytics |
CLICKHOUSE_USER |
Usuario administrador | prod_user |
CLICKHOUSE_PASSWORD |
Contraseña | strongpass |
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT |
Habilitar RBAC (1/0) | 1 |
Lo que NO funciona: CLICKHOUSE_HTTP_PORT, CLICKHOUSE_TCP_PORT — el entrypoint los ignora. Cambia los puertos mediante ports: en compose o montando una configuración.
Health Check: mi lista de verificación completa
Después de iniciar cualquier compose, ejecuto:
# 1. HTTP ping (debe devolver "Ok.")
curl http://localhost:8123/ping
# 2. Versión mediante HTTP
curl "http://localhost:8123/?query=SELECT+version()"
# 3. Crear tabla de prueba
docker exec -it clickhouse-dev clickhouse-client --query "CREATE TABLE test.t (id UInt64) ENGINE = MergeTree ORDER BY id"
# 4. Insertar y seleccionar
docker exec -it clickhouse-dev clickhouse-client --query "INSERT INTO test.t SELECT number FROM numbers(1000)"
docker exec -it clickhouse-dev clickhouse-client --query "SELECT count() FROM test.t"
# 5. HTTP con autenticación (si se estableció contraseña)
curl -u developer:devpass123 "http://localhost:8123/?query=SELECT+user()"
Qué hacer si no funciona — errores comunes de Docker
Error: Code: 210. DB::NetException: Connection refused
Solución: El contenedor aún no ha iniciado. Agrega depends_on y healthcheck, o haz sleep 5 en scripts.
Error: Cannot create directory /var/lib/clickhouse: Permission denied
Solución: En un host con SELinux, agrega :Z al volumen: -v ./data:/var/lib/clickhouse:Z. O usa volúmenes con nombre.
Error: Max connections limit reached
Solución: Aumenta en la configuración: <max_connections>4096</max_connections> y reinicia.
El contenedor consume toda la memoria del host
Solución: Limita mediante Docker:
docker update --memory=4g --memory-swap=4g clickhouse-dev
O en compose:
deploy:
resources:
limits:
memory: 4G
Conclusión: Cuándo usar Docker y cuándo no
Docker es ideal para ClickHouse en desarrollo, staging y entornos de producción pequeños. Pero si tienes un clúster de 10+ nodos con 100 TB de datos — los paquetes nativos sin capas adicionales son mejores.
Por ahora, toma mi compose de configuración de apuestas, cambia las contraseñas y empieza a contar apuestas en tiempo real.
Todas las configuraciones tomadas de proyectos reales. Nombres cambiados, problemas permanecen.
← Anterior: Instalación de ClickHouse en Ubuntu/Debian: Guía paso a paso de alguien que se quemó con permisos incorrectos
→ Siguiente: Cliente de ClickHouse: Cómo me hice amigo de la consola y la API HTTP en un proyecto de apuestas
— Editorial Team
Aún no hay comentarios.