Menú de navegaciónMenú
Categorías

La mejor forma de Aprender Programación online y en español www.campusmvp.es

?id=9befd0fd-21e1-4140-badf-7f8a3708f41d

Docker: diferencia entre los comandos docker run, docker start, docker create y docker exec

Imagen ornamental: un puente-grúa moviendo contenedores en un puerto, por José Manuel Alarcón, CC BY-ND 4.0

Cuando estás empezando con Docker hay 4 comandos que son muy parecidos y que pueden llevarte a confusión: run, startcreate y exec o lo que es lo mismo: "correr", iniciar, crear y ejecutar.

Los nombres se parecen mucho y sus funciones parecen similares. Por ejemplo, ¿no es lo mismo iniciar un contenedor que ejecutarlo?

Pues no exactamente. Así que vamos a verlo para que quede claro:

  • docker create: este comando crea un nuevo contenedor a partir de una imagen. Pero no lo ejecuta.
  • docker start: pone en funcionamiento un contenedor que esté parado. Es decir, cuando has creado un contenedor con el comando anterior (docker create) puedes ejecutarlo con docker start.
  • docker run: es una combinación de los dos anteriores, ya que este comando crea el contenedor a partir de la imagen que le indiquemos y, acto seguido, lo pone en funcionamiento. Es más, puede sustituir también al comando docker pull para descargar la imagen si no está en local todavía.
  • docker exec: ejecuta dentro del contenedor un comando que se le indique. Algo totalmente diferente a lo anterior.

Un ejemplo de Docker paso a paso

Para ver las diferencias en la práctica, vamos a hacer un ejercicio paso a paso.

Evidentemente, para poder seguirlo tienes que tener instalado Docker. Si estás en Windows o en macOS, Docker Desktop.

En primer lugar, vamos a descargarnos una imagen con la que trabajar. Usaremos la mítica hello-world, que es una imagen oficial y contiene un pequeño ejemplo que se limita a saludarnos, pero que es perfecta para hacer pruebas y ver que todo funciona como es debido. Para ello usaremos el siguiente comando:

 docker pull hello-world

que se traerá a nuestra máquina una copia de la imagen.

Podemos ver la imagen en el listado de imágenes locales escribiendo:

docker images

La siguiente figura muestra el resultado de ejecutar estos dos comandos:

Muestra la línea de comandos con la imagen hello-world listada en local

Ahora que ya tenemos la imagen, vamos a crear un contenedor a partir de ella, para lo cual usaremos el siguiente comando:

docker create --name hw-01 hello-world

Esto crea el contendor, pero no lo ejecuta. De hecho, si vamos a listar los contenedores en ejecución:

docker ps

no lo verás por ningún lado. Sólo puedes verlo si indicas que se muestren todos los contenedores, independientemente de su estado de ejecución:

docker ps -a

En la siguiente captura puedes ver la ejecución de estos 3 comandos:

La línea de comandos muestra al final el contenedor hello-world pero sin que se haya ejecutado

Vale. Ahora vamos a ejecutar la imagen con docker run, especificando un nombre diferente al anterior, en este caso hw-02:

docker run --name hw-02 hello-world

Ahora, recibimos por consola el resultado de ejecutar el contenedor, con el mensaje de "Hola Mundo" y una explicación de cómo se ha ejecutado para poder hacerlo:

El resultado de ejecutar, con un saludo (marcado) y una explicación en las siguientes líneas

O sea, a diferencia del comando anterior, que solo creó el contenedor, docker run ha creado un nuevo contenedor (hw-02) y además lo ha iniciado (y por lo tanto ejecutado), cosa que podemos ver por el resultado pero también si listamos los contenedores que tenemos:

Ahora tenemos 2 contenedores, de la misma imagen

Y no está en ejecución porque este contenedor se detiene automáticamente cuando termina de hacer lo suyo, ya que no tiene ningún proceso de tipo "daemon" o servicio que se ejecute todo el tiempo.

Vamos a lanzarlo de nuevo, pero esta vez con docker start. Como hemos visto, esto permite iniciar (y ejecutar, por tanto, lo que tenga dentro) un contenedor que ya exista y esté parado:

docker start hw-02

El resultado de iniciar la imagen y listar los contenedores

Fíjate en una cosa importante de la imagen anterior: cuando ejecutamos el contenedor con docker start, lo ejecuta, pero no captura la salida (técnicamente el STDOUT o salida estándar). Por lo tanto no se ve el resultado de la ejecución como antes, solo el nombre del contenedor. Si hacemos un listado de los contenedores, vemos que no hay ninguno en ejecución, pero que se acaba de ejecutar el que queríamos, solo que no hemos visto nada.

Si el contenedor fuese uno que queda en ejecución (por ejemplo, el de la imagen ubuntu, que ejecuta ese sistema operativo), entonces sí que lo veríamos en ejecución con docker ps, pero no en este caso.

Podemos recuperar la salida estándar con docker start si le pasamos el parámetro -a (o --attach):

Ahora sí vemos el resultado de la ejecución

Otra cosa a tener en cuenta es que el contenedor hw-01, que creamos antes y está sin utilizar, lo tenemos que ejecutar con docker start, ya que docker run crea siempre uno nuevo, y luego lo inicia. Esta es una diferencia crucial, aparte del hecho de que docker run tiene muchos más modificadores para variar su comportamiento.

docker start hw-01

El contenedor se ejecuta y sale. Con docker run crearía uno nuevo

Fíjate en cómo el contenedor se ha ejecutado y se ha detenido tras la ejecución.

En cuanto a docker exec, es capaz de ejecutar los comandos que necesitemos en cualquier contenedor que esté en ejecución. Esto último es muy importante, ya que no se encarga de lanzarlos y luego ejecutar el comando.

Por ejemplo, si descargamos una imagen de alguna distribución Linux, por ejemplo la de Alpine Linux, y la ejecutamos:

docker run alpine

Ejecutamos la imagen, que se descarga primero, y vemos que el contenedor creado se detiene

Vemos que descarga la imagen (puesto que no la teníamos en local), acto seguido crea un contenedor y lo ejecuta, pero se detiene inmediatamente, ya que es una imagen de propósito general, pensada para tener una base del sistema operativo para nuevas imágenes, pero no para ejecutar un comando concreto. Si intentásemos ejecutar algo con docker exec no funcionaría porque no está el contenedor en ejecución.

Así que, para probar exec, tendremos que ejecutar el contenedor y mantener a Alpine Linux en funcionamiento. Para ello podemos usar el comando run así:

docker run -it --rm alpine

Esto ejecutará un nuevo contenedor a partir de la imagen alpine y abrirá una sesión interactiva con él y un terminal (-it). De este modo lo tendremos funcionando en segundo plano y podremos probar el lanzamiento de comandos con exec. La opción --rm sirve para eliminar automáticamente el contenedor una vez se deje de ejecutar

Al hacer esto tendremos una consola abierta con el contenedor, pero lo importante aquí es que lo dejamos en ejecución y podremos usar exec contra él.

Si abres otra terminal y listas los contenedores en ejecución, verás que el nuevo contenedor está ahí esperando:

Los dos comandos descritos. El contenedor está en funcionamiento.

Nota: en un contenedor que contenga algún servicio o proceso, que será lo habitual si es para una de nuestras aplicaciones, no sería necesario esto, ya que estará en ejecución mientras no lo paremos. En este ejemplo que estamos haciendo el uso de docker exec no tiene mucho sentido porque ya tenemos una sesión bash abierta contra el contenedor tras hacer el docker run (podríamos haberlo mantenido en ejecución de muchas otras maneras, por ejemplo con tail -f /dev/null), pero es solo a efectos de mostrarte cómo funciona docker exec. En un contenedor real, siempre en funcionamiento, usaremos exec cuando queramos.

Vale, ahora que ya tenemos el contenedor en funcionamiento, vamos a usar docker exec para ejecutar algo en él. Por ejemplo, empecemos por algo sencillo: averiguar la ruta actual en la que estamos posicionados en el contenedor al lanzar los comandos:

docker exec -it c8b pwd

En este caso devuelve la raíz (/).

Nota: fíjate que en lugar del nombre del contenedor he utilizado su ID, el cual no hace falta introducirlo entero y llega con los 2 o 3 primeros números mientras no coincidan con los de otro contenedor (es harto difícil). El parámetro -it es la combinación de 2 y sirve para que nos devuelva la salida del comando en nuestro terminal.

Podemos especificar el directorio en el que queremos ejecutar el comando con el parámetro -w (o su versión larga: --workdir), así:

docker exec -it -w /root c8b pwd

En este caso devuelve, como es lógico, /root, que es el que le hemos indicado como directorio base.

Pues de la misma manera podemos ejecutar cualquier comando (por ejemplo, la lista de subdirectorios y archivos del directorio actual):

 docker exec -it c8b ls

o incluso instrucciones que se le pasen a un comando:

docker exec -it c8b sh -c "echo Hola desde Alpine Linux"

Con exec podremos modificar cosas en el sistema operativo subyacente a nuestro contenedor, administrar ciertos parámetros, etc... aunque debemos tener en cuenta que nada que modifiquemos en el contenedor se conservará cuando lo detengamos (las imágenes son inmutables).

Este es el resultado de ejecutar todos estos ejemplos:

Todos los comandos exec anteriores

Cuando hayas terminado sal del contenedor en ejecución, en el primer terminal, escribiendo simplemente el comando exit. Esto lo detendrá y además lo eliminará porque especificamos el modificador --rm en docker run.

En resumen

Aunque estos cuatro comandos de Docker tienen nombres parecidos y algunos se pueden usar para cosas parecidas, son muy diferentes entre sí y cada uno tiene su utilidad. Espero que te hayan quedado claros.

Si quieres aprender Docker y Kubernetes a fondo, sin recetas mágicas y a prueba de futuro, nuestro curso Docker y Kubernetes: desarrollo y despliegue de aplicaciones basadas en contenedores es justo lo que estabas buscando. Y su tutor es mucho mejor que yo en esto 😉

José Manuel Alarcón Fundador de campusMVP, es ingeniero industrial y especialista en consultoría de empresa. Ha escrito diversos libros, habiendo publicado hasta la fecha cientos de artículos sobre informática e ingeniería en publicaciones especializadas. Microsoft lo ha reconocido como MVP (Most Valuable Professional) en desarrollo web desde el año 2004 hasta la actualidad. Puedes seguirlo en Twitter en @jm_alarcon o leer sus blog técnico o personal. Ver todos los posts de José Manuel Alarcón
Archivado en: Herramientas

¿Te ha gustado este post?
Pues espera a ver nuestro boletín mensual...

Suscríbete a la newsletter

La mejor formación online para desarrolladores como tú

Agregar comentario

Los datos anteriores se utilizarán exclusivamente para permitirte hacer el comentario y, si lo seleccionas, notificarte de nuevos comentarios en este artículo, pero no se procesarán ni se utilizarán para ningún otro propósito. Lee nuestra política de privacidad.