Guía de Docker: qué es, por qué usarlo y cómo trabajar con él

En esta guía, vamos a entender para qué se utiliza Docker y Docker Compose, qué es la contenerización y las imágenes de Docker, y cómo desplegar una aplicación web simple utilizando PHP-FPM, Nginx y Postgres.
Imaginemos a un desarrollador que comienza a trabajar en una nueva empresa. Durante el proceso de incorporación, el líder del equipo le asigna una tarea divertida: explorar el sitio web de la organización y encontrar la página con fotos de gatitos.

El desarrollador descubre que el sitio web de la empresa funciona utilizando el servidor web Nginx, el gestor de procesos PHP-FPM y el sistema de gestión de bases de datos Postgres. Ahora el programador busca la página deseada. El proceso de búsqueda es el siguiente:
  • 1
    El desarrollador ingresa la dirección del sitio web en el navegador
  • 2
    El navegador solicita la página HTML con los gatitos en la dirección especificada
  • 3
    El servidor HTTP Nginx recibe la solicitud y delega la creación de la página a PHP-FPM
  • 4
    PHP-FPM solicita los datos de los gatitos de la base de datos Postgres, construye la página HTML y la devuelve al servidor Nginx, que a su vez la envía al navegador del cliente
  • 5
    El desarrollador ve la página con los gatitos
El desarrollador completa su primera tarea en la computadora del líder del equipo, donde ya están instalados Nginx, PHP-FPM y Postgres. Al día siguiente, le entregan una nueva computadora en la que no están instalados estos programas.

Para comenzar a trabajar en el sitio web junto con sus compañeros, el desarrollador configura el proyecto, es decir, instala y configura todo lo necesario para trabajar. Lo hace de la siguiente manera:
  • Instala Nginx
  • Instala PHP-FPM y todas las extensiones necesarias
  • Configura la colaboración entre Nginx y PHP-FPM
  • Instala Postgres, crea usuarios, bases de datos y esquemas necesarios
La instalación lleva mucho tiempo: hay que esperar a que se instale un programa y luego otro. Además, la situación se complica porque todo su equipo trabaja en el proyecto con diferentes sistemas operativos: algunos en macOS, otros en Ubuntu o Windows.

Para no perder tiempo instalando programa tras programa, el desarrollador podría automatizar sus acciones utilizando Docker. Esta herramienta despliega el proyecto del programador en cuestión de minutos.
¿Qué es Docker?
Docker es un programa popular basado en la tecnología de contenerización. Docker permite ejecutar contenedores de Docker a partir de plantillas predefinidas llamadas imágenes de Docker.

La contenerización es una tecnología que ayuda a ejecutar aplicaciones de forma aislada del sistema operativo. La aplicación se empaqueta en un contenedor especial que contiene el entorno necesario para su funcionamiento.

En pocas palabras, un contenedor es un entorno aislado para ejecutar tus aplicaciones.
En la imagen se puede ver que la aplicación 1 y la aplicación 2 están aisladas tanto entre sí como del sistema operativo.

Otras características de Docker incluyen:
  • Gestión de aplicaciones aisladas
  • Aceleración y automatización del despliegue de aplicaciones
  • Entrega de aplicaciones a servidores
  • Escalado de aplicaciones
  • Ejecución de diferentes versiones de un mismo programa en una misma computadora
¿Cómo funciona Docker?
Es más fácil entender el concepto de Docker en la práctica. Primero, instalemos Docker en nuestra computadora y ejecutemos el servidor web HTTP Nginx. Para hacerlo, ingresamos el siguiente comando:
docker run -p 8080:80 nginx:latest
Luego abrimos el navegador y escribimos en la barra de direcciones: 127.0.0.1:8080. Se abrirá la página de bienvenida de Nginx.
Ahora veamos en detalle qué sucede cuando ingresamos el comando docker run -p 8080:80 nginx:latest. Este comando realiza lo siguiente:
  • 1
    Descarga la imagen de Docker, que es una plantilla para crear un contenedor de Docker, llamada nginx:latest, desde el repositorio público Docker Hub (si aún no se ha descargado). La imagen de Docker contiene todo lo necesario para ejecutar la aplicación: código, entorno de ejecución, bibliotecas, variables de entorno y archivos de configuración. En la página de Nginx en Docker Hub, puedes encontrar la imagen de Docker nginx:latest, donde "latest" es una etiqueta que se refiere a la imagen de Docker más reciente y la describe.
  • 2
    Ejecuta un contenedor de Docker utilizando la imagen de Docker descargada
  • 3
    Reenvía el puerto. Como explicamos anteriormente, los procesos en los contenedores de Docker se ejecutan de forma aislada del sistema operativo, lo que significa que todos los puertos entre el sistema operativo y el contenedor de Docker están cerrados. Para poder acceder a Nginx, es necesario reenviar el puerto, lo cual se realiza con la opción -p 8080:80, donde 80 es el puerto de Nginx dentro del contenedor y 8080 es el puerto en la red local del sistema operativo.
¿Cómo crear tu propia imagen de Docker?
Ahora intentemos crear nuestra propia imagen de Docker basada en nginx:latest. Docker puede crear imágenes de Docker leyendo comandos de texto que se encuentran en un archivo llamado Dockerfile.

Aquí tienes un ejemplo de un Dockerfile muy simple:
FROM nginx:latest

RUN echo 'Hi, we are building a custom docker image from nginx:latest!'

COPY nginx-custom-welcome-page.html /usr/share/nginx/html/index.html
El comando FROM establece la imagen de Docker base (imagen principal) y siempre se ejecuta primero. El comando COPY copia archivos al contenedor de Docker.

Con COPY, puedes reemplazar la página de bienvenida estándar de Nginx con esta página:
<!DOCTYPE html>
<html>
<body>
<h1>Welcome to custom Nginx page!</h1>
</body>
</html>
Puedes obtener más información sobre estos y otros comandos de Docker en la documentación oficial.

Ahora que hemos entendido qué hacen estos comandos, creemos una imagen de Docker a partir del Dockerfile:
$ docker build -t nginx_custom:latest -f  /opt/src/docker-for-kids/dockerFiles/Nginx-custom/Dockerfile /opt/src/docker-for-kids
Sending build context to Docker daemon  139.3kB
Step 1/3 : FROM nginx:latest
latest: Pulling from library/nginx
31b3f1ad4ce1: Pull complete
fd42b079d0f8: Pull complete
30585fbbebc6: Pull complete
18f4ffdd25f4: Pull complete
9dc932c8fba2: Pull complete
600c24b8ba39: Pull complete
Digest: sha256:0b970013351304af46f322da1263516b188318682b2ab1091862497591189ff1
Status: Downloaded newer image **for** nginx:latest
---**>** 2d389e545974
Step 2/3 : RUN echo 'Hi, we are building a custom docker image from nginx:latest!'
---**>** Running **in** 05ffd060061f
Hi, we are building a custom docker image from nginx:latest!
Removing intermediate container 05ffd060061f
---**>** 9ac62be4252a
Step 3/3 : COPY nginx-custom-welcome-page.html /usr/share/nginx/html/index.html
---**>** 704121601a45
Successfully built 704121601a45
Successfully tagged nginx_custom:latest
Explicaremos los comandos utilizados en este código:
  • -t nginx_custom:latest es el nombre de la imagen de Docker que se creará, donde "latest" es una etiqueta.
  • -f /opt/src/docker-for-kids/dockerFiles/Nginx-custom/Dockerfile es la ruta del Dockerfile.
  • /opt/src/docker-for-kids es el directorio en el que se creará la imagen de Docker. El contexto es todo lo que está disponible para los comandos en el Dockerfile durante la construcción de la imagen. El proceso de creación de una imagen de Docker puede hacer referencia a cualquier archivo en el contexto.
Ahora ejecutamos el comando:
$ docker run -p 8080:80 Nginx_custom:latest
La imagen de Docker está lista.
¿Qué es Docker Compose y cómo funciona?
A medida que aumenta el número de contenedores de Docker, se vuelve más difícil mantenerlos. La configuración de cada contenedor se describe en su propio Dockerfile y deben ejecutarse con comandos separados. Lo mismo ocurre con la construcción o reconstrucción de los contenedores.

El trabajo se simplifica con Docker Compose, una herramienta para describir aplicaciones multinivel. Con Docker Compose, puedes crear un solo archivo que describa todos los contenedores de forma clara. Además, Docker Compose permite construir, detener y ejecutar archivos con un solo comando.

Para describir las aplicaciones, se utiliza un archivo YAML.
version: '3'

services:
  nginx:
    container_name: nginx-test # nombre del contenedor de Docker
    build: # crear una imagen de Docker a partir de un Dockerfile
      context: . # ruta en la que se creará la imagen de Docker
      dockerfile: ./dockerFiles/nginx/Dockerfile # ruta del Dockerfile desde el que se construirá la imagen de Docker
    ports: # reenvío de puertos
      - "80:80"
    networks: # nombre de la red a la que se conectará el contenedor de Docker
      - test-network
    depends_on: # este programa se ejecutará solo después de que se ejecute el servicio llamado php-fpm
      - php-fpm
    volumes: # montaje de directorios, directorio-en-el-host: directorio-en-Docker
      - ./:/var/www/hello.dev/
  php-fpm:
    container_name: php-fpm-test
    build:
      context: .
      dockerfile: ./dockerFiles/php-fpm/Dockerfile
    networks:
      - test-network
    volumes:
      - ./:/var/www/hello.dev/
  postgres:
    container_name: postgres-test
    image: postgres:14.1-alpine # etiqueta de la imagen de Docker desde https://hub.docker.com/
    environment:
      postgres_PASSWORD: mysecretpass # variables de entorno utilizadas por el contenedor de Docker
    networks:
      - test-network
networks: # redes explícitamente declaradas
  test-network:
    driver: bridge

Si representamos este código de manera esquemática, la descripción de la aplicación se vería así:
Cada servicio se encuentra dentro de un contenedor de Docker. El punto de entrada de la aplicación, al igual que en el caso del desarrollador y el sitio web de la empresa, es Nginx. Los usuarios del sitio web realizan solicitudes a Nginx, que tiene el puerto 80 reenviado.

Veamos algunos comandos más que Docker implementa:
  • 1
    network. Como explicamos anteriormente, cada aplicación en un contenedor de Docker está aislada. networks une todos los contenedores de Docker en una red llamada test-network, lo que permite acceder a un contenedor específico por su nombre.
  • 2
    volumes es un mecanismo para almacenar datos fuera del contenedor de Docker, es decir, en el sistema de archivos de nuestro sistema operativo. volumes resuelve el problema del uso compartido de archivos.
Puedes encontrar todos los ejemplos, así como los archivos Dockerfile originales, en este repositorio en GitHub.
Cómo crear una aplicación web simple utilizando Docker
Creemos una aplicación web simple que muestre un mensaje de conexión exitosa a la base de datos. En lugar de utilizar la dirección de la base de datos, utilizaremos host=postgres, que es el mismo nombre del servicio en el archivo YAML. Recordemos que esto es posible gracias a la red compartida test-network.
index.php
<?php

try {
    $pdo = new \PDO("pgsql:host=postgres;dbname=postgres", 'postgres', 'mysecretpass');
    echo "¡Conexión a la base de datos establecida correctamente! <br>";

    return;
} catch (PDOException $exception) {
    echo "Error al conectar a la base de datos<br><b>{$exception->getMessage()}</b><br>";
}
PDO es una interfaz para acceder a bases de datos en PHP. Puedes obtener más información sobre esto en la documentación oficial.

Ahora, para crear todas las imágenes de Docker y ejecutar los contenedores de Docker, debemos ejecutar el siguiente comando:
docker-compose up --build
Ejecutamos index.php y vemos una conexión exitosa a la base de datos.
Puedes encontrar la aplicación web para ejecutarla por tu cuenta en este repositorio en GitHub.
Conclusión
Al dominar Docker, los desarrolladores pueden implementar todos los servicios necesarios en cualquier computadora. Esta herramienta también es excelente para la entrega rápida a servidores y pruebas. Aprender Docker no es tan difícil como puede parecer para los principiantes, pero esta habilidad puede ahorrarles mucho tiempo en la instalación manual de software. Puedes obtener más información sobre Docker en el sitio web oficial.
Leer otros artículos de Blog
Lee otros artículos relevantes del mundo de la tecnología y el espíritu empresarial.