Manual de Docker Compose para empezar: de cero a tener tus servicios en un solo archivo
Qué es Docker Compose, cómo se lee un docker-compose.yml, los comandos del día a día, volúmenes, puertos y variables, con ejemplos reales de mi servidor casero.
Todos los servicios de mi Raspberry Pi viven en un solo archivo: docker-compose.yml. Con él arranco, paro, actualizo o reconstruyo todo en segundos. Si empiezas con Docker, Compose es lo primero que deberías aprender después del docker run.
Qué problema resuelve
Sin Compose, cada contenedor se arranca con un comando larguísimo:
docker run -d --name gitea --restart unless-stopped \
-p 3000:3000 -p 2222:22 -v ./gitea:/data \
-e USER_UID=1000 -e USER_GID=1000 docker.gitea.com/gitea:1.25
Con cinco servicios, te acabas olvidando de qué opciones pusiste. Compose guarda todo eso en un archivo que se lee fácil y se puede versionar con Git.
Anatomía de un docker-compose.yml
services:
gitea: # nombre del servicio
image: docker.gitea.com/gitea:1.25 # imagen y versión
container_name: gitea # nombre fijo del contenedor
restart: unless-stopped # que arranque solo tras reiniciar
environment: # variables de configuración
USER_UID: "1000"
USER_GID: "1000"
ports: # anfitrión:contenedor
- "3000:3000"
- "2222:22"
volumes: # dónde se guardan los datos
- ./gitea:/data
uptime-kuma:
image: louislam/uptime-kuma:2
container_name: uptime-kuma
restart: unless-stopped
ports: ["3001:3001"]
volumes: [./uptime-kuma:/app/data]
Es YAML: la sangría (con espacios, nunca tabuladores) importa. Vamos por partes.
image
La imagen y su etiqueta (versión). Mi consejo: fija la versión (:1.25, :2) en vez de :latest. Con latest, un día haces pull y te instala una versión mayor sin avisar.
restart
unless-stopped: arranca solo al encender la máquina, salvo que lo pararas tú. Es el que uso casi siempre.always: arranca siempre, aunque lo pararas.no: nunca solo (útil para tareas que se ejecutan una vez).
ports
Formato anfitrión:contenedor. "8080:80" significa: «lo que llegue al puerto 8080 de mi máquina, mándalo al 80 de dentro del contenedor».
Y un truco de seguridad: si pones una IP delante, solo escucha en esa interfaz:
ports: ["127.0.0.1:8222:80"] # solo accesible desde la propia máquina
Así tengo mi gestor de contraseñas: nadie de la red puede llegar directamente.
volumes
Lo más importante de todo. Un contenedor es desechable: si lo borras, todo lo que había dentro se pierde. Los volumes sacan los datos fuera:
./gitea:/data→ la carpetagiteajunto al archivo compose se monta en/datadentro. Este tipo es el más fácil de copiar: la copia de seguridad es copiar la carpeta.datos:/data(sin./) → un volumen con nombre que gestiona Docker. Más limpio, pero hay que saber dónde está para copiarlo./ruta:/ruta:ro→ el:rolo monta en solo lectura.
environment
Variables que el programa lee para configurarse. Cada imagen documenta las suyas.
Para contraseñas y secretos, no los escribas en el compose (acabarán en Git). Usa un archivo .env al lado, que no subes:
environment:
DB_PASSWORD: ${DB_PASSWORD}
# .env (no se sube a Git)
DB_PASSWORD=algo-largo-y-secreto
Los comandos del día a día
Siempre desde la carpeta del docker-compose.yml:
| Comando | Qué hace |
|---|---|
docker compose up -d |
Crea y arranca todo en segundo plano |
docker compose up -d gitea |
Solo ese servicio |
docker compose ps |
Qué está en marcha |
docker compose logs -f gitea |
Ver los registros en directo (Ctrl+C para salir) |
docker compose stop |
Parar sin borrar |
docker compose down |
Parar y borrar los contenedores (los datos de los volumes se quedan) |
docker compose pull |
Descargar versiones nuevas de las imágenes |
docker compose exec gitea sh |
Abrir una terminal dentro del contenedor |
Actualizar un servicio
docker compose pull uptime-kuma
docker compose up -d uptime-kuma
up -d detecta que la imagen ha cambiado y recrea el contenedor con la nueva. Los datos, en su volume, siguen ahí. Después, para liberar espacio de imágenes viejas:
docker image prune
Errores típicos
- «port is already allocated»: otro programa usa ese puerto. Búscalo con
sudo ss -ltnp | grep :PUERTOy cambia el puerto de la izquierda. - Permisos en la carpeta de datos: muchos contenedores corren con un usuario concreto (por eso las variables
PUID/PGIDoUSER_UID). Si no puede escribir, revisa quién es el dueño de la carpeta. - Tabuladores en el YAML: error de sintaxis. Solo espacios.
- Olvidar el volume: todo funciona… hasta que recreas el contenedor y los datos desaparecen. Comprueba siempre dónde guarda la imagen sus datos.
Mi forma de organizarlo
~/servicios/
├── docker-compose.yml
├── .env
├── gitea/
├── uptime-kuma/
├── homepage/
└── vaultwarden/
Un archivo, una carpeta por servicio. La copia de seguridad es copiar ~/servicios, y reconstruir el servidor en otra máquina es copiar la carpeta y hacer docker compose up -d.
¿Te has quedado con la idea?
Tres preguntas rápidas. Cada acierto suma 10 XP.