Creación de Dockerfiles y Construcción de Imágenes

Docker permite descargar imágenes, iniciar contenedores y ejecutar aplicaciones dentro de ellos mediante comandos manuales. Sin embargo, este enfoque es repetitivo e ineficiente, ya que requiere introducir comandos uno por uno. Para solucionar este problema, se desarrollaron los Dockerfiles: scripts que contienen una secuencia de instrucciones que Docker puede ejecutar automáticamente. Esto facilita la construcción, modificación y actualización repetitiva de imágenes.

Un Dockerfile es un script interpretado por Docker, compuesto por instrucciones que corresponden a comandos de Linux. Docker traduce estas instrucciones en comandos reales del sistema operativo, resolviendo las dependencias entre ellos de manera similar a un Makefile. Al leer el Dockerfile, Docker genera una imagen personalizada. A diferencia de las imágenes, que son "cajas negras", los Dockerfiles son scripts transparentes que muestran claramente cómo se crea la imagen. Cuando se necesita personalizar requisitos adicionales, basta con agregar o modificar instrucciones en el Dockerfile y regenerar la imagen, evitando la necesidad de escribir comandos manualmente.

En resumen, un Dockerfile se compone de cuatro partes: información de la imagen base, información del creador, instrucciones de operación de la imagen e instrucciones de ejecución al iniciar el contenedor.

Esencialmente, las operaciones manuales como descargar imágenes, ejecutarlas, instalar entornos de aplicación o ejecutar aplicaciones se transforman en un script siguiendo el formato de Dockerfile. Al ejecutar este script, se completan automáticamente todos los pasos que antes requerían intervención manual.

Reglas de Escritura e Instrucciones de Dockerfile

Las instrucciones de un Dockerfile son insensibles a mayúsculas y minúsculas, aunque se recomienda usar mayúsculas. Utilizan # para comentarios y solo permiten una instrucción por línea, aunque cada una puede llevar múltiples parámetros.

Las instrucciones se clasifican en dos tipos: instrucciones de construcción (que se usan para crear imágenes y no se ejecutan en los contenedores resultantes) e instrucciones de configuración (que se ejecutan en los contenedores cuando se inician).

  1. **FROM (especificar imagen base)**Es una instrucción de construcción obligatoria que debe aparecer al principio del Dockerfile. Todas las instrucciones siguientes dependen de esta imagen base especificada, que puede estar en un repositorio remoto o local.

Formatos:

FROM <imagen>

Especifica la última versión modificada de la imagen.

FROM <imagen>:<etiqueta>

Especifica una versión específica de la imagen por su etiqueta.

Ejemplo para instalar vim en la imagen:

RUN yum install -y vim

  1. **MAINTAINER (especificar información del creador)**Instrucción de construcción que registra información del creador en la imagen. Al ejecutar docker inspect en la imagen, se mostrará esta información.

Formato:

MAINTAINER <nombre>

  1. **RUN (para instalar software)**Instrucción de construcción que ejecuta cualquier comando compatible con la imagen base. Por ejemplo, si la imagen base es Ubuntu, solo se pueden usar comandos de Ubuntu.

Formatos:

RUN <comando>

RUN ["ejecutable", "param1", "param2", ...]

  1. **CMD (configurar la operación al iniciar el contenedor)**Instrucción de configuración que especifica la operación a ejecutar al iniciar el contenedor. Puede ser un script personalizado o un comando del sistema. Solo puede aparecer una vez en el archivo; si hay múltiples, solo se ejecuta la última.

Formatos:

CMD ["ejecutable","param1","param2"]
CMD comando param1 param2

Cuando se especifica ENTRYPOINT, se usa el siguiente formato:

CMD ["param1","param2"]

Donde ENTRYPOINT especifica la ruta de un script o ejecutable, y los parámetros se pasan desde CMD.

  1. **ENTRYPOINT (configurar la operación al iniciar el contenedor)**Instrucción de configuración que especifica el comando a ejecutar al iniciar el contenedor. Puede especificarse varias veces, pero solo el último tiene efecto.

Formatos:

ENTRYPOINT ["ejecutable", "param1", "param2"]
ENTRYPOINT comando param1 param2

Puede usarse de dos maneras: solo o combinado con CMD. Cuando se usa solo, si también se especifica CMD como un comando completo, se sobrescriben mutuamente, quedando solo el último.

Cuando se usa con CMD para especificar parámetros predeterminados para ENTRYPOINT, CMD solo debe contener parámetros, no un comando completo:

FROM ubuntu
CMD ["-l"]
ENTRYPOINT ["/usr/bin/ls"]

  1. **USER (configurar el usuario del contenedor)**Instrucción de configuración que establece el usuario para iniciar el contenedor. Por defecto es el usuario root.

Ejemplo:

ENTRYPOINT ["memcached"]
USER daemon

O:

ENTRYPOINT ["memcached", "-u", "daemon"]

  1. **EXPOSE (especificar puertos para mapear)**Instrucción de configuración que declara los puertos del contenedor que se mapearán al host. Para completar el mapeo, se debe usar el indicador -p al ejecutar el contenedor.

Formato:

EXPOSE <puerto> [<puerto>...]

Ejemplos:

# Mapear un puerto
EXPOSE 8080
docker run -p 8080 imagen

# Mapear múltiples puertos
EXPOSE 8080 8081 8082
docker run -p 8080 -p 8081 -p 8082 imagen

# Mapear a puertos específicos del host
docker run -p 8888:8080 -p 8889:8081 -p 8890:8082 imagen

  1. **ENV (configurar variables de entorno)**Instrucción de configuración que establece variables de entorno para el contenedor en tiempo de ejecución.

Formato:

ENV <clave> <valor>

Las variables pueden ser utilizadas por instrucciones posteriores y son visibles con docker inspect o al ejecutar docker run --env clave=valor.

Ejemplo para Java:

ENV JAVA_HOME /ruta/a/java

  1. **ADD (copiar archivos al contenedor)**Instrucción de construcción que copia archivos del host al contenedor. Si el archivo es un formato comprimido reconocido, Docker lo descomprimirá automáticamente.

Formato:

ADD <origen> <destino>

Donde <origen> puede ser un archivo, directorio o URL remota, y <destino> es una ruta absoluta en el contenedor.

  1. **VOLUME (especificar puntos de montaje)**Instrucción de configuración que permite a un directorio en el contenedor tener almacenamiento persistente. Esto es útil porque los contenedores usan sistemas de archivos efímeros como AUFS, donde los datos se pierden al detener el contenedor.

Formato:

VOLUME ["<punto_montaje>"]

Ejemplo:

FROM base
VOLUME ["/datos"]

Otro contenedor puede usar este volumen compartido:

docker run -t -i --volumes-from container1 imagen2 bash

  1. **WORKDIR (cambiar directorio)**Instrucción de configuración que cambia el directorio de trabajo. Afecta a las instrucciones RUN, CMD y ENTRYPOINT posteriores.

Formato:

WORKDIR /ruta/al/directorio

Ejemplo:

WORKDIR /dir1
WORKDIR dir2
RUN vim archivo.txt

  1. **ONBUILD (ejecutar en imágenes hijas)**Instrucción que especifica comandos que se ejecutarán no en la construcción actual, sino en las imágenes que heredan de esta.

Formato:

ONBUILD <instrucción_dockerfile>

Ejemplos de Uso de Dockerfile

  1. Despliegue de entorno JDK1.7 + Tomcat7 usando Dockerfile
# Verificar imágenes disponibles en el host
docker images

# Crear Dockerfile
cat > Dockerfile << EOF
# Imagen base
FROM ubuntu:latest

# Información del mantenedor
MAINTAINER "Nombre del Mantenedor"

# Actualizar fuentes del sistema
RUN echo "deb http://mirror.example.com/ubuntu/ focal main restricted universe multiverse" > /etc/apt/sources.list
RUN apt-get update

# Instalar curl
RUN apt-get -y install curl

# Instalar JDK 7
RUN cd /tmp && curl -L 'http://download.oracle.com/otn-pub/java/jdk/7u80-b15/jdk-7u80-linux-x64.tar.gz' -H 'Cookie: oraclelicense=accept-securebackup-cookie' | tar -xz
RUN mkdir -p /usr/lib/jvm
RUN mv /tmp/jdk1.7.0_80/ /usr/lib/jvm/java-7-oracle/

# Establecer JDK 7 como predeterminado
RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-7-oracle/bin/java 300
RUN update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-7-oracle/bin/javac 300

# Configurar variables de entorno
ENV JAVA_HOME /usr/lib/jvm/java-7-oracle/

# Instalar Tomcat7
RUN cd /tmp && curl -L 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.82/bin/apache-tomcat-7.0.82.tar.gz' | tar -xz
RUN mv /tmp/apache-tomcat-7.0.82/ /opt/tomcat7/

# Configurar variables de entorno de Tomcat
ENV CATALINA_HOME /opt/tomcat7
ENV PATH $PATH:$CATALINA_HOME/bin

# Agregar script de inicio
ADD tomcat7.sh /etc/init.d/tomcat7
RUN chmod 755 /etc/init.d/tomcat7

# Exponer puertos
EXPOSE 8080

# Comando de inicio predeterminado
ENTRYPOINT service tomcat7 start && tail -f /opt/tomcat7/logs/catalina.out
EOF

# Crear script de inicio para Tomcat
cat > tomcat7.sh << EOF
export JAVA_HOME=/usr/lib/jvm/java-7-oracle/
export TOMCAT_HOME=/opt/tomcat7

case \$1 in
start)
  sh \$TOMCAT_HOME/bin/startup.sh
;;
stop)
  sh \$TOMCAT_HOME/bin/shutdown.sh
;;
restart)
  sh \$TOMCAT_HOME/bin/shutdown.sh
  sh \$TOMCAT_HOME/bin/startup.sh
;;
esac
exit 0
EOF

# Construir la imagen
docker build -t mi-jdk-tomcat --rm=true .

# Verificar que la imagen se creó correctamente
docker images

# Ejecutar contenedor
docker run -ti -d --name mi-tomcat -p 8080:8080 mi-jdk-tomcat /bin/bash

# Verificar el contenedor en ejecución
docker ps

# Verificar que Tomcat está funcionando
docker exec -mi-tomcat /bin/bash
ps -ef|grep tomcat

Es importante evitar modificar directamente los archivos de aplicaciones dentro de los contenedores, ya que si el contenedor se daña y no se puede reiniciar, los datos se perderán. En su lugar, se debe realizar un mapeo de directorios al crear el contenedor. Esto permite compartir archivos o directorios del host con el contenedor, facilitando el mantenimiento.

Para ejecutar el contenedor con mapeo de directorios:

# Copiar directorio de aplicaciones del contenedor al host
docker cp mi-tomcat:/opt/tomcat7/webapps /opt/

# Ejecutar contenedor con mapeo de directorios
docker run -ti -d --name mi-tomcat -v /opt/webapps:/opt/tomcat7/webapps -p 8080:8080 mi-jdk-tomcat /bin/bash

  1. Creación de una imagen de Tomcat con CentOS como imagen base
# Verificar imágenes disponibles
docker images

# Asegurarse de tener los paquetes de Tomcat y Java descargados
# En el directorio /usr/local/src del host:
ls
# jdk-8u221-linux-x64.tar.gz  apache-tomcat-9.0.31.tar.gz

# Crear Dockerfile
cat > Dockerfile << EOF
# Usar imagen base CentOS
FROM centos:latest

# Información del mantenedor
MAINTAINER "Nombre del Mantenedor"

# Copiar JDK y Tomcat a la imagen
ADD ./jdk-8u221-linux-x64.tar.gz /usr/local
ADD ./apache-tomcat-9.0.31.tar.gz /usr/local

# Configurar variables de entorno
ENV JAVA_HOME /usr/local/jdk1.8.0_221
ENV PATH $JAVA_HOME/bin:$PATH
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.31

# Definir punto de entrada al iniciar el contenedor
ENTRYPOINT $CATALINA_HOME/bin/startup.sh && tail -F $CATALINA_HOME/logs/catalina.out
EOF

# Construir la imagen
docker build -t mi-tomcat-centos --rm=true .

# Verificar que la imagen se creó correctamente
docker images

# Ejecutar contenedor
docker run -ti -d --name tomcat-prueba -p 8888:8080 mi-tomcat-centos /bin/bash

# Verificar el contenedor en ejecución
docker ps

# Verificar que Tomcat está funcionando
docker exec -it tomcat-prueba /bin/bash
ps -ef|grep tomcat

  1. Creación de una imagen de Nginx usando Dockerfile
# Verificar imágenes disponibles
docker images

# Crear Dockerfile
cat > Dockerfile << EOF
# Usar imagen base CentOS
FROM centos:latest

# Información del mantenedor
MAINTAINER "Nombre del Mantenedor"

# Instalar dependencias necesarias para Nginx
RUN yum install -y pcre pcre-devel openssl openssl-devel gcc gcc-c++ wget vim net-tools

# Crear usuario para Nginx
RUN useradd nginx -M -s /sbin/nologin

# Descargar y compilar Nginx
RUN cd /usr/local/src && wget http://nginx.org/download/nginx-1.18.0.tar.gz && tar -zxvf nginx-1.18.0.tar.gz
RUN cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_stub_status_module --with-http_ssl_module && make && make install

# Configurar punto de entrada
ENTRYPOINT /usr/local/nginx/sbin/nginx && tail -f /usr/local/nginx/logs/access.log
EOF

# Construir la imagen
docker build -t mi-nginx --rm=true .

# Verificar que la imagen se creó correctamente
docker images

# Ejecutar contenedor
docker run -ti -d --name nginx-prueba -p 8888:80 mi-nginx /bin/bash

# Verificar el contenedor en ejecución
docker ps

# Verificar que Nginx está funcionando
docker exec -it nginx-prueba /bin/bash
ps -ef|grep nginx

Consideración Importante: Al ejecutar contenedores en modo daemon, ya sea con ENTRYPOINT o CMD, el último comando debe ser un proceso que se ejuta continuamente para evitar que el contenedor se cierre.

Formas inválidas:

ENTRYPOINT /usr/local/nginx/sbin/nginx  # El contenedor se cerrará después de unos segundos
CMD /usr/local/nginx/sbin/nginx        # El contenedor se cerrará después de unos segundos

Formas válidas:

ENTRYPOINT /usr/local/nginx/sbin/nginx && tail -f /usr/local/nginx/logs/access.log  # Asegura que el contenedor permanezca en ejecución
CMD /usr/local/nginx/sbin/nginx && tail -f /usr/local/nginx/logs/access.log          # Asegura que el contenedor permanezca en ejecución

La creación de Dockerfiles para otras aplicaciones sigue un patrón similar.

Etiquetas: Docker dockerfile contenedores imagenes Nginx

Publicado el 6-30 02:55