Docker 101: ¿Qué es Docker y para que se usa?
Tags: docker, devops, spanish
Published: 2020-09-28 22:42:49

Antes de definir qué es Docker, quiero contarles un problema que tuvimos en un proyecto en el que estuve trabajando.

Con el equipo empezamos configurando nuestro proyecto, definiendo la arquitectura, luego pasamos a la etapa de desarrollo de componentes, test, etc etc. Cuando llegó el momento de hacer un deploy en los ambientes del cliente, nos enteramos que todo ese proceso lo hacían a mano. Entonces, con la idea de que quién vaya a realizar esos deploy lo haga de la manera más fácil posible generamos una documentación de cómo clonar el proyecto, cómo instalar las dependencias y como ejecutarlo.

Una vez realizado el deploy, nos encontramos con la sorpresa de que nuestra app no corría como debía correr. Luego de debuggear un rato detectamos que el servidor donde estaba corriendo la app tenía instalado la v.14 de Node.js, y nuestra app tenía un lockdown a la v.12 lo que generaba algunos problemas de compatibilidad.

¿Por qué les cuento esto? Porque esto es uno de los típicos problemas que Docker viene a prevenir que sucedan.

Ahora si...

## ¿Qué es Docker?

Docker puede definirse como una forma de empaquetar software para que pueda ejecutarse en cualquier hardware.

Siguiendo el caso anterior, Docker nos permite generar un *paquete* que contiene todo lo necesario, código fuente, configuraciones y las dependencias, para que nuestra aplicación funcione de la manera adecuada.

## Sus principales partes

Docker tiene un montón de partes muy interesantes y útiles, como los *volumens*, pero en este artículo nos enfocaremos solo en las principales, que son:

- **Dockerfile**: el Dockerfile es un archivo que sirve para indicarle a Docker cómo se debe construir la imagen del proyecto. En otras palabras, es la receta que Docker usa para generar un paquete con nuestra aplicación.
- **Image**: es un template para ejecutar el proyecto en cualquier contenedor. Una imagen de Docker es nuestro *paquete*.
- **Container**: un container es básicamente, una imagen en ejecución.

## ¿Cómo *dockerizar*?

Lo primero que tenemos que hacer es generar nuestro proyecto. Vamos a generar un simple archivo html y un server en express para servir este archivo.

Para esto, dentro del directorio donde vamos a trabajar, iniciamos un nuevo proyecto con `npm init` y dejamos todas las configuraciones por defecto, de tal manera que nos quede un `package.json` así:

```json
{
  "name": "docker101",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Luciano Peñafiel",
  "license": "ISC"
}
```

Lo siguiente que vamos a hacer es crear un archivo `index.html` con un simple encabezado y lo colocaremos en un directorio que lo vamos a llamar `build`.

```html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Docker 101</title>
</head>
<body>
    <h1>Hola mundo!</h1>
</body>
</html>
```

A continuación instalamos express ejecutando `npm i express` y creamos un archivo `server.js` que va a ser el encargado de servir nuestro html y agregamos un script en nuestro `package.json` para levantar este server.

```js
const express = require("express");
const app = express();

app.use(express.static("build"));

app.get("/", (req, res) => {
  res.send("ok");
});

app.listen(3000, () =>
  console.log(`App listening on port http://localhost:3000`)
);
```

```json
{
  "name": "docker101",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
		"start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Luciano Peñafiel",
  "license": "ISC"
}
```

Listo, ya tenemos nuestra app. Ahora solo ejecutamos el comando `npm start` y vamos a tener ver nuestra app corriendo en el puerto 3000.

## ¿Y Docker?

Ahora llegó el momento de trabajar con Docker, y lo primero que tenemos que hacer es instalarlo desde la [web oficial de Docker](https://www.docker.com/).

Una vez instalado y corriendo, ejecutamos en nuestra terminal el comando `docker ps` para ver todas las imágenes que están corriendo actualmente. De momento, no deberíamos tener ninguna, y obtener una respuesta así:

![docker ps](https://dev-to-uploads.s3.amazonaws.com/i/ejiqwqo351imy71kbh1f.png)

Lo siguiente es construir nuestro Dockerfile y para esto creamos en la raíz de nuestro repositorio un archivo con ese nombre (y sin extensión) con el siguiente contenido:

```yaml
FROM node:12-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm ci

COPY . ./

CMD ["npm", "start"]
```

Entendamos un poco este archivo. Primero que nada es muy importante entender que cada instrucción funciona como una capa, y Docker intentará mantener en caché cada una de ellas si nada se ha modificado, por lo tanto es recomendable poner primero aquellas capas que menos van a cambiar a lo largo del proyecto.

Todo Dockerfile empieza con el comando `FROM` que sirve para indicarle a Docker cuál va a ser la imagen base de nuestra imagen. Sí, cada imagen parte de otra imagen.

Lo siguiente que tenemos hacer es agregar el código fuente de nuestra app a la imagen. Para eso primero definimos la raíz del directorio usando la instrucción `WORKDIR`. Todo lo siguiente que hagamos va a comenzar desde esa ruta.

Luego vamos a instalar todas nuestras dependencias usando el comando `COPY` que recibe dos argumentos, primero lo que queremos copiar y al último la ubicación donde queremos copiar esos archivos. 

Luego vamos a instalar todo lo que tenemos en nuestro `package.json` con el comando `RUN npm ci`.

Una vez instaladas todas nuestra dependencias, copiamos el código fuente de nuestra app. En este punto se presenta un problema, y es que en los pasos anteriores instalamos nuestras dependencias, lo que generó dentro de la imagen un directorio `node_modules` y al copiar todo lo que tenemos en nuestro código fuente, vamos a sobre escribir esos módulos y no es algo que queremos que suceda. Para esto creamos un archivo `.dockerignore` (si, igual que un .gitignore) y colocamos dentro `node_modules`.

Y por último le indicamos a Docker como debe inicializar nuestra app con la instrucción `CMD`, que es la instrucción por defecto de Docker para iniciar una imagen.

Una vez creado nuestro `Dockerfile`, ahora sí vamos a generar una imagen del proyecto, y esto lo hacemos con el comando `docker build -t docker101:v1 .`

![docker build -t docker101-v1](https://dev-to-uploads.s3.amazonaws.com/i/9d6ahdrp41b4tdno6cxk.png)

¿Qué hace esto? Bien, `docker build` va a generar nuestra imagen, y con el flag `-t docker101:v1` le asignamos una etiqueta a nuestra imagen y le indicamos que está en la versión 1.

Ahora si corremos el comando `docker images` vamos a ver todas las imágenes que tenemos en nuestro Docker con sus respectivos IDs.

Y por fin llegó la hora de ejecutar nuestra app dentro de un contenedor, esto lo hacemos con el comando `docker run -p 3000:3000 docker101:v1` y *voila,* tenemos nuestra app corriendo en el puerto 3000.

![Hola mundo](https://dev-to-uploads.s3.amazonaws.com/i/e33rd0ksq17q5yjh78o2.png)

## ¿Qué sigue?

La idea, siguiendo el ejemplo del principio, es entregarle a nuestro cliente todo empaquetado. Y ¿cómo hacemos esto? Bueno, hay muchas maneras de hacerlo, pero la más usada es subiendo nuestra imagen a un *registry* de imágenes como los es Docker Hub o GitHub Packages para que luego nuestro cliente pueda descargar la imagen.


Eso es todo por ahora, nos vemos pronto 👋🏻.