logocloudstic.png

Resumen

Docker es un proyecto open source creado en 2013 y que ha supuesto una revolución para el desarrollo y despliegue de operaciones. Docker abstrae el hardware y el sistema operativo del host ejecutando las aplicaciones en contenedores, compartimentos aislados que contienen todos los recursos para una aplicación o servicio.

En este seminario veremos cómo usar Docker para el desarrollo de aplicaciones sencillas, aprendiendo a crear servicios con Docker Compose y concer la técnica de desarrollo de aplicaciones multicontenedor con Docker Compose

Objetivos
  • Conocer los componentes básicos de Docker

  • Crear contenedores a partir de imágenes de Docker Hub

  • Aprender a usar Dockerfile para la creación de imágenes

  • Usar volúmenes para almacenamiento persistente

  • Usar Docker Compose para construir entornos de contenedores

  • Aprender la técnica de desarrollo de aplicaciones multicontenedor con Docker Compose

1. Conceptos básicos

1.1. Qué es Docker

  • Docker es una plataforma para que desarrolladores y administradores puedan desarrollar, desplegar y ejecutar aplicaciones en un entorno aislado denominado contenedor.

  • Docker permite separar las aplicaciones de la infraestructura acelerando el proceso de entrega de software a producción.

  • Proyecto open source creado en 2013 que hace uso de LXC (Linux Containers). LXC es un método de virtualización de a nivel de S.O.

Docker permite empaquetar una aplicación con todas sus dependencias para que pueda ser ejecutada en plataformas diferentes. El proceso de despliegue es rápido y repetible.

Basta con ejecutar los tres comandos siguientes en una máquina con Docker instalado para tener una aplicación web que muestra un catálogo de clientes almacenados en una base de datos MySQL.

$ git clone https://github.com/ualmtorres/docker_customer_catalog.git
$ cd docker_customer_catalog
$ docker-compose up -d
CustomerCatalog.png
Figura 1. Aplicación sencilla que muestra un listado de clientes de una base de datos

Para detener el ejemplo anterior, desde la carpeta donde se ha desplegado la aplicación ejecutaremos

$ docker-compose down

Esto eliminará todo lo que se ha creado para este ejemplo (contenedores y red de interconexión de dichos contenedores).

1.2. Docker vs Máquinas virtuales

DockerVsMV.png
Figura 2. Docker vs Máquinas virtuales
  • Una máquina virtual proporciona un entorno con más recursos de los que necesitan la mayoría de las aplicaciones

  • Mayor número de contenedores que de MV en el mismo hardware.

  • Los contenedores se pueden ejecutar en hosts que sean máquinas virtuales.

1.3. Ventajas

  • Ligeros: Los contenedores comparten el kernel del host.

  • Intercambiables: Depliegue de actualizaciones en caliente.

  • Portables: Build local y ejecución en cualquier lugar.

  • Escalables: Aumento y distribución automática de réplicas de contenedores.

  • Apilables: Aumento del stack de servicios en caliente.

Docker supone una revolución en los entornos de CI/CD. Tras la actualización del repositorio de proyecto, se crean contenedores para pasar las pruebas, se construyen las nuevas imágenes y se despliega la nueva versión de la aplicación sin parada del sistema.

1.4. Contenedores e imágenes

  • Un contenedor se lanza ejecutando una imagen.

  • Una imagen es una plantilla con las instrucciones de creación de un contenedor Docker:

    • Código

    • Runtime

    • Librerías

    • Variables de entorno

    • Archivos de configuración

2. Un ejemplo sencillo

2.1. Antes de nada

2.1.1. Instalación:

Obtenemos:

  • Daemon de docker

  • Cliente de docker

  • Docker compose

Configuración de Shared Drices en Docker for Windows

A la hora de configurar volúmenes para poder ofrecer persistencia a los contenedodres, los discos locales han de ser accesibles desde los contenedores creados. En versiones anteriores de Docker Desktop para Windows hay que que configurar Docker Desktop desde la barra de menús.

Seleccionar Settings | Shared Drives y seleccionar las unidades que pueden ser usadas por los contenedores.

SharedDrives.png

2.1.2. Crear cuenta en Docker Hub

Docker Hub es un registro público de imágenes (Lugar donde se almacenan imágenes): https://hub.docker.com

Docker Hub permite en su plan libre tener un repositorio privado de imágenes. También permite automatizar la construcción de imágenes y su despliegue con repositorios GitHub y Bitbucket

2.2. Docker engine

DockerEngine.png
Figura 3. Componentes de Docker Engine

2.3. El Hola mundo

$ docker --version
Docker version 18.09.2, build 6247962

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9bb5a5d4561a: Pull complete
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
....
Registro local y Registros remotos

La creación de un contenedor se realiza a partir de una imagen. Al instalar Docker se habilitará en el sistema un registro o repositorio local de imágenes. En ese registro se almacenarán las imágenes para crear contenedores en nuestro sistema. Inicialmente el registro local de imágenes está vacío.

Cuando ejecutamos docker run para crear y ejecutar un contenedor, Docker busca en el registro local la imagen para crear el contenedor. Si la imagen no está disponible en nuestro registro local de imágenes, Docker la descargará desde un registro remoto (normalmente Docker Hub) a nuestro registro local y desde dicho registro local se creará el contenedor.

Por eso, al ejecutar el comando docker run hello-world anterior, Docker informó que no pudo encontrar la imagen hello-world en el registro local y procedió a la descarga (pull).

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9bb5a5d4561a: Pull complete
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest

Es posible usar otros registros diferentes a Docker Hub e incluso contar con un registro privado de imágenes. Consultar sección Instalar un registro de imágenes propio

2.4. Crear un contenedor Apache

$ docker run -d -p 82:80 --name apache httpd
  • Descarga una imagen Apache (httpd) si no existe localmente, lanza un contenedor y asocia el puerto 82 del host al puerto 80 del contenedor

  • -d lanza el contenedor en modo dettached y libera la terminal

  • -p 82:80 asocia el puerto local 82 al puerto 80 del contenedor

  • -name apache asigna el nombre apache al contenedor para que luego se más fácil interactuar con él (p.e. para ver sus logs, iniciar una sesión interactiva, eliminarlo, …​)

El primer puerto que aparece es el del host y el segundo el del contenedor

También podemos usar el parámetro --name <nombre> para darle un nombre al contenedor. De forma predeterminada, Docker asigna un nombre aleatorio a los contenedores creados. El asignar un nombre a los contenedores creados es útil para poder identificarlos más fácilmente al realizar operaciones de administración (pausa, eliminación, …​)

Apache.png
Figura 4. Contenedor ejecutando Apache

2.5. Funcionamiento básico con Docker

FuncionamientoBasico.png
Figura 5. Funcionamiento básico con Docker

2.6. Imágenes interesantes de Docker

En https://hub.docker.com/explore/ se encuentran las imágenes ordenadas por popularidad. Destacamos:

  • alpine: Linux reducido

  • nginx: Servidor web Nginx

  • httpd: Servidor web Apache

  • ubuntu: Ubuntu

  • redis: Base de datos Redis (clave-valor)

  • mongo: Base de datos MongoDB (documentos)

  • mysql: Base de datos MySQL (relacional)

  • postgres: Base de datos PostgreSQL (relaional)

  • node: Node.js

  • registry: Registro de imágenes on-premise

  • php, elasticsearch, haproxy, wordpress, rabbitmq, python, openjdk, tomcat, jenkins, redmine, flink, spark, …​

2.7. Operaciones sobre contenedores

2.7.1. Mostrar contenedores

$ docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
99f6727e2506        httpd               "httpd-foreground"   4 seconds ago       Up 3 seconds        0.0.0.0:82->80/tcp   apache

Los nombres generados para los contenedores son aleatorios si no se usa el parámetro -name al crearlos.

2.7.2. Detener y reanudar contenedores

Primero, obtener con docker ps el CONTAINER ID o el nombre del contenedor que queremos detener.

$ docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                NAMES
99f6727e2506        httpd               "httpd-foreground"   4 seconds ago       Up 3 seconds        0.0.0.0:82->80/tcp   apache

Detener el contenedor

Podemos detener el contenedor de dos formas, bien a partir de su nombre, que es más sencillo localizarlo, o bien a partir de su CONTAINER ID

  • Detener el contenedor mediante su nombre: docker stop apache

  • Detener el contenedor mediante su nombre: docker stop 99f6727e2506

Al hacer docker ps no se muestran los contenedores que estén detenidos.

Mostrar todos los contenedores, también los detenidos

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                        PORTS                    NAMES
99f6727e2506        httpd               "httpd-foreground"       20 minutes ago      Exited (0) 2 minutes ago                               apache

Reanudar un contenedor

$ docker start apache

También se podría haber reanudado a partir de su CONTAINER ID

$ docker start 99f6727e2506

Tras reanudar el contenedor, vuelve a aparecer cuando hacemos docker ps

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
99f6727e2506        httpd               "httpd-foreground"       9 hours ago         Up 10 seconds       0.0.0.0:82->80/tcp       apache

Detener todos los contenedores en ejecución

Primero obtenenemos los identificadores de los contenedores en ejecución con docker ps -q. Ese comando lo podemos encerrar entre apóstrofes y pasar su resultado a otro comando en la misma línea.

$ docker stop `docker ps -q`

Iniciar una lista de contenedores

$ docker start 99f6727e2506 9811efbf6e45 178c2d03f2e7

2.7.3. Abrir un terminal en un contenedor

Se puede iniciar especificando el nombre del contenedor (apache) o bien su CONTAINER ID. En este ejemplo se abre el terminal usando el CONTAINER ID

$ docker exec -it 99f6727e2506 bash
root@99f6727e2506:/usr/local/apache2#

Git Bash no permite abrir un terminal en un contenedor en Windows. Usar PowerShell o Símbolo del sistema.

Se inicia una sesión como root en el contenedor. En la terminal del contenedor podemos ejecutar comandos del sistema operativo (ls, df -h, cat /proc/cpuinfo, …​). La cantidad y el tipo de comandos dependerá de la imagen usada para crear el contenedor.

2.7.4. Copia de datos

El almacenamiento en un contenedor no es persistente. Se eliminan los datos escritos en él tras su eliminación.

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH

Como ejemplo vamos a crear en nuestro host un archivo index.html y lo copiaremos en el contenedor para sustituir la página de inicio del servidor Apache.

<!-- Ejemplo de archivo index.html -->
<html>
  <body>
    <h1>Docker es una maravilla</h1>
  </body>
</html>

Ahora copiamos el archivo index.html al contenedor con docker cp. Se usará el nombre del contenedor o su CONTAINER ID para hacer referencia al contenedor.

$ docker cp index.html apache:/usr/local/apache2/htdocs/
CambioIndexApache.png
Figura 6. Cambio de página de inicio

2.7.5. Eliminación de un contenedor

Primero paramos el contenedor con docker stop y luego lo eliminamos con docker rm

$ docker stop apache
$ docker rm apache

También se puede eliminar directamente un contenedor en ejecución forzando su eliminación

$ docker rm -f <name-or-container-id>

Al crear un nuevo contenedor a partir de la imagen httpd comprobamos que la página de inicio modificada anteriormente se eliminó junto al contenedor eliminado.

$ docker run -d -p 82:80 httpd

Podemos eliminar todos los contenedores creados a partir de una imagen con la secuencia de comandos siguiente (p.e. eliminar todos los contenedores creados a partir de una imagen wordpress)

$ docker rm -f `docker ps -a | grep "wordpress" | awk '{print $1}'`

Para eliminar todos los contenedores parados ejecutaremos

$ docker container prune

2.8. Resumen de comandos básicos para contenedores

$ docker info
$ docker version
$ docker run <image> // Crea un contenedor a partir de una imagen. Si no tenemos la imagen en local, la descarga
$ docker run -d -p 82:80 --name my-nginx nginx: Crea un contenedor denominado my-nginx en modo deattached accesible desde el puerto 82
$ docker stop|start <name-or-id>: Detiene|Continúa un contenedor
$ docker ps -a: Listado de contenedores (-a muestra también los parados)
$ docker ps -q: Listado de los ids de los contenedores
$ docker stop `docker ps -q`: Para todos los contenedores que devuelve el subcomando `docker ps -q`
$ docker rm <name-or-id>: Borra un contenedor si está parado
$ docker rm -f <name-or-id>: Fuerza el borrado de un contenedor aunque esté parado
$ docker container prune: Elimina todos los contenedores parados
$ docker exec -it <name-or-id> sh: Abre una terminal en el contenedor
$ docker exec <name-or-id> ls: Ejecuta el comando ls en el contenedor para mostrar sus archivos
$ docker cp <name-or-id>:./dockerenv .: Copia el fichero dockerenv del contenedor en nuestro sistema de archivos local
$ docker rm -f `docker ps -a | grep "wordpress" | awk '{print $1}'`: Eliminar todos los contenedores creados a partir de una imagen

Hay muchas Cheat Sheets con resumen de los comandos principales de Docker. Aquí puedes encontrar una que está bastante bien.

3. Bind mounts

Un bind mount permite montar un archivo o directorio de nuestro sistema en un contenedor.

Dado que los contenedores no ofrecen almacenamiento persistente, todo lo que se almacene en ellos se perderá al eliminar el contenedor. A continuación se ilustran algunas situaciones habituales y cómo los bind mounts resultan útiles:

  • Uso de contenedores para el desarrollo de aplicaciones. El código de desarrollo estará en el sistema de archivos de nuestro host y usaremos un bind mount que permite ejecutar en el contenedor el código almacenado en nuestro host.

  • Uso de contenedores de bases de datos. La base de datos tiene que estar en el sistema de archivos de nuestro host y usaremos un bind mount para ejecutar el contenedor con la base de datos almacenada en nuestro host.

Los bind mounts (se puede usar más de uno) se definen en el momento de lanzar el contenedor con el parámetro -v, indicando en primer lugar la ruta del sistema de archivo local y en segundo lugar la ruta del sistema de archivos del contenedor. Por ejemplo

-v /home/ubuntu/webEstaticaBasica:/usr/local/apache2/htdocs

indica un bind mount que monta la carpeta local /home/ubuntu/webEstaticaBasica en la carpeta /usr/local/apache2/htdocs del contenedor.

Example 1. Ilustración de un bind mount con una aplicación web sencilla
  1. Crear una carpeta para este ejemplo y entrar en ella.

  2. Descargar este repositorio. Contiene una web estática sencilla con un único archivo (index.html)

  3. Lanzar un contenedor Apache con un bind mount sobre la carpeta de la aplicación. Asignaremos el nombre my-web al contenedor

$ git clone https://github.com/ualmtorres/webEstaticaBasica.git
$ docker run -d \
    -p 80:80 \ (1)
    --name my-web \ (2)
    -v $(pwd)/webEstaticaBasica:/usr/local/apache2/htdocs \ (3)
    httpd (4)
1 Conservar el puerto original del contenedor
2 Asignar el nombre my-web al contenedor
3 Crear un bind mount entre la carpeta webEstaticaBasica del host a la carpeta /usr/local/apache2/htdocs del contenedor.
4 Usar la imagen httpd de Apache

El resultado sería el siguiente

webEstaticaBasica.png
Bind Mounts con pwd en Windows

Existen varias formas de expresar el directorio actual para facilitar la especificación de la ruta local.

  • Interfaz de comandos de Windows: %cd%

  • Linux y Mac OS: ${PWD} ó $(pwd)

  • Ruta combinada en Git Bash:

    • //"$(pwd)"/webEstaticaBasica

  • Ruta combinada en Powershell:

    • ${PWD}/webEstaticaBasica

Example 2. Ilustración de bind mount con una base de datos MySQL
  1. Crear una carpeta para este ejemplo y entrar en ella.

  2. Descargar este script de inicialización de la base de datos Sporting Goods

    $ curl https://gist.githubusercontent.com/ualmtorres/eb328b653fcc5964f976b22c320dc10f/raw/448b00c44d7102d66077a393dad555585862f923/init.sql --output init.sql
  3. Lanzar un contenedor MySQL con dos bind mounts, uno para inyectar el archivo de inicialización anterior, y otro para la carpeta de datos

$ docker run -d \
    -p 3306:3306 \ (1)
    --name my-mysql \ (2)
    -v $(pwd)/init.sql:/docker-entrypoint-initdb.d/init.sql \ (3)
    -v $(pwd)/data:/var/lib/mysql \ (4)
    -e MYSQL_ROOT_PASSWORD=secret \ (5)
    mysql (6)
1 Conservar los puertos del contenedor
2 Asignar el nombre my-mysql al contenedor
3 bind mount para pasar un script SQL de inicialización de una base de datos
4 bind mount para almacenar los datos del contenedor localmente en la carpeta data
5 Inicialización de la contraseña del root. Se configura inicializando una variable de entorno en el contenedor.
6 Usar la imagen de MySQL

En Windows (con Git Bash o PowerShell) sería:

docker run -d \
    -p 3306:3306 \
    --name my-mysql \
    -v /"$(pwd)"/init.sql:/docker-entrypoint-initdb.d/init.sql \
    -v /"$(pwd)"/data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=secret \
    mysql

En Windows (con Git Bash o PowerShell) el comando sería

$ docker run -d -p 80:80 --name my-web -v /"$(pwd)"/webEstaticaBasica:/usr/local/apache2/htdocs httpd

A partir de este ejemplo, usando un cliente local de MySQL se podría acceder al contenedor directamente como localhost.

Si no se dispone de un cliente MySQL para ver si se ha inicializado correctamente la base de datos, se puede iniciar una sesión interactiva en el contenedor creado

$ docker exec -it my-mysql bash (1)

root@3c51f13a1046:/# mysql -u root -p (2)
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.19 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases; (3)
+--------------------+
| Database           |
+--------------------+
| SG                 | (4)
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)
1 Iniciar una sesión interactiva en el contenedor MySQL
2 Iniciar una sesión como root en MySQL. Recordar la contraseña facilitada al crear el contenedor (secret)
3 Mostrar las bases de datos
4 Base de datos inializada por el script durante la creación del contenedor

4. Creación de imágenes propias

4.1. El Dockerfile

  • Para construir una imagen, se crea un Dockerfile con las instrucciones que especifican lo que va a ir en el entorno, dentro del contenedor (redes, volúmenes, puertos al exterior, archivos que se incluyen.

  • Indica cómo y con qué construir la imagen.

  • Conseguimos que el build de la aplicación definida en el contenedor se comporte de la misma forma en cualquier lugar que se ejecute. Hacemos que sea repetible.

Ejemplo de Dockerfile

# Use an official Python runtime as a parent image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

Fragmento de Dockerfile para construir una imagen con Ubuntu como base y definiendo dónde se montará un volumen externo

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev
WORKDIR /app
ENV DEBUG=True
EXPOSE 80
VOLUME /data (1)
1 Crea un punto de montaje en el contenedor. A la hora de crearlo le haremos corresponder normalmente un directorio del host

4.2. Imágenes

  • Se construyen con docker build a partir de un Dockerfile

  • Se crean en un contexto (normalmente añadiendo archivos del directorio de trabajo del host a la imagen -p.e. el código fuente de la aplicación)

  • Con FROM (normalmente primera instrucción del Dockerfile) inicializamos el sistema de archivos de la imagen (p.e. si es ubuntu obtenemos el sistema de archivos de Ubuntu)

  • Muchas imágenes disponibles en Docker Hub usan Alpine (una distribución ligera de Linux) en lugar de Ubuntu, Fedora o CentOS, debido a su menor tamaño

  • Cada instrucción del Dockerfile genera una nueva capa (con la diferencia) en ese sistema de archivos

  • Al hacer build las capas existentes en el registro local no se vuelven a crear

Una imagen comprimida de Alpine está en torno a los 2 MB, mientras que una imagen comprimida de Ubuntu está entre 40 y 80 MB

4.3. Distribución y ejecución de aplicaciones mediante Dockerfile

Supongamos el siguiente escenario. Contamos con el código de una aplicación disponible en un repositorio. Con la ayuda de Dockerfile podemos crear una imagen local con todo el software y configuración que necesita la aplicación para ejecutarse y crear después un contenedor a partir de dicha imagen. El código de la aplicación podrá ser copiado directamete al contenedor o se podrá montar un volumen en el sistema de archivos del host de forma que se pueda editar el código y no se pierdan los cambios al eliminar el contenedor.

Por tanto, una buena forma de distribuir una aplicación puede ser incluir un Dockerfile con la configuración de software que necesita para ejecutarse junto con el código de la aplicación.

El Dockerfile siguiente contiene los pasos a seguir para:

  • Crear una imagen con Apache, PHP y el framework Phalcon.

  • Incluir el código de la aplicación en el contenedor.

  • Exponer el puerto deseado.

  • Crear un punto de montaje en el contenedor. Este punto de montaje se podrá conectar al sistema de archivos del host con un bind mount al iniciar el contenedor.

FROM ualmtorres/phalcon-apache-ubuntu (1)

ADD webEstaticaBasica /var/www/html (2)

EXPOSE 80 (3)

VOLUME /var/www/html (4)
1 Imagen de base. Incluye Apache, PHP y el framework Phalcon
2 Añade el código de la carpeta webEstaticaBasica del sistema de archivos local a la carpeta /var/www/html del contenedor
3 Informa del puerto en el que escucha el contenedor
4 Ofrece la carpeta /var/www/html como punto de montaje

Si se quieren ofrecer varios puntos de montaje se hará a través de un array.

VOLUME ["/var/www/html", "/var/log/apache2", "/etc/apache2"]

Si se quieren exponer varios puertos se hará enumerando la lista de puertos

EXPOSE 80 443

Configura tus propias imagen de base siguiendo el ejemplo del anexo Configuración de imágenes de base

En un caso sencillo, podríamos reducir a que una aplicación está formada su base de código y el entorno en el que se ejecuta. El código de esa aplicación posiblemente esté en un repositorio Vamos a construir un contenedor a partir del código del repositorio y lo ofrezca al host como un volumen. El proceso a seguir es:

  1. Descargar el repositorio del código de la aplicación.

    $ git clone https://github.com/ualmtorres/webEstaticaBasica.git
  2. Creación del Dockerfile en la carpeta webEstaticaBasica para la construcción de la imagen.

    FROM ualmtorres/phalcon-apache-ubuntu
    
    ADD index.html /var/www/html
    
    EXPOSE 80
    
    VOLUME /var/www/html

    Es buena idea incluir en el repositorio de la aplicación el Dockerfile. Así se contará tanto con el código de la aplicación como con las instrucciones (en forma de Dockerfile) para crear el contenedor de la aplicación con todo lo necesario.

  3. Construcción de la imagen

4.3.1. Construcción de la imagen

El comando docker build crea una imagen nueva usando las instrucciones del Dockerfile.

$ docker build -t ualmtorres/web-estatica-basica:v0 .

  • Con -t definimos una etiqueta o nombre de la imagen. Al construir la imagen pasa a nuestro registro local.

  • Con . indicamos a Docker que utilice el directorio actual como contexto para hace el build

Es buena práctica crear etiquetas con el nombre de usuario el Docker Hub, el nombre de la imagen y la versión.

4.3.2. Listado de imágenes locales

$ docker image list
REPOSITORY                                   TAG                 IMAGE ID            CREATED             SIZE
ualmtorres/web-estatica-basica               v0                  ed27de86aa03        30 seconds ago      309MB

4.4. Ejecución de la aplicación a partir de la imagen creada

Usaremos un bind mount para poder modificar el código de la aplicación y poder conservar los cambios. Posteriormente, podremos subir los cambios de la aplicación al repositorio.

$ docker run -d \
    -p 83:80 \
    --name webEstaticaBasica \
    -v $(pwd):/var/www/html \
    ualmtorres/web-estatica-basica:v0

En Windows sería

docker run -d \
    -p 83:80 \
    --name webEstaticaBasica \
    -v /"$(pwd)":/var/www/html \
    ualmtorres/web-estatica-basica:v0
---

A la hora de distribuir y actualizar aplicaciones podemos incluir la aplicación en la imagen. Con un ciclo de CI/CD tendríamos la aplicación actualizada al actualizar su repositorio.

4.5. Subida de imágenes a Docker Hub

Hasta ahora la imagen creada está en el repositorio local de imágenes. Para subirla a un repositorio remoto, como Docker Hub, primero iniciaremos sesión con docker login y después podremos subir la imagen con el comando siguiente

docker push <user>/<image>:<tag>
  • Al hacer push las capas que ya estén subidas no se vuelven a subir. En cuanto una instrucción del Dockerfile cambia una capa, invalida al resto y se volcerán a crear las capas restantes. Por tanto, colocaremos antes en el Dockerfile lo que menos cambie.

  • Al hacer pull sólo se descargan las capas nuevas.

  • Si cambiamos en el host archivos de los que se incluyen en la imagen se genera una capa nueva invalidando la caché.

$ docker pull wordpress
$ docker run -d -p 80:80 --name my_wordpress wordpress

4.6. Resumen de comandos básicos para imágenes

$ docker login
$ docker run -d nginx
$ docker pull <image>
$ docker image ls: Lista imágenes locales
$ docker inspect <image>: Propiedades de una imagen
$ docker image rm <image>: Elimina una imagen local

5. Aplicaciones con varios contenedores

  • Docker Compose es una herramienta para definir y ejecutar aplicaciones Docker con varios contenedores.

  • De forma predeterminada, usaremos un archivo docker-compose.yml para configurar los servicios de la aplicación. Los servicios son los componentes de la aplicación (p.e. un servicio para el almacenamiento de los datos y otro para el front-end)

  • En un mismo host podemos tener varios entornos aislados. Compose usa nombres de proyecto para mantener a los entornos aislados. De forma predeterminada, Compose usa el nombre del directorio desde donde se lanza la aplicación.

  • docker-compose --version para obtener la versión y saber si está instalado.

  • Instalación desde https://docs.docker.com/compose/install

5.1. Flujo de trabajo básico con Docker Compose

  1. Crear el archivo docker-compose.yml con los servicios de la aplicación (p.e. php y mysql)

  2. Construir y lanzar el entorno en modo dettached con docker-compose up -d

  3. Echar abajo el entorno con docker-compose down

El comando docker-compose down necesita el archivo docker-compose.yml para echar abajo el entorno. Por tanto, siempre tiene que ejecutarse en el directorio donde se lanzó docker-compose up. Sin embargo, en ocasiones cerraremos la ventana desde donde se lanzó el entorno y necesitamos alguna forma de poder recordar dónde estaba.

El script siguiente devuelve los nombres de directorio desde los que se hayan lanzado todos los entornos de Compose que se tengan en ejecución.

docker ps --filter "label=com.docker.compose.project" -q |
    xargs docker inspect \
    --format='{{index .Config.Labels "com.docker.compose.project"}}'|
    sort |
    uniq

A partir de ahí, se trata de buscar en el sistema de archivos del host los nombres de directorio devueltos.

5.2. Comandos básicos para Docker Compose

$ docker-compose up -d      Construye y lanza el entorno en modo dettached
$ docker-compose pull       Descarga las imágenes pero no inicia los contenedores
$ docker-compose rm [-fs]   Borra los contedores parados. Con -fs los detiene y fuerza su borrado

5.3. Ejemplo: Aplicación web (PHP) con soporte de Base de datos (MySQL)

  • Aplicación que muestra un listado de clientes almacenado en una base de datos MySQL.

  • Podemos distribuirla con un repositorio que incluya una carpeta html con la aplicación PHP.

  • Al iniciar el servicio MySQL se ejecutará un script de inicialización de la base de datos.

  • Usaremos volúmenes externos para la base de datos y para la aplicación web para asegurar la persistencia de los cambios.

Comencemos clonando el repositorio de la aplicación:

$ git clone https://github.com/ualmtorres/docker_customer_catalog.git

En ese repositorio se encuentra:

  • Un archivo docker-compose.yml que configura dos servicios: un servicio para almacenamiento de datos con MySQL y otro servicio para la aplicación PHP.

  • Una carpeta html con la aplicación. Esta carpeta será la que monte la aplicación PHP de forma que el código de la aplicación no esté almacenada en el contenedor.

  • Un script SQL init.sql que inicializa la base de datos de nuestra aplicación. La base de datos se almacena en nuestro host, garantizando almacenamiento persistente.

docker-compose.yml

version: '2'
services:
  mysql:
    container_name: mysql (1)
    restart: always
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 'secret' # TODO: Change this
    ports:
      - "3306:3306"
    volumes:
      - ./data:/var/lib/mysql (2)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql (3)
  php:
    container_name: php
    restart: always
    image: ualmtorres/phalcon-apache-ubuntu
    ports:
      - "80:80"
    volumes:
      - ./html:/var/www/html (4)
1 Nombre del contenedor. Este será el nombre que usará el contendor de la aplicación PHP para poder acceder a este contenedor
2 Montar una carpeta data de nuestro host en la ruta en la que el servicio mysql almacena la base de datos
3 La imagen de MySQL ejecutará al inicio cualquier script que encuentre en /docker-entrypoint-initdb.d/
4 Montar una carpeta html de nuestro host en la ruta en la que el servicio php almacena la aplicación

Para lanzar la aplicación multicontenedor ejecutaremos el comando

$ cd docker_customer_catalog
$ docker-compose up -d

Este archivo despliega un contenedor denominado mysql en el puerto 3306 del host y otro contenedor denominado php en el puerto 80 del host. No podrá haber otros contenedores en el host, aunque estén parados` con esos nombres y esos puertos deberán estar libres.

Esto creará un contenedor para cada servicio y una red para que los contenedores de los servicios se puedan comunicar entre sí. El nombre de la red vendrá determinado por el nombre del directorio desde donde se lance Docker Compose. Los contenedores podrán referenciarse unos a otros por el nombre del contenedor.

Para probar esto, abrir una sesión interactiva en el contenedor PHP y hacer ping mysql

$ docker exec -it php bash

root@61d202a9f1bf:/app# ping mysql (1)

PING mysql (192.168.32.3) 56(84) bytes of data.
64 bytes from mysql.docker_customer_catalog_default (192.168.32.3): icmp_seq=1 ttl=64 time=0.132 ms
64 bytes from mysql.docker_customer_catalog_default (192.168.32.3): icmp_seq=2 ttl=64 time=0.185 ms
64 bytes from mysql.docker_customer_catalog_default (192.168.32.3): icmp_seq=3 ttl=64 time=0.120 ms
1 Se usa el nombre del contenedor asignado en docker-compose.yml para referenciarlo

Aunque en el docker-compose.yml es posible asignar un nombre diferente al contenedor que al servicio al que pertenece, se recomienda usar el mismo nombre. Los contenedores desplegados con Docker Compose realmente usan el nombre de los contenedores para referenciarse unos a otros. Por eso, para evitar confusiones, mejor usar el mismo nombre para servicio y para contenedor.

Example 3. Archivo index.php con el código de la aplicación
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>Web PHP-MySQL con Docker</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>
<body>
  <div class = "container">
    <div class="jumbotron">
      <h1 class="display-4">Docker app</h1>
      <p class="lead">Ejemplo de aplicacion PHP y MySQL con contenedores</p>
      <hr class="my-4">
      <p>Usa un contenedor para Apache/PHP y otro para MySQL con almacenamiento de aplicación y de datos en volúmenes externos</p>
    </div>
    <table class="table table-striped table-responsive">
      <thead>
        <tr>
          <th>Name</th>
          <th>Credit Rating</th>
          <th>Address</th>
          <th>City</th>
          <th>State</th>
          <th>Country</th>
          <th>Zip</th>
        </tr>
      </thead>
      <tbody>
        <?php
        $conexion = new mysqli("mysql", "root", "secret", "SG"); (1)

        $cadenaSQL = "select * from s_customer";
        $resultado = $conexion->query($cadenaSQL);

        while ($fila = $resultado->fetch_object()) {
         echo "<tr><td> " .$fila->name .
         "</td><td>" . $fila->credit_rating .
         "</td><td>" . $fila->address .
         "</td><td>" . $fila->city .
         "</td><td>" . $fila->state .
         "</td><td>" . $fila->country .
         "</td><td>" . $fila->zip_code .
         "</td></tr>";
       }

       ?>
     </tbody>
   </table>
 </div>
 <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
 <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
</body>
</html>
1 El nombre con el que se accede a la base de datos MySQL es el nombre del contenedor usado en docker-compose.yml

Archivo init.sql para inicializar la base de datos Descargar init.sql

La aplicación quedará disponible

CustomerCatalog.png
Figura 7. Aplicación web PHP que muestra listado de clientes almacenados en MySQL

6. Desarrollo con Docker Compose

A la hora de desarrollar una aplicación con varios contenedores que tienen que trabajar de forma coordinada (p.e. una aplicación de frontend y backend) usaremos Docker Compose creando un servicio para cada componente (p.e. uno para el front y otro para la API). Cada uno de esos componentes es susceptible de empaquetarse como una imagen Docker. Por tanto, cada componente debería incluir su Dockerfile para construir su imagen correspondiente. Así, la estructura recomendada para desarrollar con Docker Compose podría ser algo parecido a esto:

.
├── docker-compose.yml (1)
├── servicio-1 (2)
│   ├── Dockerfile (3)
│   └── Base de código del servicio 1
├── servicio-2
│   ├── Dockerfile
│   └── Base de código del servicio 2
...
└── servicio-n
    ├── Dockerfile
    └── Base de código del servicio n
1 Archivo con la configuración de ejecución
2 Directorio para cada servicio/componente
3 Instrucciones para la creación de la imagen del servicio

Para ilustrar esto usaremos un ejemplo ficticio que desarrolle una API con calificaciones y un front para presentar los datos de la API. A continuación se muestran los pasos:

$ mkdir calificaciones
$ cd calificaciones

6.1. Desarrollo de la API

Se trata de una API en PHP con Phalcon. La API contiene los datos directamente en JSON para no añadir otro componente de bases de datos al ejemplo, conseguir un ejemplo más sencillo y no perdernos en los detalles. El objetivo es ver cómo desarrollar con Docker Compose.

  1. Desde el directorio del proyecto crear una carpeta api para la API.

  2. Crear un archivo .htaccess que es necesario para rescribir las rutas en las peticiones a la API

  3. Crear un archivo index.php con el código de la API.

  4. Crear el Dockerfile con las instrucciones para crear la imagen de la API

    FROM ualmtorres/phalcon-apache-ubuntu (1)
    
    ADD . /var/www/html (2)
    
    VOLUME /var/www/html (3)
    
    EXPOSE 80 (4)
    1 Imagen de base para la ejecución de la API. Incluye Apache, PHP y el framework Phalcon
    2 Incluir el código de la API en la carpeta de publicación de Apache
    3 Crear un punto de montaje para que se pueda tener la base de código fuera del contenedor
    4 Indicar el puerto por el que escucha el contenedor (80 por ser Apache)
  5. Crear en el directorio de la aplicación (un nivel por encima de api) el archivo docker-compose.yml para poder ejecutar la API. El archivo docker-compose.yml lo creamos un directorio por encima de api porque la especificación del volumen del código de la API en el archivo docker-compose.yml se hace un nivel por encima del directorio api

    version: '2'
    services:
      calificaciones-api:
        container_name: calificaciones-api
        restart: always
        image: ualmtorres/phalcon-apache-ubuntu (1)
        ports:
          - "80:80"
        volumes:
          - ./api:/var/www/html (2)
    1 Imagen base para ejecutar la API
    2 Volumen en el host montado en el directorio /var/www/html del contenedor

Si desplegamos el docker-compose.yml veremos la API ejecutándose en el puerto 80 (http://localhost/calification)

calificaciones api.png

6.2. Desarrollo del front

  1. Desde el directorio del proyecto crear una carpeta front para el código del front.

  2. Crear un archivo index.php con el código del front.

  3. Crear el Dockerfile con las instrucciones para crear la imagen del front

    FROM php:7.2-apache (1)
    
    ADD . /var/www/html (2)
    
    VOLUME /var/www/html (3)
    
    EXPOSE 80
    1 Imagen de base para la ejecución del front. Incluye Apache y PHP
    2 Incluir el código del front en la carpeta de publicación de Apache
    3 Crear un punto de montaje para que se pueda tener la base de código fuera del contenedor
    4 Indicar el puerto por el que escucha el contenedor (80 por ser Apache)

Modificar el archivo docker-compose.yml (un nivel por encima de front) añadiéndole el servicio para poder ejecutar el front.

version: '2'
services:
  calificaciones-api:
    container_name: calificaciones-api
    restart: always
    image: ualmtorres/phalcon-apache-ubuntu
    ports:
      - "80:80"
    volumes:
      - ./api:/var/www/html
  calificaciones-front: (1)
    container_name: calificaciones-front
    restart: always
    image: php:7.2-apache (2)
    ports:
      - "8088:80" (3)
    volumes:
      - ./front:/var/www/html (4)
1 Servicio para el front
2 Imagen base para ejecutar el front
3 Mapping de puertos para el front (El 80 está ocupado con la API)
4 Volumen en el host montado en el directorio /var/www/html del contenedor

Si volvemos a desplegar el docker-compose.yml con docker-compose up -d, se mantiene intacto lo ya desplegado y se despliegan las modificaciones. El front se verá ejecutándose en el puerto 8088

calificaciones front.png

6.3. Construcción de las imágenes

Una vez desarrollados los servicios de la aplicación podríamos proceder a la construcción de sus imágenes. Se trataría de un proceso manual en el que ejecutaríamos el comando docker build en cada una de los directorios de los servicios desarrollados

  • En el directorio de la API: docker build -t ualmtorres/calificaciones-api:v0 .

  • En el directorio del front: docker build -t ualmtorres/calificaciones-front:v0 .

6.4. Prueba de la aplicación

Si ahora quisiéramos desplegar la aplicación con Docker Compose nos encontramos con el problema que el archivo docker-compose.yml usa como imagen base las imágenes base creadas para el proceso de desarrollo. Sin embargo, ahora queremos desplegar la aplicación usando las imágenes creadas para la API y el front. Este problema lo podemos solventar rápidamente creando otro archivo para Docker Compose (p.e. docker-compose-produccion.yml) y lanzar Docker Compose con ese archivo. docker-compose-produccion.yml incluiría las imágenes creadas montando igualmente los directorios de las bases de código de la API y del front.

version: '2'
services:
  calificaciones-api:
    container_name: calificaciones-api
    restart: always
    image: ualmtorres/calificaciones-api:v0 (1)
    ports:
      - "80:80"
    volumes:
      - ./api:/var/www/html
  calificaciones-front:
    container_name: calificaciones-front
    restart: always
    image: ualmtorres/calificaciones-front:v0 (2)
    ports:
      - "8088:80"
    volumes:
      - ./front:/var/www/html
1 Imagen de la API como base
2 Imagen del front como base

6.5. Docker Compose con la opción build

El tener varios archivos para Docker Compose puede resultar confuso y en algunas ocasiones nos puede llevar a errores pensando que hemos usado uno cuando realmente estábamos usando el otro. Además, la creación de imágenes en un despliegue con gran cantidad de servicios implica la construcción manual de cada una de sus imágenes.

Para evitar esta situación podemos usar la opción build. La opción build crea las imágenes a partir de los archivos Dockerfile que indiquemos y posteriormente crea el contenedor a partir de esa imagen. Para hacer esto, incluiremos dos particularidades en el archivo docker-compose.yml con respecto a los que hemos estado usando hasta ahora:

  • Incluir una opción build que indica el contexto donde encontrar el Dockerfile para crear la imagen del servicio.

  • El nombre de la imagen ahora no es el nombre de la imagen de base para crear el servicio, sino el nombre que se dará a la imagen construida para desplegar el servicio.

Example 4. Archivo docker-compose.yml para la construcción de imágenes
version: '2'
services:
  calificaciones-api:
    build: (1)
      context: ./api (2)
    container_name: calificaciones-api
    restart: always
    image: ualmtorres/calificaciones-api:v0 (3)
    ports:
      - "80:80"
    volumes:
      - ./api:/var/www/html

  calificaciones-front:
    build: (4)
      context: ./front (5)
    container_name: calificaciones-front
    restart: always
    image: ualmtorres/calificaciones-front:v0 (6)
    ports:
      - "8088:80"
    volumes:
      - ./front:/var/www/html
1 Opción build para construir la imagen
2 Ruta del Dockerfile de la API
3 Nombre para la imagen de la API
4 Opción build para construir la imagen
5 Ruta del Dockerfile del front
6 Nombre para la imagen del front

Para comprobar el funcionamiento de este nuevo procedimiento, eliminaremos todo rastro de lo anterior echando abajo el despliegue anterior y borrando las imágenes creadas anteriormente.

$ docker-compose down
$ docker image rm ualmtorres/calificaciones-api:v0
$ docker image rm ualmtorres/calificaciones-front:v0

A continuación, basta con volver a levantar el entorno com docker-compose up -d y veremos como se construyen las imágenes para el despliegue de los contenedores y los servicios de Docker Compose funcionan correctamte.

calificaciones front.png

Cuando introduzcamos cambios en la base de código y queramos volver a desplegarlos sobre las imágenes podremos ejecutar cualquiera de estos dos comandos:

$ docker-compose build
$ docker-compose up --build

7. Otras cosas interesantes

7.1. Configuración de imágenes de base

Aquí se muestra cómo configurar una imagen con Ubuntu 18.04, PHP y el framework Phalcon. También se muestran los dos archivos auxiliares necesarios para la configuración de Apache que necesita Phalcon.

Example 5. Dockerfile
FROM ubuntu:18.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install -yq --no-install-recommends \
    apt-utils \
    curl \
    # Install git
    git \
    # Install apache
    apache2 \
    # Install last version of PHP
    php \
    libapache2-mod-php \
    php-mcrypt \
    php-mysql \
    php-curl \
    nano \
    ca-certificates \
    locales \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

RUN curl -s "https://packagecloud.io/install/repositories/phalcon/stable/script.deb.sh" | /bin/bash

RUN apt-get install -y php7.0-phalcon

# Set locales
RUN locale-gen en_US.UTF-8 en_GB.UTF-8 es_ES.UTF-8

COPY conf/apache2.conf /etc/apache2/apache2.conf
COPY conf/dir.conf /etc/apache2/mods-available/dir.conf

RUN a2enmod rewrite
RUN a2enmod headers
RUN service apache2 restart

EXPOSE 80 443

WORKDIR /var/www/html

RUN rm /var/www/html/index.html

HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl -f http://localhost || exit 1

CMD apachectl -D FOREGROUND
Example 6. Archivo conf/apache2.conf
Mutex file:${APACHE_LOCK_DIR} default

PidFile ${APACHE_PID_FILE}

Timeout 300

KeepAlive On

MaxKeepAliveRequests 100

KeepAliveTimeout 5

User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

HostnameLookups Off

ErrorLog ${APACHE_LOG_DIR}/error.log

LogLevel warn

IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf

Include ports.conf

<Directory />
	Options FollowSymLinks
	AllowOverride None
	Require all denied
</Directory>

<Directory /usr/share>
	AllowOverride None
	Require all granted
</Directory>

<Directory /var/www/>
	Options Indexes FollowSymLinks
	AllowOverride All
	Require all granted
	Header set Access-Control-Allow-Origin "*"
</Directory>

AccessFileName .htaccess

<FilesMatch "^\.ht">
	Require all denied
</FilesMatch>

LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

IncludeOptional conf-enabled/*.conf

IncludeOptional sites-enabled/*.conf
Example 7. Archivo conf/dir.conf
<IfModule mod_dir.c>
	DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm
</IfModule>

7.2. Microservicios y contenedores

Con microservicios:

  • Establecemos un contrato, normalmente mediante una API REST, versionada para no romper funcionalidad a usuarios anteriores

  • Ocupan un tamaño reducido y suelen realizar una tarea muy concreta

    • Autenticación,

    • API REST. Toda la API vs cada endpoint

    • Estadísticas consumo de recursos

    • Exportar salida a central de logs

    • …​

  • Dockerizar con cabeza

    • Comenzamos pasando todo nuestro sistema o MV a un contenedor Docker. Con sólo eso ya conseguimos ejecutar nuestra sistema en distintas máquinas con distintos SO y configuraciones.

    • No intentar pasar de una vez de aplicación monlítica a microservicios diminutos

KeepCalmAndUseDocker.png

7.3. El ecosistema Docker

DockerEcosystem.png

7.4. Instalar un registro de imágenes propio

Es posible tener un registro propio para imágenes por cuestiones de seguridad y confidencialidad. Veamos cómo crear un registro propio mediante contenedores (uno para el registro en sí y otro cono Web UI).

El ejemplo será obtener una imagen Alpine de Docker Hub y subirla a nuestro propio registro

// En el servidor (p.e. 192.168.65.103)
$ docker run -d -p 5000:5000 --restart always --name registry registry:2
$ docker run \
  -d \
  -e ENV_DOCKER_REGISTRY_HOST=192.168.65.103 \
  -e ENV_DOCKER_REGISTRY_PORT=5000 \
  -p 8080:80 \
  konradkleine/docker-registry-frontend:v2

// En nuestro equipo
$ docker pull alpine (1)
$ docker image list | grep alpine (2)
$ docker tag 3e640a41799a 192.168.65.103:5000/alpine (3)
$ docker push 192.168.65.103:5000/alpine (4)
1 Descargar una imagen de prueba de Alpine al registro local
2 Obtener el identificador de la imagen Alpine descargada (p.e. 3e640a41799a)
3 La imagen se etiqueta añadiéndole como prefijo host:puerto de nuestro registro
4 Subida de la imagen al registro
RegistroPropio.png

Harbor es una opción muy interesante para disponer de un registro propio de imágenes. Permite definir reglas de control de acceso, analizar imágenes en busca de vulnerabilidades y añadir una firma de confianza a las imágenes.

Para subir una imagen a Harbor:

  1. Es necesario pertenecer a un proyecto

  2. Iniciar sesión

    $ docker login <harbor-server>
  3. Etiquetar la imagen siguiendo este patrón

    $ docker tag <image> <server>/<project>/<image>[:<version>]
  4. Subir imagen

    $ docker push <server>/<project>/<image>[:<version>]