1-3-Nginx + MySQL con Dockerfile y Docker Compose

 

Nginx + MySQL con Dockerfile y Docker Compose (continuación)

Este tutorial es la continuación directa del anterior. Antes servíamos una web estática con Nginx usando un Dockerfile. Ahora daremos un paso más: levantaremos dos servicios con Docker Compose:

  1. Nginx (con nuestra web personalizada, construida con Dockerfile)

  2. MySQL (para guardar una cadena de texto llamada tarea)

La web de Nginx mostrará la tarea almacenada en MySQL, demostrando cómo dos contenedores se comunican dentro de una red interna de Docker.


Requisitos previos

  • Haber completado el tutorial anterior (Dockerfile con Nginx).

  • Docker instalado (docker --version).

  • Docker Compose disponible (docker compose version).


Objetivo del proyecto

Al final tendrás:

  • Una imagen propia de Nginx construida con Dockerfile que sirve tu index.html.

  • Un contenedor MySQL con una base de datos y una tabla que guarda la cadena tarea.

  • Un archivo docker-compose.yml que orquesta ambos servicios.

  • La web accesible en http://localhost:8080.

⚠️ Nota: Como index.html es estático, no puede consultar MySQL directamente desde el navegador. Para que la web muestre la tarea, usaremos un pequeño script en el arranque del contenedor Nginx que genere el HTML con el valor leído de MySQL. Lo veremos paso a paso.


Estructura final del proyecto

text
mi-stack-nginx-mysql/
├── docker-compose.yml
├── web/
│   ├── Dockerfile
│   ├── index.html
│   └── entrypoint.sh
└── init/
    └── init.sql

Paso 1: Crear la estructura de carpetas

bash
mkdir mi-stack-nginx-mysql
cd mi-stack-nginx-mysql
mkdir web init

Paso 2: Crear el script de inicialización de MySQL

Este script se ejecuta la primera vez que arranca MySQL. Crea la base de datos, la tabla e inserta la cadena tarea.

bash
nano init/init.sql

Contenido:

sql
CREATE DATABASE IF NOT EXISTS tareas_db;
USE tareas_db;

CREATE TABLE IF NOT EXISTS tareas (
    id INT AUTO_INCREMENT PRIMARY KEY,
    descripcion VARCHAR(255) NOT NULL,
    creada_en TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO tareas (descripcion) VALUES ('tarea');

Paso 3: Crear la web (HTML + Dockerfile + entrypoint)

3.1. El archivo index.html

Creamos un HTML base que luego el entrypoint.sh reemplazará con el valor real de la tarea.

bash
nano web/index.html

Contenido:

html
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Mi Web con Nginx + MySQL</title>
    <style>
        body { font-family: sans-serif; text-align: center; margin-top: 80px; }
        h1 { color: #0db7ed; }
        .tarea { font-size: 1.5em; color: #333; background: #f4f4f4; padding: 20px; display: inline-block; border-radius: 8px; }
    </style>
</head>
<body>
    <h1>¡Mi Web con Nginx + MySQL!</h1>
    <p>La tarea guardada en la base de datos es:</p>
    <div class="tarea">__TAREA__</div>
</body>
</html>

💡 Fíjate en el marcador __TAREA__. El entrypoint.sh lo reemplazará al arrancar el contenedor.

3.2. El script entrypoint.sh

Este script se ejecuta al iniciar el contenedor Nginx. Espera a que MySQL esté disponible, consulta la tarea y genera el HTML final.

bash
nano web/entrypoint.sh

Contenido:

bash
#!/bin/sh
set -e

echo "Esperando a MySQL..."
until mysql -h db -u usuario -puserpass -e "SELECT 1" >/dev/null 2>&1; do
  sleep 2
done

echo "MySQL listo. Consultando tarea..."
TAREA=$(mysql -h db -u usuario -puserpass -N -B -e "SELECT descripcion FROM tareas_db.tareas LIMIT 1;")

echo "Tarea obtenida: $TAREA"

# Reemplazar el marcador en el HTML
sed -i "s/__TAREA__/${TAREA}/g" /usr/share/nginx/html/index.html

echo "Arrancando Nginx..."
exec nginx -g "daemon off;"

Dale permisos de ejecución:

bash
chmod +x web/entrypoint.sh

3.3. El Dockerfile

Ahora construimos una imagen basada en Nginx que:

  • Copia el index.html y el entrypoint.sh.

  • Instala el cliente mysql (para poder consultar la base).

  • Usa el entrypoint.sh como punto de entrada.

bash
nano web/Dockerfile

Contenido:

dockerfile
FROM nginx:latest

# Instalamos el cliente de MySQL (para consultar la BD al arrancar)
RUN apt-get update && \
    apt-get install -y default-mysql-client && \
    rm -rf /var/lib/apt/lists/*

# Copiamos el HTML y el entrypoint
COPY index.html /usr/share/nginx/html/index.html
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]

Explicación de cada instrucción

InstrucciónDescripción
FROM nginx:latestImagen base oficial de Nginx.
RUN apt-get install default-mysql-clientInstala el cliente mysql dentro del contenedor.
COPY index.html ...Copia el HTML a la ruta que Nginx sirve.
COPY entrypoint.sh ...Copia el script de arranque.
ENTRYPOINT ["/entrypoint.sh"]Ejecuta nuestro script al iniciar el contenedor.

Paso 4: Crear el docker-compose.yml

bash
nano docker-compose.yml

Contenido:

yaml
services:
  db:
    image: mysql:8.0
    container_name: mysql_tareas
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: tareas_db
      MYSQL_USER: usuario
      MYSQL_PASSWORD: userpass
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init:/docker-entrypoint-initdb.d
    networks:
      - red_app

  web:
    build: ./web
    container_name: nginx_web
    restart: unless-stopped
    ports:
      - "8080:80"
    depends_on:
      - db
    networks:
      - red_app

volumes:
  mysql_data:

networks:
  red_app:

Explicación de las partes clave

ElementoDescripción
services.dbServicio de MySQL.
environmentConfigura contraseñas, usuario y base de datos.
volumes: ./init:/docker-entrypoint-initdb.dEjecuta init.sql en el primer arranque.
services.webServicio de Nginx, construido desde ./web.
build: ./webEn lugar de image:, aquí usamos build para construir desde el Dockerfile.
ports: "8080:80"Expone Nginx al host en el puerto 8080.
depends_on: dbArranca web después de db (no espera a que esté listo, por eso el entrypoint.sh reintenta).
networks: red_appAmbos servicios comparten la misma red y se ven por nombre (db, web).
volumes: mysql_dataPersistencia de los datos de MySQL.

💡 Clave: Dentro de la red de Docker, el servicio web se conecta a MySQL usando el hostname db (el nombre del servicio), no localhost.


Paso 5: Levantar el stack

Desde la carpeta raíz del proyecto:

bash
docker compose up -d --build

Explicación

ParámetroDescripción
upCrea y arranca los servicios.
-dEn segundo plano.
--buildFuerza la construcción de la imagen de web (útil si cambiaste el Dockerfile).

Salida esperada:

text
[+] Building 25.3s (10/10) FINISHED
[+] Running 4/4
 ✔ Network mi-stack-nginx-mysql_red_app   Created
 ✔ Volume "mi-stack-nginx-mysql_mysql_data" Created
 ✔ Container mysql_tareas                 Started
 ✔ Container nginx_web                    Started

Paso 6: Verificar que todo funciona

6.1. Ver el estado de los servicios

bash
docker compose ps

Salida esperada:

text
NAME            IMAGE                     STATUS         PORTS
mysql_tareas    mysql:8.0                 Up 30 seconds  3306/tcp, 33060/tcp
nginx_web       mi-stack-nginx-mysql-web  Up 28 seconds  0.0.0.0:8080->80/tcp

6.2. Ver los logs del servicio web

bash
docker compose logs web

Deberías ver algo como:

text
Esperando a MySQL...
MySQL listo. Consultando tarea...
Tarea obtenida: tarea
Arrancando Nginx...

6.3. Acceder a la web

Abre tu navegador en:

text
http://localhost:8080

Deberías ver:

¡Mi Web con Nginx + MySQL!
La tarea guardada en la base de datos es:
tarea

🎉 ¡Has conectado Nginx con MySQL usando Docker Compose!


Paso 7: Verificar directamente en MySQL (opcional)

Si quieres comprobar que la tarea está en la base de datos:

bash
docker compose exec db mysql -u usuario -puserpass -e "SELECT * FROM tareas_db.tareas;"

Salida esperada:

text
+----+-------------+---------------------+
| id | descripcion | creada_en           |
+----+-------------+---------------------+
|  1 | tarea       | 2026-09-14 10:00:00 |
+----+-------------+---------------------+

Paso 8: Cambiar la tarea y ver el cambio reflejado

Opción A: Cambiar el valor en MySQL y reiniciar web

bash
docker compose exec db mysql -u usuario -puserpass -e "UPDATE tareas_db.tareas SET descripcion='nueva tarea' WHERE id=1;"
docker compose restart web

Refresca http://localhost:8080 y verás la nueva tarea.

Opción B: Cambiar el init.sql y reiniciar todo (⚠️ borra datos)

bash
# Editar init/init.sql con la nueva tarea
nano init/init.sql

docker compose down -v
docker compose up -d --build

💡 Recuerda: init.sql solo se ejecuta si el volumen está vacío.


Paso 9: Detener y limpiar

ComandoEfecto
docker compose stopDetiene los contenedores (los datos persisten).
docker compose downDetiene y elimina contenedores y redes (los datos persisten en el volumen).
docker compose down -vAdemás elimina el volumen (⚠️ pierdes la base de datos).
docker compose down --rmi allAdemás elimina las imágenes construidas.

Estructura final del proyecto (resumen)

text
mi-stack-nginx-mysql/
├── docker-compose.yml          # Orquesta los servicios db y web
├── web/
│   ├── Dockerfile              # Construye la imagen de Nginx personalizada
│   ├── index.html              # HTML con marcador __TAREA__
│   └── entrypoint.sh           # Consulta MySQL y reemplaza el marcador
└── init/
    └── init.sql                # Crea BD, tabla e inserta "tarea"

Comparativa con el tutorial anterior

AspectoTutorial anteriorEste tutorial
ServiciosSolo NginxNginx + MySQL
Imagen de NginxDockerfile simple con COPYDockerfile con entrypoint y cliente MySQL
Orquestacióndocker run manualdocker compose
PersistenciaNo aplicaVolumen mysql_data
RedPor defectoRed red_app compartida
Contenido dinámicoHTML estáticoHTML generado desde MySQL

Bonus: Añadir Adminer para ver la BD desde el navegador

Agrega este servicio a tu docker-compose.yml:

yaml
  adminer:
    image: adminer:latest
    container_name: adminer_tareas
    restart: unless-stopped
    ports:
      - "8081:8080"
    depends_on:
      - db
    networks:
      - red_app

Luego:

bash
docker compose up -d

Accede a http://localhost:8081 y conéctate con:

CampoValor
SistemaMySQL
Servidordb
Usuariousuario
Contraseñauserpass
Base de datostareas_db

Comandos útiles de Docker Compose

ComandoDescripción
docker compose up -d --buildLevanta todo y reconstruye imágenes.
docker compose psEstado de los servicios.
docker compose logs -f webLogs en tiempo real del servicio web.
docker compose exec db bashShell dentro del contenedor de MySQL.
docker compose restart webReinicia solo el servicio web.
docker compose down -vElimina todo incluyendo volúmenes.
docker compose configValida el YAML.

Conclusión

Has aprendido a:

  • Construir una imagen propia de Nginx con un Dockerfile que instala el cliente MySQL.

  • Orquestar dos servicios (Nginx + MySQL) con Docker Compose.

  • Comunicar contenedores a través de una red interna usando el nombre del servicio.

  • Persistir datos en MySQL con un volumen.

  • Inicializar la base de datos con un script SQL automático.

  • Generar contenido dinámico en Nginx leyendo la tarea desde MySQL.

  • Gestionar el ciclo de vida del stack con docker compose.

Este patrón (frontend + base de datos) es la base de prácticamente cualquier aplicación web moderna en contenedores. A partir de aquí puedes añadir un backend (Node, Python, PHP), cache (Redis), etc. 🚀


Próximos pasos sugeridos

  • Añadir un backend (Node.js/Python) que exponga una API REST y reemplace el entrypoint.sh por una llamada fetch desde el navegador.

  • Usar un archivo .env para no hardcodear contraseñas.

  • Añadir healthchecks en docker-compose.yml para que depends_on espere de verdad a que MySQL esté listo.

  • Explorar Docker Compose Watch para desarrollo en caliente.

  • Migrar a Kubernetes cuando necesites escalar

*********************************************

Limpieza completa del stack Nginx + MySQL

Para dejar tu computadora limpia después de este tutorial (contenedores, imágenes, volúmenes, redes y la carpeta del proyecto), sigue estos pasos en orden.


Paso 1: Ir a la carpeta del proyecto

bash
cd mi-stack-nginx-mysql

Si ya no estás en ella, ubícala con cd ruta/a/mi-stack-nginx-mysql.


Paso 2: Detener y eliminar todo con Docker Compose

El comando más potente es:

bash
docker compose down -v --rmi all --remove-orphans

¿Qué hace cada bandera?

BanderaEfecto
downDetiene y elimina contenedores y redes creadas por el compose.
-vElimina los volúmenes (⚠️ borra los datos de MySQL: mysql_data).
--rmi allElimina todas las imágenes usadas por los servicios (incluida la construida mi-stack-nginx-mysql-web).
--remove-orphansElimina contenedores huérfanos de servicios que ya no están en el compose (por ejemplo, si agregaste adminer y luego lo quitaste).

💡 Si no quieres borrar la imagen mysql:8.0 (porque la usas en otros proyectos), usa --rmi local en lugar de --rmi all. Eso solo borra las imágenes construidas localmente (la de Nginx), no las descargadas.


Paso 3: Verificar que no queda nada

bash
# Contenedores (incluyendo detenidos)
docker ps -a

# Imágenes
docker images

# Volúmenes
docker volume ls

# Redes
docker network ls

No deberías ver:

  • Contenedores: mysql_tareas, nginx_web, adminer_tareas

  • Imagen: mi-stack-nginx-mysql-web

  • Volumen: mi-stack-nginx-mysql_mysql_data

  • Red: mi-stack-nginx-mysql_red_app


Paso 4: Eliminar la carpeta del proyecto

Regresa al directorio padre y borra la carpeta completa:

Linux / macOS:

bash
cd ..
rm -rf mi-stack-nginx-mysql

Windows (PowerShell):

powershell
cd ..
Remove-Item -Recurse -Force mi-stack-nginx-mysql

Windows (CMD):

cmd
cd ..
rmdir /s /q mi-stack-nginx-mysql

Paso 5: Verificación final

bash
# ¿Quedó alguna carpeta del proyecto?
ls | grep mi-stack

# ¿Quedó algún contenedor o imagen relacionados?
docker ps -a | grep -E "mysql_tareas|nginx_web|adminer"
docker images | grep -E "mi-stack|adminer"
docker volume ls | grep mysql_data
docker network ls | grep red_app

Si ningún comando devuelve resultados, tu sistema quedó limpio. ✅


🧾 Resumen rápido (todo en uno)

bash
cd mi-stack-nginx-mysql
docker compose down -v --rmi all --remove-orphans
cd ..
rm -rf mi-stack-nginx-mysql

🧠 Extra: Limpieza total de Docker (opcional)

Si además quieres liberar todo el espacio que Docker esté ocupando sin usar (caché de build, imágenes huérfanas, redes, contenedores detenidos):

bash
docker system prune -a --volumes

⚠️ Cuidado: esto elimina todo lo que no esté en uso activo, incluyendo imágenes de otros proyectos que hayas descargado. Úsalo solo si estás seguro.

Para ver cuánto espacio estás recuperando antes de borrar:

bash
docker system df

Salida típica:

text
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          5         0         1.2GB     1.2GB (100%)
Containers      0         0         0B        0B
Local Volumes   1         0         150MB     150MB (100%)
Build Cache     20        0         300MB     300MB

⚠️ Notas importantes

  1. El volumen mysql_data contiene tu base de datos. Si algún día quieres conservarla, no uses -v en docker compose down.

  2. La imagen mysql:8.0 puede que la quieras conservar si la reutilizas en otros proyectos. En ese caso usa --rmi local en lugar de --rmi all.

  3. Si agregaste Adminer (el bonus del tutorial), el --remove-orphans se encargará de limpiarlo si ya no está en el compose; si aún está, down lo elimina igual.

  4. Los datos en init/init.sql desaparecen con la carpeta del proyecto, pero MySQL ya los había copiado al volumen; al borrar el volumen se pierden definitivamente.

Comentarios

Entradas más populares de este blog

12. Hola Mundo en Docker.

2.1-Instalación de Docker (nuevo método oficial), configuración de un proyecto Laravel con Nginx y MySQL en contenedores

11¿Qué es Docker? y ¿Por qué debo saberlo?