Hoy empiezo con Docker

Me ha llevado tiempo, pero por fin me he animado a empezar con Docker. Seguramente existen muchísimos sitios con tutoriales de cómo empezar, pero yo necesito escribirlo para mí, así es como sé si me he enterado o no de lo que voy aprendiendo (cada uno… ). Si tu también estás pensando en empezar con Docker, este es tu artículo 🙂 Obviamente, este es un artículo de principiante en la materia, que es lo que soy yo ahora mismo. Así que es posible que inicialmente sean demasiado básicos o me falten algunos puntos a tratar. No te preocupes, la idea es justo esa: ir descubriendo Docker poco a poco para que la curva de aprendizaje sea lo menos curva posible 🙂

Pero ¿Qué es Docker?

Según la propia documentación de Docker, se trata de una plataforma para desarrolladores y administradores, la cual nos permite desarrollar, desplegar y ejecutar aplicaciones en contenedores. Un contenedor es una instancia de una imagen, como un objeto lo es de una clase. Una imagen, para mi, es la receta y los ingredientes que necesita tu aplicación para poder ser ejecutada. Y estos son los dos conceptos que debes entender por ahora, que quedarán más claros con el ejemplo práctico.

Esto nos lleva, irremediablemente, a comparar los contenedores con las máquinas virtuales ¿no? La respuesta corta de por qué contenedores en lugar de máquinas virtuales es que estos no necesitan de un sistema operativo propio con todo lo que ello conlleva por cada aplicación, ya que los contenedores se aprovechan del sistema operativo del host. Por lo tanto, el tamaño de estos es mucho más pequeño que el de una máquina virtual y su mantenimiento también es mucho menor.

¿Qué necesito para empezar?

Lo primero que debes hacer es instalar el motor de Docker en tu máquina. Para ello accede a la página de instalación de Docker y elige la versión más apropiada, acorde a tu operativo. Aunque en la página se indica que Docker está disponible para Windows 10 Pro y Enterprise, yo lo he instalado en un Windows Server 2019 🙂

Docker is now up and running en Windows Server 2019

Una vez que lo tengas instalado, comprueba que funciona correctamente a través de una consola escribiendo el siguiente comando:

docker info

Deberías de ver un resultado parecido al siguiente:

docker info
docker info

Siempre que se empieza con docker, se hace la típica prueba de generar un contenedor con la imagen hello-world, sobre todo para comprobar que la instalación que acabas de realizar funciona correctamente.

docker run hello-world

Lo primero que ocurrirá es que intentará localizar la imagen en local. Al ser la primera vez que quieres instanciar esta imagen, esta no estará disponible, por lo que se irá a buscarla a Docker Hub (ya veremos más adelante qué es esto). Una vez descargada mostrará por pantalla el mensaje de bienvenida.

docker run hello-world

¡Perfecto! ya tienes todo lo que necesitas para crear imágenes y ejecutar contenedores en tu máquina local. Ahora pasemos al ejemplo práctico con una aplicación del día a día.

Dockerizando una aplicación

Ya has comprobado que todo funciona correctamente, pero necesitamos algo más del día a día para poder entender qué me aporta a mi todo esto.
Para empezar, voy a crear una aplicación web con Node.js muy simple, con el objetivo de que esta termine ejecutándose en un contenedor de Docker. Lo primero que voy a hacer es crear una carpeta en mi local llamada nodejs-webapp y voy a lanzar el comando de iniciación del proyecto Node.js

npm init -y

Voy a instalar express, que me ayudará a montar mi servidor web:

npm install --save express

y voy a crear un archivo llamado server.js, donde voy a incluir el siguiente código.

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.sendFile(__dirname + '/index.html');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

Por otro lado, he creado un archivo llamado index.html también muy simple, que es el que será devuelto cuando acceda a mi aplicación.

<!DOCTYPE html>
<html>

<head>
    <title>Hello World from Node.js</title>
</head>

<body>
    <h1>Hello World from a container!</h1>
</body>

</html>

Hasta ahora todo normal ¿verdad? Sin embargo, si te das cuenta, para poder ejecutar este ejemplo en tu máquina al menos necesitarás tener instalado Node.js para poder generar la solución, instalar las dependencias y por supuesto ejecutar el código. Si además utilizara características específicas de una versión de Node.js en concreto, debería asegurarme de que en todos los sitios donde se instala mi aplicación está instalada esa versión de Node.js. La verdad es que por ahora nada de esto es nuevo, pero quiero que entiendas la utilidad más importante de Docker, o al menos una de ellas.

Ahora que tenemos nuestra aplicación creada, vamos a hacer que entienda de Docker. Crea un fichero llamado Dockerfile en la raíz de tu aplicación. Copia el siguiente contenido en él:

FROM node:10.13-alpine

WORKDIR /app

COPY ["package.json", "package-lock.json", "./"]

RUN npm install 

COPY . /app

EXPOSE 3000

CMD [ "node", "server.js" ]

Se puede decir que es la receta de todos los ingredientes que necesitas para que tu aplicación en Node.js pueda ser ejecutada en un contenedor. Esto es lo que significa línea por línea:

  1. FROM node:10.13-alpine: la palabra clave FROM nos indica que vamos a utilizar una imagen padre para montar la nuestra. En este caso vamos a hacer uso de la imagen node, su versión 10.13-alpine. El objetivo de esto es que no tengamos que preocuparnos de instalar node desde cero.
  2. WORKDIR /app: es el directorio de trabajo, dentro del contenedor, donde estará tu aplicación.
  3. COPY [“package.json”, “package-lock.json”, “./”]: copiamos primero los archivos package.json y package-lock.json que son los que contienen las dependencias que necesita nuestra aplicación para funcionar.
  4. RUN npm install: ejecuta npm install para instalar todos los paquetes necesarios para tu aplicación, de la misma forma que lo haríamos sin contenedores.
  5. COPY . /app: copia todos los archivos del directorio donde se encuentra el archivo Dockerfile en la carperta app.
  6. EXPOSE 3000: Como nuestra aplicación está a la escucha por el puerto 3000, exponemos el mismo puerto en el contenedor para que la aplicación pueda ser accesible desde fuera.
  7. CMD [“node”, “server.js”]: una vez finalice todo lo anterior, ejecutaremos nuestra aplicación dentro del contenedor, de la misma forma que haríamos con node server.js.

Al igual que hacemos con los repositorios de Github, vamos a crear otro archivo llamado .dockerignore que nos permita establecer qué directorios y archivos no deben formar parte de la imagen.

node_modules
npm-debug.log
Dockerfile*
docker-compose*
.dockerignore
.git
.gitignore
.env
*/bin
*/obj
README.md
LICENSE
.vscode

Ahora necesitamos que todo esto se materialice en una imagen, que después podremos instanciar en un contenedor. Lanza el siguiente comando para generar la misma:

docker build --tag=nodejs-webapp .

El parámetro tag es el nombre que hemos dado a nuestra imagen. El punto se utiliza para indicar que queremos construirla con la información que hay en el directorio actual, donde encontrará nuestro fichero Dockerfile con las instrucciones a seguir. Según vaya transcurriendo la creación, irá pasando por todos los pasos, en este caso 6, e irá mostrando el output de cada uno de ellos.

docker build output
docker build output

Si ahora lanzas el siguiente comando verás que tu nueva imagen se encuentra entre las existentes:

docker images

Llegó el momento de crear un contenedor que contenga tu aplicación. Para ello se utiliza el comando docker run de la siguiente forma:

docker run -p 4000:3000 nodejs-webapp

A parte de decirle cuál es la imagen que queremos lanzar, a través del comando -p le estamos diciendo cuál es el puerto que quiero utilizar, mapeándolo siempre con el que definimos dentro de la imagen, en este caso el 3000. Si ahora accedes desde el navegador a http://localhost:4000 verás que tu aplicación web está funcionando correctamente sobre un contenedor de Docker.

Hello World from a container!

Para parar la ejecución, basta con utilizar el siguiente comando:

docker stop CONTAINER_NAME/CONTAINER_ID

Como primera toma de contacto no está mal ¿no? Sin embargo, quedan muchos puntos que ver e iremos avanzando, pero poco a poco 😉 En el siguiente artículo te cuento cómo publicar esta imagen en Docker Hub.

¡Saludos!

Imagen de portada por kyohei ito en Flickr.