Skip to content
GitHub

Container PostgreSQL

El container mother ejecuta PostgreSQL 16 en una imagen Docker personalizada y endurecida, basada en Debian 13 (trixie-slim). Está diseñado para entornos de desarrollo y producción con foco en seguridad y estabilidad.

  • Directorypg16-clean/
    • Dockerfile - Imagen base endurecida
    • docker-compose.yml - Configuración del servicio
    • docker-entrypoint.sh - Script de inicio customizado
    • Directoryconf/
      • pg_hba.conf - Control de acceso
      • postgresql.conf.sample - Configuración del servidor
    • Directoryinitdb/ - Scripts de inicialización

La imagen está construida con los siguientes principios de seguridad:

Imagen Minimal

Base debian:13-slim sin paquetes innecesarios. Build-tools eliminados después de compilación.

Usuario No-Root

PostgreSQL corre como usuario postgres (UID 999) usando gosu para cambio seguro.

Filesystem Read-Only

Container en modo read_only: true con tmpfs para directorios de escritura.

Capabilities Mínimas

cap_drop: ALL con solo SETUID y SETGID habilitados para gosu.

# Base mínima
FROM debian:13-slim
# PostgreSQL 16 desde repositorio oficial PGDG
ARG PG_MAJOR=16
ARG PG_VERSION=16.10-1.pgdg13+1
# Autenticación SCRAM obligatoria desde initdb
gosu postgres initdb -D "$PGDATA" \
--auth-local=scram-sha-256 --auth-host=scram-sha-256 \
--encoding=UTF8 --locale="en_US.utf8"
# Healthcheck integrado
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=5 \
CMD pg_isready -U "${POSTGRES_USER:-postgres}" || exit 1

Control de acceso basado en host con SCRAM-SHA-256 obligatorio:

# Local: SCRAM obligatorio
local all all scram-sha-256
# IPv4 - Solo localhost y red Docker bridge
host all all 127.0.0.1/32 scram-sha-256
host all all 172.17.0.0/16 scram-sha-256
# IPv6 loopback
host all all ::1/128 scram-sha-256
  1. SCRAM-SHA-256: Hash de passwords resistente a ataques de diccionario y replay. Reemplaza MD5 deprecado.

  2. Read-Only Filesystem: El container no puede escribir en su filesystem excepto en volúmenes montados y tmpfs.

  3. CAP_DROP ALL: Elimina todas las Linux capabilities excepto las mínimas necesarias.

  4. No Root: El proceso PostgreSQL nunca corre como root. El entrypoint usa gosu para cambiar a postgres.

  5. Volumen Externo: Los datos viven en nostromo_pgdata, un volumen Docker externo y persistente.

# Conexiones
listen_addresses = '*'
password_encryption = 'scram-sha-256'
# Memoria
shared_buffers = 256MB
work_mem = 16MB
maintenance_work_mem = 128MB
# WAL (Write-Ahead Logging)
wal_level = replica
max_wal_size = 1GB
min_wal_size = 80MB
# Logging
log_destination = 'stderr'
logging_collector = on
log_min_duration_statement = 500ms
log_line_prefix = '%m [%p] %u@%d '
ParámetroValorJustificación
shared_buffers256MBCache de datos en memoria, ~25% de RAM disponible
work_mem16MBMemoria por operación de sort/hash
wal_levelreplicaPreparado para streaming replication
log_min_duration_statement500msLoguea queries lentos para optimización

El script docker-entrypoint.sh maneja la inicialización del cluster:

flowchart TD
    A[Container Start] --> B{PG_VERSION existe?}
    B -->|Si| C[Cluster existente]
    B -->|No| D[Ejecutar initdb]
    D --> E[Copiar configs endurecidas]
    E --> F[Iniciar server temporal]
    F --> G{Scripts en initdb.d?}
    G -->|Si| H[Ejecutar .sh/.sql/.sql.gz]
    G -->|No| I[Saltar]
    H --> I
    I --> J[Detener server temporal]
    J --> K[Marcar .nostromo_init_done]
    C --> L[Iniciar PostgreSQL]
    K --> L
Terminal window
# Si existe PG_VERSION, el cluster ya está inicializado
if gosu postgres bash -lc "test -f '$PGDATA/PG_VERSION'"; then
echo ">> existing cluster detected, skipping initdb"
else
# Ejecutar initdb con SCRAM
gosu postgres initdb -D "$PGDATA" \
--auth-local=scram-sha-256 \
--auth-host=scram-sha-256
fi
services:
mother:
image: nostromo/postgres:16-hardened-stable
container_name: mother
restart: unless-stopped
environment:
POSTGRES_USER: "chris"
TZ: "America/Santiago"
ports:
- "5432:5432"
volumes:
- nostromo_pgdata:/var/lib/postgresql/data
- ./initdb:/docker-entrypoint-initdb.d:ro
read_only: true
tmpfs:
- "/run/postgresql:rw,nosuid,nodev,mode=3777,size=32m"
- "/var/lib/postgresql/tmp:rw,nosuid,nodev,mode=1777,size=256m"
cap_drop:
- ALL
cap_add:
- SETUID
- SETGID
healthcheck:
test: ["CMD-SHELL", "pg_isready -U chris -d postgres"]
interval: 30s
timeout: 5s
retries: 5
MountTipoPropósito
nostromo_pgdataVolume externoDatos persistentes del cluster
./initdbBind mount (ro)Scripts de inicialización
/run/postgresqltmpfsSocket Unix y locks
/var/lib/postgresql/tmptmpfsArchivos temporales de queries

El container incluye healthcheck integrado:

Terminal window
pg_isready -U chris -d postgres
  • Intervalo: Cada 30 segundos
  • Timeout: 5 segundos máximo
  • Start Period: 30 segundos de gracia inicial
  • Retries: 5 intentos antes de marcar unhealthy
Terminal window
cd c:\pg16-clean
docker compose build --no-cache
docker compose up -d