Docker Compose: una app con su base de datos en un solo archivo – RocPair

Docker Compose: una app con su base de datos en un solo archivo

Docker Compose: una app con su base de datos en un solo archivo

Docker Compose es una herramienta para definir y ejecutar aplicaciones de varios contenedores a partir de un único archivo YAML. En lugar de recordar tres o cuatro comandos docker run largos, con sus puertos, variables y volúmenes, describes cada servicio una vez y lo levantas todo con docker compose up.

Docker Compose: una app con su base de datos en un solo archivo
Foto: Carl Lender from Sunrise, USA — CC BY 2.0, vía Wikimedia Commons

Esta guía funciona como una ficha de trabajo: primero la lista de lo que necesitas, luego la preparación paso a paso y al final las variaciones y los problemas típicos. El resultado es una pequeña API de tareas en Node.js conectada a PostgreSQL, que puedes usar como plantilla para tus propios proyectos.

Ficha rápida

ElementoDetalle
Qué vas a montarAPI REST en Node.js (Express) + base de datos PostgreSQL
Tiempo aproximado20 a 30 minutos si ya tienes Docker instalado
RequisitosDocker Desktop o Docker Engine con el plugin Compose v2
Archivospackage.json, server.js, Dockerfile, init.sql, compose.yaml, .env
Comando principaldocker compose up -d --build

Un detalle de sintaxis antes de empezar: la versión actual de Compose se usa como subcomando de Docker, con un espacio: docker compose. La herramienta antigua, escrita en Python y llamada docker-compose con guion, dejó de recibir actualizaciones hace años. Si un tutorial viejo usa el guion, cambia el guion por un espacio y casi todo funcionará igual. Comprueba tu versión con docker compose version.

Los ingredientes: seis archivos

Crea una carpeta llamada tareas y coloca dentro estos archivos.

package.json

{
  "name": "tareas-api",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.19.2",
    "pg": "^8.12.0"
  }
}

server.js

La aplicación lee la cadena de conexión desde una variable de entorno. Así el mismo código sirve en tu computador, en un servidor de pruebas y en producción, sin tocar una línea.

const express = require("express");
const { Pool } = require("pg");

// La URL de conexión llega desde compose.yaml
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const app = express();
app.use(express.json());

app.get("/tareas", async (req, res) => {
  try {
    const { rows } = await pool.query(
      "SELECT id, titulo, hecha FROM tareas ORDER BY id"
    );
    res.json(rows);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "No se pudieron leer las tareas" });
  }
});

app.post("/tareas", async (req, res) => {
  const titulo = (req.body.titulo || "").trim();
  if (!titulo) {
    return res.status(400).json({ error: "El título es obligatorio" });
  }
  try {
    const { rows } = await pool.query(
      "INSERT INTO tareas (titulo) VALUES ($1) RETURNING id, titulo, hecha",
      [titulo]
    );
    res.status(201).json(rows[0]);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: "No se pudo guardar la tarea" });
  }
});

app.listen(3000, () => console.log("API escuchando en el puerto 3000"));

Observa el $1 en la consulta de inserción: es un parámetro. El valor viaja separado del SQL, lo que evita inyecciones. Nunca construyas consultas concatenando texto que viene del usuario.

Dockerfile

FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Si tu proyecto ya tiene un package-lock.json, cambia npm install por npm ci: instala exactamente las versiones del archivo de bloqueo y falla si hay inconsistencias, que es lo que quieres en una imagen.

init.sql

CREATE TABLE IF NOT EXISTS tareas (
  id      INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  titulo  TEXT NOT NULL,
  hecha   BOOLEAN NOT NULL DEFAULT false
);

La imagen oficial de PostgreSQL ejecuta automáticamente los archivos .sql que encuentre en /docker-entrypoint-initdb.d/, pero solo la primera vez, cuando el directorio de datos está vacío. Si cambias este archivo después, no se volverá a ejecutar a menos que borres el volumen.

.env

POSTGRES_PASSWORD=cambia-esta-clave

Compose lee automáticamente un archivo .env ubicado junto a compose.yaml y usa sus valores para reemplazar las expresiones ${...}. Agrega .env a tu .gitignore para que la contraseña no termine en el repositorio.

compose.yaml

El archivo central. El nombre recomendado hoy es compose.yaml, aunque Compose sigue reconociendo docker-compose.yml.

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/tareas
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: tareas
    volumes:
      - db-datos:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d tareas"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  db-datos:

Notarás que el archivo no empieza con una línea version:. Esa clave quedó obsoleta: Compose v2 la ignora y muestra un aviso si la encuentra, así que puedes eliminarla de archivos antiguos.

Preparación paso a paso

  1. Abre una terminal en la carpeta tareas.
  2. Valida el archivo con docker compose config. Este comando muestra la configuración final con las variables ya reemplazadas; si hay un error de sangría o una clave mal escrita, aparecerá aquí.
  3. Construye y levanta todo con docker compose up -d --build.
  4. Revisa el estado con docker compose ps. La base de datos debe aparecer como healthy antes de que arranque la API.
  5. Sigue los registros de la API con docker compose logs -f api hasta ver "API escuchando en el puerto 3000".
  6. Prueba la API creando una tarea y luego listándolas:
curl -X POST http://localhost:3000/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo": "Revisar el informe"}'

curl http://localhost:3000/tareas

Si quieres revisar los datos directamente en la base, abre una consola de psql dentro del contenedor:

docker compose exec db psql -U app -d tareas

Qué está pasando por dentro

Tres piezas hacen que esta configuración funcione, y entenderlas te ahorra horas de depuración.

La red interna. Compose crea una red para el proyecto y conecta ahí todos los servicios. Dentro de esa red, cada servicio se encuentra por su nombre. Por eso la URL de conexión usa db como host y no localhost: desde el contenedor de la API, localhost es la propia API.

Los puertos publicados. Solo la API tiene una sección ports. La base de datos es accesible para la API a través de la red interna, pero no queda expuesta en tu computador. Es una buena práctica por defecto. Si necesitas conectarte desde un cliente gráfico como DBeaver o pgAdmin, agrega temporalmente ports: ["5432:5432"] al servicio db.

El orden de arranque. depends_on con condition: service_healthy hace que Compose espere a que el chequeo de salud de PostgreSQL sea exitoso antes de iniciar la API. Sin la condición, Compose solo espera a que el contenedor de la base arranque, no a que acepte conexiones, y la API puede fallar en su primer intento.

Variaciones útiles

Un archivo extra para desarrollo

Compose combina automáticamente compose.yaml con compose.override.yaml si este último existe. Es el lugar ideal para ajustes que solo quieres en tu máquina, como montar el código para ver cambios sin reconstruir o publicar el puerto de la base:

services:
  api:
    volumes:
      - ./server.js:/app/server.js
    command: ["node", "--watch", "server.js"]
  db:
    ports:
      - "5432:5432"

La opción --watch de Node reinicia el proceso cuando cambia el archivo. Está disponible de forma estable en las versiones recientes de Node, incluida la 22 que usa esta imagen.

Servicios opcionales con perfiles

Si agregas una herramienta como pgAdmin que no siempre necesitas, márcala con profiles: ["herramientas"]. Ese servicio no arrancará con docker compose up normal, solo cuando ejecutes docker compose --profile herramientas up -d.

Una nota sobre PostgreSQL 18 en adelante

Este ejemplo usa la imagen postgres:17 con el volumen en /var/lib/postgresql/data. A partir de la versión 18, la imagen oficial cambió la ubicación recomendada de los datos y sugiere montar el volumen en /var/lib/postgresql. Si subes de versión, revisa la página de la imagen en Docker Hub antes de cambiar la etiqueta, y nunca saltes de versión mayor sobre un volumen con datos sin hacer antes una copia con pg_dump.

Problemas frecuentes

La API arranca y muere con "connection refused". Revisa que el host en DATABASE_URL sea db y que el depends_on incluya la condición de salud.

Cambié init.sql y la tabla no cambia. El script solo se ejecuta con el volumen vacío. En desarrollo puedes empezar de cero con docker compose down -v, sabiendo que eso borra todos los datos.

"password authentication failed". La contraseña de PostgreSQL se fija la primera vez que se inicializa el volumen. Si después cambias el valor en .env, la base sigue esperando la contraseña antigua. Cámbiala desde psql o recrea el volumen.

Los cambios en server.js no se ven. Sin el archivo de override, la imagen contiene una copia del código. Ejecuta docker compose up -d --build para reconstruir.

Comandos que vas a usar a diario

  • docker compose up -d --build: construye lo necesario y levanta todo en segundo plano.
  • docker compose ps: estado de cada servicio.
  • docker compose logs -f api: registros en vivo de un servicio.
  • docker compose exec db psql -U app -d tareas: consola dentro de un servicio en marcha.
  • docker compose restart api: reinicia un servicio sin tocar los demás.
  • docker compose down: detiene y elimina contenedores y red, conservando los volúmenes.
  • docker compose down -v: lo mismo, pero borrando también los volúmenes. Úsalo con cuidado.

Si quieres conocer otros artículos parecidos a Docker Compose: una app con su base de datos en un solo archivo puedes visitar la categoría Guias y Tutoriales.

Contenido que te pude interesar

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir