7 consejos para escribir Containerfiles

También conocidos como Dockerfiles

Adrian Ramos
Publicado el 5 de abril de 2023

En este artículo os cuento algunas buenas prácticas que sigo para desarrollar Dockerfiles/Containerfiles y que sean seguros y fáciles de mantener. Al igual que el código, los Dockerfiles/Containerfiles pueden evolucionar con el tiempo, y es importante estructurarlos de forma que se faciliten actualizaciones. Además, es crucial garantizar que las imágenes de contenedores resultantes sean seguras y no introduzcan vulnerabilidades innecesarias que puedan derivar en futuros ataques. Para optimizar el almacenamiento y la velocidad de red, es importante que las imágenes resultantes sean lo más compactas posible. Un Dockerfile/Containerfile bien elaborado debe ser fácil de comprender y utilizar.

7 consejos para escribir Dockerfiles/Containerfiles

1. Utiliza la última versión de la imagen base, pero nunca con la etiqueta latest

Para garantizar la seguridad de tus imágenes de contenedor, es importante utilizar la última versión de la imagen base, que debería incluir los últimos parches de seguridad en el momento de su compilación, pero no lo hagas con la etiqueta latest.

NO HAY QUE HACER ESTO: haproxy:latest.

Esta etiqueta siempre hace referencia a la última versión de la imagen publicada, lo cual “es BIEN”, pero nos hace perder la noción de la versión empleada. Es por ello que recomiendo usar la versión en “números”. Además, para mantener un repositorio de código (git) de Dockerfiles/Containerfiles, es ideal que la versión de la imagen base aparezca escrita, y con esto tenemos un control de versiones pleno.

Entonces, cuando esté disponible una nueva versión de la imagen base, debemos averiguar el alias de la etiqueta “latest” para modificar nuestro Containerfile y reconstruir la imagen para incorporar las últimas correcciones.

SÍ HAY QUE HACER ESTO: haproxy:2.7.6.

Adicional a esto, es ideal realizar un análisis periódico de vulnerabilidades para comprobar si existen vulnerabilidades de seguridad conocidas en la imagen base, dando por sentado que eso ya lo hacemos con la aplicación que incorporamos a la imagen final 😜.

2. Utiliza imágenes base adaptadas al uso final

Al seleccionar una imagen base para el Dockerfile/Containerfile, recomiendo usar la imagen más pequeña posible, por ejemplo que no incluya un sistema operativo completo, con utilidades de sistema preinstaladas, etc. En su lugar, usa una imagen pequeña e instala, durante el proceso de compilación del Dockerfile/Containerfile, sólo las herramientas y utilidades necesarias.

Aquí también recomiendo el uso de imágenes alpine, que están basadas en Alpine Linux, una distribución basada en musl y BusyBox, que tiene como objetivo ser ligera y segura por defecto sin dejar de ser útil para tareas de propósito general.

Con esto, podemos minimizar los riesgos de ataque de la imagen final y reducir la probabilidad de que se introduzcan vulnerabilidades. Además, el tamaño final de la misma será más compacto, por lo que obtenemos beneficio doble.

3. Usa Containerfiles multietapas (multi-stage)

Para optimizar el tamaño de las imágenes finales, recomiendo el uso de Dockerfiles/Containerfiles multietapas (o multi-stage).

Esto consiste en dividir el proceso de compilación en varias etapas (o capas) y descartar los artefactos resultantes innecesarios de cada etapa para producir una imagen final de menor tamaño. Por ejemplo, si desarrollas una aplicación que necesita compilación, podrías utilizar una primera etapa para compilar y construir la aplicación, y otra etapa para copiar sólo los artefactos binarios compilados y dependencias en la imagen, ignorando cualquier archivo superfluo (típicos archivos temporales que se generan y ocupan espacio). Del mismo modo, para una aplicación ReactJS, podrías realizar los pasos de instalación y compilación de npm en una etapa, y luego copiar los artefactos compilados resultantes en una etapa posterior. Esto ayuda a minimizar el tamaño de la imagen final y a que quede super organizada.

He aquí un ejemplo:

# Stage 1
###################

FROM node:19.8.1-alpine AS builder
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . ./
RUN npm run build

#Stage 2
###################

FROM nginx:1.23.4-alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=builder /app/build .
ENTRYPOINT ["nginx", "-g", "daemon off;"]

4. Usar .dockerignore o .containerignore

El archivo .containerignore (.dockerignore para los amantes del legado) es el gran ignorado.

Podman y Docker normalmente buscan un archivo llamado .containerignore o .dockerignore en el directorio raíz del “contexto de construcción” antes de construir una imagen. Si uno de estos archivos existe, el motor del contenedor ajusta el contexto excluyendo cualquier archivo y directorio que coincida con los patrones especificados. Esto impide que esos archivos se añadan a la imagen mediante las instrucciones ADD o COPY.

Para especificar patrones de exclusión, podemos crear un archivo .containerignore o .dockerignore en el directorio raíz del contexto. Este archivo debe contener, en cada línea, patrones que coincidan con los nombres de los archivos a excluir, similar a los file globs utilizados en Unix. El directorio raíz del contexto se considera tanto el directorio de trabajo como el directorio raíz. Por ejemplo, los patrones /carpeta1/archivo1 y carpeta1/archivo1 excluirían un archivo o directorio llamado “archivo1” en el subdirectorio “carpeta1” de la ruta y no excluiría nada más.

Si una línea en este archivo comienza con el símbolo “#”, se considera un comentario y será ignorado por el motor del contenedor.

5. Organiza los comandos en el Dockerfile/Containerfile

Para optimizar el proceso de compilación de imágenes, recomiendo organizar los comandos en un orden concreto, especialmente el comando COPY. Las líneas del Dockerfile/Containerfile que tienen más probabilidad de cambiar el contenido de la imagen deben colocarse al final para mejorar la velocidad de compilación. Esto se hace para aprovechar el sistema de capas y caché del compilador de imágenes, que almacena cada resultado de compilación como una capa para su reutilización en futuras compilaciones.

No obstante, si el resultado de un comando cambia con respecto a la última ejecución en caché, el resto de comandos del Containerfile también se ejecutarán de nuevo en cascada (aún cuando el resultado de la ejecución de ese comando concreto sea el mismo).

Por todo esto, recomiendo llevar a la parte inferior del Containerfile el comando COPY, ya que suele estar sujeto a cambios frecuentes. Así, aprovecharemos al máximo la caché del compilador de imágenes.

6. Compacta todos los comandos RUN que puedas

Con esto quiero decir que es mejor hacer esto:

RUN dnf update -y && dnf install vim sl -y

Que esto:

RUN dnf update -y
RUN dnf install vim -y
RUN dnf install sl -y

Este último ejemplo generará una capa de compilación por cada comando, mientras que el primer ejemplo generará una única capa, por lo que ocupará menos espacio en caché y tardará menos tiempo en compilar. También ocupará menos espacio la imagen final resultante.

7. Emplea USER

Para mejorar la seguridad de las imágenes es importante especificar un usuario no root en el Dockerfile/Containerfile, y para ello utilizamos el comando USER. Es crucial configurar los permisos de archivos y directorios para que coincidan con el usuario, si no tendremos problemas de acceso. Por defecto, cuando usamos Docker, las imágenes de contenedores se ejecutan como root, ya que el propio demonio Docker se ejecuta con privilegios de root. Esto podría suponer un riesgo para la seguridad, ya que cualquier proceso con malas intenciones también tendría acceso root y podría comprometer el equipo host.

Existe una alternativa, que recomiendo ENCARECIDAMENTE: USAR PODMAN.

Este está diseñado para no correr como demonio, lo que lo hace intrínsecamente más seguro. Cuando se corre un contenedor, se hace con el usuario que lo corre, directamente, con los permisos que le corresponden.

Containerfiles y Podman

En todo el artículo hago mención constante a Containerfiles. Esto es porque me gustaría que se normalice el uso de este nombre, en detrimento de Dockerfiles.

Containerfiles hace referencia a lo mismo que Dockerfiles, pero de la mano de Podman, herramienta open source para desarrollar, gestionar y ejecutar contenedores con una arquitectura inclusiva, sin daemons y más segura.

Esto es una breve comparativa entre Podman y Docker:

Docker Podman
Daemon Usa demonio Arquitectura sin demonio
Root Corre contenedores solo como root Corre contenedores con root y sin root
Imágenes Construye imágenes Se apoya en otros proyectos como Buildah para construir imágenes
Plataforma monolítica No
Docker-swarm Soportado No soportado
Docker-compose Soportado Soportado
Corre nativo en Linux, macOS, Windows Linux, macOS, Windows (en WSL)

Pero esto es harina para otro día…

Si continúas navegando consideramos que aceptas la política del sitio. Más información X Cerrar