Servicios, redes y volumenes
Un fichero Compose describe servicios (procesos), redes (como se descubren) y volumenes (que datos sobreviven al contenedor). Dominar estos tres bloques evita el 80% de los problemas de "no conecta" o "perdi los datos".
Servicio: image vs build
Imagen publicada
services:
redis:
image: redis:7.4-alpine
command: ["redis-server", "--appendonly", "yes"]
restart: unless-stoppedBuild local
services:
api:
build:
context: ./api
dockerfile: Dockerfile
args:
NODE_ENV: development
image: mi-org/api:dev
ports:
- "3000:3000"context: directorio enviado al daemon como contexto de build.image: etiqueta resultante; sin ella Compose genera un nombre derivado del proyecto.- Tras cambiar el Dockerfile:
docker compose up -d --build.
Puedes combinar ambos: Compose construye y etiqueta con image. Util para reutilizar la misma imagen en CI.
Puertos
ports:
- "3000:3000" # host:contenedor (TCP)
- "127.0.0.1:5432:5432" # solo localhost del host
- "8080:80/tcp"Publicar 0.0.0.0:5432:5432 expone Postgres a toda la red local. En desarrollo suele bastar 127.0.0.1:5432:5432.
Los servicios que solo deben hablar entre si no necesitan ports: se alcanzan por DNS interno.
Redes
Por defecto Compose crea una red bridge <proyecto>_default y conecta todos los servicios. El DNS interno resuelve el nombre del servicio.
Contenedor api --DNS--> db:5432
Contenedor api --DNS--> redis:6379Red custom
services:
proxy:
image: nginx:1.27-alpine
ports:
- "80:80"
networks:
- frontend
api:
image: mi-org/api:dev
networks:
- frontend
- backend
db:
image: postgres:16-alpine
networks:
- backend
networks:
frontend:
backend:El proxy no alcanza db directamente (no esta en backend). La API hace de frontera. Util cuando quieres aislar datos de servicios expuestos.
Alias DNS
services:
db:
image: postgres:16-alpine
networks:
backend:
aliases:
- postgres
- databaseDesde otro servicio en backend puedes usar postgres:5432 o database:5432 ademas de db:5432.
Red externa
Unir el stack a una red ya creada (otro Compose, Traefik, etc.):
docker network create edgeservices:
web:
image: nginx:1.27-alpine
networks:
- edge
networks:
edge:
external: trueSin external: true, Compose intentaria crear edge como red del proyecto y fallaria si ya existe con otro nombre completo.
Volumenes
Volumen nombrado (persistencia)
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:pgdata vive en el area de Docker (/var/lib/docker/volumes/... en Linux). Sobrevive a docker compose down. Se borra con docker compose down -v o docker volume rm.
Bind mount (codigo en desarrollo)
services:
api:
build: ./api
volumes:
- ./api:/app
- /app/node_modules
working_dir: /app
command: npm run dev./api:/app: el codigo del host se refleja dentro del contenedor (hot reload)./app/node_modules: volumen anonimo que evita que elnode_modulesdel host (quiza de otro OS) pise el del contenedor.
tmpfs (datos volatiles en RAM)
services:
api:
image: mi-org/api:dev
tmpfs:
- /tmp
- /var/cache/app:size=64mUtil para caches temporales sin ensuciar capas ni el host.
Solo lectura
services:
web:
image: nginx:1.27-alpine
volumes:
- ./html:/usr/share/nginx/html:roEl contenedor no puede escribir en ese mount. Reduce el impacto de un proceso comprometido.
Ejemplo completo: API + Postgres
services:
api:
build:
context: .
dockerfile: Dockerfile
image: demo/api:local
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:app@db:5432/app
volumes:
- ./src:/app/src:ro
depends_on:
- db
networks:
- appnet
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- appnet
# sin ports: solo accesible desde la red Compose
networks:
appnet:
volumes:
pgdata:Arranque y comprobacion de DNS:
docker compose up -d --build
docker compose exec api sh -c "getent hosts db || nslookup db"
docker compose exec api sh -c "wget -qO- http://db:5432 || true"Postgres no habla HTTP; el DNS debe resolver db a una IP privada. Para probar el puerto:
docker compose exec api sh -c "nc -zv db 5432"Restart policies
services:
api:
image: demo/api:local
restart: unless-stopped| Politica | Comportamiento |
|---|---|
no | No reinicia (default) |
always | Siempre, incluso tras reboot del daemon |
unless-stopped | Como always, salvo si lo paraste a mano |
on-failure | Solo si exit != 0; opcional on-failure:5 |
En desarrollo local unless-stopped o no suele bastar. En un VPS con un solo nodo, unless-stopped es habitual.
Recursos y limites (opcional)
services:
api:
image: demo/api:local
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256MEn Compose V2 sin Swarm, deploy.resources se aplica en Docker Engine reciente al usar docker compose up. Verifica con:
docker compose config
docker statsSi tu engine ignora limites, usa mem_limit / cpus en la forma larga legacy solo si lo documentas para tu version.
Inspeccion util
docker compose ps
docker network ls | grep $(basename "$PWD")
docker volume ls | grep $(basename "$PWD")
docker compose exec api printenv
docker inspect $(docker compose ps -q db) --format '{{json .NetworkSettings.Networks}}'Errores habituales
- Publicar el puerto de la DB a
0.0.0.0y descubrir Postgres desde otra maquina de la oficina. - Usar bind mount de
./entero y pisar artefactos generados en la imagen (node_modules,.venv). - Esperar que
localhostdentro del contenedor sea el host: para hablar con un servicio hermano usa el nombre del servicio, nolocalhost. - Declarar
volumes: pgdata:a nivel servicio sin el bloque top-levelvolumes:(Compose lo trata como bind relativo raro o falla segun sintaxis). - Dos servicios con el mismo
ports: "5432:5432"en el mismo host: conflicto de bind. - Olvidar
external: trueal referenciar una red de Traefik u otro stack.
Buenas practicas
- Redes explicitas cuando haya frontera de seguridad (frontend/backend).
- Persistencia de DB siempre en volumen nombrado, nunca solo en la capa writable del contenedor.
- Bind mounts para codigo fuente; volumenes nombrados para datos.
- Tags de imagen concretos (
postgres:16.4-alpine), no:latest. - No publiques puertos internos salvo que necesites un cliente en el host (psql, redis-cli).
Ejercicios
- Monta un stack con dos redes (
frontend,backend). Pon Nginx solo en frontend y Postgres solo en backend. Demuestra condocker compose execque Nginx no resuelvedby la API (en ambas redes) si. - Anade un volumen nombrado a Postgres, inserta un dato, haz
docker compose down(sin-v), vuelve aupy verifica que el dato sigue. - Cambia el bind de la API a
:roy demuestra que untouch /app/src/xdentro del contenedor falla. - Publica Postgres solo en
127.0.0.1:5432y conecta desde el host conpsqlodocker run --rm -it --network host postgres:16-alpine psql ....
Siguiente paso
En Variables de entorno separas config de secretos, usas .env para interpolacion y evitas filtrar passwords al repositorio.
