Cómo usar el comando docker compose run para ejecutar tareas puntuales

DockerDockerBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás a utilizar eficazmente el comando docker compose run para ejecutar tareas puntuales dentro de tus servicios de Docker Compose. Esta es una técnica poderosa para ejecutar comandos administrativos, depurar o realizar operaciones específicas sin iniciar toda la pila de servicios.

Exploraremos varios escenarios, incluyendo la sobreescritura del comando predeterminado del servicio, la habilitación de puertos del servicio para interacción, el mapeo manual de puertos, la ejecución de comandos sin iniciar servicios vinculados y la eliminación automática del contenedor después de la ejecución. Al finalizar este laboratorio, serás competente en el uso de docker compose run para diversas tareas puntuales.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("Docker")) -.-> docker/ContainerOperationsGroup(["Container Operations"]) docker(("Docker")) -.-> docker/ImageOperationsGroup(["Image Operations"]) docker(("Docker")) -.-> docker/NetworkOperationsGroup(["Network Operations"]) docker/ContainerOperationsGroup -.-> docker/run("Run a Container") docker/ContainerOperationsGroup -.-> docker/ps("List Running Containers") docker/ContainerOperationsGroup -.-> docker/stop("Stop Container") docker/ContainerOperationsGroup -.-> docker/rm("Remove Container") docker/ImageOperationsGroup -.-> docker/pull("Pull Image from Repository") docker/NetworkOperationsGroup -.-> docker/network("Manage Networks") subgraph Lab Skills docker/run -.-> lab-555091{{"Cómo usar el comando docker compose run para ejecutar tareas puntuales"}} docker/ps -.-> lab-555091{{"Cómo usar el comando docker compose run para ejecutar tareas puntuales"}} docker/stop -.-> lab-555091{{"Cómo usar el comando docker compose run para ejecutar tareas puntuales"}} docker/rm -.-> lab-555091{{"Cómo usar el comando docker compose run para ejecutar tareas puntuales"}} docker/pull -.-> lab-555091{{"Cómo usar el comando docker compose run para ejecutar tareas puntuales"}} docker/network -.-> lab-555091{{"Cómo usar el comando docker compose run para ejecutar tareas puntuales"}} end

Ejecutar un comando puntual sobrescribiendo el comando del servicio

En este paso, aprenderemos cómo ejecutar un comando puntual en un contenedor Docker, sobrescribiendo el comando predeterminado especificado en la imagen Docker o Dockerfile. Esto es útil para ejecutar tareas administrativas, depurar o ejecutar un script específico dentro del entorno del contenedor sin iniciar el servicio principal.

Primero, descarguemos una imagen Docker simple que podamos usar para esta demostración. Utilizaremos la imagen ubuntu.

docker pull ubuntu:latest

Deberías ver una salida que indica que la imagen se está descargando.

Using default tag: latest
latest: Pulling from library/ubuntu
...
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

Ahora, ejecutemos un comando puntual en un contenedor basado en la imagen ubuntu. Usaremos el comando docker run con el nombre de la imagen y el comando que queremos ejecutar. Por ejemplo, ejecutemos el comando ls -l / para listar el contenido del directorio raíz en el contenedor.

docker run ubuntu ls -l /

Este comando creará un nuevo contenedor a partir de la imagen ubuntu, ejecutará el comando ls -l / dentro de él y luego saldrá. Deberías ver una salida similar a esta, mostrando el contenido del directorio raíz:

total 68
drwxr-xr-x   2 root root  4096 Oct 26 00:00 bin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 boot
drwxr-xr-x   5 root root   360 Nov  1 00:00 dev
drwxr-xr-x  19 root root  4096 Nov  1 00:00 etc
drwxr-xr-x   2 root root  4096 Oct 26 00:00 home
drwxr-xr-x   7 root root  4096 Oct 26 00:00 lib
drwxr-xr-x   2 root root  4096 Oct 26 00:00 lib64
drwxr-xr-x   2 root root  4096 Oct 26 00:00 media
drwxr-xr-x   2 root root  4096 Oct 26 00:00 mnt
drwxr-xr-x   2 root root  4096 Oct 26 00:00 opt
drwxr-xr-x   2 root root  4096 Oct 04 14:00 proc
drwx------   2 root root  4096 Oct 26 00:00 root
drwxr-xr-x   2 root root  4096 Oct 26 00:00 run
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sbin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 srv
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sys
drwxrwxrwt   2 root root  4096 Oct 26 00:00 tmp
drwxr-xr-x  11 root root  4096 Oct 26 00:00 usr
drwxr-xr-x  12 root root  4096 Oct 26 00:00 var

En este caso, el comando predeterminado de la imagen ubuntu suele ser un shell como /bin/bash. Al proporcionar ls -l / después del nombre de la imagen, le estamos indicando a Docker que ejecute este comando específico en lugar del predeterminado.

Probemos otro comando, por ejemplo, pwd para imprimir el directorio de trabajo actual dentro del contenedor.

docker run ubuntu pwd

La salida debería ser /, indicando que el directorio raíz es el directorio de trabajo predeterminado.

/

Esto demuestra cómo puedes ejecutar fácilmente comandos arbitrarios dentro de un contenedor sin necesidad de ingresar interactivamente al contenedor o modificar el comando predeterminado de la imagen.

Ejecutar un comando con los puertos del servicio habilitados

En este paso, exploraremos cómo ejecutar un comando en un contenedor Docker manteniendo habilitados los puertos que el servicio dentro del contenedor está configurado para exponer. Esto es útil cuando necesitas ejecutar un comando temporal para depuración o administración, pero aún deseas que el servicio principal sea accesible desde fuera del contenedor.

Para esta demostración, usaremos una imagen simple de servidor web. Descarguemos la imagen nginx, que es un servidor web popular.

docker pull nginx:latest

Deberías ver una salida que indica que la imagen se está descargando.

Using default tag: latest
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

La imagen predeterminada de nginx está configurada para escuchar en el puerto 80 dentro del contenedor. Para hacer este puerto accesible desde nuestra máquina host, necesitamos mapear un puerto del host al puerto del contenedor usando la bandera -p con el comando docker run. Mapeemos el puerto 8080 del host al puerto 80 del contenedor.

Ahora, en lugar de ejecutar el servicio Nginx predeterminado, ejecutemos un comando simple como echo "Hola desde el contenedor" manteniendo el mapeo de puertos habilitado.

docker run -p 8080:80 nginx echo "Hola desde el contenedor"

Podrías esperar que esto inicie el servidor Nginx y luego imprima "Hola desde el contenedor". Sin embargo, cuando proporcionas un comando después del nombre de la imagen en docker run, ese comando reemplaza el comando predeterminado. Entonces, en este caso, el contenedor ejecutará el comando echo y luego saldrá. El servidor Nginx no se iniciará, aunque hayamos especificado el mapeo de puertos.

La salida será simplemente:

Hola desde el contenedor

Y si intentas acceder a http://localhost:8080 en tu navegador web o usando curl, encontrarás que la conexión es rechazada porque el servidor Nginx no se está ejecutando.

curl http://localhost:8080

La salida probablemente será:

curl: (7) Failed to connect to localhost port 8080 after ... connection refused

Esto ilustra un punto importante: cuando anulas el comando predeterminado con docker run <imagen> <comando>, el contenedor solo ejecutará el comando proporcionado y no iniciará el servicio para el que la imagen está diseñada. Por lo tanto, el mapeo de puertos, aunque configurado, no estará activo porque el servicio que escucha en ese puerto no se está ejecutando.

Para ejecutar un comando mientras el servicio está en ejecución y sus puertos están habilitados, normalmente iniciarías el servicio en segundo plano y luego ejecutarías tu comando. Sin embargo, el comando docker run está diseñado para ejecutar un solo comando y luego salir. Para lograr el efecto de ejecutar un comando junto con un servicio en ejecución, normalmente usarías docker exec en un contenedor que ya esté ejecutando el servicio. Exploraremos docker exec en un paso posterior.

Para el propósito de este paso, la conclusión clave es entender que proporcionar un comando a docker run anula el entrypoint/comando predeterminado, y por lo tanto el servicio configurado para ejecutarse por defecto no se iniciará, incluso si se especifican mapeos de puertos.

Ejecutar un comando con mapeo manual de puertos

En este paso, continuaremos explorando el mapeo de puertos con docker run, enfocándonos en cómo especificar explícitamente los puertos del host y del contenedor. Mientras que el paso anterior mostró que sobrescribir el comando predeterminado evita que el servicio se ejecute, entender el mapeo manual de puertos es crucial para cuando sí deseas que el servicio sea accesible.

Continuaremos usando la imagen nginx. Como recordatorio, la imagen nginx expone el puerto 80 dentro del contenedor. Para hacerlo accesible desde nuestra máquina host, usamos la bandera -p seguida de puerto_host:puerto_contenedor.

Ejecutemos el contenedor nginx y mapeemos el puerto 8081 del host al puerto 80 del contenedor. Esta vez, no proporcionaremos un comando de sobrescritura, por lo que el servicio Nginx predeterminado se iniciará. También lo ejecutaremos en modo desatendido (-d) para que se ejecute en segundo plano.

docker run -d -p 8081:80 nginx

Deberías ver una cadena larga de caracteres, que es el ID del contenedor, indicando que el contenedor se ha iniciado en modo desatendido.

<container_id>

Ahora que el contenedor está en ejecución y el puerto está mapeado, puedes acceder a la página de bienvenida de Nginx desde tu máquina host usando curl o un navegador web.

curl http://localhost:8081

Deberías ver el contenido HTML de la página de bienvenida predeterminada de Nginx. Esto confirma que el servidor Nginx se está ejecutando dentro del contenedor y es accesible desde tu máquina host a través del puerto 8081.

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>

Esto demuestra cómo el mapeo manual de puertos te permite controlar qué puerto del host se utiliza para acceder a un servicio que se ejecuta en un puerto específico dentro de un contenedor. Esto es esencial para evitar conflictos de puertos en tu máquina host y para hacer accesibles los servicios en contenedores.

Para detener el contenedor en ejecución, puedes usar el comando docker stop seguido del ID o nombre del contenedor. Puedes encontrar el ID del contenedor ejecutando docker ps.

docker ps

Esto te mostrará una lista de contenedores en ejecución. Encuentra el ID del contenedor nginx que acabas de iniciar.

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
<container_id>   nginx     "nginx -g 'daemon off"   About a minute ago   Up About a minute   0.0.0.0:8081->80/tcp   <container_name>

Ahora, detén el contenedor usando su ID. Reemplaza <container_id> con el ID real de tu salida.

docker stop <container_id>

El ID del contenedor se imprimirá nuevamente, confirmando que el contenedor se ha detenido.

<container_id>

Si intentas acceder a http://localhost:8081 nuevamente después de detener el contenedor, la conexión será rechazada.

Ejecutar un comando sin iniciar servicios vinculados

En este paso, aprenderemos cómo ejecutar un comando en un contenedor Docker que forma parte de una aplicación multi-contenedor, sin iniciar los demás servicios vinculados. Esto es particularmente útil para ejecutar migraciones de bases de datos, scripts de configuración o comandos de depuración en un servicio sin necesidad de levantar toda la pila de la aplicación.

Aunque Docker Compose es la herramienta estándar para gestionar aplicaciones multi-contenedor y tiene funciones para ejecutar comandos puntuales en servicios específicos, aquí demostraremos los conceptos subyacentes de Docker. Como Docker Compose no está preinstalado en este entorno, nos centraremos en usar el comando docker run con redes.

Simularemos un escenario simple con dos contenedores: una aplicación web y una base de datos. Usaremos una imagen genérica ubuntu para representar nuestra aplicación web y una imagen postgres para la base de datos.

Primero, descarguemos la imagen postgres:

docker pull postgres:latest

Deberías ver una salida que indica que la imagen se está descargando.

Using default tag: latest
latest: Pulling from library/postgres
...
Status: Downloaded newer image for postgres:latest
docker.io/library/postgres:latest

Ahora, creemos una red Docker para que nuestros contenedores puedan comunicarse entre sí por nombre.

docker network create my-app-network

Deberías ver el ID de la red impreso.

<network_id>

A continuación, ejecutemos el contenedor postgres y conectémoslo a nuestra red. También estableceremos una contraseña para el usuario de PostgreSQL.

docker run -d --network my-app-network --name my-database -e POSTGRES_PASSWORD=mypassword postgres

Deberías ver el ID del contenedor impreso, indicando que el contenedor de la base de datos se está ejecutando en segundo plano.

<container_id>

Ahora, imaginemos que nuestro contenedor de "aplicación web" necesita ejecutar un comando que interactúe con la base de datos, como un script de migración. Normalmente, si estuviéramos usando Docker Compose, podríamos ejecutar un comando en el servicio web y Docker Compose manejaría la configuración de red y los enlaces.

Usando solo docker run, si ejecutáramos el contenedor de la aplicación web e intentara conectarse a my-database, normalmente necesitaría estar en la misma red.

Ejecutemos un comando en un contenedor ubuntu conectado a la misma red, simulando un comando que podría interactuar con la base de datos. Intentaremos hacer ping al contenedor de la base de datos por su nombre (my-database).

docker run --network my-app-network ubuntu ping -c 4 my-database

Este comando:

  1. Creará un nuevo contenedor desde la imagen ubuntu.
  2. Lo conectará a my-app-network.
  3. Ejecutará el comando ping -c 4 my-database dentro del contenedor.

Como el contenedor ubuntu está en la misma red que el contenedor my-database, puede resolver el nombre my-database a la dirección IP del contenedor de la base de datos y hacerle ping.

Deberías ver una salida mostrando las solicitudes y respuestas de ping:

PING my-database (172.18.0.2) 56(84) bytes of data.
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=4 ttl=64 time=0.054 ms

--- my-database ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3060ms
rtt min/avg/max/mdev = 0.050/0.053/0.054/0.001 ms

Esto demuestra que puedes ejecutar un comando puntual en un contenedor y hacer que interactúe con otros contenedores en la misma red, sin necesidad de iniciar el servicio predeterminado del contenedor que ejecuta el comando (en este caso, el contenedor ubuntu no tiene un "servicio" típico). La clave es conectar el contenedor que ejecuta el comando a la misma red que los servicios con los que necesita interactuar.

Finalmente, limpiemos el contenedor de la base de datos en ejecución y la red.

docker stop my-database
my-database
docker rm my-database
my-database
docker network rm my-app-network
my-app-network

Ejecutar un comando y eliminar automáticamente el contenedor

En este paso, aprenderemos cómo ejecutar un comando en un contenedor Docker y eliminar automáticamente el contenedor una vez que finalice el comando. Esto es muy útil para tareas puntuales, scripts o trabajos donde no necesitas que el contenedor persista después de completar su ejecución. La eliminación automática de contenedores ayuda a mantener tu sistema limpio y evita la acumulación de contenedores detenidos.

Usaremos nuevamente la imagen ubuntu para esta demostración. Ejecutaremos un comando simple, como imprimir un mensaje y luego salir. Para eliminar automáticamente el contenedor después de que finalice el comando, usamos la bandera --rm con el comando docker run.

Ejecutemos un contenedor con la bandera --rm y ejecutemos el comando echo "Este contenedor se eliminará automáticamente".

docker run --rm ubuntu echo "Este contenedor se eliminará automáticamente"

Este comando:

  1. Creará un nuevo contenedor desde la imagen ubuntu
  2. Ejecutará el comando echo "Este contenedor se eliminará automáticamente" dentro del contenedor
  3. Una vez que el comando echo finalice, el contenedor se eliminará automáticamente

Deberías ver la salida del comando echo:

Este contenedor se eliminará automáticamente

Después de que finalice el comando, el contenedor se detendrá y eliminará. Para verificar que el contenedor fue eliminado, puedes usar el comando docker ps -a, que lista todos los contenedores, incluidos los detenidos.

docker ps -a

No deberías ver el contenedor que acaba de ejecutarse en la lista. Si ejecutaras el comando sin la bandera --rm, el contenedor aún aparecería en la salida con un estado de "Exited".

Probemos otro ejemplo. Ejecutaremos un comando que pausa durante unos segundos usando sleep y luego sale, nuevamente con la bandera --rm.

docker run --rm ubuntu sh -c "echo 'Iniciando sleep...'; sleep 5; echo 'Sleep finalizado.'"

Este comando usa sh -c para ejecutar un script simple que imprime mensajes antes y después de dormir durante 5 segundos.

Verás el primer mensaje inmediatamente:

Iniciando sleep...

Luego, después de unos 5 segundos, verás el segundo mensaje:

Sleep finalizado.

Una vez que el script finalice, el contenedor se eliminará automáticamente. Puedes verificar esto nuevamente con docker ps -a.

docker ps -a

El contenedor que ejecutó el comando sleep no debería aparecer en la lista de contenedores.

Usar la bandera --rm es una buena práctica para contenedores diseñados para ejecutar una tarea específica y luego salir, ya que ayuda a gestionar el espacio en disco y mantiene tu lista de contenedores limpia.

Resumen

En este laboratorio, aprendimos cómo usar el comando docker compose run para ejecutar tareas puntuales dentro de un servicio de Docker Compose. Comenzamos comprendiendo cómo sobrescribir el comando predeterminado definido en la configuración de un servicio, lo que nos permite ejecutar comandos arbitrarios con fines administrativos o de depuración.

Luego exploramos cómo gestionar la conectividad de red para estas tareas puntuales, específicamente habilitando puertos de servicio y mapeando puertos manualmente. Finalmente, aprendimos a controlar dependencias ejecutando un comando sin iniciar servicios vinculados y cómo eliminar automáticamente el contenedor después de que el comando finalice, asegurando un entorno limpio.