Docker Compose para PostgreSQL paso a paso
Un compose.yaml de PostgreSQL funcional con volumen persistente, healthcheck pg_isready, .env.example y los comandos clave, más por qué cambiar la contraseña no funciona si conservas el volumen viejo.
Esta guía monta una base de datos PostgreSQL con Docker Compose para desarrollo local: la base de datos que tu app usará mientras programas, sin instalar Postgres en tu máquina. Está pensada para quien empieza con Docker y para quien ya lo usa pero quiere un compose limpio que no pierda datos ni filtre contraseñas.
compose.yaml mínimo y funcional
Guarda esto como compose.yaml. No lleva la clave version: porque en Compose v2 está obsoleta.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "127.0.0.1:5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
volumes:
postgres_data:Y este es el .env.example que lo acompaña. Cópialo a .env, pon una contraseña de verdad y añade .env a tu .gitignore.
POSTGRES_DB=app POSTGRES_USER=app POSTGRES_PASSWORD=CHANGE_ME
Por qué cada parte está ahí
- image: postgres:16-alpine. Una versión fija, no
:latest. Así undocker compose pullmañana no te cambia la versión mayor sin avisar. - environment con ${VARIABLE}. Las credenciales se interpolan desde el
.env, no se escriben en claro en el compose. El secreto real nunca entra en git. - volumes: postgres_data. Los datos viven en un volumen con nombre montado en
/var/lib/postgresql/data. Sin esto, al hacerdownperderías la base de datos entera. - ports: 127.0.0.1:5432:5432. Publica Postgres solo en tu loopback, para que puedas conectar con
psqlo un cliente gráfico sin abrirlo a la red. - healthcheck con pg_isready. Comprueba que Postgres acepta conexiones. El comando usa
$$POSTGRES_USER: el$$hace que Compose pase un solo$al contenedor, donde la variable sí existe en tiempo de ejecución.
Comandos del día a día
docker compose up -d # levanta en segundo plano docker compose ps # estado y salud de los servicios docker compose logs -f postgres # sigue los logs de Postgres docker compose exec postgres \ psql -U app -d app # abre una sesión psql dentro docker compose down # para y elimina contenedores (datos a salvo) docker compose down -v # ADEMÁS borra el volumen (datos eliminados)
La diferencia entre down y down -v es justo la que más sustos da: -v borra el volumen y, con él, toda la base de datos. Úsalo a propósito cuando quieras empezar de cero, nunca por costumbre.
El error clásico: cambié la contraseña y no funciona
Es la duda número uno con Postgres en Docker. Cambias POSTGRES_PASSWORD en el .env, reinicias y sigue pidiéndote la contraseña vieja. No está roto: la imagen de Postgres solo inicializa el usuario y la base de datos la primera vez, cuando el volumen está vacío. En arranques posteriores los datos ya existen y las variables POSTGRES_* se ignoran.
Tienes dos caminos, según si quieres conservar los datos:
- Empezar de cero (desarrollo):
docker compose down -vpara borrar el volumen y luegodocker compose up -d. Postgres se reinicializa con la contraseña nueva. - Conservar los datos: cambia la contraseña por SQL:
docker compose exec postgres psql -U app -d app -c "ALTER USER app PASSWORD 'nueva';", y actualiza el.envpara que coincida.
Local y desarrollo, no producción
Este compose es honesto sobre su alcance: sirve para programar con una base de datos real en tu equipo. No es hardening de producción. Antes de poner Postgres de cara a usuarios necesitarás, como mínimo, backups automáticos y probados, una contraseña fuerte fuera de git, normalmente ningún puerto publicado al host, y una decisión consciente sobre actualizaciones de versión. La guía de healthchecks y secretos entra en más detalle sobre esas decisiones.
Siguientes pasos
Genera este stack (con o sin puerto local, con o sin healthcheck) en el generador de Docker Compose. Si vas a conectar tu propia app a esta base de datos, sigue con Node.js + PostgreSQL o añade una interfaz web con PostgreSQL + pgAdmin.
Preguntas frecuentes
- Cambié POSTGRES_PASSWORD en el .env pero no me deja entrar con la nueva, ¿por qué?
- Porque la imagen de Postgres solo crea el usuario y la base de datos la primera vez, cuando el directorio de datos está vacío. Si ya arrancaste el contenedor una vez, el volumen postgres_data conserva la contraseña original y las variables de entorno se ignoran en arranques posteriores. Para empezar de cero en desarrollo: docker compose down -v (borra el volumen) y vuelve a levantar. Si necesitas conservar los datos, cambia la contraseña por SQL con ALTER USER, no por la variable.
- ¿Por qué uso 127.0.0.1:5432:5432 y no 5432:5432?
- Con 5432:5432 publicas Postgres en todas las interfaces de tu máquina, así que cualquiera en tu red podría intentar conectarse. Atándolo a 127.0.0.1 solo lo alcanzas desde tu propio equipo (psql, un cliente gráfico), que es lo que normalmente quieres en desarrollo. Si otro contenedor del mismo compose necesita la base de datos, no hace falta publicar puerto: se conecta por el nombre del servicio (postgres:5432) en la red interna.
- ¿Necesito el healthcheck si solo es desarrollo local?
- No es obligatorio, pero es barato y útil. pg_isready te dice cuándo Postgres acepta conexiones de verdad, no solo cuándo el contenedor arrancó. En cuanto añadas una app que dependa de la base de datos, ese healthcheck es lo que permite que depends_on con condition: service_healthy espere correctamente y evita errores de conexión al arrancar.
- ¿Cómo hago un backup de la base de datos?
- Un volumen no es un backup. Para un volcado lógico usa pg_dump dentro del contenedor: docker compose exec postgres pg_dump -U app app > backup.sql. Guarda ese archivo fuera del host y, en serio, prueba a restaurarlo de vez en cuando: un backup que nunca has restaurado no es un backup que sepas que funciona.
- ¿Este compose vale para producción?
- Es un buen punto de partida para desarrollo, no un despliegue de producción. Para producción necesitas backups automatizados y probados, una contraseña fuerte gestionada fuera de git, normalmente no publicar ningún puerto al host, límites de recursos y una estrategia de actualización de la versión mayor de Postgres. Trátalo como la base sobre la que construir, no como el resultado final.