Docker En Rails - Parte 3
Published: 2022-09-26 20:18:03

Cada vez nos estamos acercando más al final de toda esta documentación / tutorial / estudio. Por lo que antes de movernos a ver contenedores, imágenes de diferentes servicios, el docker compose (que lo amo por cierto), etcétera; vamos a tunear un poquito nuestro Docker para hacernos la vida más sencilla.

## Actualizando nuestra imagen de Docker.
Primeramente empezaremos por etiquetar nuestra imagen de la aplicación, si bien recordamos que contamos con un comando para correr Rails el cual es:
```Docker
docker run -p 3000:3000 458acbbc731a bin/rails s -b 0.0.0.0
```
Como sabemos nos referimos a nuestra imagen por medio de su ID que en este caso es **458acbbc731a** y siendo sincero no hay forma en que podamos recordar esta. 
Por lo tanto una mejor opción es etiquetar ese ID con un nombre con la finalidad de que esto sea más sencillo mediante el siguiente comando.
```Docker
docker tag 458acbbc731a theaapp
```
Enseguida para verificar que el cambio funcionó de manera correcta solo será necesario listar las imágenes con **docker images** lo cual nos dará como resultado lo siguiente.
![Imagen 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xvw8it7ecup679jw9czo.png)
Una vez que tenemos nombrada nuestra imagen ahora podremos iniciar nuestro servidor de Rails utilizando esta etiqueta como se muestra enseguida.
```Docker
docker run -p 3000:3000 theapp bin/rails s -b 0.0.0.0
```
Definitivamente esto es mucho mejor que tener que estar ingresando el id de la imagen.
Siguiendo con la afinación de nuestra imagen si nos damos cuenta cada vez que deseamos ejecutar el servidor de Rails en un contenedor tenemos que especificar explícitamente el comando **bin/rails s -b 0.0.0.0** todo esto como parte del comando **docker run**, es decir.
```Docker
docker run -p 3000:3000 theapp bin/rails s -b 0.0.0.0
```
Esto viene a ser algo tedioso tener que ejecutarlo cada momento; por lo tanto lo que podemos hacer es agregar una nueva instrucción a nuestro Dockerfile especificando el comando por defecto a ejecutarse cuando un contenedor inicie desde una imagen.
```Docker
FROM ruby:2.6

RUN apt-get update -yqq
RUN apt-get install -yqq --no-install-recommends nodejs

COPY . /usr/src/app

WORKDIR /usr/src/app
RUN bundle install

CMD ["bin/rails", "s", "-b", "0.0.0.0"]
```
Como podemos observar esta nueva línea CMD ["bin/rails", "s", "-b", "0.0.0.0"] es una instrucción  en donde CMD omite la notación del arreglo por lo que en teoría lo que estaría ejecutándose lo siguiente.
```Docker
CMD bin/rails s -b 0.0.0.0
```
Una vez realizado lo anterior será momento de reconstruir nuestra imagen con la nueva instrucción CMD mediante el siguiente comando.
```Docker
docker build -t theapp .
```
Completada la reconstrucción de nuestra imagen podremos iniciar el servidor de Rails con el siguiente comando y tendremos desplegado nuestra aplicación en localhost:3000 como siempre.
```Docker
docker run -p 3000:3000 theapp
```
### Mantenimiento extra.
* Ignorando archivos.

Es necesario restringir qué archivos serán enviados como parte del contexto de construcción de una imagen, debido a que entre mas archivos enviemos  más lenta será la construcción y levantamiento de las imagenes, ademas como tema de seguridad podemos evitar adjuntar archivos con información sensible, como archivos de variables de ambiente.
Para excluir ciertos archivos y directorios de ser mandados como parte del contexto de construcción deberemos de crear un archivo llamado **.dockerignore** el cual estara situado en la raíz de nuestra aplicación con la siguiente información.
```Docker
# Git
.git
.gitignore

# Logs
log/*

# temp files
tmp/*

# Editor temp files
*.swp
*.swo
```
Con esto puesto en marcha deberemos de reconstruir nuestra imagen y en un futuro poder agregar archivos que no sean necesarios para la construcción de la imagen.
```Docker
docker build -t theapp .
```
* El caché de la imagen.

Durante toda esta etapa de estar descubriendo Docker estamos reconstruyendo la imagen una y otra vez, ya sea para instalar gemas o para agregar cierta configuración al proyecto, es algo que estamos haciendo.
En este momento si volvemos a reconstruir nuestra imagen tendremos como resultado lo siguiente.
![Imagn 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j89i8om2hng3hq71qult.png)
La imagen se construye de una manera rápida y de hecho si miramos la salida podremos observar que en cada paso tenemos la sentencia **CACHED** lo cual indica a Docker que no necesita crear una nueva imagen para este paso ya que solamente reuitiliza una imagen intermedia almacenada en cache de una construcción de imagen previa.
Teniendo en cuenta lo anterior es necesario señalar que contamos con dos pequeños detalles en el cacheo de nuestro Dockerfile.

1.- Actualizando paquetes.
Actualmente nuestro Dockerfile tiene las siguientes líneas:
```Docker
RUN apt-get update -yqq
RUN apt-get install -yqq --no-install-recommends nodejs
```
Aunque esto funciona existe un problema, digamos que en unos días más es necesario agregar el paquete de vim al proyecto.
```Docker
RUN apt-get update -yqq
RUN apt-get install -yqq --no-install-recommends nodejs vim
```
la instrucción **RUN apt-get update** se mantendrá sin cambiar y utilizara la versión cacheada del repositorio, todo esto en lugar de obtener la versión más nueva del paquete que agregamos.
Es por eso que es recomendado utilizar siempre la combinación entre **apt-get update** y **apt-get install** en una sola instrucción, es decir.
```Docker
RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \
    nodejs
```
Esto asegura que cuando cambiemos los paquetes a ser instalados, también contaremos con la información del último repositorio al mismo tiempo. Con esto puedo en marcha será momento de reconstruir la imagen para incluir estos cambios.
```Docker
docker build -t theapp .
```
2.- Gemas instaladas.
Actualmente si hacemos un cambio en nuestro archivo Readme, lo almacenamos con los nuevos cambios y procedemos a reconstruir la imagen nuevamente tendremos como consecuencia que todas las gemas van a ser de igual manera reconstruidas en nuestro proyecto. 
Por lo tanto una alternativa para esto es actualizar nuestro Dockerfile de la siguiente manera.
```Docker
FROM ruby:2.6

RUN apt-get update -yqq && apt-get install -yqq --no-instal-recommends \
		nodejs

COPY GEMFILE* /usr/src/app/
WORKDIR /usr/src/app
RUN bundle install

COPY . /usr/src/app/

CMD ["bin/rails", "s", "-b", "0.0.0.0"]
```
El cambio sustancial aquí es que contamos con una línea **COPY Gemfile* /usr/src/app** que lo que hace es copiar nuestra Gemfile y Gemfile.lock dentro de nuestra imagen antes del resto de nuestro código.
Esto crea una capa separada e independiente en donde el caché de Docker para esta capa solo se disparará si alguno de estos dos archivos cambia.
Después de haber copiado nuestro Gemfile y Gemfile.lock dentro de la imagen, podremos cambiarnos al directorio en donde están e instalar nuestras gemas.
```Docker
WORKDIR /usr/src/app
RUN bundle install
```
Finalmente con nuestras gemas instaladas podemos copiar lo que queda de nuestros archivos fuente dentro de la imagen.
```Docker
COPY . /urs/src/app
```
Volvemos a regresar nuestro archivo README a su estado original, reconstruimos la imagen y podremos ver cómo es que el proceso fue más rápido que la última vez que lo ejecutamos.
![Imagen 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l05q1b8x6ivymjhfgiyl.png)