Como al final del anterior post, os recomiendo que tengáis el ordenador a mano mientras leeis. ¡Hoy toca picar muchos comandos!

Muy bien, chic@s. Vamos a ver un par de comandos más de la Docker machine y nos pondremos ya de inmediato a lanzar nuestros contenedores.

Estado de nuestra VM Docker

En el anterior post lo habíamos dejado con nuestra máquina virtual Docker (que habíamos llamado default) up and running.

Pero entiendo que desde entonces hasta ahora, habéis apagado el equipo. Por lo que si ejecutáis

docker-machine ls

veremos que el estado de nuestra máquina es stopped.

Nuestra VM Docker descansando de un día duro de trabajo

Nuestra VM Docker descansando de un día duro de trabajo

Para lanzarla basta con ejecutar:

docker-machine start default

Tardará un poco (lamentablemente, estamos lanzando toda una máquina virtual, y aunque ligera, lleva su tiempo). Pero finalmente tendremos nuestra máquina en estado running:

Nuestra VM Docker up and running

Nuestra VM Docker up and running

También decir que, de una manera similar, podemos detenerla con el siguiente comando:

docker-machine stop default

Por último, nos conectamos a nuestra VM para poder operar bajo un sistema que tenga soporte nativo para Docker:

docker-machine ssh default

Con nuesto host Docker ya ejecutándose y conectados a él, pasamos a lanzar contenedores!

Lanzando nuestro primer contenedor

Lancemos nuestro primer contenedor a partir de la imagen de alpine que ya deberíamos tener descargada si hemos seguido lo indicado en el anterior post. Y antes de darle un enter, poned el cronómetro:

docker run -t -i --name bloody_mary alpine /bin/sh

¿Cuánto ha tardado en lanzar el contenedor? ¿Medio milisegundo? :)

Desgranemos el comando:

  • docker run: le indicamos al intérprete de Docker que vamos a ejecutar un comando run, que se traduce por un “lánzame un contenedor”. Esto es, se conectará vía HTTPS e invocará los métodos de la API REST necesarios para que el servidor Docker realice esta tarea.
  • -t: le indicamos que nos habilite un terminal tty para ese contenedor.
  • -i: le indicamos que queremos interactuar con el contenedor, es decir, que lo lance en modo interactivo.
  • –name bloody_mary: le asignamos el nombre bloody_mary a nuestro contenedor. Si no especificamos este parámetro, Docker escogerá uno por nosotros. Y como curiosidad, en Github tenéis el código en Go de esta tarea.
  • alpine: le indicamos que queremos que use la imagen alpine como sistema de directorios de nuestro contenedor.
  • /bin/sh: por último, le indicamos qué proceso queremos que ejecute cuando lance nuestro contenedor. En este caso, que ejecute el shell. Como hemos habilitado una terminal tty y estamos en modo interactivo, la ejecución de este comando nos devolverá un prompt a nuesro contenedor, como si nos acabáramos de conectar por SSH.

El apasionante resultado de la ejecución de este comando, a continuación:

Un precioso prompt a nuestro contenedor alpine

Un precioso prompt a nuestro contenedor alpine

Ejecutad un ls. Ahí lo tenéis. Los binarios de la distribución alpine.

La distribución alpine en todo su esplendor

La distribución alpine en todo su esplendor

Ahora teclead exit y dadle un enter para salir de nuestro contenedor y volver a nuestra VM Docker.

Para listar los contenedores, ejecutamos:

docker ps

Un momento… parece que la lista está vacía:

¿Dónde está nuestro recién lanzado contenedor?

¿Dónde está nuestro recién lanzado contenedor?

Lo que ha pasado aquí es sencillo de entender. Al ejecutar exit no sólo hemos salido de nuestro contenedor, sino que hemos finalizado el proceso /bin/sh que se ejecutaba en él. No había ningún otro proceso en foreground ejecutándose en dicho contenedor, por lo tanto éste directamente se ha detenido.

Podemos verlo con la opción -a (all, es decir, los que se están ejecutando y los que no) del anterior comando:

docker ps -a

Ahí lo tenemos. Si os fijáis en la columna STATUS, nos indica que hemos salido hace 24 segundos:

¡Aquí estaba!

¡Aquí estaba!

Otro parámetro para el comando ps es -q, que nos lista únicamente la columna CONTAINER ID

docker ps -a -q

Volvamos a lanzar nuestro contenedor a partir de nuestra imagen de alpine e insertemos una modificación en su sistema de ficheros:

> docker start bloody_mary
> docker exec -t -i bloody_mary /bin/sh
/ # echo "Sin tele y sin cerveza, Homer pierde la cabeza!" > sabiduria.txt
/ # exit

Con la primera línea, docker start bloody_mary estamos relanzando nuestro contenedor (que, recordemos, estaba detenido).

Pero fijaros que en esta ocasión Docker no nos devuelve el prompt a nuestro contenedor.

Para poder abrir ahora un terminal a nuestro contenedor ejecutamos la segunda línea docker exec -t -i bloody_mary /bin/sh.

Es decir, ejecuta el comando /bin/sh en el contenedor bloody_mary con un terminal tty y en modo interactivo.

Ahora sí que estamos dentro de nuestro contedor.

Pues bien, con la tercera línea lo que hacemos es crear un fichero sabiduria.txt cuyo contenido será “Sin tele y sin cerveza, Homer pierde la cabeza!”.

Y por último, salimos del contenedor. Como hemos visto antes, esto hará que el contenedor se detenga.

Pero si volvemos a entrar y listamos el contenido del directorio raíz haciendo un ls:

> docker exec -t -i bloody_mary /bin/sh
/ # ls

Veremos que nuestro fichero sabiduria.txt sigue estando ahí.

Como habíamos dicho en post anteriores,la capa de lectura/escritura es persistente siempre y cuando no eliminemos el contenedor, sólo lo detengamos, como es el caso.

Ahora eliminemos nuestro contenedor:

docker stop bloody_mary
docker rm 1a6b2d03fa14

El numeraco 1a6b2d03fa14 se corresponde precisamente con el CONTAINER ID del contenedor que queremos borrar.

Si queremos eliminar todos nuestros contenedores, ejecutándose o no, de una tacada, podemos hacer:

docker rm $(docker ps -a -q)

Es decir, al comando docker rm le concatenamos la salida del comando docker ps -a -q que nos devolverá todos los CONTAINER ID de todos nuestros contenedores.

Volvamos a crear un contenedor a partir de nuestra imagen de alpine, en modo iteractivo, con un terminal tty y ejecutando el proceso /bin/sh para que nos sitúe el prompt dentro de nuestro contenedor. Y a continuación, hagamos un ls:

> docker run -t -i --name bloody_mary alpine /bin/sh
/ # ls

Vemos que ahora no hay ningún fichero sabiduria.txt. ¡Evidentemente! Hemos eliminado el contenedor anterior, eliminando a su vez su capa de lectura/escritura.

Al lanzar el nuevo, utiliza la imagen alpine para su sistema de ficheros… ¡y crea una nueva capa de lectura/escritura limpia y reluciente y sin rastro del fichero sabiduria.txt!

¿Cómo podemos incorporar esa capa? Pues convirtiendo el sistema de ficheros de ese contenedor en una nueva imagen, precisamente lo que vamos a hacer a continuación.

Creando nuevas imágenes

Repetimos proceso: creamos un contenedor a partir de la imagen de alpine y modificamos su capa de lectura/escritura:

> docker run -t -i --name tequila_sunrise alpine /bin/sh
/ # echo "Sin tele y sin cerveza, Homer pierde la cabeza!" > sabiduria.txt
/ # exit

Ahora ejecutamos el siguiente comando:

docker commit -m "A new awesome cocktail" -a "Moises Vilar" tequila_sunrise moisesvilar/alpine

Este comando crea una nueva imagen con nombre moisesvilar/alpine (utilizad si queréis vuestro nombre de usuario en Docker hub, en vez de moisesvilar) cuyo autor es “Moisés Vilar” y con un mensaje de creación “A new awesome cocktail” a partir del sistema de ficheros que actualmente posee el contenedor tequila_sunrise, incluyendo su capa de lectura y escritura.

Si ahora ejecutamos

docker images

veremos que nuestra imagen se ha creado correctamente:

Nuestra nueva imagen moisesvilar/alpine

Nuestra nueva imagen moisesvilar/alpine

Bien, pues ejecutemos un nuevo contenedor a partir de esta imagen y hagamos un ls

> docker run -t -i --name old_fashioned moisesvilar/alpine /bin/sh
/ # ls

¡Ahí tenemos nuestro fichero sabiduria.txt!

Nuestro fichero sabiduria.txt existe en el nuevo contenedor creado a partir de la imagen moisesvilar/alpine

Nuestro fichero sabiduria.txt existe en el nuevo contenedor creado a partir de la imagen moisesvilar/alpine

Lo que hemos hecho es “congelar” el sistema de ficheros del contenedor tequila_sunrise, creando una nueva imagen que, ahora sí, incorpora una capa que contiene el fichero sabiduria.txt y, a continuación, hemos creado el contenedor old_fashioned a partir de dicha imagen.

Conclusiones

¡Por fin tenéis comandos! En este post hemos aprendido un buen puñado de cosas:

  1. Hemos visto cómo controlar el estado de nuestra VM Docker (nuestro host para Docker) con los comandos docker-machine start y docker-machine stop.
  2. Hemos lanzado nuestro primer contenedor con el comando docker run.
  3. Hemos listado nuestos contenedores con el comando docker ps
  4. Los hemos iniciado y detenido con los comandos docker start y docker stop.
  5. Los hemos eliminado con el comando docker rm.
  6. Hemos ejecutado procesos en contenedores previamente iniciados, con el comando docker exec.
  7. Hemos creado una imagen a partir del sistema de ficheros de un contenedor con el comando docker commit.

Si queréis bucear más a fondo en estos comandos, podéis recurrir a la referencia oficial de Docker.

Por el momento, nosotros lo dejamos aquí. En el siguiente post montaremos una imagen “a mano” con un entorno de ejecución (quizás PHP o quizás NodeJS, aún no lo tengo decidido, si tenéis alguna preferencia… dejadla en los comentarios!).

¡Nos vemos en nada!