En el mundo de la informática existen muchos cambios disruptivos que producen un antes y un después. Uno de esos cambios es el mundo de los contenedores. Antes de los contenedores, las máquinas virtuales gobernaban el ámbito de los entornos de desarrollo y producción. Sin embargo, con la tecnología de contenedores este hecho ha cambiado, pues ofrecen más ventajas que las máquinas virtuales en cuestiones de despliegue, aislamiento (del inglés isolation) frente a virtualización y migración entre servidores.

Sin duda alguna, docker es el rey en tecnología de contenedores. Gracias a la API unificada de docker, miles de proyectos pudieron utilizar la tecnología de contenedores sin reparar en cómo funcionan internamente y simplificando el proceso de creación y gestión del propio contenedor. Este hecho, apoyado por la multitud de sistemas que nacieron derivados de docker, como el propio Kubernetes, hizo tremendamente popular la tecnología de contenedores frente a otros sistemas.

Sin embargo, aunque docker sí ofrece sencillez en cuanto a la API que conecta con el sistema operativo y permite su uso sin necesidad de conocer en profundidad el mismo, es posible que genere dudas sobre algunos de sus comandos cuando un desarrollador, generalmente uno novel, se enfrenta a sus primeros Dockerfile. En concreto me estoy refiriendo a ¿qué diferencia hay entre el comando de docker CMD y el comando ENTRYPOINT?. En este artículo voy a intentar explicártelo para que no vuelvas a tener dudas cuando te toque enfrentarte a esta cuestión.

Repaso rápido entre imagen y contenedor en Docker

Permíteme un repaso rápido para ponernos en situación. En el mundo de los contenedores de docker debemos diferenciar dos conceptos “similares” pero que tienen funciones diferentes: las imágenes y los contenedores.

Las imágenes son plantillas de solo lectura que definen cómo será el contenedor que las use. Esta imagen contiene todo el código, las dependencias que el código necesita y cualquier otra librería. Por otro lado, el contenedor de docker es la instanciación de la imagen. En palabras más sencillas, un contenedor es un entorno vivo (de lectura y escritura) que se crea a partir del código y de las configuraciones que definimos en la imagen que hemos creado. Por hacer un símil, la imagen es una definición de una clase en cualquier lenguaje de programación (por ejemplo, class MyImagen{}), mientras que el contenedor es como hacer un “new MyImagen()“.

El fichero en el que damos a docker las instrucciones para crear la imagen se llama Dockerfile y es aquí donde usamos los comandos CMD y ENTRYPOINT. Estos comandos le indican a docker qué instrucciones deberá ejecutar el contenedor cuando se arranque y parecen muy similares, pero cada uno tiene sus particularidades y están destinados a diferentes tareas. Si bien, conocerlos te permitirá crear contenedores más fáciles de personalizar de cara a su arranque.

Qué función hace el comando ENTRYPOINT en docker

El comando ENTRYPOINT simplemente define qué fichero ejecutable se lanzará cuando arranque el contenedor. Por defecto, si no se indica ninguno (en tu imagen si es desde cero o en la imagen en la que te estés basando) es /bin/sh -c.

Esto significa que, salvo que lo sobrescribas, cuando arranques tu contenedor con docker run, todo lo que coloques al final de esa orden será añadido al ENTRYPOINT. Es decir, que si ejecutas docker run [...] date desde tu consola, el contenedor iniciará con /bin/sh -c date.

El parámetro -c que se le pasa a /bin/sh consigue que el intérprete (sh en este caso) lea el comando desde el string que acompaña en vez de leerlo desde la entrada estándar. Dicho de otro modo, sh procesará el texto que acompaña a ese -c como si lo hubieras escrito en una terminal.

Ya sabemos que ENTRYPOINT representa el ejecutable que se debe lanzar cuando arranque el contenedor, pero debemos tener mucho cuidado con la sintaxis de esta función. La sintaxis permitida soporta tanto la forma ENTRYPOINT ["command"] (también llamada exec form) y la forma ENTRYPOINT command (llamada shell form). Sin embargo, siempre se recomienda exec form, debido a que no se ejecuta como un proceso de la shell.

En cualquier caso, para clarificar el concepto, el comando ENTRYPOINT define la instrucción principal que se ejecutará en el contenedor de docker cuando arranque. Solo el ejecutable, no los parámetros. Es ahí cuando entra el comando CMD.

Qué función tiene CMD en docker

Por otro lado, la sentencia CMD tiene como función añadir parámetros al ENTRYPOINT que hayas definido en el fichero Dockerfile de tu proyecto. Además, cabe la posibilidad de anular el CMD; es decir, no pasarle parámetros. Estos hechos, unido a que es relativamente más fácil sobrescribir un CMD al arrancar un contenedor, hace que sea más sencillo personalizarlo para diferentes tareas.

Por resumir, mientras que el ENTRYPOINT define la aplicación principal, CMD define los argumentos. Pero una imagen siempre vale más que mil palabras. Vamos a verlo con un ejemplo. Imagina que queremos dockerizar una aplicación para que muestre por pantalla un texto. La clave es que el ENTRYPOINT es echo porque siempre debe inicializarse mientras que el texto a mostrar es el CMD porque es variable.

FROM alpine:3.14
ENTRYPOINT ["echo"]
CMD ["Hola usuario desconocido"]

Construyamos la imagen con docker build -t nombre_original . y arranquémosla con docker run -it --rm nombre_original. Cuando ejecutemos el contenedor creado con esta imagen, devolverá Hola usuario desconocido porque tiene como punto de entrada “echo” y como parámetro por defecto (o CMD) “Hola usuario desconocido”. Sin embargo, podemos desde la misma instrucción de docker run personalizar (o sobrescribir) el CMD por defecto: si ejecutamos docker run -it --rm nombre_original "Hola amigo" y analizamos qué se muestra por pantalla, veremos que se ha sobrescrito el CMD.

Vale, el ejemplo anterior es demasiado simple, pero ahora vamos a aplicarlo a la vida real. Un contenedor contiene una aplicación principal (porque seguimos siempre las reglas de docker en donde nos dicen que un contenedor = una aplicación). Nuestra aplicación será en ENTRYPOINT y por defecto no tendrá configurado un CMD. Esto permite que se cargue la aplicación en modo normal, por defecto.

Pero si esta aplicación necesita arrancarse en algún modo especial, como por ejemplo “backup” o “migration”, podemos lanzarla usando la misma imagen: docker run image backup. Mientras nuestra app pueda determinar si se ha invocado con parámetros, el contenedor arrancará con el modo backup de nuestra app.

Conclusión

Ya hemos visto que el comando ENTRYPOINT y CMD tiene muchas similitudes, pero una diferencia de concepto principal: ENTRYPOINT se usa como el ejecutable a ejecutar (valga la redundancia) y CMD se usa para añadir (o no) parámetros a dicho ejecutable. Esto nos permite personalizar cómo arrancará el contenedor de docker o, mejor dicho, cómo queremos que arranque el contenedor de docker.

Si bien es posible usar docker usando solo ENTRYPOINT o usando solo CMD (pues ENTRYPOINT tiene un valor por defecto), lo recomendable es ser cuidadosos y usarlos para lo que fueron diseñados. Por ello, te invito a que a partir de ahora, los uses para lo que fueron diseñados.