Crea tu primer docker-compose.yml paso a paso

Crea tu primer docker-compose.yml paso a paso

Crea tu primer docker-compose.yml paso a paso

Si siempre has instalado servicios a pelo y quieres pasarte a Docker, por aquí. Vemos cómo escribir un docker-compose.yml real, arrancarlo y no perder los datos por el camino.

Por Javier · Actualizado: 2025-04-06

Un archivo docker-compose.yml define en YAML los servicios, redes y volúmenes de tu stack: cada servicio equivale a un contenedor. Con docker compose up -d arranca todo en segundo plano. Ojo: el binario legacy docker-compose llegó a EOL en julio de 2023; hoy el plugin oficial es docker compose (sin guion).

Para el que ya tiene servidor pero aún lo lleva a pelo

Si tienes un servidor en casa —una Pi, un mini PC, una torre reciclada— y gestionas tus servicios instalando paquetes a mano, editando configs por toda la máquina y rezando para que nada se rompa cuando actualizas, este post es para ti.

Quizás has visto pasar tutoriales de Docker y te ha parecido más lío que solución. ¿Para qué dockerizar algo que ya funciona? Esa duda es razonable. La curva existe: hay que entender cómo se mapean puertos, dónde viven los datos, qué pasa cuando el contenedor se reinicia. No es copiar-pegar y listo.

Pero cuando llevas un tiempo con tres o cuatro servicios conviviendo en el mismo sistema, empiezas a notar el coste real de no tener separación: dependencias que se pisan, actualizaciones que rompen cosas indescifrables, y cero forma de reproducir el entorno si tienes que migrar. Aquí vamos a ver, paso a paso, cómo escribir tu primer docker-compose.yml para que entiendas qué hace cada línea —y puedas usarlo de verdad, no solo copiarlo de Stack Overflow.

Por qué importa

Un archivo, todo definido

Servicios, redes y volúmenes en un solo YAML. Menos comandos sueltos, más configuración reproducible.

Datos que sobreviven reinicios

Los volúmenes nombrados persisten aunque hagas `docker compose down`. Tus datos no desaparecen entre actualizaciones.

Arranque automático tras fallo

Con `restart: unless-stopped` el contenedor vuelve solo tras un cuelgue o reinicio del host, sin intervención manual.

Orden de arranque declarado

Usa `depends_on` para que la base de datos arranque antes que la app; añade `healthcheck` si necesitas esperar a que esté lista de verdad.

Qué contiene un docker-compose.yml y para qué sirve

Un archivo docker-compose.yml es un YAML que describe todos los contenedores que forman una aplicación: qué imagen usa cada uno, qué puertos expone, dónde guarda datos y cómo se conectan entre sí. En lugar de lanzar cada contenedor a mano con docker run y sus veinte flags, defines todo en un sitio y arrancas el stack entero con un comando.

El formato actual es la Compose Spec, que unificó los viejos formatos de las versiones 2.x y 3.x. Si ves un version: "3.9" al principio del YAML, puedes borrarlo: ese campo está obsoleto y Docker lo ignora. No rompe nada, pero genera confusión innecesaria.

Un ejemplo mínimo con un solo servicio:

services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

Con esto le dices a Docker que quieres un contenedor llamado web, usando la imagen nginx:alpine, y que el puerto 80 del contenedor sea accesible desde el puerto 8080 del host.

Asegúrate de tener Compose v2 instalado

Docker Compose v1 —el binario docker-compose escrito en Python— llegó al fin de su vida en julio de 2023. La versión activa es Compose v2, integrada como plugin del CLI de Docker. La diferencia más visible es el espacio en el comando: docker compose (sin guion) en lugar de docker-compose.

Para comprobarlo:

docker compose version

Si devuelve algo como Docker Compose version v2.x.x, estás bien. Si el comando no existe o ves la versión 1.x, toca actualizar Docker Engine o instalar el plugin desde los repositorios oficiales de Docker.

En sistemas Linux actuales (Ubuntu 22.04+, Debian 12, Fedora 38+) el plugin viene incluido cuando instalas docker-ce desde los repositorios de Docker, no desde los de la distro. Los paquetes de la distro suelen ir rezagados varias versiones.

Escribe tu primer docker-compose.yml: un ejemplo real

Vamos a crear un stack pequeño pero real: Gitea —un GitHub self-hosted ligero— con su base de datos PostgreSQL. Es un buen ejemplo porque tiene dos servicios que se necesitan mutuamente, volúmenes para persistir datos y variables de entorno.

Crea un directorio para el proyecto:

mkdir ~/docker/gitea && cd ~/docker/gitea

El archivo base

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: cambia_esto
      POSTGRES_DB: gitea
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: unless-stopped

  gitea:
    image: gitea/gitea:latest
    ports:
      - "3000:3000"
      - "2222:22"
    volumes:
      - gitea_data:/data
    depends_on:
      - db
    restart: unless-stopped

volumes:
  db_data:
  gitea_data:

Qué hace cada clave

  • image: la imagen que usará el contenedor, descargada de Docker Hub si no está en caché local.
  • environment: variables de entorno inyectadas al arrancar. Aquí configuramos usuario, contraseña y nombre de la base de datos de Postgres.
  • volumes: db_data:/var/lib/postgresql/data monta un volumen nombrado en la ruta donde Postgres guarda sus datos. Si el contenedor se para o se recrea, los datos siguen ahí.
  • ports: la sintaxis es HOST:CONTAINER. Con "3000:3000" el puerto 3000 del contenedor es accesible en el 3000 del host; el "2222:22" es para SSH de Git.
  • depends_on: le dice a Compose que arranque db antes que gitea. Solo garantiza orden de arranque, no que Postgres esté listo para aceptar conexiones.
  • restart: unless-stopped: el contenedor se relanza solo si falla o si reinicias el host, pero no si lo paras manualmente.

Variables de entorno en un .env separado

Mezclar contraseñas directamente en el YAML está bien para probar, pero si el archivo va a git conviene separarlas. Docker Compose carga automáticamente un archivo .env que esté en el mismo directorio.

Archivo .env:

POSTGRES_PASSWORD=mi_contraseña_segura

En el docker-compose.yml:

environment:
  POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

Así el YAML puede estar en git y el .env queda fuera —añádelo a .gitignore.

Arrancar, parar y revisar el stack

Con el YAML listo, estos son los comandos del día a día:

# Arrancar en segundo plano
docker compose up -d

# Ver el estado de los contenedores
docker compose ps

# Ver logs de todos los servicios (Ctrl+C para salir)
docker compose logs -f

# Ver logs solo de gitea
docker compose logs -f gitea

# Parar los contenedores sin borrar datos
docker compose stop

# Parar y eliminar contenedores (los volúmenes quedan intactos)
docker compose down

# Parar, eliminar contenedores Y borrar volúmenes — datos incluidos
docker compose down -v

El comando down sin flags es seguro para el día a día: para y borra los contenedores, pero los volúmenes nombrados (db_data, gitea_data) se quedan en el host. El flag -v es el modo nuclear; úsalo solo cuando quieras empezar de cero.

depends_on y healthcheck: el orden de arranque de verdad

depends_on en su forma básica solo garantiza que Docker inicie los contenedores en el orden indicado. No espera a que el servicio esté operativo. Si Postgres tarda unos segundos en arrancar, Gitea puede intentar conectarse antes de que la base de datos esté lista y fallar en el primer intento.

La solución es añadir un healthcheck al servicio del que dependes y cambiar la condición en depends_on:

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: gitea
    volumes:
      - db_data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U gitea"]
      interval: 10s
      timeout: 5s
      retries: 5

  gitea:
    image: gitea/gitea:latest
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "3000:3000"
      - "2222:22"
    volumes:
      - gitea_data:/data
    restart: unless-stopped

Con condition: service_healthy, Compose espera a que pg_isready devuelva OK antes de arrancar Gitea. Esto sí resuelve la carrera de arranque.

Errores frecuentes al empezar

  • «bind: address already in use» — Hay otro proceso usando ese puerto en el host. Cambia el puerto del host en ports (por ejemplo, "3001:3000" si el 3000 está ocupado) o para el proceso que lo ocupa.
  • Los datos desaparecen tras un docker compose down — Revisa si usaste -v o si tus volúmenes son bind mounts apuntando a una carpeta que no existía. Los volúmenes nombrados persisten por defecto; los bind mounts dependen de la carpeta real del host.
  • El contenedor arranca pero el servicio no responde — Empieza por los logs: docker compose logs nombre_servicio. El error casi siempre está ahí escrito. Problemas habituales: permisos en carpetas montadas, variables de entorno que faltan o configuración inicial requerida por la imagen.
  • Cambié el YAML pero el contenedor no cambiadocker compose up -d recrea solo los contenedores cuya configuración ha cambiado. Si modificas una variable de entorno o un volumen, haz docker compose down && docker compose up -d. Si cambiaste la imagen, primero docker compose pull.

La mayoría de problemas tienen respuesta en los logs. Antes de buscar en foros, docker compose logs -f suele darte la pista en cuestión de segundos.

Preguntas frecuentes

Q: ¿Qué diferencia hay entre `docker-compose` y `docker compose`?

A: Son dos generaciones distintas: `docker-compose` (v1) es el binario legacy escrito en Python que llegó a su fin de vida en julio de 2023. `docker compose` (v2) es el plugin oficial integrado en el CLI de Docker y el que debes usar hoy. Si tu sistema todavía tiene el binario viejo, conviene desinstalarlo para evitar confusiones.

Q: ¿Qué pasa si borro el contenedor con `docker compose down`?

A: Los contenedores y redes definidas en el compose se eliminan, pero los volúmenes nombrados sobreviven por defecto. Tus datos persisten hasta que ejecutes `docker compose down --volumes` explícitamente. Los bind mounts tampoco se tocan: son carpetas del host y Docker no las borra nunca.

Q: ¿Para qué sirve realmente el campo `version:` en el YAML?

A: Hoy en día no sirve para nada útil. Con la Compose Spec unificada, ese campo es obsoleto y se ignora. Puedes eliminarlo de tus archivos sin ningún efecto; de hecho, herramientas modernas como Docker Desktop ya avisan de que está deprecado.

Q: ¿Cómo sé que un servicio está listo antes de arrancar el siguiente?

A: `depends_on` solo garantiza el orden de arranque, no que el proceso dentro del contenedor esté listo para aceptar conexiones. Si necesitas esperar a que una base de datos esté operativa antes de lanzar la app, tienes que añadir la clave `healthcheck` en el servicio dependiente y combinarla con `condition: service_healthy` en `depends_on`.

Q: ¿Vale Docker Compose para gestionar varios servidores a la vez?

A: No está pensado para eso. Docker Compose opera sobre un único host; si necesitas orquestar contenedores en varios nodos simultáneamente, necesitas Docker Swarm o Kubernetes. Para un homelab con una sola máquina o una Raspberry Pi, Compose es más que suficiente y mucho más sencillo de mantener.

Deja una respuesta

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