Home / Bases de Datos / psql en la terminal: guía práctica para administrar PostgreSQL
psql en la terminal: guía práctica para administrar PostgreSQL

psql en la terminal: guía práctica para administrar PostgreSQL

14 November 2026 · 26 min de lectura ··

Por qué dominar psql sigue siendo una ventaja

Las interfaces gráficas para bases de datos son cómodas, pero tarde o temprano todo desarrollador termina frente a un servidor sin escritorio, dentro de un contenedor o conectado por SSH a una máquina de producción. En ese momento, la herramienta que siempre está disponible es psql, el cliente interactivo de terminal que se distribuye junto con PostgreSQL. No necesita instalación adicional, consume pocos recursos y permite hacer prácticamente cualquier tarea: desde una consulta rápida hasta la restauración completa de una base de datos.

En este tutorial vas a aprender a administrar PostgreSQL desde la terminal de principio a fin. Veremos cómo conectarte, cómo moverte con los metacomandos, cómo crear y modificar bases de datos y tablas, cómo ejecutar scripts, importar datos desde archivos CSV, medir el rendimiento de las consultas y crear copias de seguridad con pg_dump y pg_restore. Además, iremos más allá de lo básico: configuraremos el archivo .psqlrc, usaremos variables, el modo expandido, el comando \watch, el archivo .pgpass, la gestión de roles y el trabajo con PostgreSQL dentro de Docker.

Si todavía no tienes claros conceptos como tabla, clave primaria o clave foránea, te recomiendo repasar primero estos fundamentos de DBMS y SQL con PostgreSQL y SQLite, porque aquí nos centraremos en la herramienta más que en la teoría relacional.

Para los ejemplos usaremos un escenario propio: una pequeña tienda de bicicletas que necesita registrar proveedores, productos y ventas.

Versiones de PostgreSQL vigentes en noviembre de 2026

Antes de instalar nada conviene saber en qué punto está el proyecto. El 12 de noviembre de 2026 el grupo de desarrollo global de PostgreSQL publicó una actualización para todas las ramas soportadas: 18.7, 17.12, 16.16, 15.20 y 14.25. Esa versión 14.25 es la última de su rama, porque PostgreSQL 14 alcanzó el final de su vida útil y ya no recibirá correcciones de seguridad ni de errores. Si todavía lo utilizas, vale la pena leer el análisis sobre cómo PostgreSQL 14 llega a su fin con 52 vulnerabilidades corregidas y planificar la migración.

Las ramas que siguen recibiendo soporte son la 15, 16, 17 y 18. PostgreSQL 19 se encuentra en fase de candidata a lanzamiento (RC 2) y su disponibilidad general está planificada para el 19 de noviembre de 2026, de modo que para producción la recomendación actual es PostgreSQL 18.

Un detalle importante: psql es compatible hacia atrás con servidores más antiguos, pero algunas funciones de sus metacomandos dependen de la versión. Lo ideal es usar un cliente de la misma versión mayor que el servidor o una más reciente. Puedes comprobar la versión del cliente así:

psql --version
psql (PostgreSQL) 18.7

En Debian y Ubuntu, el repositorio oficial de PostgreSQL (PGDG) ofrece los paquetes más recientes. Si solo necesitas el cliente, puedes instalar únicamente ese paquete:

sudo apt install postgresql-client-18

En macOS con Homebrew existe la fórmula postgresql@18, y en Windows el instalador oficial incluye psql dentro de la carpeta bin de la instalación, que conviene añadir a la variable PATH para poder ejecutarlo desde cualquier consola.

Conectarte a un servidor PostgreSQL con psql

La forma más directa de conectarte es indicar la base de datos, el usuario, el host y el puerto con sus opciones cortas:

psql -h localhost -p 5432 -U postgres -d postgres

Si el servidor solicita contraseña, psql la pedirá de forma interactiva. Al entrar verás un prompt con el nombre de la base de datos actual seguido de =# si eres superusuario o de => si eres un usuario normal:

psql (18.7)
Type "help" for help.

postgres=#

Además de las opciones sueltas, psql acepta una cadena de conexión en formato URI, muy útil cuando copias credenciales de un servicio en la nube:

psql "postgresql://[email protected]:5432/tienda_bicis?sslmode=require"

También puedes usar variables de entorno para no repetir datos en cada comando. Las más habituales son PGHOST, PGPORT, PGUSER, PGDATABASE y PGPASSWORD, aunque esta última no es recomendable porque la contraseña puede quedar visible en el historial o en la lista de procesos:

export PGHOST=localhost
export PGUSER=tienda_admin
export PGDATABASE=tienda_bicis
psql

En Linux, cuando PostgreSQL se instala desde los paquetes del sistema, el usuario administrador del sistema operativo se llama postgres y la autenticación local usa el método peer. Por eso la forma típica de entrar la primera vez es:

sudo -u postgres psql

Una vez dentro, el metacomando \conninfo te confirma a qué servidor, base de datos y usuario estás conectado. En PostgreSQL 18 este comando muestra la información en forma de tabla, con más detalles sobre el protocolo y el cifrado SSL:

\conninfo

Metacomandos: la navaja suiza de psql

Todo lo que escribes en psql pertenece a una de dos familias. Por un lado están las sentencias SQL, que el servidor ejecuta y que deben terminar con punto y coma. Por otro lado están los metacomandos, que empiezan con una barra invertida, los interpreta el propio cliente y no necesitan punto y coma. Entender esa diferencia evita uno de los despistes más frecuentes: escribir una consulta sin ; y ver que el prompt cambia a -# porque psql sigue esperando el final de la sentencia.

Los metacomandos que usarás a diario son estos:

\?            -- ayuda de todos los metacomandos
\h            -- lista de sentencias SQL con ayuda disponible
\h ALTER TABLE -- sintaxis detallada de una sentencia concreta
\l            -- listar bases de datos
\c tienda_bicis -- cambiar de base de datos
\dn           -- listar esquemas
\dt           -- listar tablas del esquema de búsqueda
\d productos  -- describir una tabla
\du           -- listar roles
\df           -- listar funciones
\dv           -- listar vistas
\di           -- listar índices
\q            -- salir de psql

Casi todos los comandos de listado aceptan el sufijo + para mostrar información extendida, como tamaño en disco, descripción o método de acceso. Por ejemplo, \l+ añade el tamaño de cada base de datos y \dt+ el tamaño de cada tabla. También aceptan patrones: \dt venta* lista solo las tablas cuyo nombre empieza por venta, y \dt inventario.* muestra las tablas del esquema inventario.

Cuando la salida es más larga que la pantalla, psql la envía a un paginador (normalmente less). Para salir del paginador basta con pulsar la tecla q. Si prefieres desactivarlo, usa \pset pager off.

Un truco poco conocido es la opción -E al iniciar psql: con ella, cada metacomando muestra la consulta SQL interna que ejecuta contra el catálogo del sistema. Es una forma excelente de aprender cómo funciona pg_catalog por dentro.

psql -E -d tienda_bicis

Crear, listar y eliminar bases de datos

Crear una base de datos es tan sencillo como ejecutar una sentencia SQL. Vamos a preparar la base de datos de la tienda:

CREATE DATABASE tienda_bicis
  WITH ENCODING 'UTF8'
  TEMPLATE template0;

Usar template0 junto con una codificación explícita evita sorpresas cuando la plantilla por defecto (template1) tiene una configuración regional distinta. Después comprueba que existe con \l y conéctate a ella:

\l
\c tienda_bicis
You are now connected to database "tienda_bicis" as user "postgres".

Para eliminar una base de datos se usa DROP DATABASE. Añadir IF EXISTS evita un error si ya no existe, algo muy práctico en scripts:

DROP DATABASE IF EXISTS tienda_pruebas;

Hay dos restricciones a tener en cuenta. Solo el propietario de la base de datos o un superusuario pueden eliminarla, y no puedes borrar la base de datos a la que estás conectado. Además, si hay otras sesiones abiertas, el servidor rechazará la operación. Desde PostgreSQL 13 existe la opción WITH (FORCE), que termina esas conexiones antes de borrar:

DROP DATABASE IF EXISTS tienda_pruebas WITH (FORCE);

Úsala con cuidado: cualquier aplicación conectada perderá su sesión inmediatamente.

Diseñar las tablas de la tienda

Con la base de datos lista, creamos las tablas. En lugar del antiguo tipo SERIAL, la recomendación actual es usar columnas de identidad definidas por el estándar SQL, que tienen permisos más simples y se comportan mejor al copiar tablas:

CREATE TABLE proveedores (
  proveedor_id  integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  nombre        varchar(120) NOT NULL,
  pais          varchar(60)  NOT NULL,
  email         varchar(150) UNIQUE
);

CREATE TABLE productos (
  producto_id   integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku           varchar(20)   NOT NULL UNIQUE,
  descripcion   varchar(200)  NOT NULL,
  precio        numeric(10,2) NOT NULL CHECK (precio > 0),
  stock         integer       NOT NULL DEFAULT 0
);

Olvidamos relacionar cada producto con su proveedor. No pasa nada: ALTER TABLE permite añadir columnas y restricciones sobre una tabla existente:

ALTER TABLE productos
  ADD COLUMN proveedor_id integer REFERENCES proveedores (proveedor_id);

Ahora insertamos algunos datos de ejemplo:

INSERT INTO proveedores (nombre, pais, email) VALUES
  ('Cuadros Andinos', 'Perú', '[email protected]'),
  ('Ruedas del Norte', 'México', '[email protected]'),
  ('Pedalia Ibérica', 'España', '[email protected]');

INSERT INTO productos (sku, descripcion, precio, stock, proveedor_id) VALUES
  ('BIC-MTB-29', 'Bicicleta de montaña rodado 29', 689.90, 12, 1),
  ('RUE-700C',   'Rueda de ruta 700c',               129.50, 40, 2),
  ('CAS-URB-M',  'Casco urbano talla M',              54.00, 75, 3),
  ('BIC-GRV-54', 'Bicicleta gravel talla 54',       1149.00,  5, 1);

Para describir la tabla y comprobar columnas, índices y claves foráneas, usa \d:

\d productos
                                   Table "public.productos"
    Column    |          Type          | Collation | Nullable |           Default
--------------+------------------------+-----------+----------+------------------------------
 producto_id  | integer                |           | not null | generated always as identity
 sku          | character varying(20)  |           | not null |
 descripcion  | character varying(200) |           | not null |
 precio       | numeric(10,2)          |           | not null |
 stock        | integer                |           | not null | 0
 proveedor_id | integer                |           |          |
Indexes:
    "productos_pkey" PRIMARY KEY, btree (producto_id)
    "productos_sku_key" UNIQUE CONSTRAINT, btree (sku)
Check constraints:
    "productos_precio_check" CHECK (precio > 0::numeric)
Foreign-key constraints:
    "productos_proveedor_id_fkey" FOREIGN KEY (proveedor_id) REFERENCES proveedores(proveedor_id)

Renombrar o eliminar tablas sigue la misma lógica que con las bases de datos:

ALTER TABLE productos RENAME TO articulos;
ALTER TABLE articulos RENAME TO productos;
DROP TABLE IF EXISTS tabla_temporal;

Si una tabla tiene otras que dependen de ella mediante claves foráneas, DROP TABLE fallará. Puedes añadir CASCADE para eliminar también las restricciones dependientes, pero revisa antes qué objetos se verán afectados.

Consultas cómodas: modo expandido, formatos y valores nulos

Cuando una tabla tiene muchas columnas, la salida en formato horizontal se vuelve ilegible. El modo expandido muestra cada fila como una lista vertical de pares columna-valor. Puedes activarlo con \x on, aunque la opción más práctica es \x auto, que lo activa solo cuando la salida no cabe en la terminal:

\x auto
SELECT * FROM productos WHERE sku = 'BIC-GRV-54';
-[ RECORD 1 ]+---------------------------
producto_id  | 4
sku          | BIC-GRV-54
descripcion  | Bicicleta gravel talla 54
precio       | 1149.00
stock        | 5
proveedor_id | 1

Si solo quieres el formato expandido para una consulta concreta, termina la sentencia con \gx en lugar de punto y coma. Otro ajuste muy recomendable es mostrar los valores nulos de forma visible, porque por defecto aparecen como una cadena vacía y se confunden fácilmente con texto vacío:

\pset null '(nulo)'

El metacomando \pset también controla el formato de salida. Algunas opciones útiles son \pset format csv para generar CSV directamente, \pset format markdown para pegar resultados en documentación y \pset border 2 para tablas con bordes completos. Combinado con \o archivo.txt, que redirige la salida a un archivo, puedes generar informes sin salir de la terminal:

\pset format csv
\o /tmp/stock_bajo.csv
SELECT sku, descripcion, stock FROM productos WHERE stock < 10;
\o
\pset format aligned

El último \o sin argumentos devuelve la salida a la pantalla.

Escribir consultas largas con \e y ejecutar scripts con \i

Escribir una consulta de veinte líneas directamente en el prompt es incómodo. El metacomando \e abre el último comando en tu editor de texto; al guardar y cerrar, psql lo ejecuta. El editor se elige con las variables de entorno PSQL_EDITOR, EDITOR o VISUAL, en ese orden de prioridad:

export PSQL_EDITOR="nano"

Para tareas repetibles es mejor guardar el SQL en archivos y ejecutarlos con \i. Supongamos esta estructura de proyecto:

tienda-bicis/
├── sql/
│   ├── 01_esquema.sql
│   ├── 02_datos_iniciales.sql
│   └── 03_vistas.sql
└── datos/
    └── ventas_octubre.csv

Desde psql puedes ejecutar cada script con su ruta:

\i sql/01_esquema.sql
\i sql/02_datos_iniciales.sql

La ruta de \i es relativa al directorio desde el que lanzaste psql. Si quieres que sea relativa al script que se está ejecutando, por ejemplo cuando un script incluye a otro, usa \ir. En Windows también puedes usar barras normales en las rutas, lo que evita problemas con la barra invertida.

Los scripts también se pueden ejecutar sin entrar al modo interactivo, algo ideal para automatizaciones y pipelines de CI:

psql -d tienda_bicis -v ON_ERROR_STOP=1 -f sql/03_vistas.sql

La variable ON_ERROR_STOP hace que psql se detenga en el primer error y devuelva un código de salida distinto de cero. Sin ella, el script continúa ejecutando el resto de sentencias aunque una falle, lo que puede dejar la base de datos en un estado inconsistente. Si además quieres que todo el script sea atómico, añade la opción --single-transaction (o -1): si algo falla, se revierte todo.

Importar datos desde CSV con \copy

Cargar datos desde hojas de cálculo es una de las tareas más comunes. Imagina que el equipo comercial te envía el archivo ventas_octubre.csv con este contenido:

sku,cantidad,fecha_venta,canal
BIC-MTB-29,2,2026-10-03,tienda
CAS-URB-M,5,2026-10-03,web
RUE-700C,4,2026-10-11,web
BIC-GRV-54,1,2026-10-19,tienda
CAS-URB-M,3,2026-10-27,marketplace

Primero creas la tabla de destino:

CREATE TABLE ventas (
  venta_id     bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  sku          varchar(20) NOT NULL REFERENCES productos (sku),
  cantidad     integer     NOT NULL CHECK (cantidad > 0),
  fecha_venta  date        NOT NULL,
  canal        varchar(20) NOT NULL
);

Y después cargas el archivo con \copy, indicando las columnas en el mismo orden en que aparecen en el CSV:

\copy ventas (sku, cantidad, fecha_venta, canal) FROM 'datos/ventas_octubre.csv' WITH (FORMAT csv, HEADER true)
COPY 5

Conviene entender la diferencia entre \copy y la sentencia SQL COPY. COPY se ejecuta en el servidor y lee archivos del sistema de archivos del servidor, por lo que necesita permisos especiales (ser superusuario o pertenecer al rol pg_read_server_files). \copy, en cambio, lee el archivo en tu máquina local y lo envía por la conexión. Por eso la importación de CSV con \copy es la opción adecuada cuando trabajas contra un servidor remoto o un servicio administrado en la nube, donde no tienes acceso a su disco.

Los nombres de columna de la tabla no tienen por qué coincidir con los encabezados del CSV; lo importante es que el orden de la lista de columnas sea el mismo que el del archivo. Si el archivo usa punto y coma como separador, algo habitual en hojas de cálculo con configuración regional en español, añade DELIMITER ';'. Si la codificación no es UTF-8, indica ENCODING 'LATIN1' o la que corresponda.

La exportación funciona en sentido inverso, y puedes exportar el resultado de una consulta completa:

\copy (SELECT canal, SUM(cantidad) AS unidades FROM ventas GROUP BY canal ORDER BY unidades DESC) TO 'resumen_canales.csv' WITH (FORMAT csv, HEADER true)

Ten en cuenta que \copy debe escribirse en una sola línea, porque es un metacomando y termina en el salto de línea.

Medir el rendimiento con \timing y EXPLAIN

Cuando una consulta parece lenta, el primer paso es medirla. El metacomando \timing activa o desactiva la impresión del tiempo de ejecución de cada sentencia:

\timing on
SELECT p.descripcion, SUM(v.cantidad) AS vendidas
FROM ventas v
JOIN productos p ON p.sku = v.sku
GROUP BY p.descripcion
ORDER BY vendidas DESC;
         descripcion            | vendidas
--------------------------------+----------
 Casco urbano talla M           |        8
 Rueda de ruta 700c             |        4
 Bicicleta de montaña rodado 29 |        2
 Bicicleta gravel talla 54      |        1
(4 rows)

Time: 1.284 ms

El tiempo que muestra \timing incluye el viaje por la red y el formateo del resultado, así que es una medida desde el punto de vista del cliente. Para entender qué hace el servidor por dentro, usa EXPLAIN ANALYZE. En PostgreSQL 18, EXPLAIN ANALYZE incluye por defecto la información de búferes, lo que te dice cuántas páginas se leyeron de la caché y cuántas del disco:

EXPLAIN ANALYZE
SELECT * FROM ventas WHERE fecha_venta >= DATE '2026-10-15';

Si el plan muestra un recorrido secuencial sobre una tabla grande para una condición muy selectiva, probablemente necesites un índice:

CREATE INDEX idx_ventas_fecha ON ventas (fecha_venta);

Vigilar consultas en tiempo real con \watch

El metacomando \watch repite la última consulta cada cierto número de segundos, similar al comando watch de Linux. Es perfecto para observar cómo evoluciona una carga de datos, el número de conexiones o el progreso de un proceso:

SELECT state, count(*)
FROM pg_stat_activity
WHERE datname = 'tienda_bicis'
GROUP BY state
\watch 5

Las versiones recientes de psql permiten indicar un límite de repeticiones con \watch count=10 5 o detener la ejecución cuando la consulta devuelva al menos cierto número de filas con la opción min_rows. Para detener \watch manualmente, pulsa Ctrl+C.

Una consulta muy útil para vigilar con \watch es la que muestra las sentencias activas que llevan más tiempo ejecutándose:

SELECT pid, now() - query_start AS duracion, left(query, 60) AS consulta
FROM pg_stat_activity
WHERE state = 'active' AND pid <> pg_backend_pid()
ORDER BY duracion DESC
\watch 2

Si detectas una consulta bloqueada, puedes cancelarla con SELECT pg_cancel_backend(pid); o, en último caso, terminar la sesión con pg_terminate_backend(pid).

Variables y lógica condicional en scripts de psql

psql tiene su propio sistema de variables, independiente del SQL. Se definen con \set y se interpolan con dos puntos. Existen tres formas de interpolación: :variable inserta el valor tal cual, :'variable' lo inserta como literal de texto correctamente escapado y :"variable" lo inserta como identificador entre comillas dobles:

\set canal_objetivo 'web'
\set tabla 'ventas'

SELECT sku, cantidad, fecha_venta
FROM :"tabla"
WHERE canal = :'canal_objetivo';

Las variables también se pueden pasar desde la línea de comandos con -v, lo que convierte un script SQL en una pequeña herramienta parametrizable:

psql -d tienda_bicis -v canal_objetivo=marketplace -f sql/informe_canal.sql

Otra pieza muy potente es \gset, que guarda el resultado de una consulta en variables de psql usando los nombres de las columnas:

SELECT count(*) AS total_ventas, max(fecha_venta) AS ultima_venta FROM ventas \gset
\echo 'Ventas registradas:' :total_ventas 'Última:' :ultima_venta

Con \if, \elif, \else y \endif puedes añadir lógica condicional. Este fragmento evita cargar datos si la tabla ya tiene registros:

SELECT count(*) > 0 AS hay_datos FROM ventas \gset
\if :hay_datos
  \echo 'La tabla ventas ya tiene datos, se omite la carga.'
\else
  \copy ventas (sku, cantidad, fecha_venta, canal) FROM 'datos/ventas_octubre.csv' WITH (FORMAT csv, HEADER true)
\endif

Personalizar tu entorno con el archivo .psqlrc

Cada vez que psql arranca, lee el archivo ~/.psqlrc (en Windows, %APPDATA%\postgresql\psqlrc.conf) y ejecuta su contenido. Es el lugar ideal para dejar configurada tu forma de trabajar. Un ejemplo razonable:

-- ~/.psqlrc
\set QUIET 1
\pset null '(nulo)'
\x auto
\timing on
\set HISTSIZE 5000
\set HISTCONTROL ignoredups
\set COMP_KEYWORD_CASE upper
\set PROMPT1 '%n@%M:%>/%/%R%# '
\set ON_ERROR_ROLLBACK interactive
\set conexiones 'SELECT datname, usename, state, count(*) FROM pg_stat_activity GROUP BY 1, 2, 3 ORDER BY 4 DESC;'
\unset QUIET

Repasemos las líneas menos evidentes. QUIET evita que se impriman mensajes mientras se carga la configuración. COMP_KEYWORD_CASE hace que el autocompletado con la tecla Tab escriba las palabras clave en mayúsculas. PROMPT1 personaliza el prompt para mostrar usuario, host, puerto y base de datos, algo que resulta muy valioso para no confundir un servidor de pruebas con uno de producción.

La variable ON_ERROR_ROLLBACK interactive merece mención aparte. Dentro de una transacción, cualquier error obliga a deshacerla entera. Con este ajuste, psql crea puntos de guardado implícitos en sesiones interactivas, de modo que un error tipográfico no arruina el trabajo previo de la transacción, pero el comportamiento de los scripts no cambia.

La última línea define una consulta guardada. Para ejecutarla, basta con escribir :conexiones en el prompt. Puedes crear tus propios atajos para las consultas de diagnóstico que usas a menudo.

Si algún día necesitas arrancar sin tu configuración, por ejemplo para depurar un comportamiento extraño, usa psql -X.

Contraseñas seguras con el archivo .pgpass

Escribir la contraseña cada vez es tedioso, y ponerla en scripts o en PGPASSWORD es inseguro. La solución estándar es el archivo ~/.pgpass, donde cada línea sigue el formato host:puerto:basededatos:usuario:contraseña y admite el asterisco como comodín:

localhost:5432:tienda_bicis:tienda_admin:Cambia-Esta-Clave-2026
db.ejemplo.local:5432:*:informes_ro:Otra-Clave-Larga

En sistemas tipo Unix el archivo debe tener permisos restrictivos o psql lo ignorará:

chmod 0600 ~/.pgpass

En Windows el archivo equivalente se llama pgpass.conf y se ubica en %APPDATA%\postgresql\. Para entornos con muchas conexiones, complementa .pgpass con el archivo de servicios ~/.pg_service.conf, que agrupa host, puerto, base de datos y usuario bajo un nombre corto:

[tienda_prod]
host=db.ejemplo.local
port=5432
dbname=tienda_bicis
user=informes_ro
sslmode=verify-full
psql service=tienda_prod

Gestión de roles y permisos desde la terminal

Conectar todas las aplicaciones con el superusuario postgres es una mala práctica muy extendida. PostgreSQL usa roles, que pueden actuar como usuarios (si tienen el atributo LOGIN) o como grupos. Una configuración sensata para la tienda separa un rol propietario, un rol de aplicación y un rol de solo lectura:

CREATE ROLE tienda_owner NOLOGIN;
CREATE ROLE tienda_app LOGIN;
CREATE ROLE informes_ro LOGIN;

\password tienda_app
\password informes_ro

El metacomando \password solicita la contraseña de forma interactiva y la envía ya cifrada con SCRAM-SHA-256, de modo que no queda en texto plano en el historial ni en los registros del servidor. Es preferible a escribir ALTER ROLE ... PASSWORD '...' directamente.

Después se asignan los permisos:

ALTER DATABASE tienda_bicis OWNER TO tienda_owner;
GRANT CONNECT ON DATABASE tienda_bicis TO tienda_app, informes_ro;
GRANT USAGE ON SCHEMA public TO tienda_app, informes_ro;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO tienda_app;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO informes_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO informes_ro;

La última sentencia es clave: GRANT ... ON ALL TABLES solo afecta a las tablas que ya existen, mientras que ALTER DEFAULT PRIVILEGES se aplica a las tablas que se creen en el futuro. Olvidarla es la causa de muchos errores permission denied semanas después de configurar los roles. Para alternativas más simples, el rol predefinido pg_read_all_data concede lectura sobre todas las tablas de todas las bases de datos.

Para revisar el resultado tienes \du (roles y atributos), \dp productos (privilegios sobre una tabla) y \ddp (privilegios por defecto).

Copias de seguridad con pg_dump

pg_dump no se ejecuta dentro de psql, sino directamente en la terminal del sistema. Genera una copia lógica y consistente de una base de datos sin bloquear las escrituras de las aplicaciones. Su formato más sencillo es un script SQL en texto plano:

pg_dump -h localhost -U tienda_admin -d tienda_bicis -f tienda_bicis_2026-11-15.sql

El formato plano es legible y fácil de revisar con un editor, pero tiene limitaciones: solo puede restaurarse con psql y siempre de forma completa. Para uso real, el formato personalizado (-Fc) es mucho más flexible, porque está comprimido y permite restaurar objetos de forma selectiva:

pg_dump -h localhost -U tienda_admin -d tienda_bicis -Fc -v -f tienda_bicis.dump

Para bases de datos grandes, el formato de directorio (-Fd) admite volcado en paralelo con la opción -j, que reparte el trabajo entre varios procesos:

pg_dump -d tienda_bicis -Fd -j 4 -f respaldo_tienda/

También puedes limitar qué se respalda. La opción -t ventas exporta solo esa tabla, -n inventario un esquema completo, --schema-only únicamente la estructura y --data-only solo los datos.

Hay algo que pg_dump no incluye: los objetos globales del clúster, como los roles y los tablespaces. Para ellos existe pg_dumpall:

pg_dumpall -h localhost -U postgres --globals-only -f globales.sql

Una estrategia de copias de seguridad con pg_dump completa combina el volcado de cada base de datos en formato personalizado con un volcado de los objetos globales. Para recuperaciones a un punto exacto en el tiempo o bases de datos de varios cientos de gigabytes, conviene complementarla con copias físicas mediante pg_basebackup y archivado de WAL, ya que la copia lógica refleja solo el instante en que se tomó.

Restaurar bases de datos con psql y pg_restore

La forma de restaurar depende del formato. Un volcado en texto plano se restaura con psql sobre una base de datos vacía:

createdb -U postgres tienda_restaurada
psql -U postgres -d tienda_restaurada -v ON_ERROR_STOP=1 --single-transaction -f tienda_bicis_2026-11-15.sql

Los formatos personalizado y de directorio se restauran con pg_restore, que también admite trabajo en paralelo:

pg_restore -U postgres -d tienda_restaurada -j 4 -v tienda_bicis.dump

Si prefieres que pg_restore cree la base de datos por ti, conéctate a otra base (por ejemplo postgres) y añade -C. Si quieres sobrescribir objetos en una base existente, combina --clean con --if-exists para que primero se eliminen los objetos sin generar errores cuando no existan.

Una de las funciones más útiles del formato personalizado es la restauración selectiva. Primero listas el contenido del archivo:

pg_restore -l tienda_bicis.dump > contenido.lst

Editas contenido.lst comentando con punto y coma las entradas que no quieres restaurar, y luego lo usas como guía:

pg_restore -d tienda_restaurada -L contenido.lst tienda_bicis.dump

Cuando restauras en un servidor donde no existen los mismos roles que en el origen, las opciones --no-owner y --no-privileges evitan errores por propietarios inexistentes y asignan los objetos al usuario que ejecuta la restauración.

Por último, recuerda que una copia que nunca se ha restaurado es solo una suposición. Programa restauraciones de prueba periódicas en un entorno aislado y verifica el número de filas de las tablas críticas.

PostgreSQL en Docker: psql dentro y fuera del contenedor

Docker es la manera más rápida de levantar un servidor de PostgreSQL 18 para desarrollo. Ten en cuenta que, a partir de la imagen oficial de la versión 18, el directorio de datos se organiza bajo /var/lib/postgresql con una subcarpeta por versión mayor, por lo que el volumen se monta en esa ruta y no en /var/lib/postgresql/data como en imágenes anteriores:

docker run -d --name pg-tienda \
  -e POSTGRES_USER=tienda_admin \
  -e POSTGRES_PASSWORD=Cambia-Esta-Clave-2026 \
  -e POSTGRES_DB=tienda_bicis \
  -p 5432:5432 \
  -v pgdatos_tienda:/var/lib/postgresql \
  postgres:18

Tienes dos formas de usar psql. Si lo tienes instalado en tu máquina, conéctate al puerto publicado como a cualquier servidor. Si no, ejecuta el cliente que ya trae la imagen mediante docker exec; si quieres profundizar en sus opciones, aquí tienes una guía completa de docker exec para ejecutar comandos en contenedores:

docker exec -it pg-tienda psql -U tienda_admin -d tienda_bicis

Las copias de seguridad también funcionan a través del contenedor. Gracias a la redirección de la salida estándar, el archivo queda directamente en tu máquina:

docker exec pg-tienda pg_dump -U tienda_admin -d tienda_bicis -Fc > tienda_bicis.dump

Para restaurar, el flujo es el inverso, enviando el archivo por la entrada estándar con la opción -i:

docker exec -i pg-tienda pg_restore -U tienda_admin -d tienda_bicis --clean --if-exists < tienda_bicis.dump

Para cargar un CSV con \copy desde dentro del contenedor, el archivo debe existir en el sistema de archivos del contenedor. La alternativa más limpia es copiarlo antes con docker cp o usar el psql del host conectado al puerto publicado.

Errores frecuentes y cómo resolverlos

Algunos mensajes aparecen tarde o temprano en cualquier proyecto. Conocer su causa ahorra mucho tiempo.

psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory

El servidor no está en ejecución o escucha en otro puerto o socket. Comprueba el servicio con systemctl status postgresql y verifica el valor de port en postgresql.conf. Si el servidor está en otra máquina, indica -h para conectarte por TCP en lugar de por socket.

FATAL:  Peer authentication failed for user "tienda_admin"

La conexión local usa el método peer, que exige que el usuario del sistema operativo coincida con el rol. Conéctate con -h localhost para usar autenticación por contraseña o ajusta la regla correspondiente en pg_hba.conf y recarga la configuración con SELECT pg_reload_conf();.

FATAL:  no pg_hba.conf entry for host "10.0.3.25", user "tienda_app", database "tienda_bicis", no encryption

El servidor no tiene ninguna regla que permita esa combinación de origen, usuario y base de datos. Añade una línea específica en pg_hba.conf con el método scram-sha-256 y evita reglas demasiado amplias.

ERROR:  permission denied for table ventas

El rol no tiene privilegios sobre la tabla. Revisa con \dp ventas y recuerda la diferencia entre GRANT ... ON ALL TABLES y ALTER DEFAULT PRIVILEGES que vimos antes.

ERROR:  could not open file "datos/ventas_octubre.csv" for reading: No such file or directory

Usaste COPY en lugar de \copy, y el servidor busca el archivo en su propio disco. Cambia a \copy o revisa la ruta relativa al directorio desde el que lanzaste psql.

pg_restore: error: input file appears to be a text format dump. Please use psql.

Intentaste restaurar un volcado en texto plano con pg_restore. Para ese formato debes usar psql -f.

En Windows es común ver un aviso sobre la página de códigos de la consola que no coincide con la de Windows. Puedes cambiarla con chcp 1252 antes de iniciar psql para que los acentos y la letra eñe se muestren correctamente.

Buenas prácticas para trabajar con psql en producción

Trabajar con un cliente tan potente en servidores reales requiere disciplina. Estas son las recomendaciones que más problemas evitan:

  • Usa un prompt que muestre claramente el host y la base de datos, y si es posible, un color distinto para producción mediante las secuencias de PROMPT1.
  • Abre una transacción explícita con BEGIN antes de cualquier UPDATE o DELETE manual. Revisa el número de filas afectadas y solo entonces confirma con COMMIT; si algo no cuadra, ejecuta ROLLBACK.
  • Activa \set AUTOCOMMIT off en sesiones delicadas para que cada cambio requiera confirmación explícita.
  • Conéctate con roles de privilegios mínimos. Para consultas de análisis, usa un rol de solo lectura o abre la sesión con SET default_transaction_read_only = on;.
  • Ejecuta los scripts siempre con ON_ERROR_STOP=1 y, cuando tenga sentido, con --single-transaction.
  • Exige cifrado en las conexiones remotas con sslmode=verify-full en la cadena de conexión o en el archivo de servicios.
  • Guarda las contraseñas en .pgpass con permisos 0600 o en un gestor de secretos, nunca en el historial de la shell.
  • Mantén el cliente actualizado a la última versión menor de tu rama; las actualizaciones menores incluyen correcciones de seguridad que también afectan a psql, pg_dump y pg_restore.

Un ejemplo de flujo seguro para una corrección manual de datos sería este:

BEGIN;
UPDATE productos SET precio = precio * 1.05 WHERE proveedor_id = 2;
SELECT sku, precio FROM productos WHERE proveedor_id = 2;
-- Si los valores son correctos:
COMMIT;
-- Si no lo son:
-- ROLLBACK;

Cuándo elegir psql frente a otras herramientas

psql no es la única opción para trabajar con PostgreSQL, y conviene saber cuándo encaja mejor cada alternativa. Los clientes gráficos como pgAdmin o DBeaver son cómodos para explorar un esquema desconocido, ver diagramas de relaciones o editar datos de forma visual. Otros clientes de terminal, como pgcli, añaden autocompletado más inteligente y resaltado de sintaxis, aunque no siempre están disponibles en los servidores.

La gran ventaja de psql es su ubicuidad y su capacidad de automatización. Está presente en cualquier instalación de PostgreSQL, en las imágenes oficiales de Docker y en la mayoría de entornos de CI. Un script que funciona en tu portátil funcionará igual en un servidor remoto, y ese es el flujo de trabajo en terminal que facilita reproducir tareas, versionarlas en Git y revisarlas en un pull request. Para tareas de administración, migraciones, cargas de datos y copias de seguridad, sigue siendo la herramienta de referencia.

Una combinación habitual en equipos profesionales es usar un cliente gráfico para exploración y diseño, y psql con scripts versionados para todo lo que deba repetirse o ejecutarse en producción.

Conclusiones

psql es mucho más que una consola donde escribir SQL. Con un puñado de metacomandos puedes explorar cualquier base de datos, describir tablas, revisar roles y permisos sin consultar el catálogo a mano. Con \i, \copy, las variables y la lógica condicional, se convierte en un entorno de scripting suficiente para automatizar cargas de datos e informes. Y con pg_dump, pg_restore y pg_dumpall dispones de un sistema completo de copias de seguridad lógicas que funciona igual en una máquina local que dentro de un contenedor.

En noviembre de 2026, PostgreSQL 18.7 es la versión estable recomendada, PostgreSQL 14 ya no recibe soporte y PostgreSQL 19 está a punto de publicarse. Mantener tanto el servidor como el cliente actualizados es parte de una administración responsable.

El siguiente paso es invertir unos minutos en tu archivo .psqlrc, configurar .pgpass y un archivo de servicios para tus entornos, y convertir en scripts versionados las tareas que hoy haces a mano. Cuando lo hayas hecho, notarás que la terminal deja de ser un último recurso y pasa a ser la forma más rápida y fiable de trabajar con PostgreSQL.