--- title: "Cómo instalar QuestDB en Ubuntu 24.04 con Docker Compose" description: "Despliega QuestDB en Ubuntu 24.04 con Docker Compose, crea tablas particionadas con deduplicación, ingiere datos por ILP sobre HTTP y consulta con SQL y psql." category: "Bases de Datos Especializadas" language: es source: https://cubepath.com/es/docs/bases-de-datos-especializadas/questdb-base-de-datos-de-series-temporales updated: 2026-09-25 --- # Cómo instalar QuestDB en Ubuntu 24.04 con Docker Compose QuestDB es una base de datos de series temporales en formato columnar, pensada para ingerir millones de filas por segundo y consultarlas con SQL. Acepta datos por el protocolo de línea de InfluxDB (ILP) y por el protocolo de PostgreSQL, así que puedes usar clientes y herramientas que ya conoces. En este tutorial desplegarás QuestDB en Ubuntu 24.04 con Docker Compose, crearás una tabla para datos de sensores con particionado y deduplicación, enviarás datos por ILP y los consultarás desde la consola web y desde `psql`. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS con al menos 2 GB de RAM y disco SSD, por ejemplo un VPS de CubePath. - Un usuario no root con privilegios `sudo` que pertenezca al grupo `docker`. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker. ## Paso 1: Ajustar los límites del kernel QuestDB mapea en memoria muchos archivos de columnas y particiones. Con los valores por defecto de Linux puede quedarse sin mapas de memoria cuando la base de datos crece, así que la documentación de QuestDB recomienda subir `vm.max_map_count`: ```bash echo 'vm.max_map_count = 1048576' | sudo tee /etc/sysctl.d/60-questdb.conf sudo sysctl --system ``` Comprueba el valor: ```bash sysctl vm.max_map_count ``` ```output vm.max_map_count = 1048576 ``` El límite de archivos abiertos se ajusta para el contenedor en el archivo de Compose del paso siguiente. ## Paso 2: Crear el proyecto de Docker Compose Crea un directorio para el despliegue: ```bash mkdir -p ~/questdb cd ~/questdb ``` QuestDB expone por el protocolo de PostgreSQL el usuario `admin` con contraseña `quest` por defecto. Genera una contraseña propia y guárdala en un archivo `.env` que Compose leerá automáticamente: ```bash echo "QDB_PG_PASSWORD=$(openssl rand -hex 24)" > .env chmod 600 .env ``` Crea el archivo de Compose: ```bash nano compose.yaml ``` ```yaml services: questdb: image: questdb/questdb:10.0.1 container_name: questdb restart: unless-stopped ports: - "127.0.0.1:9000:9000" - "127.0.0.1:8812:8812" environment: QDB_PG_USER: admin QDB_PG_PASSWORD: ${QDB_PG_PASSWORD} QDB_TELEMETRY_ENABLED: "false" volumes: - questdb_data:/var/lib/questdb ulimits: nofile: soft: 1048576 hard: 1048576 volumes: questdb_data: ``` Algunos detalles de esta configuración: - Consulta la última versión en la [página de releases de QuestDB](https://github.com/questdb/questdb/releases) y fija la etiqueta de la imagen, en lugar de `latest`, para controlar cuándo actualizas. - El puerto 9000 sirve la consola web, la API REST y la ingesta ILP por HTTP. El 8812 es el protocolo de PostgreSQL. - Ambos puertos se publican solo en `127.0.0.1`. La consola web y el endpoint de ingesta de QuestDB open source no piden autenticación, así que no deben quedar abiertos a Internet. - Cualquier opción de `server.conf` se puede pasar como variable de entorno con el prefijo `QDB_`, en mayúsculas y con `_` en lugar de puntos: `pg.password` se convierte en `QDB_PG_PASSWORD`. ## Paso 3: Arrancar QuestDB Descarga la imagen y arranca el contenedor en segundo plano: ```bash docker compose up -d ``` Comprueba que está en marcha: ```bash docker compose ps ``` ```output NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS questdb questdb/questdb:10.0.1 "/docker-entrypoint.…" questdb 12 seconds ago Up 11 seconds 127.0.0.1:8812->8812/tcp, 127.0.0.1:9000->9000/tcp ``` Lanza una consulta de prueba por la API REST. El endpoint `/exec` recibe SQL en el parámetro `query` y devuelve JSON: ```bash curl -G http://127.0.0.1:9000/exec --data-urlencode "query=SELECT now()" ``` ```output {"query":"SELECT now()","columns":[{"name":"now","type":"TIMESTAMP"}],"timestamp":-1,"dataset":[["2026-09-25T10:31:07.412593Z"]],"count":1} ``` Si no responde, revisa los registros con `docker compose logs --tail 50 questdb`. ## Paso 4: Abrir la consola web por un túnel SSH Como el puerto 9000 solo escucha en localhost, accede a la consola web desde tu ordenador mediante un túnel SSH. Ejecuta esto en tu máquina local, no en el servidor: ```bash ssh -L 9000:127.0.0.1:9000 your_user@your_server_ip ``` Mientras la sesión esté abierta, abre `http://localhost:9000` en el navegador. Verás el editor SQL de QuestDB con el listado de tablas a la izquierda. Puedes ejecutar en él todas las consultas SQL de esta guía. ## Paso 5: Crear una tabla de series temporales En QuestDB cada tabla de series temporales tiene una columna de marca de tiempo designada, que determina el orden físico de los datos y el particionado. Crea una tabla para lecturas de sensores: ```bash curl -G http://127.0.0.1:9000/exec --data-urlencode "query= CREATE TABLE sensores ( ts TIMESTAMP, sensor SYMBOL, ubicacion SYMBOL, temperatura DOUBLE, humedad DOUBLE ) TIMESTAMP(ts) PARTITION BY DAY WAL DEDUP UPSERT KEYS(ts, sensor);" ``` ```output {"ddl":"OK"} ``` Qué hace cada parte: - `SYMBOL` es un tipo para cadenas repetitivas (identificadores, ubicaciones). Se guarda como un diccionario de enteros, lo que ahorra espacio y acelera los filtros. - `TIMESTAMP(ts)` marca `ts` como marca de tiempo designada. - `PARTITION BY DAY` guarda cada día en un directorio aparte, así que las consultas por rango de fechas solo leen las particiones necesarias y borrar datos antiguos es inmediato. - `WAL` activa el registro de escritura previa, necesario para la deduplicación y para aceptar escrituras concurrentes. - `DEDUP UPSERT KEYS(ts, sensor)` hace que una fila con el mismo `ts` y `sensor` que otra ya existente la sustituya en lugar de duplicarla. Es útil cuando un dispositivo reenvía datos tras un corte. ## Paso 6: Ingerir datos con ILP sobre HTTP El protocolo de línea de InfluxDB (ILP) es la forma más rápida de escribir en QuestDB. Cada línea tiene el formato `tabla,simbolo=valor campo=valor marca_de_tiempo`. Envía dos lecturas al endpoint `/write`; al omitir la marca de tiempo, QuestDB usa la hora del servidor: ```bash curl -i -X POST http://127.0.0.1:9000/write --data-binary $'sensores,sensor=s1,ubicacion=almacen temperatura=21.4,humedad=48.2\nsensores,sensor=s2,ubicacion=oficina temperatura=23.1,humedad=41.0\n' ``` ```output HTTP/1.1 204 No Content ``` Un `204` significa que QuestDB aceptó las filas. En las tablas WAL los datos se aplican de forma asíncrona y aparecen en las consultas en unos milisegundos. En producción no escribas ILP a mano: usa los clientes oficiales de QuestDB (Python, Java, Go, Rust, Node.js, C/C++, .NET), que agrupan filas, reintentan y gestionan los errores, o Telegraf con su salida `influxdb_v2` apuntando a `http://127.0.0.1:9000`. Comprueba ahora la deduplicación. Envía dos veces la misma lectura del sensor `s9` con una marca de tiempo explícita en nanosegundos, cambiando solo la temperatura: ```bash curl -s -X POST http://127.0.0.1:9000/write --data-binary 'sensores,sensor=s9,ubicacion=lab temperatura=20.0,humedad=50.0 1790000000000000000' curl -s -X POST http://127.0.0.1:9000/write --data-binary 'sensores,sensor=s9,ubicacion=lab temperatura=20.5,humedad=50.0 1790000000000000000' curl -G http://127.0.0.1:9000/exec --data-urlencode "query=SELECT ts, temperatura FROM sensores WHERE sensor = 's9'" ``` ```output {"query":"SELECT ts, temperatura FROM sensores WHERE sensor = 's9'","columns":[{"name":"ts","type":"TIMESTAMP"},{"name":"temperatura","type":"DOUBLE"}],"timestamp":0,"dataset":[["2026-09-21T14:13:20.000000Z",20.5]],"count":1} ``` Solo hay una fila, con el último valor recibido. ## Paso 7: Consultar series temporales con SQL QuestDB añade a SQL varias extensiones para series temporales. Ejecuta estas consultas en la consola web o con `curl` como en los pasos anteriores. `SAMPLE BY` agrega por intervalos de tiempo. Esta consulta calcula la media de temperatura y la humedad máxima por sensor en intervalos de un minuto durante la última hora: ```sql SELECT ts, sensor, avg(temperatura) AS temp_media, max(humedad) AS humedad_max FROM sensores WHERE ts > dateadd('h', -1, now()) SAMPLE BY 1m; ``` `LATEST ON` devuelve la última lectura de cada sensor, algo habitual en paneles de estado: ```sql SELECT * FROM sensores LATEST ON ts PARTITION BY sensor; ``` Para filtrar por intervalo de fechas, el operador `IN` con una cadena de tiempo solo lee las particiones afectadas: ```sql SELECT count() FROM sensores WHERE ts IN '2026-09'; ``` ## Paso 8: Conectarte por el protocolo de PostgreSQL Cualquier cliente de PostgreSQL puede consultar QuestDB por el puerto 8812, lo que permite usar Grafana con su fuente de datos de PostgreSQL, librerías como `psycopg` o herramientas de BI. Instala el cliente `psql`: ```bash sudo apt install postgresql-client ``` Conéctate con el usuario `admin` a la base de datos `qdb`, usando como contraseña el valor de `QDB_PG_PASSWORD` del archivo `.env`: ```bash psql -h 127.0.0.1 -p 8812 -U admin -d qdb ``` ```text qdb=> SELECT sensor, temperatura FROM sensores LATEST ON ts PARTITION BY sensor; sensor | temperatura --------+------------- s1 | 21.4 s2 | 23.1 s9 | 20.5 (3 rows) ``` Si la conexión se rechaza con `invalid username/password`, comprueba que el contenedor recibió la variable con `docker compose exec questdb env | grep QDB_PG`. ## Paso 9: Limitar la retención de datos Las series temporales crecen sin parar, así que conviene decidir cuánto tiempo guardarlas. Con particiones diarias puedes configurar un TTL para que QuestDB borre automáticamente las particiones más antiguas que el periodo indicado: ```sql ALTER TABLE sensores SET TTL 30 DAYS; ``` También puedes borrar particiones a mano, por ejemplo antes de un cambio de política: ```sql ALTER TABLE sensores DROP PARTITION WHERE ts < dateadd('d', -30, now()); ``` Borrar una partición elimina su directorio completo, así que es instantáneo aunque contenga millones de filas. ## Solución de problemas - **El contenedor se reinicia con errores de `mmap` o `too many open files`**: revisa que `vm.max_map_count` está aplicado en el host (paso 1) y que el bloque `ulimits` está en `compose.yaml`. - **`curl: (7) Failed to connect to 127.0.0.1 port 9000`**: QuestDB todavía está arrancando o se ha detenido. Consulta `docker compose logs questdb`. - **Los datos enviados por ILP no aparecen**: la respuesta de `/write` incluye el error si una línea es inválida (tipo de columna distinto al existente, por ejemplo). Envía con `curl -i` y lee el cuerpo de la respuesta. - **La consola web no carga por el túnel**: el túnel debe seguir abierto y el puerto local 9000 libre en tu ordenador. Prueba con otro puerto local: `ssh -L 19000:127.0.0.1:9000 ...` y abre `http://localhost:19000`. ## Conclusión QuestDB está funcionando en Ubuntu 24.04 con Docker Compose, accesible solo desde localhost, con una tabla particionada que deduplica lecturas, ingesta por ILP y consultas por REST, consola web y `psql`. Como siguientes pasos, conecta Grafana con la fuente de datos de PostgreSQL o el plugin oficial de QuestDB, envía métricas reales con uno de los clientes ILP y planifica copias de seguridad del volumen `questdb_data`.