Desde hace algunos años, Docker se ha convertido en una tecnología de uso habitual para cualquier profesional IT. Programadores, administradores de sistemas y profesionales de la seguridad suelen utilizarlo con frecuencia. Sus beneficios son innegables y ha ayudado a definir las bases de que hoy en día conocemos como la “arquitectura basada en microservicios“. Si bien se “parece” a las tan extendidas y conocidas máquinas virtuales, existen diferencias notables, especialmente relacionadas con el rendimiento y el uso que se le suele dar a un contenedor. De hecho, la siguiente imagen ilustra bastante bien las diferencias entre una máquina virtual clásica y un contenedor en Docker.

Las máquinas virtuales dependen de un Hypervisor y sobre éste, se van montando diferentes sistemas operativos con todo lo que esto implica (librerías, aplicaciones, etc). En el caso de Docker, cada contenedor es gestionado por el servicio de Docker y éste se aprovecha de las características del sistema operativo subyacente.
Esta breve introducción probablemente ya te la conoces y no es el objetivo de este post explicar cómo utilizar las funcionalidades básicas de Docker sino comentar algunas de las mejores prácticas o “tips” para utilizar esta tecnología. No obstante, si estás interesado en aprender las bases y todo lo necesario para dominar Docker y Docker-Compose, te recomiendo que le eches un vistazo al curso en Udemy: Docker y DevOps de Novato a Experto.

Usar ficheros .dockerignore.

Cuando se ejecuta el comando “docker build” todos los ficheros del PATH o URL indicada, se envían al servidor de Docker (Docker Engine). Es recomendable usar un directorio vacío, incluir en él solamente aquello que se vaya a necesitar y posteriormente construir la imagen con docker build. Es común encontrarse que el contenido de dicho directorio crezca en la medida que se van desarrollando o implementando nuevas características, en tal caso para mejorar el rendimiento y no enviar al Docker Engine ficheros innecesarios se puede utilizar un fichero especial llamado .dockerignore. En este fichero se puede incluir por cada línea, un directorio o fichero que se excluirá de la imagen. Por ejemplo, se podrían excluir ficheros con una extensión concreta o nombre, algo parecido a lo que se hace con otras tecnologías como GIT.

No instalar paquetes innecesarios.

Para reducir la complejidad, dependencias, tiempo de creación y tamaño de la imagen, se debe evitar instalar paquetes y servicios innecesarios. Lo recomendable es comenzar con una imagen mínima, como por ejemplo “scratch” o “alpine” e instalar los paquetes que hagan falta partiendo de ella.

Minimizar el número de capas.

Tal como se ha indicado anteriormente, cada comando en el Dockerfile se encarga de generar una capa, la cual se puede borrar o cachear dependiendo de su uso en el proceso de construcción de la imagen. Es recomendable utilizar el menor número de comandos posible para reducir el número de capas intermedias y por ende, el procesamiento que debe realizar el Docker Engine a la hora de generar la imagen final.
En la medida de lo posible y para facilitar futuros cambios, hay que organizar los argumentos de las instrucciones que contengan múltiples líneas en una sola, esto evitará que durante el proceso de construcción de la imagen se deban construir múltiples capas intermedias y por supuesto, hará que el archivo sea más fácil de leer.
Por ejemplo, es mejor hacer esto:

RUN apt-get update && apt-get install -y \
git \
wget \
apache2 \
php5

En lugar de esto:

RUN apt-get update
RUN
apt-get install -y git
RUN apt-get install -y
wget
RUN apt-get install -y apache2
RUN apt-get install -y php5

Limitar los privilegios.

Cuando se inicia un contenedor, Docker se encarga de crear un grupo de namespaces, los cuales simplemente “acordonan” el contenedor para que no interfiera con procesos del sistema host u otros contenedores. Se trata del mecanismo estándar que incluye Docker para evitar que un contenedor mal configurado o con brechas de seguridad pueda ser utilizado para atacar el sistema host. Aunque los namespaces suponen un mecanismo de seguridad potente en Docker, cuando se habla de seguridad siempre hay que intentar aplicar el principio del “último privilegio”, el cual indica que la arquitectura de un sistema debe tener únicamente las características necesarias para su correcto funcionamiento y que dichas funciones deben de estar debidamente limitadas. Quiere decir que los contenedores y todos los elementos que incluyen deben tener un conjunto muy limitado de funciones, servicios, librerías y herramientas: Únicamente aquello que sea estrictamente necesario para su funcionamiento.  Dicho esto, es recomendable ajustar los privilegios del contenedor, en primer lugar evitando que se ejecute como root y en segundo lugar ajustando las “capabilities” con opciones como “–cap-drop” y “–cap-add”.

Esto es todo de momento, en el siguiente post se verán algunas otras recomendaciones de seguridad a la hora de trabajar con Docker.

Un saludo y Happy Hack!
Adastra.