
Organizando contenedores - docker compose
Cuando trabajamos con contenedores muchas veces se vuelve complejo manejar manualmente uno por uno, ya que en un entorno de trabajo usualmente se requieren, además de la app, varios servicios como base de datos, un servicio de queues, redis, casandra, etc.
Existen diversas formas de manejar esta situation, pero vamos a concentrar en las más usuales, primero veremos como lograr esto manualmente y después nos iremos a docker compose
Usar docker o algún software para manejo de contenedores puede verse tan simple como lo siguiente:
$ docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
$
Esto funciona bastante bien, es simple y efectivo, pero cuando trabajas en cualquier proyecto es obvio que no usaras un solo contenedor, imaginemos la siguiente situación, necesitas poner en contenedores los siguientes servicios:
- Tu app (Asumamos que necesitas PHP, pero puede ser cualquier otro lenguaje)
- Nginx: Tu servidor
- PostgreSQL: Una base de datos relacional
- Redis: Redis para cache
Entonces basado en dichos requerimientos necesitaríamos construir los siguientes contenedores:
# run postgress
$ docker run -d \
--name postgres \
-e POSTGRES_USER=user \
-e POSTGRES_PASSWORD=pass \
-e POSTGRES_DB=app_db \
-v postgres_data:/var/lib/postgresql/data \
postgres:15
# run redis
$ docker run -d \
--name redis \
--network my_network \
-v redis_data:/data \
redis:7
# run your app
$ docker run -d \
--name app \
--network my_network \
-v $(pwd)/app/src:/var/www/html \
-v $(pwd)/app/nginx.conf:/etc/nginx/nginx.conf \
-v $(pwd)/app/php.ini:/usr/local/etc/php/php.ini \
-p 8080:80 \
app
Con tanto código, hay muchas cosas que pueden fallar al usar los comandos anteriores, y ni hablar de depurar, cambiar algo o añadir más servicios.
Entonces, ¿cómo lo solucionamos? Hay varias maneras, y empezaremos con la más sencilla.
Docker compose
Docker Compose es una herramienta para definir y ejecutar aplicaciones multicontenedor. Compose simplifica el control de toda la pila de aplicaciones, lo que facilita la administración de servicios, redes y volúmenes en un único archivo de configuración YAML comprensible. Luego, con un solo comando, crea e inicia todos los servicios desde su archivo de configuración. https://docs.docker.com/compose/
Básicamente puedes organizar todos tus contenedores mediante un solo archivo YAML lo cual te permitirá simplificar tu rutina diaria.
- Iniciar, detener y reconstruir servicios
- Ver el estado de los servicios en ejecución
- Transmitir la salida del registro de los servicios en ejecución
- Ejecutar un comando único en un servicio
Y como hacemos esto? bueno veamos como se veria esto mismo pero desde docker compose
services:
app:
image: app
build:
context: ./app
dockerfile: Dockerfile
container_name: app
networks:
- my_network
ports:
- "8080:80"
volumes:
- ./app/src:/var/www/html
- ./app/nginx.conf:/etc/nginx/nginx.conf
- ./app/php.ini:/usr/local/etc/php/php.ini
postgres:
image: postgres:15
container_name: postgres
networks:
- my_network
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app_db
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7
container_name: redis
networks:
- my_network
volumes:
- redis_data:/data
localstack:
image: localstack/localstack
container_name: localstack
networks:
- my_network
environment:
SERVICES: s3,ec2,lambda
LOCALSTACK_API_KEY: your_api_key_here
ports:
- "4566:4566"
networks:
my_network:
driver: bridge
volumes:
postgres_data:
redis_data:
A pesar de que el código parece correcto y se identifican patrones a simple vista, un análisis detallado de cada parte del archivo es fundamental:
Service: app
app:
image: app
build:
context: ./app
dockerfile: Dockerfile
container_name: app
networks:
- my_network
ports:
- "8080:80"
volumes:
- ./app/src:/var/www/html
- ./app/nginx.conf:/etc/nginx/nginx.conf
- ./app/php.ini:/usr/local/etc/php/php.ini
¿Qué hace en cada parte? Bien veamos a detalle
- image: app: Utiliza una imagen personalizada llamada app (construida a partir del Dockerfile).
- build: Construye la imagen personalizada desde el directorio ./app y el archivo Dockerfile.
- container_name: app: Asigna el nombre app al contenedor.
- networks: Conecta el contenedor a la red my_network.
- ports: Mapea el puerto 80 dentro del contenedor al puerto 8080 en el host.
- volumes: Monta volúmenes para:
- Código fuente (src) en /var/www/html.
- Configuración personalizada de Nginx.
- Configuración personalizada de PHP.
Service: postgres
postgres:
image: postgres:15
container_name: postgres
networks:
- my_network
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app_db
volumes:
- postgres_data:/var/lib/postgresql/data
- image: postgres:15: Utiliza la imagen oficial de PostgreSQL versión 15.
- container_name: postgres: Asigna el nombre postgres al contenedor.
- networks: Conecta el contenedor a la red my_network.
- environment: Define variables de entorno:
- El usuario (user).
- La contraseña (pass).
- La base de datos inicial (app_db).
- volumes: Monta un volumen persistente (postgres_data) para almacenar datos de PostgreSQL.
Service: redis
redis:
image: redis:7
container_name: redis
networks:
- my_network
volumes:
- redis_data:/data
- image: redis:7: Utiliza la imagen oficial de Redis versión 7.
- container_name: redis: Asigna el nombre redis al contenedor.
- networks: Conecta el contenedor a la red my_network.
- volumes: Monta un volumen persistente (redis_data) para almacenar los datos de Redis.
Todo en un solo archivo ahora bien como ejecuto esto, eso es lo mejor con un sencillo comando podemos lanzar todos nuestros contenedores
$ docker compose -f docker-compose.yaml up
[+] Running 0/0
[+] Running 1/1
[+] Running 0/1 Building 0.1s
[+] Building 13.2s (14/15) docker:default
=> [app internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 1.26kB 0.0s
=> [app internal] load metadata for docker.io/library/php:8.3-apache 0.4s
=> [app internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [app 1/11] FROM docker.io/library/php:8.3-apache@sha256:fce243539486d99cfefba35724ec485fd6078f1d4928feba5728d3ca587f8820 0.0s
=> [app internal] load build context 0.0s
=> => transferring context: 1.32kB
....
En esencia, docker compose hace lo siguiente:
- Define aplicaciones multi-contenedor: Permite especificar los servicios (contenedores), las redes, los volúmenes y otras configuraciones necesarias para una aplicación compleja en un archivo YAML (normalmente llamado docker-compose.yml).
- Orquesta la ejecución: Con un solo comando (
docker compose up
), se crean y se inician todos los contenedores definidos en el archivo de configuración, junto con sus dependencias. - Simplifica la gestión del ciclo de vida: Facilita la detención (
docker compose down
), el reinicio (docker compose restart
), la escalabilidad (docker compose up --scale <servicio>=<cantidad>
, por ejemplo,docker compose up --scale web=3
), la visualización de logs (docker compose logs
), la ejecución de comandos dentro de un contenedor en ejecución (docker compose exec <servicio> <comando>
, por ejemplodocker compose exec web bash
), entre otras operaciones, de toda la aplicación multi-contenedor. - Reconstrucción de imágenes: Puedes reconstruir todas las imágenes definidas en tu archivo docker-compose.yml con el comando
docker compose build
. Esto es útil cuando has modificado los Dockerfiles o necesitas actualizar las dependencias de tus imágenes. - Ejecución en modo “detached”: Para ejecutar los contenedores en segundo plano y liberar la terminal, se utiliza el comando
docker compose up -d
. Esto permite que los contenedores sigan funcionando incluso después de cerrar la terminal. Para detener los contenedores que se ejecutan en modo detached, se usa docker compose down.
En resumen: docker compose
simplifica enormemente el desarrollo y la gestión de aplicaciones multi-contenedor, ahorrando tiempo y esfuerzo en la configuración y orquestación manual de los contenedores. Es una herramienta fundamental para cualquier desarrollador que trabaje con Docker en entornos de desarrollo, pruebas o incluso producción