.. _docker-const: Construcción ************ La función de :program:`Docker` no es sólo aislar una aplicación tomando una imagen y alterando su configuración, sino también la de distribuir aplicaciones propias o servicios preconfigurados. Esto implica generar nuestras propias imágenes para que terceros sean capaces de crear sus propios contenedores a partir de ellas. Para ello necesitamos: - Un registro con el que poder hacer la distribución y :kbd:`docker push` para exportar la imagen desde nuestro repositorio local. Lo más sencillo es usar una cuenta gratuita en el propio `Docker Hub`_ .. note:: Alternativamente, se puede funcionar sin registro creando localmente archivos :ref:`tar ` con las imágenes usando :manpage:`docker-save` e importar tales archivos con :manpage:`docker-load`. La distribución de las imáegnes, no obstante, se complica mucho. - Generar en sí la imagen para lo cual hay dos vías: + Convertir en imagen un contenedor. + La más apropiada que consiste en construir la imagen indicando a :program:`Docker` cómo llevarlo a cabo. .. _docker-commit: Conversión a imágenes ===================== El primer método consiste en convertir un contenedor en una imagen, para lo cual se usa :kbd:`docker commit`. Para ilustrarlo consideremos que queremos generar una imagen de :ref:`nginx ` en la que servimos una página estática que se alberga en :file:`/srv/www`:: # docker run -d --name="nginx-test" -p 80:80 nginx:alpine f7fc06d5ed70910bcf837c427775c894a0b21d4fc22c1f50ffcc9d079d910e12 # docker exec -ti nginx-test sh / # mkdir /srv/www / # cat > /srv/www/index.html Página de prueba

Página de prueba

/ # sed -ir '1,/root\s/{s:root.*:root /srv/www\;:}' /etc/nginx/conf.d/default.conf / # exit # docker stop nginx-test En este punto tenemos el contenedor con los cambios que pretendíamos. Ahora es momento de generar a partir de él la imagen y ponerle un nombre y un a etiqueta:: # docker commit -a "Perico de los Palotes " \ -m "nginx que sirve el contenido de /srv/www" nginx-test # docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE da408ad65b98 About a minute ago 19.7M # docker image tag da408ad65b98 miusuario/nginx-srv:v1 # docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE miusuario/nginx-srv v1 da408ad65b98 4 minutes ago 19.7M Obsérvese que hemos dado al contenedor el nombre *miusuario/nginx-test* donde "miusuario" es el usuario del que disponemos en `Docker Hub`_. .. _docker-push: .. _docker-login: Finalmente, ya podemos subir la imagen al repositorio\ [#]_:: # docker login -u miusuario # docker push miusuario/nginx-srv:v1 Es interesante que comparemos la imagen original (*nginx:alpine*) con nuestra imagen, cuya diferencia es únicamente una simple manipulación de ficheros. Eso se traduce en que el almacenamiento crea una capa adicional:: # docker image inspect -f '{{json .RootFS.Layers}}' nginx:alpine ["sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", "sha256:6f23cf4d16deb170554e0237bec12e4fb488c78222a20e172462ba4776affb3d"] # docker image inspect -f '{{json .RootFS.Layers}}' miusuario/nginx-srv:v1 ["sha256:531743b7098cb2aaf615641007a129173f63ed86ca32fe7b5a246a1c47286028", "sha256:6f23cf4d16deb170554e0237bec12e4fb488c78222a20e172462ba4776affb3d" "sha256:eb7259d6e25c133fc5f662d2eb25b02c24194f58694f948fa596c722d0fbcc81"] .. _docker-build: Generación de imágenes ====================== La otra alternativa es más limpia y más recomendable, y consiste en generar una imagen indicando cuáles son las acciones que deben llevarse a cabo para obtener la imagen deseada. Para ello debe crear un directorio de trabajo y dentro de él un fichero :file:`Dockerfile` con las instrucciones. Para ilustrar el procedimiento crearemos una imagen equivalente a la generada bajo el epígrafe anterior:: # mkdir /tmp/nginx-test # cd /tmp/nginx-text # cat > index.html Página de prueba

Página de prueba

# vim Dockerfile Y dentro de este fichero :file:`Dockerfile` escribiremos lo siguiente: .. code-block:: docker FROM nginx:alpine RUN sed -ir '1,/root\s/{s:root.*:root /srv/www\;:}' /etc/nginx/conf.d/default.conf ;\ mkdir /srv/www COPY index.html /srv/www No es excesivamente complicado entender qué hace casa línea. Sí es interesante tener presente que cada directiva :kbd:`RUN` o COPY :kbd:`COPY` genera una capa distinta para el driver de almacenamiento y, en consecuencia, es conveniente minimizarlas. Por ese motivo la directiva :kbd:`RUN` contiene dos órdenes, en vez de haber definido dos directivas :kbd:`RUN` para cada orden. Con todo, ya solo falta generar la imagen:: # docker build -t miusuario/nginx-test:v1b . y :ref:`subir la imagen `. Es importante tener presente también que partir de la imagen *nginx:alpine* no sólo implica partir del sistema de archivos de ese contenedor, sino también del resto de configuración. Por ese motivo, no es necesario indicar qué deseamos exponer el puerto **80** o que queremos que se ejecute :ref:`nginx `. Por eso, aunque a efectos prácticos no tenga sentido alguno, ilustremos cómo obtener una imagen semejante partiendo de la imagen original Alpine_, lo cual implica instalar :ref:`nginx ` y hacer una configuración adicional. Para ello tomemos otro directorio de trabajo en el que incluyamos un :file:`Dockerfile`:: # mkdir /tmp/nginx-test.2 # cd /tmp/nginx-test.2 # mkdir -p archives/srv/www archives/etc/nginx/conf.d # vim archives/srv/www/index.html # vim archives/etc/nginx/conf.d/default.conf # vim Dockerfile El fichero :file:`index.html` puede ser el mismo que el anterior; :file:`default.conf` puede ser, simplemente, este: .. code-block:: nginx server { listen 80; root /srv/www; try_files $uri $uri/ =404; } y :file:`Dockerfile`, el siguiente: .. code-block:: docker FROM alpine RUN apk update && apk add nginx && \ ln -s /dev/stdout /var/log/nginx/access.log;\ ln -s /dev/stderr /var/log/nginx/error.log;\ mkdir /srv/www;\ mkdir /run/nginx COPY ./archives / EXPOSE 80/tcp CMD ["nginx", "-g", "daemon off;"] Con lo cual, ya podemos generar la imagen:: # docker build -t miusuario/nginx-test:v1c . cuyo almacenamiento debe tener tres capas: la generada por la imagen de Alpine_, la generada por la directiva :kbd:`RUN` y la generada por la directiva :kbd:`COPY`. .. note:: El demonio usa una caché que almacena los resultados intermedios, por lo que puede interesar durante la fase de desarrollo de la imagen, descomponer las acciones en múltiples directivas :kbd:`RUN` y solo al final minimizar el número de capas. Las principales directivas que contiene un archivo :manpage:`Dockerfile` son las siguientes: .. code-block:: docker # Preámbulo FROM imagenbase ARG VERSION=3.9 ARG PASSWORD LABEL clave1=valor1 clave2="valor 2" LABEL clave3=valor3 # Construcción RUN ordenes... WORKDIR directorio/de/trabajo/en/la/construccion COPY anfitrion/archivo http://servidor/archivo contenedor/directorio/ ADD semejante a copy, pero desempaqueta si el archivo es un paquete. # Compartición EXPOSE 80/tcp 443/tcp VOLUME /tmp # Ejecución USER www-data ENV DEBUG=True ENTRYPOINT ["orden", "param1", "param2"] CMD ["param3", "param4"] :kbd:`ARG` Permite definir variables para tiempo de compilación (:ref:`docker build `) que pueden usarse en otras directivas: Si las variables se pasan a través de la opción :kbd:`--build-arg` de :ref:`docker build ` se sobrescribirán los valores indicados en el archivo. Por ejemplo\ [#]_: .. code-block:: docker ARG VERSION FROM alpine${VERSION:+:$VERSION} En este caso, la orden:: # docker build -t miusuario/alpine:propio . usará la última versión de alpine_, puesto que no se ha especificado versión. En cambio:: # docker build -t miusuario/nginx-test:v1v --build-arg VERSION=3.9 . utilizará la versión *3.9*. :kbd:`WORKDIR` Define cuál es el directorio de trabajo dentro del contenedor. :kbd:`VOLUME` permite definir :ref:`volúmenes anónimos ` que se crearán automáticamente al generar un contenedor a partir de la imagen sin que sea necesario declararlos con :kbd:`-v`. Por ejemplo, si hubiéramos querido hacer permanentes los registros podríamos haber utilizado este :file:`Dockerfile`: .. code-block:: docker FROM alpine RUN apk update && apk add nginx && \ mkdir /srv/www;\ mkdir /run/nginx COPY ./archives / VOLUME /var/lob/nginx EXPOSE 80/tcp CMD ["nginx", "-g", "daemon off;"] :kbd:`ENV` Permite definir variables de entorno. Por ejemplo: .. code-block:: docker ENV DEBUG=True :kbd:`ENTRYPOINT` Define una orden (con argumentos si así se desea) que se eejcutará al arrancar el contenedor. Esta orden no es sobrescrita por los argumentos posicionales de :ref:`docker run `, sino que tales argumentos se añaden a la definición de :kbd:`ENTRYPOINT`. Por ejemplo: .. code-block:: FROM alpine ENTRYPOINT ["echo"] Indefectiblemente ejecutará :ref:`echo ` al arrancar el contenedor:: $ docker build -t alpine:lorito . $ docker run alpine:lorito Mensaje del contenedor Mensaje del contenedor :kbd:`CMD` Es semejante a :kbd:`ENTRYPOINT`, pero los argumentos de :ref:`docker run ` sobrescriben lo que se haya dispuesto en la directiva. Si también se dispuso una directiva :kbd:`ENTRYPOINT`, se añade a la orden que determina ésta. .. note:: *DockerHub* permite `asociar a una imagen un repositorio de GitHub `_ para que al actualizar el repositorio, se regenere automáticamente la imagen. .. rubric:: Cnstrucción *multi-stage* Hay por último un concepto bastante interesante que es el de la cosntrucción multistage de una *imagen* que se requiere cuando para crear una imagen necesitamos la creación de otras imágenes previas intermedias. Por ejemplo, imaginemos que en nuestra imagen necesitamos incluir un programa compilado con gcc_. Dado que nuestra imagen necesita únicamente el ejecutable, no tiene sentido que incluyamos el compilador en ella, sino solamente el resultado de la compilación; así que pudemos crear una imagen intermedia previa con el compilador que genere el código compilado y la imagen definitiva que, simplemente, obtenga el resultado de esta compilación de esa primera imagen. Para ilustrarlo supongamos que creamos un directorio de trabajo:: # mkdir /tmp/multistage # cd /tmp/multistage # vim app.c # vim Dockerfile donde el código fuente :file:`app.c` es simplemente el código del "Hola, mundo": .. code-block:: c #include int main() { printf("Hola, mundo\n"); return 0; } y el :file:`Dockerfile` este: .. code-block:: docker FROM gcc as builder WORKDIR /tmp COPY app.c . RUN gcc -static -o app app.c FROM alpine COPY --from=builder /tmp/app /bin CMD ["app"] Como resulta de utlizar este :file:`Dockerfile`, obtendremos una imagen basada en Alpine_ que contiene y ejecuta nuestra aplicación compilada. .. rubric:: Notas al pie .. [#] Mediante :kbd:`docker login` se puede especificar cuál es el servidor de registro, si este no es `Docker Hub`_. .. [#] :kbd:`FROM` debe ser la primera directiva, pero desde la `versión 17.05 `_ es posible utilizar antes :kbd:`ARG` para facilitar la versión de base que se usa al crear el contenedor. .. _Docker Hub: https://hub.docker.com/ .. _Alpine: https://alpinelinux.org/ .. _gcc: https://gcc.gnu.org/