REGRESAR

Metadatos

ACTUALIZADO EL 05/11/2024

Introducción a Docker

En los últimos años Docker se ha convertido en una herramienta indispensable cuando desarrollamos software ya que, por un lado, simplifica la creación de entornos de trabajo compartidos y, por otro lado, permite la creación de imágenes optimizadas para desplegar en producción. Este curso pretende servir de guía introductoria para todo aquel que se adentra al mundo de Docker y quiera incorporarla en su operativa diaria.


Introducción

Este curso se ha diseñado como una guía práctica de introducción a Docker donde se podrá...

  • Comprender los conceptos básicos de Docker.
  • Instalar y configurar Docker en diferentes sistemas operativos.
  • Usar imágenes predefinidas y gestionar contenedores con Docker.
  • Utilizar Docker Compose para orquestar aplicaciones multi-contenedor.
  • Entender y montar puntos de montaje con Docker.
  • Entender y montar redes con Docker.

¿A quién va dirigido?

Esta formación está orientada principalmente a desarrolladores/as de software que deseen adentrarse en la cultura DevOps/GitOps y quieran desarrollar, testear y desplegar aplicaciones con Docker.

Metodología del curso

La metodología de este curso se basa en:

  • Teoría y conceptos, con explicaciones claras y concisas de los puntos clave.
  • Demostraciones, con ejemplos reales y cotidianos.

Herramientas y requisitos

Para aprovechar al máximo este curso se necesita:

  • Un equipo con acceso a Internet.
  • Privilegios de administrador para instalar software.
  • Conocimientos básicos de desarrollo web.


¿Qué es Docker?

Docker es una plataforma de virtualización que permite empaquetar aplicaciones y sus dependencias en contenedores ligeros y portables que pueden ejecutarse en cualquier máquina compatible con Docker.

Grosso modo virtualizar no es sólo emular el funcionamiento de una aplicación en una infraestructura diferente sino, además, poder hacer uso de dicha infraestructura como si fuera propia.

Ventajas de usar Docker

¿Qué nos aporta Docker?

  • Portabilidad: Los contenedores pueden ejecutarse de la misma manera en cualquier máquina que tenga Docker instalado con independencia del sistema operativo del host.
  • Consistencia: Los contenedores aseguran que las aplicaciones funcionen de la misma manera en cualquier entorno (desarrollo, testing, producción, etc.)
  • Aislamiento: Cada contenedor se ejecuta en su propio entorno aislado, lo que ayuda a evitar conflictos entre dependencias y facilita el mantenimiento y depuración de aplicaciones.
  • Escalabilidad: Docker permite escalar aplicaciones fácilmente al añadir o eliminar contenedores al vuelo según sea necesario.
  • Eficiencia: Los contenedores utilizan recursos de manera eficiente, ya que comparten el mismo núcleo del sistema operativo sin la sobrecarga de un hipervisor.

En otras palabras...

Docker nos permite crear un entorno estandarizado de desarrollo que podemos compartir con otros miembros del equipo, simplificando y unificando el proceso de instalación, configuración y puesta en marcha. También nos permite crear entornos específicos para cubrir todo el ciclo de desarrollo de la aplicación (testing, staging...) así como pipelines de CI/CD.

Componentes de Docker

Estos son los componentes de Docker:

Imágenes
Las imágenes son archivos inmutables que actúan como plantillas y que contienen todos los elementos necesarios para ejecutar una aplicación: código, entorno de ejecución, bibliotecas y dependencias, configuraciones y archivos de sistema.
Contenedores
Los contenedores son instancias en ejecución de una imagen de Docker. Los contenedores proporcionan una forma encapsulada y aislada de ejecutar aplicaciones.
Volúmenes
Los volúmenes se utilizan para almacenar datos persistentes fuera del ciclo de vida de los contenedores. Esto permite que los datos sobrevivan cuando los contenedores se detienen o eliminan.
Redes
Una red en Docker es un mecanismo de comunicación para que los contenedores puedan comunicarse entre sí y con el mundo exterior. Esto incluye redes bridge, host y overlay entre otras.

Un servicio es un constructo que representa a un contenedor y al conjunto de aplicaciones que se ejecutan en él. En general dicha relación es 1:1 (un contenedor <=> un servicio) pero no tiene por qué.

Casos de uso frecuentes

Docker se utiliza en una variedad de escenarios entre los que destacan:

Desarrollo local
Docker permite crear entornos de desarrollo consistentes que imitan el entorno de producción, con sus dependencias y configuraciones. Evitando así el efecto "en mi máquina funciona" al garantizar que todos los miembros del equipo utilizan el mismo entorno.
Pruebas y CI/CD
Docker facilita la creación de entornos de prueba aislados para ejecutar pruebas unitarias, de integración y de aceptación. También se integra bien con herramientas de CI/CD para automatizar la construcción, prueba y despliegue de aplicaciones.
Despliegue en producción
Docker permite empaquetar aplicaciones de manera optimizada para su despliegue.
Microservicios
Docker es ideal para arquitecturas de microservicios, ya que cada servicio puede ejecutarse en su propio contenedor aislado, facilitando la gestión, escalabilidad y actualización de cada servicio de manera independiente.


Instalación de Docker

Veamos cómo podemos instalar Docker en nuestro host:

Instalación en MS Windows

A continuación tenemos los pasos para instalar Docker en hosts con sistema operativo MS Windows:

  1. Descargar Docker Desktop para MS Windows desde el sitio oficial de Docker.
  2. Ejecutar el instalador y seguir las instrucciones.
  3. Reiniciar el sistema si fuera necesario.

Instalación en macOS

Si por el contrario nuestro host tiene sistema operativo macOS procedemos de la siguiente manera:

  1. Descargar Docker Desktop para Mac desde el sitio oficial de Docker.
  2. Montar la imagen .dmg y arrastrar Docker a la carpeta de Aplicaciones.
  3. Ejecutar Docker desde la carpeta de Aplicaciones.

Instalación en Linux

En cambio, si nuestro host tiene un sistema operativo basado en Linux instalamos Docker de la siguiente manera:

sudo snap refresh && sudo snap install docker

Verificar la instalación

Para comprobar qué versión de Docker se ha instalado ejecutamos el siguiente comando:

sudo docker --version
Docker version 27.3.1, build ce12230
Por defecto Docker se instala necesitando privilegios de administrador (usuario root)

Comprobar el funcionamiento

Para comprobar que Docker funciona correctamente, ejecutaremos el siguiente contenedor de pruebas:

sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:d211f485f2dd1dee407a80973c8f129f00d54604d2c90732e8e320e5038a0348
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/
Si al ejecutar el comando ves un resultado similar implica que Docker está instalado y funciona correctamente.

Ejecutar Docker como usuario no-root

Para hacer que Docker se ejecute con nuestro usuario sin necesidad de usar el comando sudo, agregaremos nuestro usuario dentro del grupo de ejecución de docker:

sudo addgroup --system docker
sudo adduser $USER docker
newgrp docker
sudo snap disable docker
sudo snap enable docker

Comprobar que Docker se ejecuta con el usuario actual

Para comprobar que todo ha ido bien, volvemos a pedirle a Docker que nos indique la versión instalada pero esta vez sin sudo:

docker --version
Docker version 27.3.1, build ce12230
Si al ejecutar el comando ves un resultado similar implica que tu usuario puede ejecutar Docker correctamente.

Gestión de imágenes

¿Qué es una imágen?

Las imágenes son archivos inmutables que actúan como plantillas y que contienen todos los elementos necesarios para ejecutar una aplicación: código, entorno de ejecución, bibliotecas y dependencias, configuraciones y archivos de sistema.

¿Qué es un container registry?

Un container registry es un servicio o repositorio donde se almacenan y gestionan imágenes de contenedores como las de Docker. Los container registries permiten almacenar, eliminar, actualizar y/o versionar imágenes y distribuirlas de manera eficiente, facilitando el despliegue y la gestión de aplicaciones en diferentes entornos.

Algunos container registries más conocidos son Docker Hub, Google Container Registry y Amazon Elastic Container Registry entre otros.

Descargando imágenes

Veamos cómo descargar imágenes desde un container registry:

Descargando una imágen desde Docker Hub

Para descargar una imágen desde Docker Hub ejecutamos:

docker pull debian
Using default tag: latest
latest: Pulling from library/debian
e756f3fdd6a3: Pull complete
Digest: sha256:3f1d6c17773a45c97bd8f158d665c9709d7b29ed7917ac934086ad96f92e4510
Status: Downloaded newer image for debian:latest
docker.io/library/debian:latest
Si al ejecutar el comando ves un resultado similar, implica que la imágen se ha descargado correctamente desde Docker Hub.
Por defecto Docker usa Docker Hub como container registry. Esto es que, si la imágen no se encuentra disponible en el host, se conectará automáticamente a Docker Hub para localizarla y descargarla.

Descargando una imágen desde Amazon Elastic Container Registry

Veamos cómo descargar una imágen desde AWS ECR:

docker pull public.ecr.aws/ubuntu/ubuntu:24.04_stable
24.04_stable: Pulling from ubuntu/ubuntu
afad30e59d72: Pull complete
Digest: sha256:fb95efe0d22be277f10250f15e5172ec0fe22c37eca2ba55e78b526c447eec23
Status: Downloaded newer image for public.ecr.aws/ubuntu/ubuntu:24.04_stable
public.ecr.aws/ubuntu/ubuntu:24.04_stable
Si al ejecutar el comando ves un resultado similar, implica que la imágen se ha descargado correctamente desde AWS ECR.

Listando imágenes

Para ver las imágenes tenemos disponibles en nuestro host ejecutamos:

docker images
REPOSITORY                      TAG             IMAGE ID        CREATED       SIZE
public.ecr.aws/ubuntu/ubuntu    24.04_stable    fec8bfd95b54    4 days ago    78.1MB

Inspeccionar imágenes

Podemos obtener información de la imágen mediante el siguiente comando:

docker inspect public.ecr.aws/ubuntu/ubuntu:24.04_stable
[
    {
        "Id": "sha256:fec8bfd95b54439b934c5033dc62d79b946291c327814f2d4df181e1d7536806",
        "RepoTags": [
            "public.ecr.aws/ubuntu/ubuntu:24.04_stable"
        ],
        "RepoDigests": [
            "public.ecr.aws/ubuntu/ubuntu@sha256:fb95efe0d22be277f10250f15e5172ec0fe22c37eca2ba55e78b526c447eec23"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2024-10-16T09:25:57.559746466Z",
        "DockerVersion": "24.0.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/bash"
            ],
            "Image": "sha256:3cfd7549922ae8741a1825df62a5d01a4d4439c4c1e5c590074b9da9386b4143",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {
                "org.opencontainers.image.ref.name": "ubuntu",
                "org.opencontainers.image.version": "24.04"
            }
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 78118185,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/95fd8ee1bc7e78f093bc8c1896ba31885b67b8e0ef245ce3d34de2ae0e20c197/merged",
                "UpperDir": "/var/lib/docker/overlay2/95fd8ee1bc7e78f093bc8c1896ba31885b67b8e0ef245ce3d34de2ae0e20c197/diff",
                "WorkDir": "/var/lib/docker/overlay2/95fd8ee1bc7e78f093bc8c1896ba31885b67b8e0ef245ce3d34de2ae0e20c197/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:27123a71e85e0540333291adccf7b9c340d089e8c3717c380d3b75cc8c7df90f"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

O bien, podemos inspeccionar propiedades directamente de la siguiente manera:

docker inspect public.ecr.aws/ubuntu/ubuntu:24.04_stable --format "{{.Id}}"
sha256:fec8bfd95b54439b934c5033dc62d79b946291c327814f2d4df181e1d7536806

O bien, usando el comando jq:

docker inspect public.ecr.aws/ubuntu/ubuntu:24.04_stable | jq ".[] | .Config.Labels"
{
  "org.opencontainers.image.ref.name": "ubuntu",
  "org.opencontainers.image.version": "24.04"
}

Eliminando imágenes

Si deseamos eliminar una imágen procedemos de la siguiente manera:

docker image rm public.ecr.aws/ubuntu/ubuntu:24.04_stable
Untagged: public.ecr.aws/ubuntu/ubuntu:24.04_stable
Untagged: public.ecr.aws/ubuntu/ubuntu@sha256:fb95efe0d22be277f10250f15e5172ec0fe22c37eca2ba55e78b526c447eec23
Deleted: sha256:fec8bfd95b54439b934c5033dc62d79b946291c327814f2d4df181e1d7536806
Deleted: sha256:27123a71e85e0540333291adccf7b9c340d089e8c3717c380d3b75cc8c7df90f

Eliminando imágenes en desuso

Podemos eliminar imágenes en desuso mediante el siguiente comando:

docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Deleted Images:
untagged: 1234567890.dkr.ecr.eu-west-1.amazonaws.com/xxx-php-fpm@sha256:ec3763c30e75e50409550b0b0c7a548498497b929c612bae7a3637ed97e704ea
deleted: sha256:a06a226f4cb17c2fef01bea1c2ea4262d49608e430189fc39a8f635288fd5104
untagged: 1234567890.dkr.ecr.eu-west-1.amazonaws.com/yyy@sha256:9838c36ffbd28daaa01740a1859c89c1efc90b848b0d9df6c0dae9c5cbd2cf2a
deleted: sha256:ce903f9dbb7ecac770b8556475480ff8f03da7a5ecd04dc2bba9ef634ea414ee
deleted: sha256:0beb4ed5bb8d1a41ce83a521821d1ed90538e5adca529e7742edab2801f98e02
deleted: sha256:6b17849287bac7148da163bbc7c22b818a292d04b8055b06e5e7faaa921f560c
deleted: sha256:85073dc3ced7b318506d7ab74c56b9ef503aa0b20dae739947d29f9951f1c43e
deleted: sha256:18c35c6df76e7eeea760b7f3e005fef97b4f72035f9a41c7edfc4ed2f3e6c38d
deleted: sha256:f800fb391dd932425032b8fdb034b65cfcacf60274c8f49fb5ac9a7f7da0eb20
deleted: sha256:771c5ba178c4c4703a88bffd3c169807d7c71ba3337fa5275782cd0ae39a5ff6
deleted: sha256:c31141764f681b21abda0d244cb638bf4244af294908078b87a011cceba2f1c3
deleted: sha256:db64b6885e45b93db1dd43836daf03246a107b9e875c2197fc610cc524516f6e
deleted: sha256:9bcea7c6121b7530a2bf5c54c336204fe3fb0b4b42b155f5062c761c572be81f
deleted: sha256:5a9e7633a4ed02545d7ddca4f6d94cc199beaa390d348806d56707b6db148ae3
deleted: sha256:899333cc3d240c34f0b2be295691b283a501fe01e8bddf04a3cb4567b0053751
deleted: sha256:84fffedbce7f446f736afb3674d9be4b2bfa5ab0a3e48a3367b201af36bf5267
deleted: sha256:931b7ff0cb6f494b27d31a4cbec3efe62ac54676add9c7469560302f1541ecaf

Total reclaimed space: 2.505GB
Este comando elimina imágenes en desuso de nuestro host, liberando espacio en disco y permitiendo a Docker funcionar de manera más eficiente.

Gestión de contenedores

¿Qué es una contenedor?

Los contenedores son instancias en ejecución de una imagen de Docker. Los contenedores proporcionan una forma encapsulada y aislada de ejecutar aplicaciones.

Ejecutando contenedores

Existen varios modos de ejecutar un contenedor:

Ejecutando comandos en contenedores

Para ejecutar un comando en un contenedor efímero, es decir, que se destruye cuando termina de ejecutar el comando, procedemos de la siguiente manera:

docker run --interactive --tty php:8.3.12-cli-alpine php -v
PHP 8.3.12 (cli) (built: Sep 26 2024 22:43:42) (NTS)
    Copyright (c) The PHP Group
    Zend Engine v4.3.12, Copyright (c) Zend Technologies

Aunque la manera más corta de hacerlo es:

docker run -it php:8.3.12-cli-alpine php -v
PHP 8.3.12 (cli) (built: Sep 26 2024 22:43:42) (NTS)
    Copyright (c) The PHP Group
    Zend Engine v4.3.12, Copyright (c) Zend Technologies
Este comando muestra la versión de PHP del contenedor.
En vez de usar las opciones extendidas de Docker (--interactive, --tty) usamos la forma compacta -it

Ejecutando contenedores en segundo plano

Podemos ejecutar un contenedor en segundo plano de la siguiente manera:

docker run -it -d caddy:2.8.4-alpine --name caddy
1c5d59fe9b0f17d58839ad388df6feaa91db129850e0c51e1755a8e36de5944e
La opción de Docker --detach, o en su forma compacta -d, permite a Docker ejecutar un contenedor en segundo plano.
Si no asignamos un nombre al contenedor, Docker nos asignará uno de manera aleatoria. Para tener organizada nuestra infraestructura se recomienda asignar un nombre autoexplicativo a cada contenedor con la opción --name

Parando contenedores

Cuando tenemos contenedores ejecutándose en segundo plano y queremos pararlos, ejecutamos el siguiente comando:

docker stop caddy
caddy
Parar un contenedor consiste en que el demonio de Docker le envíe una señal de gracefull-stop al contenedor, es decir, le solicita al contenedor que termine el comando en curso y acto seguido se para el contenedor.

Matando contenedores

Cuando tenemos parar inmediatamente la ejecución de un contenedor, ejecutamos el siguiente comando:

docker kill caddy
caddy
Cuando paramos inmediatamente un contendor el demonio de Docker mata el proceso de administración del contenedor, sin esperar a que el comando interno finalice.

Listando contenedores

Veamos cómo ver qué contenedores tenemos disponibles en nuestro host:

docker container ls
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                 NAMES
1c5d59fe9b0f   caddy:2.8.4-alpine    "caddy run --config …"   2 seconds ago    Up 2 seconds    80/tcp, 443/tcp, 2019/tcp, 443/udp    caddy

Aunque la forma más rápida es:

docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                 NAMES
1c5d59fe9b0f   caddy:2.8.4-alpine    "caddy run --config …"   2 seconds ago    Up 2 seconds    80/tcp, 443/tcp, 2019/tcp, 443/udp    caddy

Inspeccionando contenedores

Podemos obtener información de un contenedor mediante el siguiente comando:

docker inspect caddy --format "{{.Id}} {{.Name}}"
1c5d59fe9b0f17d58839ad388df6feaa91db129850e0c51e1755a8e36de5944e /caddy

Eliminando contenedores

Para eliminar un contenedor ejecutamos el siguiente comando:

docker rm caddy
caddy

Eliminando contenedores en desuso

Para eliminar los contenedores en desuso ejecutamos el siguiente comando:

docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

Orquestación de servicios

¿Qué es la orquestación?

Se define la orquestación de servicios como el comportamiento de uno o varios servicios dentro de una infraestructura determinada. Dicho comportamiento se basa en Docker Compose, una herramienta que permite definir y ejecutar aplicaciones Docker desde sus contenedores y gestionar los servicios, redes y volúmenes asociados de manera declarativa utilizando archivos YAML.

Ejemplo de orquestación de servicios

Lo primero que debemos definir es un fichero docker-compose.yml:

vi docker-compose.yml

Y definimos como su contenido...

services:
caddy:
image: caddy:2.8.4-alpine
container_name: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443

Y para arrancar el contenedor ejecutamos el siguiente comando:

docker compose up
[+] Running 6/6
 ✔ caddy Pulled                                                                                                                                                                                                 3.7s
   ✔ 43c4264eed91 Already exists                                                                                                                                                                                0.0s
   ✔ 02040ba779ee Already exists                                                                                                                                                                                0.0s
   ✔ c257707c9719 Already exists                                                                                                                                                                                0.0s
   ✔ 06ce39c94b8d Already exists                                                                                                                                                                                0.0s
   ✔ 4f4fb700ef54 Already exists                                                                                                                                                                                0.0s
[+] Running 1/0
 ✔ Container caddy  Created                                                                                                                                                                                     0.0s
Attaching to caddy
caddy  | {"level":"info","ts":1729521363.64274,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
caddy  | {"level":"info","ts":1729521363.6431518,"msg":"adapted config to JSON","adapter":"caddyfile"}
caddy  | {"level":"info","ts":1729521363.6435885,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
caddy  | {"level":"warn","ts":1729521363.6436675,"logger":"http.auto_https","msg":"server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server","server_name":"srv0","http_port":80}
caddy  | {"level":"info","ts":1729521363.6437285,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc000157000"}
caddy  | {"level":"info","ts":1729521363.643841,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
caddy  | {"level":"info","ts":1729521363.6439984,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
caddy  | {"level":"info","ts":1729521363.6440127,"msg":"serving initial configuration"}
caddy  | {"level":"info","ts":1729521363.6490457,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"840cdb6c-b78a-45cb-b673-2ddd332fd900","try_again":1729607763.649045,"try_again_in":86399.999999748}
caddy  | {"level":"info","ts":1729521363.6491115,"logger":"tls","msg":"finished cleaning storage units"}
Docker Compose simplifica la gestión de nuestra infraestructura porque en un único fichero YAML podemos tener múltiples servicios y, como hemos visto, no es necesario definirle el nombre del fichero de nuestra orquestación porque por defecto Docker Compose busca un fichero llamado docker-compose.yml en la ruta actual.

Comprobemos que está en funcionamiento:

docker ps
CONTAINER ID    IMAGE                 COMMAND                   CREATED               STATUS               PORTS                                                                                          NAMES
3645e9f8b288    caddy:2.8.4-alpine    "caddy run --config …"    About a minute ago    Up About a minute    0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 443/udp, 2019/tcp    caddy

Veamos cómo interactuar con nuestro primer servicio:

xdg-open http://127.0.0.1
Este comando abre una URL en el navegador por defecto del host. Si todo ha ido bien, deberíamos ver la pantalla por defecto de Caddy.
Página por defecto de Caddy

Cuando dejemos de necesitarlo, podemos pararlo con el siguiente comando:

docker compose down
[+] Running 2/2
✔ Container caddy      Removed                                                                                                                                                                                 0.2s
✔ Network tmp_default  Removed
Para que el contenedor pueda ser accesible por el host, Docker crea una red por defecto y le asigna un nombre compuesto por el nombre del directorio y el tipo de red.

Gestión de puntos de montaje

¿Qué es un punto de montaje?

Un punto de montaje es un acceso al sistema de ficheros del contenedor y el resto de la infraestructura (otros contenedores, el host, etc.)

Tipos de punto de montaje

Bind mounts

¿Qué es un punto de montaje de tipo bind mount?

Los bind mounts en Docker son una forma de compartir directorios y/o archivos entre el sistema de archivos del host y los contenedores. Esto permite que un contenedor pueda acceder directamente a los datos del host, lo que puede ser útil en diferentes escenarios, como desarrollo, depuración o compartir configuraciones entre el host y el contenedor.

A diferencia de los volúmenes de Docker, que son gestionados por Docker, en los bind mounts:

  • Puedes montar directorios o archivos que ya existen en el host.
  • El archivo o directorio específico del host que montas en el contenedor sigue existiendo fuera del control de Docker. Docker simplemente hace un enlace directo (bind) entre el directorio o archivo del host y el contenedor.
  • El contenedor tiene acceso a esos archivos en tiempo real, y cualquier cambio que haga en esos archivos será reflejado en el host (y viceversa, si es de lectura/escritura).

Montando un punto de montaje tipo bind mount

Para montar un punto de montaje tipo bind mount con el host ejecutamos un comando similar a:

docker run -v ./src/index.html:/var/www/html/index.html:ro caddy

O mediante orquestación con Docker Compose:

services:
caddy:
image: caddy:2.8.4-alpine
container_name: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./src/index.html:/var/www/html/index.html:ro

Volúmenes

¿Qué es un punto de montaje de tipo volumen?

Un volumen en Docker es un mecanismo para almacenar y gestionar datos persistentes de los contenedores. A diferencia de los bind mounts, los volúmenes son gestionados por Docker, lo que significa que Docker se encarga de crear, almacenar y organizar estos datos en una ubicación fuera del sistema de archivos del contenedor y del host.

Los volúmenes se caracterizan por:

  • Persistencia de datos: Los datos almacenados en volúmenes persisten incluso después de que el contenedor se elimina.
  • Independencia del sistema de archivos del host: Docker decide dónde almacenar el volumen, lo que lo hace más portátil y menos dependiente del entorno del host.
  • Facilidad de uso y gestión: Docker proporciona comandos para crear, listar y eliminar volúmenes, lo que facilita su manejo.
  • Compartir datos entre contenedores: Los volúmenes pueden ser montados en varios contenedores, permitiendo compartir datos entre ellos.

Creando un punto de montaje tipo volumen

Para montar un punto de montaje tipo volumen ejecutamos un comando similar a:

docker volume create foo --sharing readonly

Listando los puntos de montaje disponibles

Para mostrar los puntos de montaje disponibles ejecutamos el comando:

docker volume ls
DRIVER    VOLUME NAME
local     2a31133caf149aa1eb63cc9fa50ed0321b1f1fd00cf5a69046fc5ad9a5f26934
local     foo

Inspeccionando un punto de montaje

Podemos obtener información del punto de montaje mediante el siguiente comando:

docker inspect foo
[
    {
        "CreatedAt": "2024-10-01T15:09:22+02:00",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "new",
            "com.docker.compose.version": "2.29.7",
            "com.docker.compose.volume": "foo"
        },
        "Mountpoint": "/var/lib/docker/volumes/caddy/_data",
        "Name": "foo",
        "Options": null,
        "Scope": "local"
    }
]

O bien, podemos inspeccionar propiedades directamente de la siguiente manera:

docker inspect foo --format "{{.Name}}"
foo

O bien, usando el comando jq:

docker inspect foo | jq ".[] | .Labels"
{
    "com.docker.compose.project": "new",
    "com.docker.compose.version": "2.29.7",
    "com.docker.compose.volume": "foo"
}

Eliminando un punto de montaje

Para eliminar un punto de montaje ejecutamos un comando similar a:

docker volume rm foo
foo

Eliminando puntos de montaje en desuso

Podemos eliminar puntos de montaje en desuso mediante el siguiente comando:

docker volume prune
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] y
Deleted Volumes:
5d305328d48f4649634d48dfe90e7b03b47fa7e804aed3ba08f07cf53daf50c5
f7b819426cf0b5327ed1a5f007e090d6de38e3bd5be935fc98f9e8645406a9f7
749a69af366363e8ba04e5ebc827a8724f6e5a362c63f91ac66a4e39ecb21a88
foo

Total reclaimed space: 481.2kB
Ejemplo de punto de montaje

Creamos un fichero index.php:

vi index.php

Con el siguiente su contenido...

?php phpinfo();

Ahora definimos nuestro fichero docker-compose.yml:

vi docker-compose.yml

Con el siguiente su contenido...

services:
php:
container_name: php
image: php:8.3.12-fpm-alpine
restart: unless-stopped
command: php -S 0.0.0.0:80
volumes:
- ./:/var/www/html
ports:
- 80:80

Y arrancamos nuestro contenedor:

docker compose up
[+] Running 11/11
 ✔ php Pulled                                                                                                                                                                                                   7.8s
   ✔ 43c4264eed91 Already exists                                                                                                                                                                                0.0s
   ✔ bb15916673af Already exists                                                                                                                                                                                0.0s
   ✔ 9feb3258c4c6 Already exists                                                                                                                                                                                0.0s
   ✔ 32e436918c34 Already exists                                                                                                                                                                                0.0s
   ✔ e567f6a279d6 Already exists                                                                                                                                                                                0.0s
   ✔ 57c15fd3b41b Already exists                                                                                                                                                                                0.0s
   ✔ fb0b325a6065 Already exists                                                                                                                                                                                0.0s
   ✔ 6b0b83336881 Already exists                                                                                                                                                                                0.0s
   ✔ 7fe54b97407c Already exists                                                                                                                                                                                0.0s
   ✔ b118019426b3 Already exists                                                                                                                                                                                0.0s
[+] Running 1/0
 ✔ Container php  Created                                                                                                                                                                                       0.0s
Attaching to php
php  | [Tue Oct 22 09:00:39 2024] PHP 8.3.12 Development Server (http://0.0.0.0:80) started
Podemos ver que el servicio php está activo y que el servidor built-in de PHP está a la escucha de nuevas peticiones.

Ahora podemos abrir nuestro navegador y visitar la ruta http://localhost/index.php

xdg-open http://localhost/index.php
Acabamos de montar un contenedor PHP con un punto de montaje tipo bind mount, donde todo el contenido de la carpeta actual es compartido con el contenedor en el path /var/www/html. Recuerda que este tipo de montaje sincroniza automáticamente el contenido de dichos archivos o directorios en tiempo real por lo que puedes modificar el contenido del fichero index.php con tu IDE favorito y refrescar la pestaña del navegador para ver los cambios aplicados.
Para parar el contenedor actual basta con pulsar Ctrl+C y finalizar así su ejecución.

Gestión de redes

Tipos de drivers de redes

Estos son los tipos de drivers para redes soportados en Docker:

Bridge
Es el tipo de red predeterminado que usa Docker. Los contenedores conectados a una red bridge pueden comunicarse entre sí a través de sus direcciones IP, pero no pueden ser accedidos desde fuera de Docker, a menos que se expongan puertos específicos.
Host
El contenedor comparte el sistema de red con el host, es decir, usa la red del sistema anfitrión directamente. Esto elimina la sobrecarga de virtualización de la red, pero puede generar conflictos si el contenedor usa puertos que ya están ocupados en el host.
Overlay
Se utiliza principalmente en entornos Docker Swarm o Kubernetes. Permite la creación de redes entre múltiples hosts Docker, para que los contenedores en diferentes máquinas puedan comunicarse como si estuvieran en la misma red.
Macvlan
Asigna una dirección MAC única a cada contenedor, haciendo que se comporten como dispositivos físicos en la red. Es útil cuando se quiere que los contenedores se integren directamente con la red local y tengan su propia IP asignada por el router, como si fueran máquinas físicas.
None
El contenedor no tiene acceso a ninguna red. Es útil cuando quieres ejecutar contenedores completamente aislados, sin conectividad externa.

Listando las redes disponibles

Para mostrar las redes disponibles ejecutamos el comando:

docker network ls
NETWORK ID     NAME                         DRIVER    SCOPE
7dc87e11750f   bridge                       bridge    local
ac7555030bb0   host                         host      local
e28b875bf8c0   none                         null      local

Creando una red

Para crear una red ejecutamos un comando similar a:

docker network create -d bridge --subnet 172.24.0.0/16 --gateway 172.24.0.1 foo
2669335abb1594e17ae04b96c620aa8b23bcaa91f07311e8a23c33e71b8e7397

Inspeccionando una red

Para obtener información de una red ejecutamos el siguiente comando:

docker inspect foo
[
    {
        "Name": "foo",
        "Id": "2669335abb1594e17ae04b96c620aa8b23bcaa91f07311e8a23c33e71b8e7397",
        "Created": "2024-10-22T13:10:37.978135494+02:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.24.0.0/16",
                    "Gateway": "172.24.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

O bien, podemos inspeccionar propiedades directamente de la siguiente manera:

docker inspect foo --format "{{.Id}} {{.Name}}"
2669335abb1594e17ae04b96c620aa8b23bcaa91f07311e8a23c33e71b8e7397 foo

O bien, usando el comando jq:

docker inspect foo | jq ".[] | .IPAM.Config"
[
  {
    "Subnet": "172.18.0.0/16",
    "Gateway": "172.18.0.1"
  }
]

Eliminando una red

Para eliminar una red ejecutamos un comando similar a:

docker network rm foo
foo

Eliminando redes en desuso

Podemos eliminar redes en desuso mediante el siguiente comando:

docker network prune
WARNING! This will remove all custom networks not used by at least one container.
Are you sure you want to continue? [y/N] y
Ejemplo de uso de una red

Podemos crear y usar redes personalizadas mediante los siguientes comandos:

docker network create foo
docker run -d --name php --network foo -p 8080:80 -v ./:/var/www/html php:8.3.12-fpm-alpine php -S 0.0.0.0:80
e3aea50253031d36d24d2e1d7befb3d25a43846c53fdc1e81a8f1f41ba1bb86f

O bien, usando la orquestación mediante Docker Compose:

services:
php:
container_name: php
image: php:8.3.12-fpm-alpine
restart: unless-stopped
command: php -S 0.0.0.0:80
volumes:
- ./:/var/www/html
ports:
- 8080:80
hostname: server_php
networks:
- foo
networks:
foo:
driver: bridge
ipam:
config:
- subnet: 172.24.0.0/16
gateway: 172.24.0.1

Y levantamos la infraestructura con el siguiente comando:

docker compose up
[+] Running 1/1
 ✔ Container php  Recreated                                                 0.1s
Attaching to php
php  | [Tue Oct 22 11:55:07 2024] PHP 8.3.12 Development Server (http://0.0.0.0:80) started

Ahora podemos abrir nuestro navegador y visitar la ruta http://localhost:8080/index.php

xdg-open http://localhost:8080/index.php

Cuando finalicemos de trabajar con el contenedor podemos pararlo con el siguiente comando:

docker stop php
Hemos visto cómo podemos crear redes explícitamente o bien, mediante orquestación usando Docker Compose.

That's all falks!

También te puede interesar...

¿Te has quedado con ganas de más? Relacionado con este curso tienes el de Introduccion a ficheros Makefile que seguro te resultará igual de interesante.