Javier Valencia Javier Valencia
PostgreSQL desde cero (I): instalación, psql y primeros pasos

PostgreSQL desde cero (I): instalación, psql y primeros pasos

Javier Valencia · · 4 min de lectura · 1 visita · Desarrollo
bases-de-datos tutorial postgresql sql postgresql-desde-cero

Primera entrega de la serie PostgreSQL desde cero a pro. Tiempo de lectura estimado: 10 minutos.

Arranco una nueva serie de cinco posts, esta vez sobre PostgreSQL. Es la base de datos que más uso y la que más respeto. La idea es ir de no haber tocado nunca PostgreSQL a ser capaz de montarlo en producción con replicación, backups y monitorización decentes.

Si quieres una visión transversal del ecosistema de bases de datos para analítica y por qué a veces no basta con PostgreSQL, la serie ClickHouse desde cero a pro es un buen complemento.

Por qué PostgreSQL

En un mundo lleno de bases de datos exóticas, PostgreSQL sigue siendo la opción por defecto para la mayoría de aplicaciones. Motivos:

  • ACID serio: transacciones, MVCC, aislamiento configurable.
  • Extensible: tipos propios, funciones, extensiones como PostGIS, TimescaleDB, pgvector.
  • SQL moderno: CTEs, window functions, LATERAL, JSON/JSONB, arrays nativos.
  • Fiabilidad probada: más de 25 años de desarrollo, usado en producción desde startups a bancos.
  • Licencia permisiva (PostgreSQL License, similar a BSD/MIT).

Si tu aplicación no tiene requisitos muy específicos, la respuesta correcta a "¿qué base de datos uso?" es casi siempre PostgreSQL.

Instalación

Hay tres caminos razonables para empezar:

  1. Paquetes oficiales sobre Debian/Ubuntu/RHEL. Lo que usarás en producción.
  2. Docker. Lo mejor para probar en local sin ensuciar el sistema.
  3. Servicios gestionados: RDS, Cloud SQL, Neon, Supabase, Crunchy Bridge...

Para esta serie usamos paquetes nativos y Docker indistintamente. Lo que aprendas vale igual para un servicio gestionado (con matices de configuración que veremos en la entrega V).

Instalación en Debian/Ubuntu

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh

sudo apt install -y postgresql-17 postgresql-client-17
sudo systemctl enable --now postgresql

El script oficial añade el repositorio de la PostgreSQL Global Development Group, que mantiene versiones recientes para distribuciones LTS. Los paquetes del repo base suelen ir una o dos versiones por detrás.

Instalación con Docker

docker run -d \
  --name pg \
  -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 \
  -v pgdata:/var/lib/postgresql/data \
  postgres:17

El volumen pgdata persiste los datos entre reinicios. Puerto 5432 es el estándar.

Para entrar al cliente:

docker exec -it pg psql -U postgres

psql: el cliente que vas a usar cada día

psql es el cliente de consola oficial. Es mucho más que un prompt SQL: tiene meta-comandos, ejecución de scripts, formatos de salida, variables, historial persistente y autocompletado.

Meta-comandos que uso constantemente:

\l               -- listar bases de datos
\c nombre        -- conectarse a una base de datos
\dt              -- listar tablas
\dt+             -- con tamaños y descripciones
\d tabla         -- describir una tabla
\d+ tabla        -- con detalles extra
\di              -- listar índices
\df              -- listar funciones
\du              -- listar roles
\dn              -- listar schemas
\dx              -- listar extensiones instaladas
\timing on       -- mostrar tiempo de ejecución
\x               -- toggle formato expandido (útil para filas anchas)
\e               -- abrir $EDITOR para escribir la consulta
\i fichero.sql   -- ejecutar script
\q               -- salir
\?               -- ayuda de meta-comandos

Mi ~/.psqlrc básico:

\set QUIET 1
\pset null '(null)'
\pset border 2
\set PROMPT1 '%n@%/%R%# '
\set HISTFILE ~/.psql_history- :DBNAME
\set HISTCONTROL ignoredups
\set COMP_KEYWORD_CASE upper
\timing on
\set QUIET 0

Con esto ves el nombre de la base de datos en el prompt, los nulos no se confunden con strings vacíos, el historial se separa por base de datos, y tienes el tiempo de cada consulta automáticamente.

Roles, bases de datos y permisos

En PostgreSQL todo es un rol. Un rol puede ser "usuario" (LOGIN) o "grupo" (NOLOGIN). Los permisos se otorgan a roles. Un rol puede heredar permisos de otro.

-- Crear un rol de aplicación sin permisos de login directo
CREATE ROLE app NOLOGIN;

-- Crear un usuario real
CREATE ROLE javier WITH LOGIN PASSWORD 'xxx' IN ROLE app;

-- Crear una base de datos cuyo owner es el rol app
CREATE DATABASE blog OWNER app;

-- Dar permisos
GRANT CONNECT ON DATABASE blog TO app;
GRANT USAGE ON SCHEMA public TO app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;

-- Y lo más importante: que se apliquen a tablas futuras
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app;

Separar el rol dueño (propietario de las tablas) del rol de aplicación (el que usa la app en runtime) es una buena práctica: limita el daño si las credenciales se filtran.

pg_hba.conf

El fichero pg_hba.conf controla quién puede conectarse, desde dónde y con qué método de autenticación. Está en /etc/postgresql/17/main/ en Debian/Ubuntu.

Entrada típica para entornos de producción:

# TYPE  DATABASE  USER  ADDRESS         METHOD
local   all       all                   peer
host    all       all   127.0.0.1/32    scram-sha-256
host    all       all   ::1/128         scram-sha-256
hostssl all       all   10.0.0.0/8      scram-sha-256
  • peer: autenticación por usuario del sistema operativo (solo en socket local).
  • scram-sha-256: el método moderno de autenticación por contraseña.
  • hostssl: solo acepta conexiones cifradas.

Tu primera base de datos

Vamos a montar un esquema de blog muy simple. Desde psql:

\c blog

CREATE TABLE authors (
  id         BIGSERIAL PRIMARY KEY,
  name       TEXT NOT NULL,
  email      TEXT UNIQUE NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE posts (
  id           BIGSERIAL PRIMARY KEY,
  author_id    BIGINT NOT NULL REFERENCES authors(id) ON DELETE CASCADE,
  title        TEXT NOT NULL,
  slug         TEXT NOT NULL UNIQUE,
  body         TEXT NOT NULL,
  published_at TIMESTAMPTZ,
  created_at   TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX posts_published_at_idx ON posts(published_at DESC) WHERE published_at IS NOT NULL;

Un par de detalles que ya introducen ideas clave:

  • BIGSERIAL genera una columna BIGINT con una secuencia asociada. En versiones modernas hay una alternativa más estándar, GENERATED ALWAYS AS IDENTITY, que veremos en la entrega II.
  • TIMESTAMPTZ (timestamp with time zone) es casi siempre lo que quieres. Guarda el instante en UTC y lo convierte al huso horario de la sesión al leer. El TIMESTAMP sin zona suele ser una trampa.
  • REFERENCES ... ON DELETE CASCADE crea una foreign key: borrar un autor borra sus posts.
  • El último índice es parcial: solo indexa filas publicadas. Si la mayoría de posts son borradores, el índice es pequeño y más rápido.

Primeros INSERT y SELECT

INSERT INTO authors (name, email)
VALUES ('Javier', '[email protected]')
RETURNING id;
-- Devuelve 1

INSERT INTO posts (author_id, title, slug, body, published_at)
VALUES
  (1, 'Hola mundo', 'hola-mundo', 'Primer post', now()),
  (1, 'En borrador', 'borrador', 'Aún no publicado', NULL);

SELECT id, title, published_at IS NOT NULL AS publicado
FROM posts
ORDER BY created_at DESC;

RETURNING es una de esas pequeñas cosas que hacen que PostgreSQL sea tan agradable: cualquier INSERT, UPDATE o DELETE puede devolver filas. Adiós al SELECT LAST_INSERT_ID().

Transacciones

Toda operación en PostgreSQL ocurre dentro de una transacción (implícita o explícita). Para operaciones multi-statement:

BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- Si algo sale mal
-- ROLLBACK;

COMMIT;

Los savepoints permiten hacer rollback parcial dentro de una transacción grande:

BEGIN;
INSERT INTO ...;
SAVEPOINT sp1;
UPDATE ...;    -- esto puede fallar
ROLLBACK TO SAVEPOINT sp1;
COMMIT;        -- el INSERT inicial sí se guarda

Por defecto el nivel de aislamiento es READ COMMITTED. Para operaciones financieras o cargas analíticas complejas, REPEATABLE READ o SERIALIZABLE son alternativas importantes que conviene conocer.

Los ficheros que importan

Tres ficheros concentran casi toda la configuración:

  • postgresql.conf — parámetros del servidor: memoria, WAL, logs, conexiones.
  • pg_hba.conf — autenticación y redes permitidas.
  • pg_ident.conf — mapeo entre usuarios del SO y roles de PostgreSQL.

En Debian/Ubuntu están en /etc/postgresql/17/main/. El directorio de datos (PGDATA) está en /var/lib/postgresql/17/main/. Ahí viven los WAL, las tablas, los índices.

Para consultar cualquier parámetro desde psql:

SHOW work_mem;
SHOW shared_buffers;
SHOW max_connections;

Y para ver todos:

SELECT name, setting, unit, short_desc
FROM pg_settings
WHERE name LIKE '%mem%';

Por dónde seguir

Si te gustan las consultas modernas y quieres un aperitivo, el post PostgreSQL: 10 consultas que todo desarrollador debería conocer es un buen complemento a esta serie.