Categoría: Docker

  • Práctica guiada: Docker Desktop, Portainer, Apache, PHP y MySQL

    Práctica guiada: Docker Desktop, Portainer, Apache, PHP y MySQL

    1. Presentación de la práctica

    En esta actividad aprenderemos a utilizar Docker para poner en funcionamiento una pequeña aplicación web formada por dos contenedores independientes.

    El primero tendrá un servidor Apache con PHP. El segundo alojará una base de datos MySQL con información sobre videojuegos de PC.

    No tendremos que programar la aplicación: el profesor facilitará todos los archivos necesarios. Nuestro trabajo será instalar, configurar, conectar, comprobar y publicar los contenedores.

    Arquitectura que vamos a construir

    2. Material facilitado por el profesor

    He preparado un ZIP con todos los archivos necesarios para los alumnos.

    Los alumnos deberán descomprimir el ZIP en una carpeta de Windows llamada practica_docker_juegos.

    Parte 1. Instalación de Docker Desktop

    Paso 1. Descargar Docker Desktop

    1. Accede a Docker Desktop.
    2. Descarga la versión para Windows.
    3. Ejecuta el instalador.
    4. Mantén seleccionada la opción de utilizar WSL 2, si aparece.
    5. Finaliza la instalación y reinicia Windows cuando sea necesario.

    Paso 2. Comprobar el funcionamiento

    Abre Docker Desktop y espera a que el motor de Docker esté funcionando.

    Abre PowerShell y ejecuta:

    docker --version

    Después:

    docker info

    Si este último comando muestra información del servidor, Docker está funcionando.

    Si indica que WSL no está instalado, abre PowerShell como administrador y ejecuta:

    wsl --install

    Reinicia el ordenador cuando termine la instalación y vuelve a abrir Docker Desktop.

    Parte 2. Crear nuestra cuenta de Docker Hub

    Docker Hub es un repositorio en Internet que permite descargar imágenes creadas por otras personas y publicar las nuestras.

    1. Accede a Docker Hub.
    2. Selecciona Sign up.
    3. Registra tu cuenta y verifica el correo electrónico.
    4. Accede a Docker Desktop.
    5. Pulsa Sign in e inicia sesión con la cuenta recién creada.

    Guarda tu nombre de usuario de Docker Hub. Lo necesitaremos al finalizar la práctica.

    Parte 3. Instalar Portainer

    Portainer es una herramienta de administración visual de Docker. Nos permitirá controlar contenedores, imágenes, redes y otros recursos.

    Portainer CE : gérer Docker et Podman visuellement

    Paso 1. Instalar la extensión

    1. Abre Docker Desktop.
    2. Entra en Settings → Extensions y comprueba que estén habilitadas.
    3. Abre Extensions Marketplace.
    4. Busca Portainer.
    5. Selecciona la extensión Portainer CE publicada por Portainer.io.
    6. Pulsa Install o Open in Docker Desktop, según la interfaz.

    También puedes localizarla en Portainer CE para Docker Desktop.

    Paso 2. Acceder a Portainer

    Abre Portainer desde el apartado Extensions. Si solicita crear un usuario administrador, completa el asistente y selecciona el entorno local de Docker.

    Comprueba que puedes acceder al apartado Containers.

    Esta extensión se encuentra disponible oficialmente para Docker Desktop.

    Paso 2. Configuración inicial

    Si Portainer solicita crear una cuenta administradora local:

    1. Introduce un nombre de usuario.
    2. Elige una contraseña.
    3. Completa el asistente.

    Selecciona el entorno Docker local disponible. Según la versión, puede aparecer como local o con un nombre similar.

    Paso 3. Conocer Portainer

    Localiza los apartados principales.

    SecciónUtilidad
    ContainersCrear, iniciar, detener y administrar contenedores
    ImagesDescargar, etiquetar y publicar imágenes
    NetworksCrear redes para comunicar contenedores
    VolumesAdministrar almacenamiento persistente
    EventsConsultar acontecimientos del entorno
    RegistriesConfigurar repositorios como Docker Hub

    Durante esta práctica trabajaremos principalmente en Containers, Images y Networks.

    Comprobación 3: puedes abrir Portainer y acceder al entorno Docker local.

    PARTE 4. NUESTRO PRIMER CONTENEDOR: HELLO WORLD

    Antes de crear nuestra aplicación web, vamos a comprobar cómo funciona Docker utilizando una imagen muy sencilla.

    La imagen hello-world muestra un mensaje y termina su ejecución.

    Paso 1. Descargar la imagen

    Desde Portainer:

    1. Selecciona Images.
    2. Busca el apartado para descargar imágenes (Pull image).
    3. Selecciona Docker Hub como registro.
    4. Introduce:
    hello-world:latest

    5. Pulsa Pull the image.

    Espera a que termine.

    La imagen debería aparecer en el listado de imágenes disponibles.

    Paso 2. Crear el contenedor

    1. Accede a Containers.
    2. Pulsa Add container.
    3. Introduce los siguientes valores:
    CampoValor
    Namemi-hola-mundo
    Imagehello-world:latest
    NetworkDefault
    Restart policyNo
    1. No es necesario publicar ningún puerto.
    2. Pulsa Deploy the container.

    Paso 3. Comprobar el resultado

    Abre la lista de contenedores.

    Localiza mi-hola-mundo.

    Es probable que aparezca con el estado Exited o detenido.

    Esto no significa que haya fallado. El programa ha terminado su ejecución.

    Selecciona el contenedor y entra en Logs.

    Busca el mensaje:

    Hello from Docker!

    Paso 4. Eliminar el contenedor

    Vuelve al listado de contenedores.

    1. Selecciona mi-hola-mundo.
    2. Pulsa Remove.
    3. Confirma la eliminación.

    Después abre Images y comprueba que la imagen hello-world sigue disponible.

    Pregunta de reflexión

    ¿Por qué hemos podido eliminar el contenedor sin eliminar también la imagen?

    PARTE 5. CREAR NUESTRA PRIMERA RED DOCKER

    Vamos a crear una red privada para que Apache y MySQL puedan comunicarse.

    La llamaremos:

    juegos-net

    Paso 1. Crear la red desde Portainer

    1. Entra en Networks.
    2. Pulsa Add network.
    3. Introduce:
    CampoValor
    Namejuegos-net
    Driverbridge
    InternalDesactivado
    Enable IPv6Desactivado
    1. Deja el resto de opciones por defecto.
    2. Pulsa Create the network.

    Paso 2. Verificarla

    Comprueba que juegos-net aparece en el listado.

    Más adelante conectaremos ambos contenedores a esta red.

    PARTE 6. CREAR EL CONTENEDOR APACHE

    Vamos a crear nuestro primer servidor web.

    Utilizaremos una imagen oficial de Apache HTTP Server llamada httpd.

    Paso 1. Descargar Apache

    Abre Images y descarga:

    httpd:2.4-trixie

    Esta variante está basada en Debian, por lo que podremos utilizar apt para instalar programas.

    Paso 2. Crear el contenedor

    Accede a Containers → Add container.

    Introduce los siguientes datos:

    CampoValor
    Nameweb-juegos
    Imagehttpd:2.4-trixie
    Restart policyUnless stopped

    En el apartado de publicación manual de puertos, añade:

    HostContainer
    808080

    En Advanced container settings → Network, selecciona:

    juegos-net

    En Advanced container settings → Env, añade esta variable:

    NameValue
    DB_HOSTbd-juegos

    Esta variable indicará al servidor web cómo localizar la base de datos cuando la creemos.

    Pulsa Deploy the container.

    Paso 3. Verificar el estado

    En el listado de contenedores debería aparecer:

    web-juegos     running

    Paso 4. Abrir nuestra primera página web

    En Windows, abre el navegador:http://localhost:8080

    Debería aparecer el mensaje:

    It works!

    Esto significa que Apache está funcionando.

    ¿Qué hemos conseguido?

    Hemos creado un servidor web Linux dentro de Docker, sin instalar Apache directamente en Windows.

    El puerto 8080 pertenece a nuestro equipo, mientras que Apache escucha en el puerto 80 dentro del contenedor.

    PARTE 7. INSTALAR PHP Y MYSQLI EN APACHE

    Para incorporar PHP al contenedor httpd y conectarlo con tu contenedor MySQL nstalaremos PHP-FPM dentro del mismo contenedor y configuraremos Apache para que le envíe los archivos .php. Apache admite esta integración mediante mod_proxy_fcgi. Apache HTTP Server Version 2.4

    Instalar solamente php y php-mysql no basta: puedes tener funcionando php -v y que Apache todavía no interprete PHP.

    Los siguientes pasos están preparados para un contenedor httpd basado en Debian, con apt.

    1. Abre una consola dentro de tu contenedor Apache

    En Portainer: Containers → tu contenedor Apache → Console → /bin/bash, como usuario root.

    También puedes entrar desde la terminal de tu ordenador, sustituyendo web-juegos por el nombre real:

    docker exec -it web-juegos bash

    2. Instala PHP-FPM y los conectores MySQL

    Dentro del contenedor:

    apt update
    apt install -y php-fpm php-cli php-mysql

    Comprueba la instalación:

    php -v
    php -m | grep -E 'mysqli|pdo_mysql'

    Deberían aparecer mysqli y pdo_mysql.

    3. Configura PHP-FPM

    Vamos a hacer que PHP-FPM escuche en el puerto 9000, únicamente dentro del propio contenedor.

    Primero, guarda la versión instalada en una variable:

    PHP_VERSION=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')

    Comprueba su valor:

    echo "$PHP_VERSION"

    Cambia la configuración del proceso PHP-FPM:

    sed -i 's|^[[:space:]]*listen[[:space:]]*=.*|listen = 127.0.0.1:9000|' \
    "/etc/php/$PHP_VERSION/fpm/pool.d/www.conf"

    Inícialo:

    service "php${PHP_VERSION}-fpm" start

    No necesitas publicar el puerto 9000 en Docker, porque Apache y PHP-FPM están dentro del mismo contenedor.

    4. Habilita la integración en Apache

    La configuración del Apache de la imagen httpd está normalmente en:

    /usr/local/apache2/conf/httpd.conf

    Haz una copia antes de modificarla:

    cp -n /usr/local/apache2/conf/httpd.conf \
    /usr/local/apache2/conf/httpd.conf.antes-php

    Activa los dos módulos necesarios:

    sed -i \
    's|^#LoadModule proxy_module modules/mod_proxy.so|LoadModule proxy_module modules/mod_proxy.so|' \
    /usr/local/apache2/conf/httpd.conf
    sed -i \
    's|^#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so|LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so|' \
    /usr/local/apache2/conf/httpd.conf

    Crea un archivo específico para la configuración de PHP:

    cat > /usr/local/apache2/conf/extra/php-fpm.conf <<'EOF'
    DirectoryIndex index.php index.html
    
    <FilesMatch "\.php$">
        SetHandler "proxy:fcgi://127.0.0.1:9000"
    </FilesMatch>
    EOF

    Inclúyelo en la configuración principal, sin duplicarlo si ya existe:

    grep -qxF 'Include conf/extra/php-fpm.conf' /usr/local/apache2/conf/httpd.conf \
    || echo 'Include conf/extra/php-fpm.conf' >> /usr/local/apache2/conf/httpd.conf

    Comprueba la configuración:

    httpd -t

    Si aparece Syntax OK, aplica los cambios:

    httpd -k graceful

    5. Comprueba que Apache interpreta PHP

    Crea una página de prueba:

    cat > /usr/local/apache2/htdocs/prueba.php <<'EOF'
    <?php
    echo "<h1>PHP funciona dentro de mi contenedor httpd</h1>";
    echo "<p>Versión: " . PHP_VERSION . "</p>";
    echo "<p>MySQLi: " . (extension_loaded('mysqli') ? 'disponible' : 'no disponible') . "</p>";
    EOF

    Abre en tu navegador, usando el puerto que tengas publicado:

    http://localhost:puerto/prueba.php

    PARTE 9. CREAR EL CONTENEDOR MYSQL

    Ahora necesitaremos una base de datos donde almacenar nuestros videojuegos.

    Utilizaremos MySQL 8.4.

    Paso 1. Descargar MySQL

    En Portainer:

    1. Abre Images.
    2. Selecciona Docker Hub.
    3. Descarga:
    mysql:8.4

    4. Espera a que aparezca en el listado de imágenes.

    Paso 2. Crear el contenedor

    Accede a Containers → Add container.

    Introduce:

    CampoValor
    Namebd-juegos
    Imagemysql:8.4
    Restart policyUnless stopped

    En la publicación manual de puertos:

    HostContainer
    33073306

    En Advanced container settings → Network, selecciona:

    juegos-net

    Paso 3. Configurar las variables de MySQL

    Busca Advanced container settings → Env.

    Añade una por una las siguientes variables:

    NameValue
    MYSQL_ROOT_PASSWORDRootClase2026
    MYSQL_DATABASEjuegos
    MYSQL_USERalumno
    MYSQL_PASSWORDClaveJuegos2026

    Estas variables permiten inicializar MySQL automáticamente.

    Sus funciones son:

    • MYSQL_ROOT_PASSWORD: contraseña del administrador.
    • MYSQL_DATABASE: base de datos que queremos crear.
    • MYSQL_USER: usuario que utilizará la aplicación PHP.
    • MYSQL_PASSWORD: contraseña de ese usuario.

    Estas credenciales son únicamente de laboratorio. No deberán reutilizarse en servicios reales.

    Paso 4. Crear MySQL

    Pulsa Deploy the container.

    Regresa a Containers.

    Comprueba que aparece bd-juegos.

    Paso 5. Comprobar que MySQL está funcionando

    Selecciona bd-juegos y pulsa Logs.

    Espera a que aparezca un mensaje similar a:

    ready for connections

    Durante el primer arranque MySQL necesita inicializar sus archivos y crear la base de datos.

    Paso 6. Acceder a MySQL

    Dentro de Portainer:

    1. Selecciona bd-juegos.
    2. Accede a Console.
    3. Selecciona /bin/sh.
    4. Pulsa Connect.

    Escribe:

    mysql -u root -p

    Cuando solicite la contraseña, introduce:

    RootClase2026

    La contraseña no se mostrará mientras escribes.

    Una vez dentro, ejecuta:

    SHOW DATABASES;

    Debería aparecer la base de datos juegos.

    Para salir del cliente MySQL:

    exit;

    Comprobación: tenemos un servidor MySQL dentro de Docker, conectado a la misma red que Apache.

    PARTE 10. CREAR LA BASE DE DATOS DE VIDEOJUEGOS

    El profesor proporciona el siguiente código SQL.

    Este código realizará tres operaciones:

    1. Seleccionar la base de datos juegos.
    2. Crear una tabla llamada videojuegos.
    3. Insertar cinco videojuegos de ejemplo.

    Paso 1. Crear el archivo SQL dentro de MySQL

    Desde la consola Linux del contenedor bd-juegos, copia y pega todo este bloque:

    cat > /docker-entrypoint-initdb.d/01-juegos.sql <<'EOF'
    USE juegos;
    
    CREATE TABLE IF NOT EXISTS videojuegos (
        id INT AUTO_INCREMENT PRIMARY KEY,
        titulo VARCHAR(100),
        genero VARCHAR(50),
        plataforma VARCHAR(50)
    );
    
    INSERT INTO videojuegos
    (titulo, genero, plataforma)
    VALUES
    ('Minecraft', 'Sandbox', 'PC'),
    ('Age of Empires II', 'Estrategia', 'PC'),
    ('Portal 2', 'Puzles', 'PC'),
    ('Stardew Valley', 'Simulacion', 'PC'),
    ('Hollow Knight', 'Metroidvania', 'PC');
    EOF

    No es necesario escribir el código línea por línea.

    Selecciona el bloque completo, cópialo y pégalo en la consola.

    Hemos guardado el SQL en /docker-entrypoint-initdb.d/ para que también pueda utilizarse al crear nuevos contenedores a partir de nuestra futura imagen MySQL.

    Paso 2. Ejecutar el SQL

    Ejecuta:

    mysql -u root -p juegos < /docker-entrypoint-initdb.d/01-juegos.sql

    Introduce la contraseña del administrador:

    RootClase2026

    Si no aparece ningún error, el script habrá terminado.

    No vuelvas a ejecutar el script sobre esta misma base de datos, ya que volvería a insertar los registros.

    Paso 3. Verificar la tabla

    Accede nuevamente al cliente MySQL:

    mysql -u root -p

    Introduce la contraseña.

    Ahora ejecuta:

    USE juegos;

    Y después:

    SHOW TABLES;

    Debe aparecer:

    videojuegos

    Paso 4. Comprobar los datos

    SELECT * FROM videojuegos;

    El resultado esperado es:

    idtitulogeneroplataforma
    1MinecraftSandboxPC
    2Age of Empires IIEstrategiaPC
    3Portal 2PuzlesPC
    4Stardew ValleySimulacionPC
    5Hollow KnightMetroidvaniaPC

    Ya tenemos nuestra primera base de datos funcionando dentro de Docker.

    Sal del cliente MySQL escribiendo:

    exit;

    PARTE 11. INSTALAR NUESTRA APLICACIÓN PHP

    Nuestra aplicación será muy sencilla.

    Mostrará el título, el género y la plataforma de los videojuegos almacenados en MySQL.

    No tendremos que desarrollar el código; solamente introducirlo dentro del contenedor Apache.

    Paso 1. Volver al contenedor Apache

    En Portainer:

    1. Entra en Containers.
    2. Selecciona web-juegos.
    3. Pulsa Console.
    4. Abre /bin/sh.

    Paso 2. Crear el archivo PHP

    Copia todo este bloque de código y pégalo directamente en la consola de Portainer:

    cat > /usr/local/apache2/htdocs/index.php <<'EOF'
    <?php
    $conexion = new mysqli(
        getenv("DB_HOST") ?: "bd-juegos",
        "alumno",
        "ClaveJuegos2026",
        "juegos"
    );
    
    if ($conexion->connect_error) {
        die("Error de conexion con MySQL");
    }
    
    $resultado = $conexion->query("SELECT * FROM videojuegos");
    ?>
    
    <!DOCTYPE html>
    <html lang="es">
    <head>
        <meta charset="UTF-8">
        <title>Juegos de PC</title>
    </head>
    <body>
    
    <h1>Catalogo de videojuegos</h1>
    
    <?php
    while ($juego = $resultado->fetch_assoc()) {
        echo "<p>";
        echo htmlspecialchars($juego["titulo"]);
        echo " - ";
        echo htmlspecialchars($juego["genero"]);
        echo " - ";
        echo htmlspecialchars($juego["plataforma"]);
        echo "</p>";
    }
    ?>
    
    </body>
    </html>
    EOF

    El código es sencillo, pero contiene todo lo necesario:

    • Conexión con MySQL.
    • Consulta de los videojuegos.
    • Lectura de resultados.
    • Generación de una página HTML.

    El servidor MySQL se busca utilizando la variable DB_HOST, que configuramos desde Portainer.

    Paso 3. Eliminar la página predeterminada

    El servidor Apache incluye una página llamada index.html.

    Vamos a eliminarla para que se muestre directamente nuestro index.php.

    Desde la consola:

    rm /usr/local/apache2/htdocs/index.html

    Paso 4. Abrir la aplicación

    Accede desde Windows a:http://localhost:8080

    Debería mostrarse una página similar a la siguiente.

    La página real tendrá un diseño HTML mucho más sencillo, pero debe mostrar los mismos cinco videojuegos.

    Paso 5. Comprobar Portainer

    Vuelve a Portainer y verifica que los dos contenedores están activos:

    ContenedorEstadoPuerto Windows
    web-juegosRunning8080
    bd-juegosRunning3307

    Ambos deben pertenecer a juegos-net.

    ¿Cómo funciona la aplicación?

    Cuando escribimos localhost:8080 en el navegador:

    1. Docker dirige la petición al contenedor Apache.
    2. Apache identifica el archivo index.php.
    3. PHP ejecuta el programa.
    4. PHP solicita los videojuegos a MySQL.
    5. MySQL devuelve los registros.
    6. PHP genera la página HTML que vemos en el navegador.

    Hemos conseguido ejecutar una aplicación PHP conectada a una base de datos MySQL utilizando dos contenedores independientes.

    PARTE 12. CREAR NUESTRAS PROPIAS IMÁGENES

    Ahora aprenderemos una de las funciones más interesantes de Docker: crear nuestras propias imágenes a partir de los contenedores que hemos configurado.

    Esto nos permitirá reutilizar las modificaciones realizadas.

    Paso 1. Crear la imagen de Apache + PHP

    En Portainer:

    1. Accede a Containers.
    2. Selecciona web-juegos.
    3. Busca la opción Create image dentro de los detalles del contenedor.
    4. Introduce un nombre para la imagen:
    mi-web-juegos

    Y la etiqueta:

    v1

    Si Portainer muestra un único campo para el nombre y la etiqueta, introduce:

    mi-web-juegos:v1

    5. Confirma la creación de la imagen.

    Es posible que tarde un poco, ya que hemos instalado nuevos paquetes dentro del contenedor.

    Paso 2. Verificar la imagen

    Entra en Images.

    Debería aparecer:

    mi-web-juegos:v1

    Esta imagen contendrá Apache, PHP, el conector MySQL, nuestra configuración CGI y la página index.php.

    La configuración de arranque que hemos utilizado permite que Apache siga iniciándose con su comando predeterminado.

    Paso 3. Crear la imagen de MySQL

    Ahora repetiremos el procedimiento.

    1. Accede a Containers.
    2. Selecciona bd-juegos.
    3. Pulsa Create image.
    4. Introduce:
    mi-bd-juegos:v1

    5. Confirma la operación.

    Paso 4. Verificar las dos imágenes

    Abre Images.

    Deberías encontrar:

    mi-web-juegos:v1
    mi-bd-juegos:v1

    Importante sobre MySQL: las imágenes creadas desde contenedores no incorporan los datos guardados en volúmenes. Lo que hemos incluido en nuestra imagen MySQL es el archivo 01-juegos.sql, que permitirá reconstruir la tabla y sus datos al iniciar una base de datos nueva.

    PARTE 13. PUBLICAR NUESTRAS IMÁGENES EN DOCKER HUB

    Ahora compartiremos nuestras imágenes en Docker Hub.

    Para ello utilizaremos Portainer.

    Paso 1. Configurar el acceso a Docker Hub

    En Portainer, entra en el apartado Registries.

    Si Docker Hub aparece configurado, comprueba que permite utilizar tus credenciales. En caso contrario:

    1. Pulsa Add registry.
    2. Selecciona Docker Hub.
    3. Introduce tus datos de acceso. Si solicita un token de acceso, puedes crearlo desde la configuración de seguridad de tu cuenta de Docker Hub.
    4. Guarda la configuración.

    No publiques las contraseñas utilizadas para acceder a tu cuenta.

    Paso 2. Etiquetar nuestra primera imagen

    Abre Images y localiza:

    mi-web-juegos:v1

    Selecciona la opción Tag.

    Supongamos que tu usuario de Docker Hub es alumnoasir.

    Asigna la etiqueta:

    alumnoasir/mi-web-juegos:v1

    Sustituye alumnoasir por tu nombre real de usuario de Docker Hub.

    Paso 3. Publicar la imagen web

    En el listado de imágenes localiza la nueva etiqueta y selecciona Push, normalmente identificada por una flecha hacia arriba.

    Selecciona Docker Hub si Portainer solicita elegir un registro.

    Espera a que termine la subida.

    Paso 4. Publicar la imagen MySQL

    Repite el mismo procedimiento con:

    mi-bd-juegos:v1

    Asignándole:

    alumnoasir/mi-bd-juegos:v1

    Pulsa Push.

    La documentación de Portainer describe el proceso de etiquetado y publicación de imágenes desde su interfaz.

    Paso 5. Verificar Docker Hub

    Entra en:Docker Hub

    Dentro de tus repositorios deberías encontrar:

    RepositorioEtiqueta
    mi-web-juegosv1
    mi-bd-juegosv1

    Para esta práctica, crea los repositorios como privados si tu cuenta lo permite.

    Recuerda que la imagen web contiene credenciales de ejemplo y que Docker Hub no debe utilizarse para publicar secretos reales.

    Comprobación: hemos creado y publicado dos imágenes propias sin utilizar comandos Docker.

    PARTE 14. CREAR UNA SEGUNDA INSTALACIÓN CON OTROS PUERTOS

    En esta parte final vamos a demostrar que nuestras imágenes pueden utilizarse para crear otros contenedores.

    No eliminaremos los originales.

    Crearemos una segunda instalación de la aplicación.

    ElementoInstalación originalSegunda instalación
    Contenedor Apacheweb-juegosweb-juegos-copia
    Puerto web Windows80808081
    Contenedor MySQLbd-juegosbd-juegos-copia
    Puerto MySQL Windows33073308
    Red Dockerjuegos-netjuegos-copia-net

    Paso 1. Crear una segunda red

    Desde Portainer:

    1. Accede a Networks.
    2. Pulsa Add network.
    3. Nombre:
    juegos-copia-net
    1. Driver: bridge.
    2. Pulsa Create the network.

    Paso 2. Descargar nuestra imagen MySQL

    Accede a Images.

    Selecciona Docker Hub y busca:

    alumnoasir/mi-bd-juegos:v1

    Sustituye el nombre de usuario.

    Pulsa Pull the image.

    Aunque la imagen ya exista localmente, este paso permite comprobar que puede recuperarse desde Docker Hub.

    Paso 3. Crear el nuevo MySQL

    Accede a Containers → Add container.

    Introduce:

    CampoValor
    Namebd-juegos-copia
    Imagealumnoasir/mi-bd-juegos:v1
    Host port3308
    Container port3306
    Networkjuegos-copia-net

    En Advanced container settings → Env, configura:

    NameValue
    MYSQL_ROOT_PASSWORDRootClase2026
    MYSQL_DATABASEjuegos
    MYSQL_USERalumno
    MYSQL_PASSWORDClaveJuegos2026

    Deja los volúmenes con su configuración predeterminada y no reutilices el volumen de datos de la primera base de datos.

    Pulsa Deploy the container.

    Paso 4. Verificar la segunda base de datos

    Abre los registros de bd-juegos-copia.

    Espera a que MySQL termine de inicializarse.

    El archivo SQL que habíamos incluido en la imagen deberá ejecutarse durante la inicialización de esta base de datos nueva.

    Para comprobarlo, abre Console dentro de bd-juegos-copia y entra en MySQL:

    mysql -u root -p

    Introduce la contraseña:

    RootClase2026

    Ejecuta:

    USE juegos;
    SELECT * FROM videojuegos;

    Deben aparecer los cinco videojuegos.

    Sal de MySQL:

    exit;

    Paso 5. Descargar nuestra imagen Apache + PHP

    En Portainer, abre Images.

    Busca:

    alumnoasir/mi-web-juegos:v1

    Pulsa Pull the image.

    Paso 6. Crear el segundo contenedor Apache

    Entra en Containers → Add container.

    Introduce:

    CampoValor
    Nameweb-juegos-copia
    Imagealumnoasir/mi-web-juegos:v1
    Host port8081
    Container port80
    Networkjuegos-copia-net

    En Advanced container settings → Env añade:

    NameValue
    DB_HOSTbd-juegos-copia

    Esta variable es importante porque la segunda página web debe conectarse al segundo contenedor MySQL, no al primero.

    Pulsa Deploy the container.

    Paso 7. Comprobar las dos páginas

    Ahora abre las siguientes direcciones:

    Primera instalación. http://localhost:8080

    Apache y MySQL originales

    Segunda instalación. http://localhost:8081

    Contenedores creados a partir de nuestras imágenes

    Ambas páginas deberán mostrar los mismos cinco videojuegos.

    Hemos conseguido crear dos instalaciones independientes a partir de nuestras imágenes.

    Paso 8. Comprobación final desde Portainer

    Accede a Containers.

    Deberías tener cuatro contenedores activos:

    ContenedorEstado esperado
    web-juegosRunning
    bd-juegosRunning
    web-juegos-copiaRunning
    bd-juegos-copiaRunning

    PARTE 15. PEQUEÑAS COMPROBACIONES PRÁCTICAS

    Estas actividades permiten comprobar que hemos entendido el funcionamiento de los contenedores.

    Ejercicio 1. Detener Apache

    Desde Portainer, detén el contenedor web-juegos.

    Intenta abrir:

    http://localhost:8080

    ¿Qué sucede?

    Ahora vuelve a iniciar el contenedor desde Portainer y comprueba que la página funciona nuevamente.

    Ejercicio 2. Detener MySQL

    Detén bd-juegos.

    Vuelve a abrir la primera página web.

    ¿Qué sucede cuando PHP intenta consultar la base de datos?

    Inicia nuevamente MySQL y comprueba que la web recupera su funcionamiento.

    Ejercicio 3. Comprobar la independencia de las instalaciones

    Detén web-juegos y bd-juegos, pero deja funcionando los dos contenedores de la segunda instalación.

    Comprueba si sigue funcionando:

    http://localhost:8081

    Explica por qué ocurre.

    Vuelve a iniciar los contenedores originales cuando termines.

    Preguntas de comprensión

    Responde brevemente a las siguientes cuestiones:

    • ¿Por qué no debemos considerar una imagen Docker como una copia de seguridad de la base de datos?
    • ¿Qué es una imagen Docker?
    • ¿Qué es un contenedor?
    • ¿Por qué Hello World aparece detenido después de ejecutarse?
    • ¿Qué utilidad tiene Portainer?
    • ¿Para qué sirve una red Docker?
    • ¿Por qué hemos creado dos contenedores diferentes?
    • ¿Qué significa publicar el puerto 8080:80?
    • ¿Para qué utilizamos las variables de entorno de MySQL?
    • ¿Qué función tiene el conector MySQLi de PHP?
    • ¿Qué diferencia existe entre detener y eliminar un contenedor?
    • ¿Para qué sirve crear una imagen de un contenedor que hemos configurado?
    • ¿Qué ventajas tiene subir nuestra imagen a Docker Hub?
    • ¿Por qué podemos ejecutar dos servidores Apache utilizando los puertos 8080 y 8081?

    PARTE 17. CONCLUSIONES

    Con esta práctica hemos aprendido a utilizar Docker de manera visual a través de Portainer.

    Hemos instalado y configurado dos servicios independientes, Apache con PHP y MySQL, y hemos conseguido que se comuniquen mediante una red privada.

    También hemos creado nuestras propias imágenes, las hemos publicado en Docker Hub y las hemos reutilizado para desplegar una segunda instalación sin repetir manualmente la instalación de los programas.

    Lo más importante es comprender que una imagen permite crear contenedores, mientras que un contenedor es una instancia en ejecución de esa imagen.

    En una próxima práctica aprenderemos a realizar estas mismas operaciones desde la terminal y, posteriormente, a utilizar Docker Compose para automatizar el despliegue de varios servicios.

  • Crear un entorno DEV tipo XAMPP/MAMP con Docker

    Crear un entorno DEV tipo XAMPP/MAMP con Docker

    Objetivo del proyecto

    En esta práctica vamos a crear un entorno de desarrollo web usando Docker. El objetivo es tener un sistema parecido a XAMPP o MAMP, pero más profesional, portable y fácil de reconstruir.

    El entorno permitirá trabajar con:

    • Apache
    • PHP
    • Extensiones comunes de PHP
    • MySQL
    • phpMyAdmin
    • Mailpit para probar correos
    • Redis como servicio adicional opcional
    • Una carpeta local donde guardaremos todos nuestros proyectos web

    Todos los contenedores tendrán el prefijo:

    DEV_

    De esta forma podremos identificarlos fácilmente dentro de Docker.


    1. ¿Qué vamos a construir?

    El sistema tendrá esta estructura:

    Carpeta local del alumno
    │
    ▼
    +-----------------------------+
    | Carpeta DEVLOCAL |
    | |
    | proyecto1/ |
    | proyecto2/ |
    | pruebas/ |
    | _docker/ |
    +-------------+---------------+
    │
    ▼
    +-----------------------------+
    | Docker Compose |
    +-------------+---------------+
    │
    ▼
    +-----------------------------+
    | DEV_apache_php |
    | DEV_mysql |
    | DEV_phpmyadmin |
    | DEV_mailpit |
    | DEV_redis |
    +-----------------------------+

    El alumno podrá crear una carpeta con un proyecto PHP y verlo desde el navegador.

    Por ejemplo:

    http://localhost:8080/pruebas/

    2. Requisitos previos

    Cada alumno necesitará:

    • Un ordenador con Windows, Ubuntu o macOS.
    • Docker instalado.
    • Un editor de código, por ejemplo VS Code.
    • Terminal o consola.
    • Navegador web.

    Docker Compose se usará para definir y levantar varios contenedores a la vez. Docker lo describe como una herramienta para definir y ejecutar aplicaciones multicontenedor.


    3. Instalación de Docker en Windows

    En Windows se recomienda usar Docker Desktop.

    Docker Desktop para Windows incluye Docker Engine, Docker CLI y Docker Compose. La documentación oficial indica que Docker Desktop puede usar WSL 2 como motor de ejecución en Windows.

    Pasos recomendados

    1. Instalar o activar WSL 2.
    2. Instalar Docker Desktop para Windows.
    3. Reiniciar el equipo si lo solicita.
    4. Abrir Docker Desktop.
    5. Comprobar que Docker está funcionando.

    Desde PowerShell o Terminal de Windows:

    docker --version
    docker compose version

    Si ambos comandos responden con una versión, Docker está funcionando correctamente.

    Carpeta recomendada en Windows

    Para evitar problemas con permisos o sincronización, se recomienda crear la carpeta directamente en C:\.

    mkdir C:\DEVLOCAL

    No es recomendable usar carpetas dentro de OneDrive para este tipo de práctica.


    4. Instalación de Docker en Ubuntu

    En Ubuntu se puede instalar Docker Engine directamente desde los repositorios oficiales de Docker. La documentación oficial recomienda configurar primero el repositorio apt de Docker y después instalar Docker Engine y sus plugins.

    Actualizar paquetes

    sudo apt update
    sudo apt upgrade -y

    Instalar dependencias

    sudo apt install -y ca-certificates curl gnupg

    Crear carpeta para claves

    sudo install -m 0755 -d /etc/apt/keyrings

    Descargar clave oficial de Docker

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

    Dar permisos a la clave

    sudo chmod a+r /etc/apt/keyrings/docker.gpg

    Añadir repositorio oficial

    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
    https://download.docker.com/linux/ubuntu \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

    Instalar Docker

    sudo apt update
    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    Comprobar instalación

    docker --version
    docker compose version

    Permitir usar Docker sin sudo

    sudo usermod -aG docker $USER

    Después de este comando, el alumno debe cerrar sesión y volver a entrar.

    Carpeta recomendada en Ubuntu

    mkdir -p ~/DEVLOCAL

    5. Instalación de Docker en macOS

    En macOS se recomienda instalar Docker Desktop para Mac. La documentación oficial diferencia entre Mac con chip Intel y Mac con Apple Silicon, por lo que hay que descargar la versión adecuada.

    Pasos recomendados

    1. Descargar Docker Desktop para Mac.
    2. Elegir la versión correcta:
      • Mac Intel.
      • Mac Apple Silicon.
    3. Instalar la aplicación.
    4. Abrir Docker Desktop.
    5. Esperar a que Docker arranque completamente.
    6. Comprobar desde Terminal:
    docker --version
    docker compose version

    Carpeta recomendada en macOS

    En el caso de este ejemplo usaremos:

    /Users/antoniooteroveiga/DEVLOCAL

    Para otro usuario sería algo similar a:

    /Users/nombre_usuario/DEVLOCAL

    6. Crear la estructura del proyecto

    La estructura será la misma en Windows, Ubuntu y macOS.

    Windows

    Desde PowerShell:

    mkdir C:\DEVLOCAL
    mkdir C:\DEVLOCAL\_docker
    mkdir C:\DEVLOCAL\_docker\php
    mkdir C:\DEVLOCAL\_docker\apache
    mkdir C:\DEVLOCAL\pruebas

    Ubuntu

    Desde Terminal:

    mkdir -p ~/DEVLOCAL/_docker/php
    mkdir -p ~/DEVLOCAL/_docker/apache
    mkdir -p ~/DEVLOCAL/pruebas

    macOS

    Desde Terminal:

    mkdir -p /Users/antoniooteroveiga/DEVLOCAL/_docker/php
    mkdir -p /Users/antoniooteroveiga/DEVLOCAL/_docker/apache
    mkdir -p /Users/antoniooteroveiga/DEVLOCAL/pruebas

    La estructura final será:

    DEVLOCAL/
    ├── _docker/
    │ ├── docker-compose.yml
    │ ├── inicio.sh
    │ ├── inicio.bat
    │ ├── php/
    │ │ ├── Dockerfile
    │ │ ├── php.ini
    │ │ └── msmtprc
    │ └── apache/
    │ └── 000-default.conf
    └── pruebas/
    └── index.php

    7. Crear el archivo docker-compose.yml

    Este archivo define todos los servicios del entorno.

    Debe estar dentro de:

    DEVLOCAL/_docker/docker-compose.yml

    Contenido:

    services:

    apache_php:
    build:
    context: .
    dockerfile: php/Dockerfile
    container_name: DEV_apache_php
    restart: unless-stopped
    ports:
    - "8080:80"
    volumes:
    - ../:/var/www/html
    - ./apache/000-default.conf:/etc/apache2/sites-available/000-default.conf
    - ./php/php.ini:/usr/local/etc/php/conf.d/dev-custom.ini
    - ./php/msmtprc:/etc/msmtprc
    depends_on:
    - mysql
    - mailpit
    networks:
    - DEV_network

    mysql:
    image: mysql:8.4
    container_name: DEV_mysql
    restart: unless-stopped
    ports:
    - "3307:3306"
    environment:
    MYSQL_ROOT_PASSWORD: root
    MYSQL_DATABASE: devdb
    MYSQL_USER: devuser
    MYSQL_PASSWORD: devpass
    volumes:
    - DEV_mysql_data:/var/lib/mysql
    networks:
    - DEV_network

    phpmyadmin:
    image: phpmyadmin:latest
    container_name: DEV_phpmyadmin
    restart: unless-stopped
    ports:
    - "8081:80"
    environment:
    PMA_HOST: mysql
    PMA_PORT: 3306
    PMA_USER: root
    PMA_PASSWORD: root
    UPLOAD_LIMIT: 256M
    depends_on:
    - mysql
    networks:
    - DEV_network

    mailpit:
    image: axllent/mailpit:latest
    container_name: DEV_mailpit
    restart: unless-stopped
    ports:
    - "8025:8025"
    - "1025:1025"
    networks:
    - DEV_network

    redis:
    image: redis:latest
    container_name: DEV_redis
    restart: unless-stopped
    ports:
    - "6379:6379"
    networks:
    - DEV_network

    volumes:
    DEV_mysql_data:

    networks:
    DEV_network:
    name: DEV_network

    8. Crear el archivo Dockerfile

    Este archivo construirá nuestro contenedor de Apache con PHP.

    Debe estar en:

    DEVLOCAL/_docker/php/Dockerfile

    Contenido:

    FROM php:8.3-apache

    RUN apt-get update && apt-get install -y \
    git \
    unzip \
    zip \
    curl \
    msmtp \
    msmtp-mta \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libicu-dev \
    libonig-dev \
    libxml2-dev \
    libssl-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install \
    mysqli \
    pdo \
    pdo_mysql \
    zip \
    gd \
    intl \
    mbstring \
    bcmath \
    soap \
    opcache \
    && a2enmod rewrite headers expires \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

    COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

    WORKDIR /var/www/html

    Con esto tendremos disponibles muchas extensiones habituales de PHP:

    • mysqli
    • pdo_mysql
    • gd
    • zip
    • intl
    • mbstring
    • bcmath
    • soap
    • opcache

    También tendremos instalado Composer, muy útil para proyectos PHP modernos.


    9. Crear el archivo php.ini

    Debe estar en:

    DEVLOCAL/_docker/php/php.ini

    Contenido:

    display_errors = On
    display_startup_errors = On
    error_reporting = E_ALL

    upload_max_filesize = 256M
    post_max_size = 256M
    memory_limit = 512M
    max_execution_time = 300
    max_input_vars = 5000

    date.timezone = Europe/Madrid

    opcache.enable = 1
    opcache.enable_cli = 1

    sendmail_path = "/usr/bin/msmtp -t"

    Este archivo ajusta PHP para un entorno de desarrollo.

    No es una configuración pensada para producción. En producción no deberíamos mostrar errores directamente al usuario.


    10. Crear el archivo msmtprc

    Este archivo permitirá enviar correos desde PHP hacia Mailpit.

    Debe estar en:

    DEVLOCAL/_docker/php/msmtprc

    Contenido:

    defaults
    auth off
    tls off

    account mailpit
    host mailpit
    port 1025
    from devlocal@example.test

    account default : mailpit

    Mailpit capturará los correos y podremos verlos desde el navegador.


    11. Crear la configuración de Apache

    Debe estar en:

    DEVLOCAL/_docker/apache/000-default.conf

    Contenido:

    <VirtualHost *:80>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html

    <Directory /var/www/html>
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/dev_error.log
    CustomLog ${APACHE_LOG_DIR}/dev_access.log combined
    </VirtualHost>

    La directiva importante es:

    AllowOverride All

    Esto permite usar archivos .htaccess, necesarios en muchos proyectos PHP, como WordPress, Laravel o aplicaciones con URLs amigables.


    12. Crear una página de prueba

    Creamos el archivo:

    DEVLOCAL/pruebas/index.php

    Contenido:

    <?php

    echo "<h1>Entorno DEV funcionando</h1>";
    echo "<p>Apache y PHP están funcionando correctamente.</p>";

    echo "<h2>Información de PHP</h2>";
    phpinfo();

    13. Levantar el entorno manualmente

    Windows

    Desde PowerShell:

    cd C:\DEVLOCAL\_docker
    docker compose up -d --build

    Ubuntu

    Desde Terminal:

    cd ~/DEVLOCAL/_docker
    docker compose up -d --build

    macOS

    Desde Terminal:

    cd /Users/antoniooteroveiga/DEVLOCAL/_docker
    docker compose up -d --build

    14. Comprobar los contenedores

    Ejecutamos:

    docker ps

    Deberíamos ver contenedores parecidos a estos:

    DEV_apache_php
    DEV_mysql
    DEV_phpmyadmin
    DEV_mailpit
    DEV_redis

    15. Acceder al entorno desde el navegador

    Apache + PHP

    http://localhost:8080

    Proyecto de pruebas

    http://localhost:8080/pruebas/

    phpMyAdmin

    http://localhost:8081

    Datos de acceso:

    Servidor: mysql
    Usuario: root
    Contraseña: root

    También podemos usar:

    Servidor: mysql
    Usuario: devuser
    Contraseña: devpass

    Mailpit

    http://localhost:8025

    MySQL desde el equipo anfitrión

    Para conectarse desde DBeaver, DataGrip, VS Code o MySQL Workbench:

    Host: localhost
    Puerto: 3307
    Usuario: devuser
    Contraseña: devpass
    Base de datos: devdb

    MySQL desde PHP

    Desde código PHP, el host no será localhost, sino el nombre del servicio Docker:

    Host: mysql
    Puerto: 3306
    Usuario: devuser
    Contraseña: devpass
    Base de datos: devdb

    16. Probar conexión con MySQL desde PHP

    Creamos el archivo:

    DEVLOCAL/pruebas/mysql.php

    Contenido:

    <?php

    $host = "mysql";
    $db = "devdb";
    $user = "devuser";
    $pass = "devpass";

    $conexion = new mysqli($host, $user, $pass, $db);

    if ($conexion->connect_error) {
    die("Error de conexión: " . $conexion->connect_error);
    }

    echo "<h1>Conexión correcta con MySQL</h1>";
    echo "<p>La base de datos devdb está disponible.</p>";

    $conexion->close();

    Abrimos en el navegador:

    http://localhost:8080/pruebas/mysql.php

    Si todo está correcto, veremos un mensaje confirmando la conexión.


    17. Probar envío de correo con Mailpit

    Creamos el archivo:

    DEVLOCAL/pruebas/mail.php

    Contenido:

    <?php

    $destino = "alumno@example.test";
    $asunto = "Prueba desde PHP";
    $mensaje = "Este correo ha sido enviado desde PHP y capturado por Mailpit.";
    $cabeceras = "From: devlocal@example.test";

    if (mail($destino, $asunto, $mensaje, $cabeceras)) {
    echo "<h1>Correo enviado correctamente</h1>";
    echo "<p>Abre Mailpit para verlo.</p>";
    } else {
    echo "<h1>Error al enviar el correo</h1>";
    }

    Abrimos:

    http://localhost:8080/pruebas/mail.php

    Después entramos en Mailpit:

    http://localhost:8025

    Ahí debería aparecer el correo capturado.


    18. Crear script de inicio para Ubuntu y macOS

    Creamos el archivo:

    DEVLOCAL/_docker/inicio.sh

    Contenido:

    #!/bin/bash

    clear

    echo "=============================================="
    echo " ENTORNO DEVLOCAL - DOCKER PHP MYSQL"
    echo "=============================================="
    echo ""

    DEV_PATH="$(cd "$(dirname "$0")" && pwd)"

    cd "$DEV_PATH" || exit 1

    echo "Levantando contenedores DEV..."
    echo ""

    docker compose up -d

    echo ""
    echo "Esperando a que los servicios arranquen..."
    sleep 3

    echo ""
    echo "=============================================="
    echo " CONTENEDORES ACTIVOS"
    echo "=============================================="
    echo ""

    docker ps --filter "name=DEV_"

    echo ""
    echo "=============================================="
    echo " INFORMACION DE CONEXION"
    echo "=============================================="
    echo ""

    echo "Servidor Apache + PHP:"
    echo " URL principal: http://localhost:8080"
    echo " Carpeta web: Carpeta DEVLOCAL del alumno"
    echo ""

    echo "Proyecto de prueba:"
    echo " http://localhost:8080/pruebas/"
    echo ""

    echo "phpMyAdmin:"
    echo " URL: http://localhost:8081"
    echo " Servidor: mysql"
    echo " Usuario root: root"
    echo " Password root: root"
    echo ""

    echo "MySQL desde PHP:"
    echo " Host: mysql"
    echo " Puerto: 3306"
    echo " Base de datos: devdb"
    echo " Usuario: devuser"
    echo " Password: devpass"
    echo ""

    echo "MySQL desde el equipo:"
    echo " Host: localhost"
    echo " Puerto: 3307"
    echo " Base de datos: devdb"
    echo " Usuario: devuser"
    echo " Password: devpass"
    echo ""

    echo "Mailpit:"
    echo " Panel web: http://localhost:8025"
    echo " SMTP interno: mailpit:1025"
    echo ""

    echo "Redis:"
    echo " Host desde Docker: redis"
    echo " Host desde equipo: localhost"
    echo " Puerto: 6379"
    echo ""

    echo "=============================================="
    echo " COMANDOS UTILES"
    echo "=============================================="
    echo ""

    echo "Parar entorno:"
    echo " docker compose down"
    echo ""

    echo "Ver logs:"
    echo " docker compose logs -f"
    echo ""

    echo "Entrar al contenedor PHP:"
    echo " docker exec -it DEV_apache_php bash"
    echo ""

    echo "Entrar a MySQL:"
    echo " docker exec -it DEV_mysql mysql -u root -p"
    echo ""

    echo "=============================================="
    echo " ENTORNO DEV LISTO"
    echo "=============================================="
    echo ""

    Damos permisos de ejecución:

    chmod +x inicio.sh

    Lo ejecutamos:

    ./inicio.sh

    19. Crear script de inicio para Windows

    Creamos el archivo:

    DEVLOCAL/_docker/inicio.bat

    Contenido:

    @echo off
    cls

    echo ==============================================
    echo ENTORNO DEVLOCAL - DOCKER PHP MYSQL
    echo ==============================================
    echo.

    cd /d "%~dp0"

    echo Levantando contenedores DEV...
    echo.

    docker compose up -d

    echo.
    echo Esperando a que los servicios arranquen...
    timeout /t 3 > nul

    echo.
    echo ==============================================
    echo CONTENEDORES ACTIVOS
    echo ==============================================
    echo.

    docker ps --filter "name=DEV_"

    echo.
    echo ==============================================
    echo INFORMACION DE CONEXION
    echo ==============================================
    echo.

    echo Servidor Apache + PHP:
    echo URL principal: http://localhost:8080
    echo Carpeta web: Carpeta DEVLOCAL del alumno
    echo.

    echo Proyecto de prueba:
    echo http://localhost:8080/pruebas/
    echo.

    echo phpMyAdmin:
    echo URL: http://localhost:8081
    echo Servidor: mysql
    echo Usuario root: root
    echo Password root: root
    echo.

    echo MySQL desde PHP:
    echo Host: mysql
    echo Puerto: 3306
    echo Base de datos: devdb
    echo Usuario: devuser
    echo Password: devpass
    echo.

    echo MySQL desde Windows:
    echo Host: localhost
    echo Puerto: 3307
    echo Base de datos: devdb
    echo Usuario: devuser
    echo Password: devpass
    echo.

    echo Mailpit:
    echo Panel web: http://localhost:8025
    echo SMTP interno: mailpit:1025
    echo.

    echo Redis:
    echo Host desde Docker: redis
    echo Host desde Windows: localhost
    echo Puerto: 6379
    echo.

    echo ==============================================
    echo COMANDOS UTILES
    echo ==============================================
    echo.

    echo Parar entorno:
    echo docker compose down
    echo.

    echo Ver logs:
    echo docker compose logs -f
    echo.

    echo Entrar al contenedor PHP:
    echo docker exec -it DEV_apache_php bash
    echo.

    echo Entrar a MySQL:
    echo docker exec -it DEV_mysql mysql -u root -p
    echo.

    echo ==============================================
    echo ENTORNO DEV LISTO
    echo ==============================================
    echo.

    pause

    Para ejecutarlo, el alumno puede hacer doble clic sobre inicio.bat o ejecutarlo desde PowerShell.


    20. Script para parar el entorno

    Ubuntu y macOS

    Creamos:

    DEVLOCAL/_docker/parar.sh

    Contenido:

    #!/bin/bash

    cd "$(dirname "$0")" || exit 1

    echo "Parando entorno DEV..."
    docker compose down

    echo "Entorno detenido."

    Permisos:

    chmod +x parar.sh

    Ejecución:

    ./parar.sh

    Windows

    Creamos:

    DEVLOCAL/_docker/parar.bat

    Contenido:

    @echo off
    cls

    cd /d "%~dp0"

    echo Parando entorno DEV...
    docker compose down

    echo.
    echo Entorno detenido.
    pause

    21. Comandos básicos que debe conocer el alumno

    Levantar contenedores

    docker compose up -d

    Levantar y reconstruir

    docker compose up -d --build

    Parar contenedores

    docker compose down

    Ver contenedores activos

    docker ps

    Ver todos los contenedores

    docker ps -a

    Ver logs

    docker compose logs -f

    Ver logs de un servicio concreto

    docker compose logs -f apache_php

    Entrar al contenedor de PHP

    docker exec -it DEV_apache_php bash

    Entrar al contenedor de MySQL

    docker exec -it DEV_mysql mysql -u root -p

    Contraseña:

    root

    22. Crear varios proyectos web

    El alumno puede crear diferentes carpetas dentro de DEVLOCAL.

    Por ejemplo:

    DEVLOCAL/
    ├── pruebas/
    ├── tienda/
    ├── blog/
    ├── reservas/
    └── ejercicios_php/

    Cada proyecto se abrirá desde el navegador así:

    http://localhost:8080/pruebas/
    http://localhost:8080/tienda/
    http://localhost:8080/blog/
    http://localhost:8080/reservas/
    http://localhost:8080/ejercicios_php/

    23. Ejercicio práctico

    Parte 1: Comprobar PHP

    Crear una carpeta llamada:

    alumno_php

    Dentro crear un archivo:

    index.php

    Con este contenido:

    <?php

    $nombre = "Alumno";
    $curso = "Desarrollo Web con PHP";

    echo "<h1>Hola, $nombre</h1>";
    echo "<p>Estamos trabajando en el curso de $curso.</p>";

    Abrir:

    http://localhost:8080/alumno_php/

    Parte 2: Comprobar MySQL

    Crear un archivo:

    conexion.php

    Contenido:

    <?php

    $conexion = new mysqli("mysql", "devuser", "devpass", "devdb");

    if ($conexion->connect_error) {
    die("Error: " . $conexion->connect_error);
    }

    echo "Conexión correcta con MySQL";

    Abrir:

    http://localhost:8080/alumno_php/conexion.php

    Parte 3: Crear una tabla desde phpMyAdmin

    Entrar en:

    http://localhost:8081

    Seleccionar la base de datos:

    devdb

    Crear una tabla llamada:

    alumnos

    Con estos campos:

    CampoTipoExtra
    idINTAUTO_INCREMENT, PRIMARY KEY
    nombreVARCHAR(100)
    emailVARCHAR(150)
    fecha_altaDATETIME

    SQL:

    CREATE TABLE alumnos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL,
    fecha_alta DATETIME DEFAULT CURRENT_TIMESTAMP
    );

    Insertar datos:

    INSERT INTO alumnos (nombre, email) VALUES
    ('Ana', 'ana@example.test'),
    ('Luis', 'luis@example.test'),
    ('Marta', 'marta@example.test');

    Parte 4: Leer datos desde PHP

    Crear:

    listar.php

    Contenido:

    <?php

    $conexion = new mysqli("mysql", "devuser", "devpass", "devdb");

    if ($conexion->connect_error) {
    die("Error: " . $conexion->connect_error);
    }

    $resultado = $conexion->query("SELECT * FROM alumnos");

    echo "<h1>Listado de alumnos</h1>";

    echo "<table border='1' cellpadding='8'>";
    echo "<tr>";
    echo "<th>ID</th>";
    echo "<th>Nombre</th>";
    echo "<th>Email</th>";
    echo "<th>Fecha de alta</th>";
    echo "</tr>";

    while ($fila = $resultado->fetch_assoc()) {
    echo "<tr>";
    echo "<td>" . $fila["id"] . "</td>";
    echo "<td>" . $fila["nombre"] . "</td>";
    echo "<td>" . $fila["email"] . "</td>";
    echo "<td>" . $fila["fecha_alta"] . "</td>";
    echo "</tr>";
    }

    echo "</table>";

    $conexion->close();

    Abrir:

    http://localhost:8080/alumno_php/listar.php

    24. Preguntas de reflexión para el alumno

    1. ¿Qué diferencia hay entre instalar Apache, PHP y MySQL directamente en el sistema y hacerlo con Docker?
    2. ¿Qué ventaja tiene que todos los servicios estén definidos en un archivo docker-compose.yml?
    3. ¿Por qué PHP se conecta a MySQL usando el host mysql y no localhost?
    4. ¿Qué es un volumen en Docker?
    5. ¿Dónde se guardan los datos de MySQL en este proyecto?
    6. ¿Qué pasaría si ejecutamos docker compose down -v?
    7. ¿Para qué sirve phpMyAdmin?
    8. ¿Para qué sirve Mailpit?
    9. ¿Por qué no deberíamos usar contraseñas como root o devpass en producción?
    10. ¿Qué diferencia hay entre un entorno de desarrollo y un entorno de producción?

    25. Problemas frecuentes

    Docker no arranca en Windows

    Posibles causas:

    • Docker Desktop no está abierto.
    • WSL 2 no está instalado o activado.
    • La virtualización está desactivada en BIOS/UEFI.
    • El equipo necesita reiniciarse.

    Docker recomienda usar el backend WSL 2 en Windows para Docker Desktop.


    El puerto 8080 ya está ocupado

    Puede ocurrir si otro servicio ya está usando ese puerto.

    Solución: cambiar en docker-compose.yml:

    ports:
    - "8080:80"

    Por ejemplo:

    ports:
    - "8090:80"

    Después habría que entrar por:

    http://localhost:8090

    El puerto 3307 ya está ocupado

    Cambiar:

    ports:
    - "3307:3306"

    Por ejemplo:

    ports:
    - "3308:3306"

    No aparecen los cambios en PHP

    Probar a recargar el navegador con:

    Ctrl + F5

    También se puede reiniciar el contenedor:

    docker compose restart apache_php

    Error de permisos en Ubuntu

    Si Docker pide sudo, puede faltar añadir el usuario al grupo docker.

    sudo usermod -aG docker $USER

    Después hay que cerrar sesión y volver a entrar.


    phpMyAdmin no conecta

    Comprobar que MySQL está activo:

    docker ps

    También se pueden ver los logs:

    docker compose logs -f mysql

    26. Cómo borrar todo y empezar de nuevo

    Para parar los contenedores:

    docker compose down

    Para parar y borrar también los datos de MySQL:

    docker compose down -v

    Cuidado: este comando borra el volumen de MySQL. Si hay bases de datos creadas, se perderán.


    29. Ampliaciones posibles

    Cuando el entorno básico funcione, se pueden añadir mejoras:

    • Crear dominios locales como tienda.localhost.
    • Añadir HTTPS local.
    • Añadir un contenedor de Node.js.
    • Añadir un contenedor específico para Laravel.
    • Crear plantillas para WordPress.
    • Añadir copias de seguridad automáticas de MySQL.
    • Crear un script para exportar e importar bases de datos.
    • Crear varios entornos: desarrollo, pruebas y producción.

    30. Conclusión

    Con esta práctica hemos creado un entorno de desarrollo web completo usando Docker. El resultado es similar a XAMPP o MAMP, pero con una ventaja importante: todo el entorno queda definido mediante archivos.

    Esto permite:

    • Repetir la instalación en diferentes sistemas operativos.
    • Compartir el entorno con otros compañeros.
    • Evitar conflictos con instalaciones locales.
    • Trabajar con varios servicios a la vez.
    • Preparar al alumno para entornos profesionales basados en contenedores.

    Docker Compose facilita levantar todos los servicios con un solo comando, lo que lo convierte en una herramienta especialmente útil para desarrollo web moderno.

    Script Cerrar

    Además del script inicio, vamos a crear un script llamado cerrar. Este script no solamente permite apagar el entorno, sino también eliminarlo de diferentes formas según lo que necesitemos. Esto es importante porque no siempre queremos borrar los datos. A veces solo queremos detener los contenedores, y otras veces queremos empezar desde cero.


    Diferencia entre las opciones

    OpciónComando¿Borra contenedores?¿Borra datos MySQL?¿Borra imágenes?
    Parardocker compose stopNoNoNo
    Eliminar contenedoresdocker compose downSíNoNo
    Eliminar con volúmenesdocker compose down -vSíSíNo
    Destruir completodocker compose down -v --rmi localSíSíSí

    docker compose stop

    Detiene los contenedores, pero no los elimina.

    Es como apagar una máquina virtual.


    docker compose down

    Detiene y elimina los contenedores, pero mantiene los volúmenes.

    Es decir, si teníamos una base de datos MySQL con datos, normalmente se conservará.


    docker compose down -v

    Detiene y elimina los contenedores, y también borra los volúmenes.

    En este proyecto significa que se borrarán los datos de MySQL.


    docker compose down -v --rmi local

    Además de eliminar contenedores y volúmenes, elimina imágenes locales creadas por el proyecto.

    En nuestro caso, puede borrar la imagen personalizada de Apache + PHP, por lo que en el siguiente arranque habrá que reconstruirla con:

    docker compose up -d --build

    Recomendación para uso diario

    Para el día a día, lo normal es usar la opción:

    1) Parar contenedores

    o:

    2) Parar y eliminar contenedores

    La opción 3 y 4 solo deberían usarse cuando queramos empezar desde cero.

    Especialmente la opción 3:

    docker compose down -v

    porque borra los datos de MySQL.

    Script para Ubuntu y macOS: cerrar.sh

    Crear el archivo:

    nano /Users/antoniooteroveiga/DEVLOCAL/_docker/cerrar.sh

    Contenido:

    #!/bin/bash

    clear

    echo "=============================================="
    echo " CERRAR ENTORNO DEVLOCAL"
    echo "=============================================="
    echo ""

    DEV_PATH="$(cd "$(dirname "$0")" && pwd)"
    cd "$DEV_PATH" || exit 1

    echo "Este script permite cerrar o destruir el entorno Docker DEV."
    echo ""
    echo "Elige una opcion:"
    echo ""
    echo " 1) Parar contenedores"
    echo " Equivale a: docker compose stop"
    echo ""
    echo " 2) Parar y eliminar contenedores"
    echo " Equivale a: docker compose down"
    echo ""
    echo " 3) Parar, eliminar contenedores y borrar volumenes"
    echo " Equivale a: docker compose down -v"
    echo " AVISO: se borraran las bases de datos de MySQL."
    echo ""
    echo " 4) Parar, eliminar contenedores, volumenes e imagenes del proyecto"
    echo " Equivale a: docker compose down -v --rmi local"
    echo " AVISO: se borraran las bases de datos y las imagenes creadas localmente."
    echo ""
    echo " 5) Ver contenedores DEV activos"
    echo ""
    echo " 6) Cancelar"
    echo ""

    read -p "Selecciona una opcion [1-6]: " opcion

    echo ""

    case "$opcion" in

    1)
    echo "Parando contenedores..."
    docker compose stop
    echo ""
    echo "Contenedores detenidos."
    ;;

    2)
    echo "Parando y eliminando contenedores..."
    docker compose down
    echo ""
    echo "Contenedores eliminados. Los volumenes se conservan."
    ;;

    3)
    echo "ATENCION: esta opcion borrara los volumenes."
    echo "Esto eliminara los datos persistentes de MySQL."
    echo ""
    read -p "Escribe BORRAR para confirmar: " confirmacion

    if [ "$confirmacion" = "BORRAR" ]; then
    echo ""
    echo "Eliminando contenedores y volumenes..."
    docker compose down -v
    echo ""
    echo "Entorno eliminado con volumenes."
    else
    echo ""
    echo "Operacion cancelada. No se ha borrado nada."
    fi
    ;;

    4)
    echo "ATENCION: esta opcion borrara contenedores, volumenes e imagenes locales."
    echo "Esto eliminara los datos de MySQL y obligara a reconstruir la imagen PHP."
    echo ""
    read -p "Escribe DESTRUIR para confirmar: " confirmacion

    if [ "$confirmacion" = "DESTRUIR" ]; then
    echo ""
    echo "Eliminando contenedores, volumenes e imagenes locales..."
    docker compose down -v --rmi local
    echo ""
    echo "Entorno destruido."
    else
    echo ""
    echo "Operacion cancelada. No se ha borrado nada."
    fi
    ;;

    5)
    echo "Mostrando contenedores DEV..."
    echo ""
    docker ps -a --filter "name=DEV_"
    ;;

    6)
    echo "Operacion cancelada."
    ;;

    *)
    echo "Opcion no valida."
    ;;

    esac

    echo ""
    echo "=============================================="
    echo " PROCESO FINALIZADO"
    echo "=============================================="
    echo ""

    Dar permisos de ejecución:

    chmod +x /Users/antoniooteroveiga/DEVLOCAL/_docker/cerrar.sh

    Ejecutarlo:

    cd /Users/antoniooteroveiga/DEVLOCAL/_docker
    ./cerrar.sh

    Versión más portable

    Si quieres que funcione sin depender de tu usuario concreto, es mejor decirles que creen el archivo dentro de _docker y que lo ejecuten desde ahí.

    cd ~/DEVLOCAL/_docker
    nano cerrar.sh
    chmod +x cerrar.sh
    ./cerrar.sh

    El script ya detecta automáticamente la carpeta donde está, gracias a esta línea:

    DEV_PATH="$(cd "$(dirname "$0")" && pwd)"

    Por eso vale tanto para Ubuntu como para macOS.


    Script para Windows: cerrar.bat

    Crear el archivo:

    C:\DEVLOCAL\_docker\cerrar.bat

    Contenido:

    @echo off
    cls

    cd /d "%~dp0"

    :menu
    cls
    echo ==============================================
    echo CERRAR ENTORNO DEVLOCAL
    echo ==============================================
    echo.
    echo Este script permite cerrar o destruir el entorno Docker DEV.
    echo.
    echo Elige una opcion:
    echo.
    echo 1) Parar contenedores
    echo Equivale a: docker compose stop
    echo.
    echo 2) Parar y eliminar contenedores
    echo Equivale a: docker compose down
    echo.
    echo 3) Parar, eliminar contenedores y borrar volumenes
    echo Equivale a: docker compose down -v
    echo AVISO: se borraran las bases de datos de MySQL.
    echo.
    echo 4) Parar, eliminar contenedores, volumenes e imagenes del proyecto
    echo Equivale a: docker compose down -v --rmi local
    echo AVISO: se borraran las bases de datos y las imagenes creadas localmente.
    echo.
    echo 5) Ver contenedores DEV activos
    echo.
    echo 6) Cancelar
    echo.

    set /p opcion=Selecciona una opcion [1-6]:

    if "%opcion%"=="1" goto parar
    if "%opcion%"=="2" goto eliminar
    if "%opcion%"=="3" goto volumenes
    if "%opcion%"=="4" goto destruir
    if "%opcion%"=="5" goto ver
    if "%opcion%"=="6" goto cancelar

    echo.
    echo Opcion no valida.
    pause
    goto menu

    :parar
    cls
    echo Parando contenedores...
    echo.
    docker compose stop
    echo.
    echo Contenedores detenidos.
    pause
    goto fin

    :eliminar
    cls
    echo Parando y eliminando contenedores...
    echo.
    docker compose down
    echo.
    echo Contenedores eliminados. Los volumenes se conservan.
    pause
    goto fin

    :volumenes
    cls
    echo ATENCION: esta opcion borrara los volumenes.
    echo Esto eliminara los datos persistentes de MySQL.
    echo.
    set /p confirmacion=Escribe BORRAR para confirmar:

    if "%confirmacion%"=="BORRAR" (
    echo.
    echo Eliminando contenedores y volumenes...
    docker compose down -v
    echo.
    echo Entorno eliminado con volumenes.
    ) else (
    echo.
    echo Operacion cancelada. No se ha borrado nada.
    )

    pause
    goto fin

    :destruir
    cls
    echo ATENCION: esta opcion borrara contenedores, volumenes e imagenes locales.
    echo Esto eliminara los datos de MySQL y obligara a reconstruir la imagen PHP.
    echo.
    set /p confirmacion=Escribe DESTRUIR para confirmar:

    if "%confirmacion%"=="DESTRUIR" (
    echo.
    echo Eliminando contenedores, volumenes e imagenes locales...
    docker compose down -v --rmi local
    echo.
    echo Entorno destruido.
    ) else (
    echo.
    echo Operacion cancelada. No se ha borrado nada.
    )

    pause
    goto fin

    :ver
    cls
    echo Mostrando contenedores DEV...
    echo.
    docker ps -a --filter "name=DEV_"
    echo.
    pause
    goto menu

    :cancelar
    cls
    echo Operacion cancelada.
    pause
    goto fin

    :fin
    cls
    echo ==============================================
    echo PROCESO FINALIZADO
    echo ==============================================
    echo.

  • Práctica guiada: Instalación de Cypht en Ubuntu Server con Docker

    Práctica guiada: Instalación de Cypht en Ubuntu Server con Docker

    1. Introducción

    En esta práctica vamos a instalar Cypht en una máquina Ubuntu Server.

    Cypht es una aplicación web que permite consultar varias cuentas de correo desde una única interfaz. A diferencia de otros clientes webmail más clásicos, Cypht está pensado para actuar como un agregador de cuentas de correo, por lo que resulta especialmente útil cuando queremos gestionar varias direcciones desde el navegador.

    Durante la práctica utilizaremos Docker y Docker Compose, ya que esto nos permite instalar Cypht junto con su base de datos de una forma más limpia, controlada y fácil de mantener.


    2. Objetivos de la práctica

    Al finalizar esta práctica, el alumno será capaz de:

    • Crear una máquina virtual Ubuntu Server.
    • Instalar Docker en Ubuntu Server.
    • Usar Docker Compose para desplegar varios servicios.
    • Instalar Cypht junto con MariaDB.
    • Acceder a Cypht desde un navegador web.
    • Comprender el funcionamiento básico de un servicio web desplegado con contenedores.
    • Solucionar errores habituales relacionados con permisos de Docker.

    3. Escenario de la práctica

    Vamos a crear el siguiente entorno:

    Navegador del alumno
    |
    v
    http://IP_DEL_SERVIDOR:8085
    |
    v
    Ubuntu Server
    |
    +-- Contenedor Cypht
    |
    +-- Contenedor MariaDB

    Cypht será la aplicación web y MariaDB almacenará la configuración y los datos necesarios.


    4. Requisitos previos

    Para realizar esta práctica necesitamos:

    • Una máquina virtual con Ubuntu Server 24.04 o similar.
    • Acceso a Internet desde la máquina virtual.
    • Un usuario con permisos de sudo.
    • Al menos:
      • 2 CPU
      • 4 GB de RAM recomendados
      • 25 GB de disco como mínimo
    • Conocimientos básicos de terminal Linux.

    5. Configuración recomendada de la máquina virtual

    Si se trabaja con Proxmox, VirtualBox, VMware o similar, se recomienda crear la máquina con estos valores:

    RecursoValor recomendado
    Sistema operativoUbuntu Server 24.04
    CPU2 cores
    RAM4 GB
    Disco25 GB mínimo
    RedNAT o adaptador puente
    AccesoConsola o SSH

    Importante:
    Con 1 CPU y 2 GB de RAM la instalación puede funcionar, pero el sistema irá justo. Para trabajar cómodamente con Docker, Cypht y MariaDB, se recomienda usar 2 CPU y 4 GB de RAM.


    Parte 1: Preparación del sistema

    Paso 1. Actualizar el sistema

    sudo apt update

    Entramos en la máquina Ubuntu Server y actualizamos los repositorios:

    Después actualizamos los paquetes instalados:

    sudo apt upgrade -y

    Paso 2. Instalar herramientas necesarias

    Instalamos algunos paquetes que necesitaremos para añadir el repositorio oficial de Docker:

    sudo apt install -y ca-certificates curl gnupg

    Parte 2: Instalación de Docker

    Paso 3. Crear el directorio para las claves de Docker

    Ejecutamos:

    sudo install -m 0755 -d /etc/apt/keyrings

    Este directorio se usará para guardar la clave GPG del repositorio oficial de Docker.


    Paso 4. Descargar la clave GPG de Docker

    Ejecutamos:

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

    Después damos permisos de lectura a la clave:

    sudo chmod a+r /etc/apt/keyrings/docker.gpg

    Paso 5. Añadir el repositorio oficial de Docker

    Ejecutamos el siguiente comando completo:

    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
    https://download.docker.com/linux/ubuntu \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

    Actualizamos de nuevo los repositorios:

    sudo apt update

    Paso 6. Instalar Docker y Docker Compose

    Instalamos Docker:

    sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

    Comprobamos que Docker se ha instalado correctamente:

    docker --version

    Y comprobamos Docker Compose:

    docker compose version

    Si los comandos muestran una versión, la instalación ha sido correcta.


    Paso 7. Comprobar el estado del servicio Docker

    Ejecutamos:

    sudo systemctl status docker

    Si aparece como active (running), Docker está funcionando.

    Podemos salir de esa pantalla pulsando:

    q

    Si Docker no estuviera activo, lo arrancamos con:

    sudo systemctl enable --now docker

    Parte 3: Preparación de Cypht

    Paso 8. Crear la carpeta de trabajo

    Crearemos la instalación en /opt/cypht.

    sudo mkdir -p /opt/cypht

    Cambiamos el propietario de la carpeta para poder trabajar cómodamente con nuestro usuario:

    sudo chown -R $USER:$USER /opt/cypht

    Entramos en la carpeta:

    cd /opt/cypht

    Paso 9. Crear el archivo docker-compose.yml

    Creamos el archivo:

    nano docker-compose.yml

    Dentro pegamos el siguiente contenido:

    services:
    db:
    image: mariadb:10
    container_name: cypht-db
    restart: unless-stopped
    volumes:
    - ./data/mysql:/var/lib/mysql
    environment:
    MYSQL_ROOT_PASSWORD: Cambia_Esta_Clave_Root
    MYSQL_DATABASE: cypht
    MYSQL_USER: cypht
    MYSQL_PASSWORD: Cambia_Esta_Clave_Cypht
    healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-ucypht", "-pCambia_Esta_Clave_Cypht"]
    interval: 10s
    timeout: 5s
    retries: 5

    cypht:
    image: cypht/cypht:2.8.0
    container_name: cypht
    restart: unless-stopped
    depends_on:
    db:
    condition: service_healthy
    ports:
    - "8085:80"
    environment:
    AUTH_USERNAME: admin
    AUTH_PASSWORD: Cambia_Esta_Clave_Admin

    DB_CONNECTION_TYPE: host
    DB_DRIVER: mysql
    DB_HOST: db
    DB_NAME: cypht
    DB_USER: cypht
    DB_PASS: Cambia_Esta_Clave_Cypht

    SESSION_TYPE: DB
    USER_CONFIG_TYPE: DB
    volumes:
    - ./data/log/nginx:/var/log/nginx
    - ./data/log/php:/var/log/php
    - ./data/log/supervisord:/var/log/supervisord
    - ./data/fonts:/usr/local/share/cypht/site/fonts

    Guardamos el archivo en nano:

    Ctrl + O
    Enter
    Ctrl + X

    Paso 10. Cambiar las contraseñas

    Antes de arrancar los contenedores, debemos modificar las siguientes claves:

    Cambia_Esta_Clave_Root
    Cambia_Esta_Clave_Cypht
    Cambia_Esta_Clave_Admin

    Podemos editar el archivo de nuevo:

    nano docker-compose.yml

    Por ejemplo:

    MYSQL_ROOT_PASSWORD: Root_1234_segura
    MYSQL_PASSWORD: Cypht_1234_segura
    AUTH_PASSWORD: Admin_1234_segura
    DB_PASS: Cypht_1234_segura

    Importante:
    La contraseña MYSQL_PASSWORD y la contraseña DB_PASS deben coincidir, porque una define la contraseña del usuario de la base de datos y la otra es la contraseña que usará Cypht para conectarse a esa base de datos.


    Parte 4: Arranque de Cypht

    Paso 11. Levantar los contenedores

    Desde la carpeta /opt/cypht, ejecutamos:

    sudo docker compose up -d

    La opción -d significa que los contenedores se ejecutarán en segundo plano.


    Paso 12. Comprobar que los contenedores están funcionando

    Ejecutamos:

    sudo docker ps

    Deberíamos ver dos contenedores:

    • cypht
    • cypht-db

    Ejemplo de salida esperada:

    CONTAINER ID   IMAGE              PORTS                  NAMES
    xxxxxxxxxxxx cypht/cypht:2.8.0 0.0.0.0:8085->80/tcp cypht
    xxxxxxxxxxxx mariadb:10 3306/tcp cypht-db

    Paso 13. Ver los logs si algo falla

    Si algún contenedor no aparece o hay problemas, revisamos los logs:

    sudo docker compose logs -f

    Para salir de los logs:

    Ctrl + C

    También podemos ver solo los logs de Cypht:

    sudo docker compose logs -f cypht

    O solo los de MariaDB:

    sudo docker compose logs -f db

    Parte 5: Acceso desde el navegador

    Paso 14. Obtener la IP del servidor

    Ejecutamos:

    ip a

    Buscamos la dirección IP de la tarjeta de red principal. Suele aparecer en interfaces como:

    ens18
    ens33
    eth0
    enp0s3

    Ejemplo:

    inet 192.168.1.80/24

    En este caso, la IP sería:

    192.168.1.80

    Paso 15. Entrar en Cypht desde el navegador

    Desde el ordenador anfitrión o desde otro equipo de la misma red, abrimos:

    http://IP_DEL_SERVIDOR:8085

    Por ejemplo:

    http://192.168.1.80:8085

    El usuario será:

    admin

    La contraseña será la que hayamos puesto en:

    AUTH_PASSWORD

    Parte 6: Apertura del puerto en el firewall

    Paso 16. Comprobar si UFW está activo

    Ejecutamos:

    sudo ufw status

    Si aparece como inactive, no hace falta abrir nada.

    Si aparece como active, abrimos el puerto 8085:

    sudo ufw allow 8085/tcp

    Comprobamos de nuevo:

    sudo ufw status

    Parte 7: Añadir cuentas de correo en Cypht

    Paso 17. Datos necesarios para configurar una cuenta

    Para añadir una cuenta de correo en Cypht necesitamos conocer los datos del servidor de correo.

    Normalmente necesitaremos:

    DatoEjemplo
    Servidor IMAPmail.midominio.com
    Puerto IMAP993
    Seguridad IMAPSSL/TLS
    Usuariousuario@midominio.com
    ContraseñaContraseña del correo
    Servidor SMTPmail.midominio.com
    Puerto SMTP587
    Seguridad SMTPSTARTTLS

    Paso 18. Añadir una cuenta IMAP

    Dentro de Cypht:

    1. Entramos con el usuario admin.
    2. Accedemos a la configuración.
    3. Buscamos la sección de cuentas o servidores IMAP.
    4. Añadimos los datos de la cuenta.
    5. Guardamos la configuración.

    Ejemplo de configuración IMAP:

    Servidor IMAP: mail.midominio.com
    Puerto: 993
    Seguridad: SSL/TLS
    Usuario: usuario@midominio.com
    Contraseña: ********

    Paso 19. Añadir una cuenta SMTP

    Para poder enviar correos también debemos configurar SMTP.

    Ejemplo:

    Servidor SMTP: mail.midominio.com
    Puerto: 587
    Seguridad: STARTTLS
    Usuario: usuario@midominio.com
    Contraseña: ********

    Nota:
    En algunos proveedores de correo puede ser necesario activar el acceso por aplicaciones externas o generar una contraseña específica de aplicación.


    Parte 8: Comandos básicos de administración

    Ver contenedores activos

    sudo docker ps

    Ver todos los contenedores, incluso los detenidos

    sudo docker ps -a

    Parar Cypht

    cd /opt/cypht
    sudo docker compose down

    Arrancar Cypht de nuevo

    cd /opt/cypht
    sudo docker compose up -d

    Reiniciar los contenedores

    cd /opt/cypht
    sudo docker compose restart

    Ver logs

    cd /opt/cypht
    sudo docker compose logs -f

    Actualizar imágenes

    cd /opt/cypht
    sudo docker compose pull
    sudo docker compose up -d

    Parte 9: Error habitual de permisos con Docker

    Problema

    Puede aparecer este error:

    unable to get image 'mariadb:10': permission denied while trying to connect to the docker API at unix:///var/run/docker.sock

    Este error significa que el usuario actual no tiene permisos para comunicarse con Docker.


    Solución rápida

    Ejecutar el comando con sudo:

    sudo docker compose up -d

    Solución permanente

    Añadimos nuestro usuario al grupo docker:

    sudo usermod -aG docker $USER

    Después reiniciamos la máquina:

    sudo reboot

    Cuando vuelva a arrancar, comprobamos:

    docker ps

    Si ya no aparece el error, podremos usar Docker sin sudo.

    Importante:
    Añadir un usuario al grupo docker le da mucho poder sobre el sistema. En un servidor real, esto debe hacerse solo con usuarios de confianza.


    Parte 10: Comprobaciones finales

    Al terminar la práctica, debemos comprobar:

    ComprobaciónResultado esperado
    Docker está instaladodocker --version muestra una versión
    Docker Compose funcionadocker compose version muestra una versión
    Los contenedores están activossudo docker ps muestra cypht y cypht-db
    Cypht responde en navegadorSe puede acceder a http://IP:8085
    El usuario admin funcionaPodemos iniciar sesión
    El puerto está abiertoEl navegador conecta correctamente
    Se puede añadir una cuenta IMAPLa cuenta aparece en Cypht
  • Instalación y Primeros Pasos con Docker en Windows

    Instalación y Primeros Pasos con Docker en Windows

    Hay conceptos que, una vez se ven, el cerebro hace clic y ya no vuelve atrás. La diferencia entre máquinas virtuales y contenedores es uno de esos momentos fundacionales en la vida de cualquier persona que empieza a pensar como ingeniero de sistemas.

    La metáfora clásica —y sorprendentemente precisa— es esta:

    👉 Las máquinas virtuales son casas completas.
    👉 Los contenedores son apartamentos dentro del mismo edificio.

    La diferencia que cambia la forma de diseñar sistemas

    Esto es lo verdaderamente importante —y lo que merece que tus alumnos lo entiendan bien:

    Con VMs piensas en:

    «Voy a crear servidores.»

    Con contenedores empiezas a pensar:

    «Voy a desplegar servicios.»

    Instalación de Docker Desktop y Portainer en Windows

    En los entornos tecnológicos actuales, la virtualización ligera mediante contenedores se ha convertido en un estándar para el desarrollo, despliegue y administración de aplicaciones.

    Docker es una plataforma que permite empaquetar aplicaciones junto con todas sus dependencias dentro de contenedores, garantizando que funcionen de forma idéntica en cualquier sistema.

    En este tema aprenderemos a preparar un equipo Windows para trabajar con Docker Desktop y a instalar Portainer, una herramienta gráfica que facilita la gestión de contenedores.

    Esta instalación será la base de todos los proyectos que realizaremos posteriormente.


    1. ¿Por qué Docker necesita Linux en Windows?

    Docker utiliza tecnologías propias del kernel de Linux como:

    • namespaces → aislamiento de procesos
    • cgroups → control de recursos
    • union filesystems → capas de almacenamiento

    Windows no dispone de estas tecnologías de forma nativa.

    La solución es WSL2.


    ¿Qué es WSL?

    WSL (Windows Subsystem for Linux) es una característica de Windows que permite ejecutar un kernel real de Linux dentro del sistema operativo sin necesidad de usar una máquina virtual tradicional.

    • No es un emulador.
    • No es una traducción de comandos.
    • Es Linux funcionando dentro de Windows.

    Ventajas de WSL2 frente a una VM clásica:

    • Arranque casi instantáneo
    • Menor consumo de memoria
    • Mejor rendimiento de disco
    • Integración directa con el sistema

    Docker Desktop utiliza WSL2 como motor de virtualización.

    👉 Sin WSL2, Docker simplemente no puede funcionar correctamente en Windows moderno.



    3. Activar la virtualización

    La virtualización debe estar habilitada en la BIOS/UEFI. Si has estado trabajando con maquinas virtuales con Virtual Box o VMWare seguro que ya lo tienes.

    Cómo comprobarlo
    1. Abrir el Administrador de tareas
    2. Ir a la pestaña Rendimiento → CPU

    Debe aparecer:

    Virtualización: Habilitada
    

    Si está deshabilitada:

    • Reiniciar el equipo
    • Entrar en BIOS/UEFI
    • Activar alguna de estas opciones:
    Intel VT-x
    Intel Virtualization Technology
    AMD-V
    SVM Mode
    

    Guardar cambios y reiniciar.


    4. Instalación de WSL2

    Abrir PowerShell como administrador.

    Ejecutar:

    wsl --install
    

    Este comando realiza automáticamente:

    • Instalación del kernel Linux
    • Activación de componentes necesarios
    • Instalación de Ubuntu
    • Configuración de WSL2 como versión por defecto

    Reiniciar el equipo cuando lo solicite.


    Comprobar que WSL2 está funcionando

    wsl -l -v
    

    Resultado esperado:

    NAME      STATE    VERSION
    Ubuntu    Running  2
    

    Si aparece versión 1:

    wsl --set-version Ubuntu 2
    

    5. Instalación de Docker Desktop

    Descargar desde la web oficial: https://www.docker.com/products/docker-desktop/

    Evitar instaladores de terceros.


    Instalación

    Ejecutar el instalador.

    Durante el proceso es MUY importante verificar que esté marcada la opción:

    ✅ Use WSL 2 instead of Hyper-V

    Esto permitirá que Docker funcione con el backend moderno.

    Finalizar la instalación y reiniciar si es necesario.


    6. Primer arranque de Docker

    Abrir Docker Desktop desde el menú inicio.

    El primer arranque puede tardar unos minutos.

    Cuando Docker esté listo aparecerá el mensaje:

    Docker is running
    

    Verificación desde terminal

    Abrir PowerShell o CMD:

    docker version
    

    Si todo está correcto, se mostrará información del cliente y del servidor Docker.

    Probar el primer contenedor:

    docker run hello-world
    

    Si aparece un mensaje de bienvenida, la instalación es correcta.

    Este pequeño contenedor confirma que:

    • Docker descarga imágenes
    • Puede crear contenedores
    • El motor funciona

    7. Configuración recomendada de Docker Desktop

    Ir a:

    Settings → Resources
    

    Asignación recomendada:

    • RAM: 4–6 GB
    • CPU: 2 o más
    • Swap: 1–2 GB

    Si los equipos son potentes, aumentar estos valores mejora la experiencia.


    8. ¿Qué es Portainer?

    Portainer es una interfaz web que permite administrar Docker sin depender únicamente de la terminal.

    Con Portainer es posible:

    • Crear contenedores
    • Gestionar imágenes
    • Administrar volúmenes
    • Supervisar recursos
    • Eliminar servicios

    Es una herramienta muy utilizada en entornos profesionales y facilita enormemente el aprendizaje inicial.


    9. Instalación de Portainer

    Docker desktop permite instalar Portainter como un plugin mas.

    Si deseas hacerlo desde terminal tambien puedes:

    Crear un volumen para almacenar los datos de Portainer.

    docker volume create portainer_data
    

    Ahora ejecutamos el contenedor:

    docker run -d -p 9443:9443 \
    --name portainer \
    --restart=always \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    portainer/portainer-ce
    

    ¿Qué estamos haciendo?

    • -d → ejecuta el contenedor en segundo plano
    • -p 9443:9443 → expone la interfaz web
    • docker.sock → permite a Portainer comunicarse con Docker
    • volumen /data → guarda la configuración

    10. Primer acceso a Portainer

    Abrir el navegador:

    https://localhost:9443
    

    Es normal que el navegador advierta sobre el certificado.

    Continuar igualmente.




    Problemas habituales

    Docker no arranca

    Generalmente ocurre porque:

    • WSL no está activo
    • Virtualización deshabilitada
    • Conflictos con Hyper-V

    Solución típica:

    wsl --update
    wsl --shutdown
    

    Reiniciar Docker.


    Error de memoria

    Asignar más RAM desde:

    Settings → Resources
    

    WSL extremadamente lento

    Ejecutar:

    wsl --shutdown
    

    Y reiniciar Docker.


    A partir de aquí comenzaremos a trabajar con:

    • imágenes
    • contenedores
    • redes
    • volúmenes
    • orquestación

  • Docker desde Terminal

    Docker desde Terminal

    Antes de comenzar la misión, tu equipo debe estar preparado.

    Docker será el motor que dará vida a toda la infraestructura.

    Instalar Docker correctamente desde la terminal en Ubuntu utilizando el repositorio oficial.

    Evita instalar versiones antiguas desde repositorios genéricos.

    Queremos herramientas profesionales.


    Actualizar el sistema

    sudo apt update
    sudo apt upgrade -y
    

    Esto reduce la probabilidad de conflictos de dependencias.

    Los ingenieros prudentes actualizan primero.


    Instalar paquetes necesarios

    sudo apt install apt-transport-https ca-certificates curl software-properties-common -y
    

    Estos paquetes permiten a Ubuntu comunicarse de forma segura con repositorios externos.


    Añadir la clave oficial de Docker

    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
    

    Una clave GPG verifica que el software proviene de la fuente legítima.

    Sin esto, cualquiera podría hacerse pasar por Docker.

    Y eso sería… una idea terrible.


    Añadir el repositorio oficial

    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] \
      https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | \
      sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
    

    Ahora Ubuntu sabrá dónde buscar Docker.


    Instalar Docker Engine

    sudo apt update
    sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
    

    Aquí ocurre la magia.


    Verificar la instalación

    sudo systemctl status docker
    

    Debe aparecer como:

    👉 active (running)

    Ahora prueba:

    sudo docker run hello-world
    

    Si ves el mensaje de bienvenida…

    Docker está vivo.


    MUY recomendado — Usar Docker sin sudo

    Trabajar con sudo constantemente es incómodo.

    Haz esto:

    sudo usermod -aG docker $USER
    

    Después CIERRA SESIÓN o reinicia.

    No es opcional — el cambio de grupo necesita recargar permisos.

    Comprueba:

    docker run hello-world
    

    Video de la lección

    Crear y ejecutar contenedores

    Contenedor interactivo básico

    docker run -it ubuntu
    

    Qué ocurre aquí:

    • run → crea el contenedor si no existe
    • -i → mantiene STDIN abierto
    • -t → asigna terminal
    • ubuntu → imagen base

    Si no está descargada, Docker la bajará automáticamente.

    Para salir sin detener el contenedor:

    Ctrl + P + Q
    

    Para salir deteniéndolo:

    exit
    

    Nombrar un contenedor

    docker run -it --name mi-ubuntu ubuntu
    

    Nombrar contenedores evita trabajar con IDs largos e ilegibles.

    Tu yo del futuro te lo agradecerá.


    Persistencia usando carpeta del host

    docker run --name mi-ubuntu -it -v /datos-persistentes:/datos ubuntu
    

    Qué significa esto:

    HOST → CONTENEDOR
    /datos-persistentes → /datos
    

    Todo lo que guardes en /datos sobrevivirá aunque borres el contenedor.

    Si la carpeta no existe, Docker la crea.


    Persistencia usando volumen Docker (RECOMENDADO)

    Primero crea el volumen:

    docker volume create mi-volumen
    

    Ahora úsalo:

    docker run --name mi-ubuntu -it -v mi-volumen:/datos ubuntu
    

    👉 Docker gestionará la ubicación real del almacenamiento.

    Ventajas:

    • Más seguro
    • Mejor rendimiento
    • Independiente del sistema de carpetas

    ⚠️ Nota para Windows

    docker run -it -v C:\ruta\local:/ruta/en/contenedor ubuntu
    

    En Ubuntu Desktop NO se usa esta sintaxis.

    La equivalente sería:

    docker run -it -v /home/usuario/datos:/datos ubuntu
    

    Consultar información del sistema Docker

    Ver contenedores en ejecución

    docker ps
    

    Ver TODOS los contenedores

    docker ps -a
    

    Aquí aparecerán también los detenidos.

    Muy útil para debugging.


    Ver imágenes descargadas

    docker images
    

    O su versión moderna:

    docker image ls
    

    Crear imágenes personalizadas

    Imagina que has instalado paquetes dentro del contenedor y no quieres repetir el proceso.

    Puedes congelar ese estado.

    docker commit mi-ubuntu mi-imagen-personalizada
    

    Ahora tienes una nueva imagen lista para usar:

    docker run -it mi-imagen-personalizada
    

    📌 Importante para mentalidad profesional:

    docker commit es útil para aprender…

    Pero en entornos reales se prefiere usar Dockerfile porque permite reproducibilidad.


    Operaciones con contenedores

    Arrancar un contenedor detenido

    docker start mi-ubuntu
    

    Arrancar y conectarte directamente

    docker start -ai mi-ubuntu
    

    Parámetros:

    • -a → attach (conectar terminal)
    • -i → modo interactivo

    Ejecutar comandos dentro de un contenedor activo

    docker exec -it mi-ubuntu bash
    

    Esto abre una nueva shell.

    Extremadamente usado por administradores.


    Eliminar un contenedor

    ⚠️ Debe estar detenido.

    docker rm mi-ubuntu
    

    Para forzar:

    docker rm -f mi-ubuntu
    

    Limpiar contenedores detenidos automáticamente

    docker container prune
    

    Docker pedirá confirmación.

    Este comando es famoso por liberar gigas de disco en laboratorios.

  • Del Contenedor a la Base de Datos: MySQL Docker + Workbench

    Del Contenedor a la Base de Datos: MySQL Docker + Workbench

    Vamos a creae una base de datos naciendo en un contenedor, y desde fuera un cliente elegante (Workbench) hablando con ella a través del vacío digital. Eso es red, puertos y aislamiento en acción.

    1. Crear contenedor MySQL.
    2. Publicar puerto al host.
    3. Conectarse desde MySQL Workbench.
    4. Ver que el contenedor es “otro sistema”, pero accesible.

    Arquitectura:

    Workbench (Host)  →  localhost:3306  →  Contenedor MySQL

    Paso 1 — Crear contenedor MySQL

    En terminal:

    docker run -d \
      --name mysql-demo \
      -e MYSQL_ROOT_PASSWORD=1234 \
      -e MYSQL_DATABASE=demo \
      -p 3306:3306 \
      mysql:8

    Qué significa cada parte (esto es lo que debes explicar)

    • -d → modo daemon (segundo plano)
    • --name mysql-demo → nombre del contenedor
    • MYSQL_ROOT_PASSWORD → password del root (obligatorio)
    • MYSQL_DATABASE → crea BD inicial
    • -p 3306:3306 → publica puerto MySQL al host
    • mysql:8 → imagen oficial

    Paso 2 — Verificar que el contenedor está vivo

    docker ps

    Debe aparecer algo como:

    mysql-demo   mysql:8   Up ...   0.0.0.0:3306->3306/tcp

    Si no arranca:

    docker logs mysql-demo

    MySQL tarda unos segundos en inicializar. Paciencia científica.


    Paso 3 — Conectar desde MySQL Workbench

    En Workbench → New Connection

    Parámetros:

    • Host: 127.0.0.1
    • Port: 3306
    • User: root
    • Password: 1234

    Test Connection → Connect.

    Si todo va bien, verás la BD demo.


    Paso 4 — Prueba real

    En Workbench ejecuta:

    CREATE TABLE alumnos (
      id INT AUTO_INCREMENT PRIMARY KEY,
      nombre VARCHAR(50)
    );
    
    INSERT INTO alumnos (nombre) VALUES ('Neo'), ('Trinity');
    
    SELECT * FROM alumnos;

    Luego demuestra algo mágico:

    Entra al contenedor

    docker exec -it mysql-demo mysql -uroot-p1234

    Dentro:

    USE demo;
    SELECT * FROM alumnos;

    Los datos están ahí → Workbench y contenedor hablan con la misma base.

    Eso hace que los alumnos comprendan el concepto de servicio expuesto por red.

    Problemas típicos (para que no te sabotee el universo)

    Puerto ocupado

    Si tienes MySQL local:

    lsof -i :3306

    Cambia puerto:

    -p3307:3306

    Y en Workbench → puerto 3307.


    No conecta

    Comprobar:

    docker ps
    docker logs mysql-demo

    Esperar inicialización (MySQL tarda).


    Workbench no conecta pero CLI sí

    A veces el plugin de auth:

    docker exec -it mysql-demo mysql -uroot-p1234

    En SQL

    ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '1234';
    FLUSH PRIVILEGES;
  • Del Código al Contenedor: Dominando Dockerfile Paso a Paso

    Del Código al Contenedor: Dominando Dockerfile Paso a Paso

    Un Dockerfile es una receta: describes pasos para construir una imagen.
    Luego ejecutas esa imagen y eso crea un contenedor.

    • Dockerfile: receta
    • Imagen: “plato ya cocinado” (plantilla)
    • Contenedor: “plato servido” (instancia en ejecución)

    Primer Dockerfile “Hola Mundo” (Python)

    Estructura de proyecto:

    mkdir dockerfile-101 && cd dockerfile-101
    mkdir app

    Crea app/app.py:

    cat > app/app.py << 'EOF'
    print("Hola desde un contenedor 😼")
    EOF

    Crea Dockerfile:

    FROM python:3.12-slim
    
    WORKDIR /app
    COPY app/ /app/
    
    CMD ["python", "app.py"]
    EOF

    Construir la imagen:

    docker build -t hola-dockerfile:1.0 .

    Ejecutar:

    docker run --rm hola-dockerfile:1.0

    Puntos clave :

    • Qué hace FROM, WORKDIR, COPY, CMD.
    • Por qué CMD va en formato lista (["..."]) y no como string.

    FROM

    Define la imagen base sobre la que se construirá la nueva imagen.

    Ejemplo:

    FROM python:3.12-slim

    Qué ocurre:

    • Docker descarga la imagen si no existe localmente.
    • Marca el punto de partida del sistema de archivos del contenedor.
    • Solo puede existir una FROM por etapa (en multi-stage puede haber varias).

    Buenas prácticas:

    • Usar imágenes ligeras (alpine, slim).
    • Evitar latest en producción (usar versiones concretas).

    WORKDIR

    Establece el directorio de trabajo dentro del contenedor.
    Equivale a ejecutar cd permanentemente para las siguientes instrucciones.

    WORKDIR /app

    Qué ocurre:

    • Si el directorio no existe, Docker lo crea automáticamente.
    • Todas las instrucciones posteriores (COPY, RUN, CMD) se ejecutan desde esa ruta.

    Ventaja:
    Evita rutas largas y mejora la legibilidad.


    COPY

    Copia archivos desde el sistema host (tu máquina) al contenedor.

    COPY app/ /app/

    Qué ocurre:

    • Se copian los archivos del contexto de build (directorio actual).
    • Se crea una nueva capa en la imagen.
    • Si cambia cualquier archivo copiado, Docker invalida la caché desde ese punto.

    Diferencia con ADD:

    • COPY solo copia archivos.
    • ADD puede descomprimir tar y descargar URLs (menos predecible, se recomienda COPY).

    Buenas prácticas:
    Copiar primero dependencias y luego código para aprovechar la caché:

    COPY requirements.txt .RUN pip install -r requirements.txtCOPY . .

    CMD

    Define el comando por defecto que ejecutará el contenedor al iniciarse.

    CMD ["python", "app.py"]

    Si el usuario pasa otro comando en docker run, este sobrescribe CMD.

    Ejemplo:

    docker run mi-imagen python otro.py

    ¿Por qué CMD debe ir en formato lista?

    Existen dos formas:

    Forma shell (string)

    CMD python app.py

    Docker lo ejecuta así:

    /bin/sh -c "python app.py"

    Problemas:

    • Se crea un proceso intermedio (sh).
    • Señales Unix (SIGTERM, SIGINT) no llegan correctamente al proceso principal.
    • Problemas con contenedores que deben parar limpiamente (servidores, apps, etc.).

    Forma exec (lista) — RECOMENDADA

    CMD ["python", "app.py"]

    Ventajas:

    • No usa shell intermedia.
    • El proceso es PID 1 dentro del contenedor.
    • Recibe correctamente señales del sistema.
    • Comportamiento más predecible.
    • Es el estándar en producción.

    Regla mental:
    Si el comando no necesita shell (|, &&, variables, etc.) → usar formato lista.

    EXPOSE

    Documenta qué puerto usa la aplicación dentro del contenedor.

    EXPOSE80

    Importante:

    • No abre puertos.
    • Solo sirve como metadato informativo.
    • Para publicar puerto se usa -p en docker run.

    Ejemplo:

    docker run -p8080:80 mi-imagen

    ENV

    Define variables de entorno dentro del contenedor.

    ENV APP_ENV=production

    Uso típico:

    • Configuración de aplicaciones
    • Rutas
    • Flags de ejecución

    Acceso en Python:

    importosprint(os.getenv("APP_ENV"))

    RUN

    Ejecuta comandos durante la construcción de la imagen.

    RUN apt update && apt install -y curl

    Diferencia con CMD:

    • RUN ocurre en build
    • CMD ocurre en runtime

    Cada RUN crea una capa nueva → conviene agrupar comandos.


    Dockerfile con dependencias (requests) + capas

    Cambia app/app.py:

    
    
    
    
    
    import requests
    print("IP pública (si hay salida a Internet):", requests.get("https://api.ipify.org").text)
    

    Crea app/requirements.txt:

    cat > app/requirements.txt << 'EOF'
    requests==2.32.3
    EOF

    Actualiza Dockerfile (truco de cache y capas):

    FROM python:3.12-slim
    
    WORKDIR /app
    
    # 1) Copiamos requirements primero (mejor cache)
    COPY app/requirements.txt /app/requirements.txt
    RUN pip install --no-cache-dir -r requirements.txt
    
    # 2) Copiamos el código después
    COPY app/ /app/
    
    CMD ["python", "app.py"]
    

    Rebuild y run:

    docker build -t ip-check:1.0 .
    docker run --rm ip-check:1.0

    Lección nerd útil: si solo cambia el código, Docker reutiliza la capa de pip install y construye más rápido.


    .dockerignore (para no meter “basura” en la imagen)

    Crea .dockerignore:

    .git
    __pycache__/
    *.pyc
    *.log
    .DS_Store
    

    Crear un archivo “gordo” y comprobar que no entra:

    dd if=/dev/zero of=archivo_gordo.bin bs=1M count=50
    docker build -t ignore-test:1.0 .


    Dockerfile para una web (Nginx estático) en 30 segundos

    Estructura:

    mkdir -p web
    cat > web/index.html << 'EOF'
    <h1>Hola desde Nginx en Docker</h1>
    <p>Si ves esto, EXPOSE era postureo, pero ayuda a documentar 😄</p>
    EOF

    Dockerfile:

    cat > Dockerfile << 'EOF'
    FROM nginx:alpine
    COPY web/ /usr/share/nginx/html/
    EXPOSE 80
    EOF

    Build + run:

    docker build -t web-nginx:1.0 .
    docker run --rm -p 8080:80 web-nginx:1.0

    Abrir: http://localhost:8080


    Ejemplos

    Ejemplo: Dockerfile para una app en Python

    FROM python:3.11-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY . .
    EXPOSE 5000
    
    CMD ["python", "app.py"]

    Este caso muestra cómo:

    • Se usa una imagen base de Python.
    • Se instala lo necesario con pip.
    • Se copia el código de la aplicación.
    • Se expone el puerto del servicio (por ejemplo, Flask).
    • Se ejecuta la app con CMD.

    Ejemplo: Dockerfile para PHP + Apache

    FROM php:8.2-apache
    COPY ./htdocs /var/www/html/
    EXPOSE 80

    Así pueden tener un servidor web con PHP.
    Luego ejecutan:

    docker build -t miwebphp . docker run -d -p 8080:80 miwebphp

    Buenas prácticas

    • Siempre usa imágenes base oficiales.
    • Agrupa las instalaciones en una sola instrucción RUN para reducir el tamaño de la imagen.
    • Usa .dockerignore para no copiar archivos innecesarios.
    • Define variables con ENV si hay configuraciones reutilizables.
    • Usa ENTRYPOINT cuando quieras ejecutar siempre el mismo comando, incluso si el usuario añade argumentos extra.

    Instrucción WORKDIR

    WORKDIR establece el directorio de trabajo dentro del contenedor.
    Equivale a hacer un cd /app dentro del entorno del contenedor antes de ejecutar los siguientes comandos.

    Por ejemplo:

    FROM python:3.11-slim
    WORKDIR /app
    COPY . .
    RUN pip install -r requirements.txt
    
    CMD ["python", "app.py"]

    Qué hace realmente:

    1. Crea el directorio si no existe (/app en este caso).
    2. Cambia el contexto: a partir de esa línea, todos los comandos (COPY, RUN, CMD, etc.) se ejecutan dentro de /app.

    Así, COPY . . copia el contenido del proyecto local dentro del contenedor en /app, y python app.py se ejecutará desde ahí.


    Sin WORKDIR

    Si no la usas, Docker ejecuta todo desde / (la raíz del contenedor).
    Ejemplo:

    FROM python:3.11-slim
    
    COPY . .
    
    CMD ["python", "app.py"]

    Esto también funcionará, pero el código quedará desperdigado en / — algo poco limpio y poco profesional.


    ¿Qué son las variables de entorno en Docker?

    Las variables de entorno (ENV) son valores que se inyectan en el entorno del contenedor y que las aplicaciones pueden usar para configurarse.

    Por ejemplo:

    • Contraseña del root
    • Nombre de la base de datos
    • Usuario y contraseña de aplicación

    Docker las pasa al contenedor con la instrucción ENV en el Dockerfile o con la opción -e en el docker run.


    Ejemplo básico de Dockerfile con MySQL

    # Imagen base oficial de MySQL
    
    FROM mysql:8.0
    
    # Variables de entorno
    
    ENV MYSQL_ROOT_PASSWORD=supersegura
    
    ENV MYSQL_DATABASE=tienda
    
    ENV MYSQL_USER=usuario
    
    ENV MYSQL_PASSWORD=1234
    
    # Exponer el puerto de MySQL
    
    EXPOSE 3306

    Explicación:

    • MYSQL_ROOT_PASSWORD: establece la contraseña del usuario root.
    • MYSQL_DATABASE: crea automáticamente una base de datos llamada tienda.
    • MYSQL_USER y MYSQL_PASSWORD: crean un usuario adicional con permisos sobre esa base.
    • EXPOSE 3306: indica el puerto por defecto del servicio.

    Construcción y ejecución

    Construir la imagen:

    docker build -t mysql-demo .

    Ejecutar el contenedor:

    docker run -d -p 3306:3306 --name contmysql mysql-demo

    Con eso, MySQL arrancará con la configuración establecida en las variables del Dockerfile.


    Alternativa más flexible (sin tocar el Dockerfile)

    También se pueden pasar las variables en tiempo de ejecución, lo que se recomienda para producción (para no dejar contraseñas dentro del Dockerfile):

    docker run -d -p 3306:3306 \
    
    --name mysql-demo \
    
    -e MYSQL_ROOT_PASSWORD=supersegura \
    
    -e MYSQL_DATABASE=tienda \
    
    -e MYSQL_USER=usuario \
    
    -e MYSQL_PASSWORD=1234 \
    
    mysql:8.0

    Así puedes usar directamente la imagen oficial de MySQL sin construir una nueva.


    Comprobación dentro del contenedor

    Para entrar al contenedor y verificar:

    docker exec -it mysql-demo mysql -u root -p

    Introduce la contraseña (supersegura) y luego:

    SHOW DATABASES;

    Verás que existe la base tienda.


    Usando un .env externo

    Otra práctica útil es usar un archivo .env:

    Archivo .env

    MYSQL_ROOT_PASSWORD=supersegura
    
    MYSQL_DATABASE=tienda
    
    MYSQL_USER=usuario
    
    MYSQL_PASSWORD=1234

    Y luego ejecutarlo con:

    docker run -d --env-file .env -p 3306:3306 --name mysql-demo mysql:8.0

    LABORATORIO – TAREA

    Montar una infraestructura de laboratorio Dockerizada donde cada contenedor representa una parte de una red vulnerable, simulando un pequeño entorno que deben administrar, securizar o incluso “atacar” de forma controlada.


    Cada alumno desplegará una red interna en su máquina virtual Ubuntu Server, con varios contenedores Docker que simulen los componentes de una empresa ficticia hackeable llamada:

    «CyberCorp Ltd.»

    El propósito es:

    • Divertirse con un entorno “ethical hacking friendly”.
    • Aprender a crear y gestionar contenedores.
    • Comprender la comunicación entre ellos
    • Aplicar conceptos de seguridad: contraseñas, puertos, logs, aislamiento, backups.

    Infraestructura propuesta

    ServicioImagen baseRol / DescripciónPuertos
    nginx-webnginx:latestPágina corporativa vulnerable (HTML + PHP)8080
    db-servermysql:8.0Base de datos de empleados con credenciales débiles3306
    ftp-serverdelfer/alpine-ftp-serverServidor FTP para intercambio de archivos21
    mail-servermaildev/maildevServidor SMTP simulado (visualización de correos en web)1080
    monitorgrafana/grafanaPanel de monitorización interno3000
    attackerkalilinux/kali-rollingContenedor con herramientas ofensivasshell interactiva
    loggerfluentdCentralización de l

    1) Preparación en la VM (comandos iniciales)

    # Actualizar y asegurarnos de tener Docker
    sudo apt update && sudo apt install -y docker.io
    # Iniciar y activar Docker
    sudo systemctl enable --now docker
    # Añadir tu usuario al grupo docker (opcional, cerrar sesión/reiniciar)
    sudo usermod -aG docker $USER
    # Crear red y volumenes
    docker network create cybernet
    docker volume create mysql_data
    docker volume create web_html
    

    2) Estructura de carpetas (ejemplo) /home/usuario/hack-containers/

    ├─ web/
    │  ├─ Dockerfile
    │  └─ html/
    │     ├─ index.php
    │     └─ secret/flag.txt
    ├─ mysql/
    │  ├─ Dockerfile
    │  └─ init-db/
    │     └─ init.sql
    ├─ ftp/
    │  └─ Dockerfile
    ├─ mail/
    │  └─ Dockerfile
    ├─ attacker/
    │  └─ Dockerfile
    └─ logger/
       └─ Dockerfile

    3) Dockerfile + archivos — ejemplos

    A) MySQL (mysql/Dockerfile)

    Usamos la imagen oficial de MySQL como base, y añadimos un init SQL.

    FROM mysql:8.0
    
    # Copiamos scripts de inicialización (se ejecutan al primer arranque)
    
    COPY init-db/ /docker-entrypoint-initdb.d/ VOLUME ["/var/lib/mysql"]
    
    EXPOSE 3306

    mysql/init-db/init.sql (ejemplo con credenciales débiles y tabla)

    CREATE DATABASE empleados;
    
    USE empleados;
    
    CREATE TABLE usuarios ( id INT AUTO_INCREMENT PRIMARY KEY, usuario VARCHAR(50), password VARCHAR(50) );
    
    INSERT INTO usuarios (usuario, password) VALUES ('admin','admin123');

    Construir y ejecutar:

    cd mysql
    
    docker build -t ctf-mysql:1.0 . docker run -d --name mysql --network cybernet \
    
    -e MYSQL_ROOT_PASSWORD=rootroot \
    
    -v mysql_data:/var/lib/mysql \
    
    ctf-mysql:1.0

    B) Web vulnerable (PHP + Apache) (web/Dockerfile)

    FROM php:8.2-apache
    
    # Copiamos el código vulnerable
    
    COPY html/ /var/www/html/
    
    # Habilitamos mod_rewrite por si hace falta
    
    RUN a2enmod rewrite
    
    EXPOSE 80

    web/html/index.php (vulnerable ejemplo: login sin sanitizar — para SQLi)

    <?php
    $conn = new mysqli('mysql','root','rootroot','empleados');
    if(isset($_GET['user'])){
        $user = $_GET['user'];
        $res = $conn->query("SELECT * FROM usuarios WHERE usuario = '$user'");
        if($res && $res->num_rows){
            echo "Usuario encontrado<br>";
        } else {
            echo "No encontrado";
        }
    } else {
        echo '<a href="?user=admin">Comprobar usuario admin</a>';
    }
    ?>

    web/html/secret/flag.txt

    FLAG{has_encontrado_la_bandera_web}

    Construir y ejecutar:

    cd web
    docker build -t ctf-web:1.0 .
    docker run -d --name web --network cybernet \
      -p 8080:80 \
      -v web_html:/var/www/html \
      ctf-web:1.0
    # Copiar el html local al volumen (una sola vez si quieres)
    docker cp html/. web:/var/www/html/

    Nota: en index.php se conecta a host mysql (nombre del contenedor) — así los containers se resuelven por nombre dentro de la red cybernet.


    C) FTP (ftp/Dockerfile) — ejemplo simple con vsftpd

    FROM alpine:3.18
    RUN apk add --no-cache vsftpd openrc
    COPY ftp-config/vsftpd.conf /etc/vsftpd/vsftpd.conf
    # Creamos usuario demo
    RUN adduser -D -h /home/usuario ftpuser && \
        echo "ftpuser:ftp123" | chpasswd && \
        mkdir -p /home/usuario/ftp_upload && chown ftpuser:ftpuser /home/usuario/ftp_upload
    EXPOSE 21 20
    CMD ["sh", "-c", "vsftpd /etc/vsftpd/vsftpd.conf"]

    ftp-config/vsftpd.conf puede ser muy simple para prácticas. (Puedes proveerlo a los alumnos o generar uno básico.)

    Construir y ejecutar:

    cd ftp
    
    docker build -t ctf-ftp:1.0 .
    
    docker run -d --name ftp --network cybernet -p 2121:21 ctf-ftp:1.0

    (Nota: FTP activo/pasivo considera puertos extra; para la práctica usarlo localmente y documentar.)


    D) Maildev (mail server de pruebas) — Dockerfile mínimo

    FROM maildev/maildev:latest 
    EXPOSE 1080 25

    Construir y ejecutar:

    cd mail
    
    docker build -t ctf-mail:1.0 .
    
    docker run -d --name mail --network cybernet -p 1080:1080 ctf-mail:1.0

    Maildev ofrece UI web en :1080 para ver emails enviados por la aplicación vulnerable.


    E) Logger / ELK ligero (logger/Dockerfile) — ejemplo usando busybox+fluentd sería largo.

    Puedes usar Fluentd o simplemente montar un contenedor que recoja docker logs desde fuera. Para simplicidad, propondré un contenedor fluentd base:

    FROM fluent/fluentd:v1.14-1
    
    EXPOSE 24224

    Construir y ejecutar:

    cd logger
    
    docker build -t ctf-logger:1.0 .
    
    docker run -d --name logger --network cybernet -p 24224:24224 ctf-logger:1.0

    F) Contenedor atacante (Kali) (attacker/Dockerfile)

    Instalamos unas herramientas útiles (nmap, nikto, sqlmap, hydra).

    FROM kalilinux/kali-rolling
    RUN apt update && apt install -y nmap nikto sqlmap hydra netcat-openbsd curl && apt clean
    WORKDIR /root
    CMD ["/bin/bash"]

    Construir y ejecutar (modo interactivo):

    cd attacker
    
    docker build -t ctf-kali:1.0 .
    
    docker run -it --name kali --network cybernet ctf-kali:1.0

    Dentro del contenedor atacas la red cybernet (ej. nmap -sV -p- web).


    4) Notas sobre volúmenes y persistencia

    Para copiar archivos iniciales al volumen, puedes docker cp como mostré.

    Mysql usa -v mysql_data:/var/lib/mysql para persistencia.

    Web usamos -v web_html:/var/www/html para poder editar fuera y reflejar cambios.

    RECUERDA

    # Ver containers
    docker ps -a

    # Logs de un contenedor
    docker logs web

    # Entrar a un contenedor
    docker exec -it mysql bash
    docker exec -it kali bash

    # Consultar red
    docker network inspect cybernet

    # Inspeccionar variables de entorno de un contenedor
    docker inspect -f ‘{{range .Config.Env}}{{println .}}{{end}}’ mysql

    Retos

    1. Recon: desde kali hacer nmap -sV -p- --open 172.18.0.0/16 y documentar servicios.
    2. FTP-cred: conectarse a FTP (ftp user:ftpuser/ftp123) y buscar creds.txt.
    3. DB login: usar credenciales encontradas para entrar en MySQL (mysql -u admin -p admin123).
    4. SQLi: Explorar index.php y realizar un SQL injection básico para sacar datos o la flag.txt.
      • Ejemplo payload: '?user=admin' vs '?user=admin' OR '1'='1'
    5. Mail: desde web, forzar envío de correo a Maildev y ver en la UI :1080.
    6. Detección: activar logs/monitor y buscar patrones sospechosos (pings, intentos SQL).
    7. Hardening: cambiar la configuración, cerrar puertos, poner contraseñas robustas y documentar.
  • Despliegue JEE (Servlet + HTML/JS) + MySQL en Docker

    Despliegue JEE (Servlet + HTML/JS) + MySQL en Docker

    Levantar una aplicación Java (Servlet) desplegada en Tomcat y conectada a MySQL, todo en contenedores Docker, sin frameworks y sin docker-compose.

    Requisitos

    • Docker instalado y funcionando.
    • Tu aplicación empaquetada como WAR (ej: miapp.war).
    • Una carpeta de proyecto en tu PC (host).

    Estructura del proyecto (en el host)

    Crea una carpeta de trabajo, por ejemplo:

    mkdir -p ~/proyecto-jee-docker
    cd ~/proyecto-jee-docker
    mkdir -p deploy

    Copia tu WAR dentro de deploy/:

    cp /RUTA/AL/WAR/miapp.war ./deploy/

    Crear una red Docker para que se vean por nombre

    Esto permite que Tomcat encuentre MySQL usando el nombre del contenedor.

    docker network create red-jee

    Levantar MySQL con volumen (persistencia)

    2.1 Crear volumen para MySQL

    docker volume create mysql_data

    2.2 Lanzar el contenedor MySQL

    ⚠️ Cambia passwords y nombre de BD si quieres, pero respeta la idea.

    docker run -d \
      --name mysql-jee \
      --network red-jee \
      -e MYSQL_ROOT_PASSWORD=root1234 \
      -e MYSQL_DATABASE=appdb \
      -e MYSQL_USER=appuser \
      -e MYSQL_PASSWORD=app1234 \
      -v mysql_data:/var/lib/mysql \
      -p 3307:3306 \
      mysql:8.0

    Qué hace esto:

    • --network red-jee → MySQL estará accesible desde Tomcat por hostname mysql-jee
    • -v mysql_data:/var/lib/mysql → los datos sobreviven aunque borres el contenedor
    • -p 3307:3306 → MySQL será accesible desde tu PC en localhost:3307 (evita conflictos si tu 3306 está ocupado)

    2.3 Comprobar que MySQL está arriba

    docker psdocker logs mysql-jee --tail30

    Crear tablas y datos de prueba (dentro del contenedor)

    Entra a MySQL dentro del contenedor:

    docker exec -it mysql-jee mysql -u root -p

    Escribe la contraseña root1234.

    Luego (ejemplo genérico), crea una tabla y mete algo.


    Preparar el contenedor Tomcat y desplegar el WAR

    Ejecutar Tomcat y montar tu WAR (sin construir imagen propia)

    Opción simple: montas la carpeta deploy en webapps.

    docker run -d \
      --name tomcat-jee \
      --network red-jee \
      -p 8080:8080 \
      -v "$PWD/deploy:/usr/local/tomcat/webapps" \
      tomcat:10.1-jdk17

    Qué hace esto:

    • -p 8080:8080 → accedes en http://localhost:8080
    • -v .../webapps → Tomcat “ve” tu WAR y lo despliega al arrancar

    4.2 Ver logs de Tomcat (muy importante)

    docker logs -f tomcat-jee

    Busca líneas tipo “Deploying web application archive … miapp.war”.


    5) Configurar la conexión a MySQL en tu Java (punto clave)

    Dentro de Docker, NO uses localhost para MySQL desde Tomcat.
    Debes usar el nombre del contenedor:

    • Host: mysql-jee
    • Puerto interno: 3306
    • BD: appdb
    • User: appuser
    • Pass: app1234

    Ejemplo de URL JDBC:

    jdbc:mysql://mysql-jee:3306/appdb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC

    ✅ Si tu app usa un fichero de configuración (properties), ajusta eso.

    Ejemplo (si usas Properties):

    db.url=jdbc:mysql://mysql-jee:3306/appdb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
    db.user=appuser
    db.pass=app1234

    Nota: si tu WAR ya estaba compilado con localhost, toca recompilar con mysql-jee.


    6) Probar la aplicación desde el navegador

    1. Entra a tu app (depende del nombre del WAR):
    • Si el WAR se llama miapp.war, normalmente será:
      • http://localhost:8080/miapp/
    1. Prueba el endpoint del servlet que devuelve JSON:
    • Ejemplo:
      • http://localhost:8080/miapp/api/items

    7) Verificar el fetch() y el DOM (JS)

    Tu JavaScript hará algo así:

    fetch('/miapp/api/items')
      .then(r => r.json())
      .then(data => {
        // pintar DOM
      });

    Errores típicos

    • 404 en fetch → la ruta no coincide con el url-pattern del servlet.
    • El fetch devuelve HTML en vez de JSON → estás pegando a una ruta que devuelve una página, no el endpoint.
    • CORS: si sirves el HTML desde Tomcat y llamas al servlet en el mismo host/puerto, normalmente no hay CORS.
      CORS aparece si llamas a otro host/puerto distinto.

    8) Comandos útiles de operación (vida real)

    Ver contenedores

    docker ps
    docker ps-a

    Parar / arrancar

    docker stop tomcat-jee mysql-jee
    docker start mysql-jee
    docker start tomcat-jee

    Entrar a un contenedor

    docker exec -it tomcat-jee bash
    docker exec -it mysql-jee bash

    Ver redes y comprobar nombres

    docker network ls
    docker network inspect red-jee

    9) Reinicio limpio (sin perder datos)

    Si quieres borrar Tomcat y recrearlo (sin perder DB):

    docker rm -f tomcat-jee
    docker run -d \
      --name tomcat-jee \
      --network red-jee \
      -p 8080:8080 \
      -v "$PWD/deploy:/usr/local/tomcat/webapps" \
      tomcat:10.1-jdk17

    La BD se conserva porque está en el volumen mysql_data.

    Resumen de pasos a realizar:

    • He creado red-jee
    • He creado el volumen mysql_data
    • He levantado mysql-jee y responde
    • He creado tablas y datos en appdb
    • He levantado tomcat-jee con el WAR montado
    • La app carga en http://localhost:8080/...
    • El servlet devuelve JSON válido
    • El fetch() recibe JSON y pinta en el DOM
  • Práctica – Juego de retos de Docker

    Práctica – Juego de retos de Docker

    Reto1

    – Crear un contenedor docker de ubuntu.
    – Instalar python, la libreria request y de mysql
    – Crear una imgane personalizada con el contenedor

    Reto 2

    • Crear un contenedor nuevo con la imagen personalizada  de docker
    • Este contenedor tendra un volumen con una ruta en el disco del anfitrión (Bind)

    Reto 3

    • Crear un repositorio git en la carpeta del anfitrión y unirlo con un repositorio en Github

    Reto 4

    • Crear un contenedor mysql.
    • Crear una base de datos, para almacenar Coches. Los campos seran id, marca, modelo, color, km y precio
    • añadir almenos 10 coches a modo de contenido de muestra.

    Reto 5 

    • Crear en el repositorio Local un programa en python que se conecte a la base de datos y obtenga los registros de la mase de datos.
      – El programa debe listar los datos de los coches guardados en la base de datos de forma estetica.

    ID    MARCA          MODELO         COLOR     KM        PRECIO    
    ————————————————————————
    1     Toyota             Corolla              Blanco    20000     15000     
    2     Honda              Civic                Rojo        30000     17000     
    3     Ford                  Focus               Azul       25000     16000     

    Reto 6

    • Almacenar los datos de conexion a la base de datos en un archivo JSON y que el programa Python los lea de dicho archivo. 
      * crear un nuevo archivo para la modificación.
      * Realiza un commit en cada paso
    • crear el .gitignore para que no suba el archivo con los datos de conexión.

    Reto 7

    Formatear la tabla para que quede mas estetica con la libreria 

    +—-+——–+———+——-+————-+——–+
    | ID | Marca  | Modelo  | Color | Kilometraje  | Precio |
    +—-+——–+———+——-+————-+——–+
    | 1  | Toyota | Corolla | Rojo  | 25000        | 15000  |
    | 2  | Honda  | Civic   | Azul  | 30000        | 18000  |
    | 3  | Ford   | Focus   | Blanco| 40000        | 17000  |
    +—-+——–+———+——-+————-+——–+

    Reto 8

    Crear un conetenedor Mongo y conectarse desde la terminal y utilizando MongoDB
    Crea la una bd e inserta en una colecci’on coches con el criterio de de campos del reto anterior
    Crear un Script de Python  para leer los datos de colecciones de MongoDB y los imprima en una tabla.

    Reto 9

    Subir una imagen personaliza de nuestro contenedor mongodb al  hub de Docker.
    En el Linux Parrot montar un contenedor con la imagen subida al hub.

    Documento de ayuda y pistas para los retos


    🚀 RETO 1 — Tu primer contenedor personalizado

    Objetivo

    Crear un contenedor Ubuntu con Python y librerías necesarias, y convertirlo en una imagen reutilizable.

    Pistas

    Según la guía visual del PDF (pág. 1), debes:

    • Ejecutar un contenedor Ubuntu
    • Instalar:
      • python3
      • pip
      • requests
      • cliente MySQL

    Después crea una imagen personalizada con:

    docker commit <contenedor> mi-imagen-personalizada
    

    ✅ Checkpoint

    Debes poder ver tu imagen con:

    docker images
    

    🚀 RETO 2 — Persistencia con Bind Mount

    Objetivo

    Crear un nuevo contenedor usando la imagen anterior y asociarlo a una carpeta del host.

    📌 En la página 2 del PDF se muestra cómo activar modo interactivo y configurar el volumen.

    Ejemplo conceptual:

    docker run -it -v /ruta/host:/ruta/contenedor mi-imagen
    

    ¿Por qué es importante?

    Si borras el contenedor, los datos seguirán vivos.

    Bienvenido al mundo real.

    ✅ Checkpoint

    Crea un archivo dentro del contenedor y verifica que aparece en el host.


    🚀 RETO 3 — Control de versiones

    Objetivo

    Versionar tu carpeta de trabajo y conectarla a GitHub.

    Tal como aparece en la página 3 del PDF:

    1️⃣ Inicializa Git
    2️⃣ Crea un repositorio en GitHub
    3️⃣ Ejecuta:

    git add .
    git commit -m "Inicio del laboratorio"
    git remote add origin <repo>
    git push -u origin main
    

    ✅ Checkpoint

    Tu repositorio debe contener al menos:

    • scripts
    • documentación
    • archivos de configuración

    🚀 RETO 4 — MySQL en contenedor

    Objetivo

    Levantar un contenedor MySQL y crear una base de datos de coches.

    Campos:

    • id
    • marca
    • modelo
    • color
    • km
    • precio

    Inserta mínimo 10 registros.

    📌 La página 4 del PDF muestra la configuración del contenedor.

    ✅ Checkpoint

    Debe funcionar:

    SELECT * FROM coches;
    

    🚀 RETO 5 — Python contra la base de datos

    Objetivo

    Crear un script que lea los coches desde MySQL.

    Las páginas 5–7 del PDF enseñan:

    • conexión con mysql.connector
    • uso de cursor
    • fetchall()

    Ejemplo conceptual:

    cursor.execute("SELECT * FROM coches")
    for coche in cursor.fetchall():
        print(coche)
    

    ⚠️ Problema común

    En la página 9 se menciona un conflicto con el conector de MySQL en Ubuntu.

    Solución alternativa:

    pip install mysql-connector-python --break-system-packages
    

    ✅ Checkpoint

    El script debe mostrar registros sin errores.


    🚀 RETO 6 — Seguridad básica

    Objetivo

    Separar credenciales en un archivo JSON.

    Después:

    ✔ Leer ese archivo desde Python
    ✔ Crear .gitignore
    ✔ Evitar subir credenciales

    ✅ Checkpoint

    Tu repo NO debe contener:

    config.json
    .env
    credenciales
    

    🚀 RETO 7 — Salida profesional

    Objetivo

    Mostrar los datos en formato tabla.

    En la página 10 se recomienda usar:

    apt install python3-prettytable
    

    Ejemplo:

    from prettytable import PrettyTable
    

    ✅ Checkpoint

    La salida debe parecer una tabla real, no texto desordenado.


    🚀 RETO 8 — MongoDB entra en escena

    Ahora pasamos a bases de datos NoSQL.

    Según las páginas 12–16:

    Debes:

    • Crear contenedor Mongo
    • Definir usuario y contraseña
    • Crear BD
    • Insertar coches
    • Consultar datos

    Acceso desde terminal:

    docker exec -it <contenedor> bash
    mongosh -u usuario -p password --authenticationDatabase admin
    

    ✅ Checkpoint

    Debe funcionar:

    db.coches.find().pretty()
    

    🚀 RETO 9 — Python + MongoDB

    Instala pymongo (pág. 18):

    apt install python3-pymongo
    

    Conecta y recorre la colección.

    Después muestra los datos en tabla (pág. 20).

    ✅ Checkpoint

    Debes tener:

    • script funcional
    • salida formateada
    • conexión estable

    🚀 RETO FINAL — Publica tu imagen

    Sube tu imagen de Mongo personalizada a Docker Hub.

    Después:

    👉 descárgala desde otra máquina (por ejemplo Parrot)
    👉 levanta el contenedor

    Si funciona…

    Acabas de reproducir un flujo profesional real.

  • MySQL + phpMyAdmin con Docker Compose

    MySQL + phpMyAdmin con Docker Compose

    • Desplegar MySQL y phpMyAdmin con docker compose
    • Usar volumen para persistencia
    • Verificar conexión y datos
    • Practicar variables de entorno, puertos y reinicios

    1) Estructura del proyecto

    Crea una carpeta de práctica:

    mkdir mysql-pma-compose
    cd mysql-pma-compose
    

    2) Crear el archivo docker-compose.yml

    Crea el fichero:

    
    # Define los servicios (contenedores) que vamos a ejecutar
    services:
    
      # ----- SERVICIO MYSQL -----
      mysql:
        image: mysql:8.0              # Imagen oficial que se descargará de Docker Hub
        container_name: mysql1        # Nombre fijo del contenedor (opcional, pero útil)
        restart: unless-stopped       # Reinicia el contenedor automáticamente salvo que lo pares tú
    
        # Variables de entorno que usa la imagen de MySQL en su primer arranque
        environment:
          MYSQL_ROOT_PASSWORD: "RootPass_123!"   # Contraseña del usuario root
          MYSQL_DATABASE: "academia"             # Base de datos que se crea automáticamente
          MYSQL_USER: "alumno"                   # Usuario adicional que se creará
          MYSQL_PASSWORD: "AlumnoPass_123!"      # Contraseña del usuario alumno
    
        # Volúmenes → permiten persistencia (los datos sobreviven aunque borres el contenedor)
        volumes:
          - mysql_data:/var/lib/mysql   # Guarda los datos reales de MySQL fuera del contenedor
    
        # Conecta el contenedor a una red Docker interna
        networks:
          - red-bbdd
    
    
      # ----- SERVICIO PHPMYADMIN -----
      phpmyadmin:
        image: phpmyadmin:latest        # Imagen oficial de phpMyAdmin
        container_name: pma1
        restart: unless-stopped
    
        # Indica que MySQL debe arrancar antes (solo orden, no espera a que esté listo)
        depends_on:
          - mysql
    
        # Variables que indican a phpMyAdmin a qué servidor MySQL conectarse
        environment:
          PMA_HOST: "mysql"   # Nombre del servicio MySQL dentro de la red Docker
          PMA_PORT: 3306      # Puerto interno de MySQL
    
        # Publica puertos → expone el servicio al exterior
        ports:
          - "8080:80"         # Puerto HOST:PUERTO_CONTENEDOR → http://localhost:8080
    
        # Conecta phpMyAdmin a la misma red que MySQL para que puedan verse
        networks:
          - red-bbdd
    
    
    # ----- DEFINICIÓN DE VOLÚMENES -----
    volumes:
      mysql_data:   # Volumen gestionado por Docker (no es carpeta local visible normalmente)
    
    
    # ----- DEFINICIÓN DE REDES -----
    networks:
      red-bbdd:     # Red interna donde los contenedores se comunican por nombre (DNS interno)
    

    Qué está pasando aquí (muy importante)

    • services: dos contenedores, uno para MySQL y otro para phpMyAdmin.
    • volumes: mysql_data guarda la base de datos fuera del contenedor (persistencia).
    • networks: red privada para que phpmyadmin pueda acceder a mysql por nombre.
    • depends_on: arranca primero MySQL (ojo: no garantiza “ready”, solo orden de arranque).

    3) Levantar los contenedores

    docker compose up -d
    

    Verifica:

    docker compose ps
    docker compose logs mysql --tail 30
    docker compose logs phpmyadmin --tail 30
    

    4) Acceder a phpMyAdmin

    Abre en el navegador:

    • http://localhost:8080

    Credenciales:

    • Usuario: root (o alumno)
    • Password: RootPass_123! (o AlumnoPass_123!)

    Comprueba:

    • Existe la BD academia

    5) Crear tabla de prueba (verificación)

    Opción A: desde phpMyAdmin (SQL)
    Ejecuta:

    USE academia;
    CREATE TABLE prueba (
      id INT PRIMARY KEY AUTO_INCREMENT,
      texto VARCHAR(50)
    );
    INSERT INTO prueba (texto) VALUES ("hola compose");
    SELECT * FROM prueba;
    

    Opción B: desde terminal (dentro del contenedor)

    docker compose exec mysql mysql -u root -p
    



    Comandos útiles para alumnos

    docker compose ps
    docker compose logs -f
    docker compose exec mysql bash
    docker compose exec phpmyadmin bash
    docker compose down
    docker compose down -v