dbt (data build tool) transforma los datos que ya están en tu base de datos o data warehouse mediante consultas SQL versionadas, con dependencias entre modelos, tests de calidad y documentación generada automáticamente. En este tutorial instalarás dbt Core con el adaptador de PostgreSQL en Ubuntu 24.04, crearás un proyecto con datos de ejemplo, construirás una capa de staging y una tabla agregada, añadirás tests y generarás la documentación del linaje.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
- Un usuario no root con privilegios
sudo. - Conocimientos básicos de SQL.
En esta guía instalarás PostgreSQL en el mismo servidor para tener un entorno completo. Si ya dispones de un PostgreSQL remoto, sáltate el paso 1 y usa sus datos de conexión en el paso 4.
Paso 1: Instalar PostgreSQL y crear la base de datos
dbt no almacena datos: ejecuta SQL contra una base de datos existente. Instala PostgreSQL desde los repositorios de Ubuntu:
sudo apt update
sudo apt install -y postgresql
Crea un rol para dbt y una base de datos de la que sea propietario. Sustituye your_strong_password por una contraseña segura:
sudo -u postgres psql -c "CREATE ROLE dbt_user WITH LOGIN PASSWORD 'your_strong_password';"
sudo -u postgres createdb -O dbt_user analitica
Comprueba que puedes conectarte por TCP con ese usuario, igual que hará dbt. psql te pedirá la contraseña:
psql -h localhost -U dbt_user -d analitica -c 'SELECT current_user, current_database();'
current_user | current_database
--------------+------------------
dbt_user | analitica
(1 row)
Paso 2: Instalar dbt en un entorno virtual
Instalar dbt con pip dentro de un entorno virtual evita conflictos con los paquetes de Python del sistema, que Ubuntu 24.04 protege frente a pip install global. Instala el módulo venv:
sudo apt install -y python3-venv
Crea el entorno, actívalo e instala el adaptador dbt-postgres, que trae dbt-core como dependencia:
python3 -m venv ~/dbt-venv
source ~/dbt-venv/bin/activate
pip install --upgrade pip
pip install dbt-postgres
Comprueba la instalación:
dbt --version
Core:
- installed: 1.12.5
- latest: 1.12.5 - Up to date!
Plugins:
- postgres: 1.11.0 - Up to date!
Las versiones pueden ser más recientes. Recuerda ejecutar source ~/dbt-venv/bin/activate en cada sesión nueva antes de usar dbt.
Paso 3: Crear el proyecto
Inicializa un proyecto llamado tienda. La opción --skip-profile-setup evita el asistente interactivo; configurarás la conexión a mano en el paso siguiente:
cd ~
dbt init tienda --skip-profile-setup
cd ~/tienda
dbt init crea la estructura del proyecto (models/, seeds/, tests/, macros/, snapshots/, analyses/) y unos modelos de ejemplo que no necesitas. Elimínalos:
rm -rf models/example
Abre el archivo de configuración del proyecto:
nano dbt_project.yml
Al final del archivo hay un bloque models: que hace referencia a example. Sustitúyelo por este, que materializa la capa de staging como vistas y la capa de marts como tablas:
models:
tienda:
staging:
+materialized: view
marts:
+materialized: table
Comprueba también que la línea profile: del mismo archivo vale 'tienda'. dbt usará ese nombre para buscar la conexión.
Paso 4: Configurar la conexión en profiles.yml
Las credenciales viven fuera del proyecto, en ~/.dbt/profiles.yml, para que nunca acaben en el repositorio. Crea el archivo:
mkdir -p ~/.dbt
nano ~/.dbt/profiles.yml
tienda:
target: dev
outputs:
dev:
type: postgres
host: localhost
port: 5432
user: dbt_user
password: "{{ env_var('DBT_PASSWORD') }}"
dbname: analitica
schema: dbt_dev
threads: 4
La contraseña se lee de la variable de entorno DBT_PASSWORD en lugar de escribirse en claro. schema es el esquema donde dbt creará las vistas y tablas del entorno dev. Restringe los permisos del archivo y exporta la variable:
chmod 600 ~/.dbt/profiles.yml
export DBT_PASSWORD='your_strong_password'
Comprueba la configuración y la conexión:
dbt debug
...
Connection test: [OK connection ok]
All checks passed!
Paso 5: Cargar datos de ejemplo con seeds
Los seeds son ficheros CSV pequeños que dbt carga como tablas. Sirven para datos de referencia y, en este caso, para tener datos con los que trabajar. Crea el fichero:
nano seeds/ventas_raw.csv
id_venta,cliente_email,importe,fecha_venta,estado
1,[email protected] ,120.50,2026-01-15,pagada
2,[email protected],35.00,2026-01-20,completada
3,[email protected],80.00,2026-02-03,cancelada
4,[email protected],210.99,2026-02-11,pagada
5,[email protected],15.25,2026-03-02,pendiente
Carga el seed en la base de datos:
dbt seed
...
1 of 1 OK loaded seed file dbt_dev.ventas_raw ........................ [INSERT 5 in 0.08s]
...
Completed successfully
Paso 6: Crear los modelos
Un modelo es un fichero .sql con una sentencia SELECT. dbt la envuelve en el CREATE VIEW o CREATE TABLE correspondiente y resuelve el orden de ejecución a partir de las llamadas a ref(). Crea los directorios de las dos capas:
mkdir -p models/staging models/marts
El modelo de staging limpia y normaliza los datos en bruto:
nano models/staging/stg_ventas.sql
select
id_venta::integer as id_venta,
lower(trim(cliente_email)) as email_cliente,
importe::numeric(10, 2) as importe,
fecha_venta::date as fecha_venta,
case
when estado in ('completada', 'pagada') then 'completada'
when estado = 'cancelada' then 'cancelada'
else 'pendiente'
end as estado
from {{ ref('ventas_raw') }}
El modelo de marts agrega las ventas completadas por mes a partir del modelo de staging:
nano models/marts/ventas_mensuales.sql
select
date_trunc('month', fecha_venta)::date as mes,
count(*) as num_ventas,
sum(importe) as total_ventas,
count(distinct email_cliente) as clientes_unicos
from {{ ref('stg_ventas') }}
where estado = 'completada'
group by 1
Ejecuta los modelos:
dbt run
...
1 of 2 OK created sql view model dbt_dev.stg_ventas ................. [CREATE VIEW in 0.05s]
2 of 2 OK created sql table model dbt_dev.ventas_mensuales .......... [SELECT 2 in 0.04s]
...
Completed successfully
Consulta el resultado directamente en PostgreSQL:
psql -h localhost -U dbt_user -d analitica -c 'SELECT * FROM dbt_dev.ventas_mensuales ORDER BY mes;'
mes | num_ventas | total_ventas | clientes_unicos
------------+------------+--------------+-----------------
2026-01-01 | 2 | 155.50 | 2
2026-02-01 | 1 | 210.99 | 1
(2 rows)
Observa que el email [email protected] del seed se ha normalizado y cuenta como el mismo cliente que [email protected].
Para ejecutar solo una parte del proyecto, usa --select. Por ejemplo, dbt run --select stg_ventas+ ejecuta stg_ventas y todos los modelos que dependen de él.
Paso 7: Añadir tests de calidad de datos
Los tests genéricos se declaran en ficheros YAML junto a los modelos. Cada test es una consulta que busca filas que incumplen la regla; si encuentra alguna, falla. Crea el fichero de propiedades de la capa de staging:
nano models/staging/_staging.yml
version: 2
models:
- name: stg_ventas
description: "Ventas limpiadas y normalizadas a partir del seed ventas_raw."
columns:
- name: id_venta
description: "Identificador único de la venta."
data_tests:
- unique
- not_null
- name: estado
data_tests:
- accepted_values:
arguments:
values: ['completada', 'cancelada', 'pendiente']
- name: importe
data_tests:
- not_null
Para reglas propias, crea un test singular: una consulta en tests/ que debe devolver cero filas. Este comprueba que no hay importes negativos:
nano tests/assert_importe_no_negativo.sql
select *
from {{ ref('stg_ventas') }}
where importe < 0
Ejecuta los tests:
dbt test
...
Done. PASS=5 WARN=0 ERROR=0 SKIP=0 NO-OP=0 TOTAL=5
En el día a día conviene usar dbt build, que carga seeds, ejecuta modelos y lanza sus tests en orden de dependencias. Si un test de stg_ventas falla, los modelos que dependen de él se omiten:
dbt build
Paso 8: Crear un modelo incremental
Un modelo table se reconstruye entero en cada ejecución. Con tablas grandes es mejor un modelo incremental, que solo procesa las filas nuevas. Crea uno de ejemplo:
nano models/marts/ventas_incremental.sql
{{
config(
materialized='incremental',
unique_key='id_venta'
)
}}
select id_venta, email_cliente, importe, fecha_venta, estado
from {{ ref('stg_ventas') }}
{% if is_incremental() %}
where fecha_venta > (select max(fecha_venta) from {{ this }})
{% endif %}
La primera ejecución crea la tabla completa. En las siguientes, el bloque is_incremental() añade el filtro y dbt solo inserta o actualiza las ventas posteriores a la última cargada:
dbt run --select ventas_incremental
Si cambias la lógica del modelo y necesitas reconstruirlo desde cero, añade --full-refresh.
Paso 9: Generar y consultar la documentación
dbt genera un sitio web estático con la descripción de cada modelo, sus columnas, sus tests y el grafo de dependencias:
dbt docs generate
dbt docs serve --port 8080
El servidor escucha en 127.0.0.1. Para verlo desde tu equipo, abre en otra terminal un túnel SSH (sustituye your_user y your_server_ip):
ssh -L 8080:127.0.0.1:8080 your_user@your_server_ip
Visita http://localhost:8080 y abre la vista Lineage Graph para ver cómo ventas_raw alimenta a stg_ventas y este a los modelos de marts. Detén el servidor con Ctrl+C cuando termines.
Solución de problemas
Env var required but not provided: 'DBT_PASSWORD'. La variable no está definida en la sesión actual. Vuelve a ejecutar export DBT_PASSWORD='...' o añádela a un fichero que cargues antes de usar dbt.
Could not find profile named 'tienda'. El valor de profile: en dbt_project.yml no coincide con la clave de primer nivel de ~/.dbt/profiles.yml. Ambos deben ser idénticos.
password authentication failed for user "dbt_user". La contraseña de DBT_PASSWORD no coincide con la del rol. Cámbiala con sudo -u postgres psql -c "ALTER ROLE dbt_user PASSWORD 'your_strong_password';".
Error de SQL en un modelo. Revisa el SQL que dbt ha generado realmente con dbt compile --select nombre_modelo; el resultado se guarda en target/compiled/tienda/models/.
Conclusión
Has instalado dbt Core con PostgreSQL en Ubuntu 24.04 y has construido un proyecto con seeds, modelos en capas, tests de calidad, un modelo incremental y documentación del linaje. Como siguientes pasos, guarda el proyecto en Git, programa dbt build --target prod con un temporizador de systemd o cron añadiendo un segundo target en profiles.yml, y ejecuta dbt build en tu CI en cada pull request para detectar cambios que rompan los datos antes de desplegarlos.
