Categoría: Docker

  • 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 downNoNo
    Eliminar con volúmenesdocker compose down -vNo
    Destruir completodocker compose down -v --rmi local

    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 (COPYRUNCMD, 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
    

  • [Reto] – Despliegue de servicios con Docker y Docker Compose

    [Reto] – Despliegue de servicios con Docker y Docker Compose

    1. Contexto de la práctica

    En esta práctica vas a desplegar una pequeña infraestructura de servicios utilizando Docker y Docker Compose.

    El objetivo no es únicamente “levantar contenedores”, sino comprender cómo se organizan los servicios, cómo se comunican entre ellos, cómo se almacenan los datos, cómo se exponen puertos al exterior y cómo se puede documentar y justificar una instalación técnica.

    Durante la práctica se trabajará con servicios similares a los vistos en clase:

    • Servidor web HTTP con Apache.
    • PHP para ejecutar código dinámico.
    • MySQL como base de datos relacional.
    • phpMyAdmin como herramienta de administración.
    • MongoDB como base de datos NoSQL.
    • Mongo Express como herramienta de administración.
    • Redes Docker.
    • Volúmenes persistentes.
    • Variables de entorno.
    • Logs y comprobaciones.
    • Copias de seguridad básicas.
    • Documentación técnica.

    2. Objetivos de aprendizaje

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

    1. Explicar qué es Docker y para qué se utiliza.
    2. Diferenciar una imagen de un contenedor.
    3. Crear y ejecutar contenedores mediante Docker Compose.
    4. Configurar servicios conectados entre sí.
    5. Usar volúmenes para conservar datos.
    6. Usar redes Docker para aislar servicios.
    7. Configurar variables de entorno.
    8. Desplegar una aplicación PHP conectada a MySQL.
    9. Desplegar y consultar una base de datos MongoDB.
    10. Comprobar el estado de los servicios mediante comandos.
    11. Interpretar errores básicos mediante logs.
    12. Documentar una instalación técnica de forma clara.
    13. Defender oralmente el trabajo realizado.

    3. Resultado final esperado

    Al terminar la práctica deberás tener funcionando una infraestructura compuesta por varios contenedores:

    ServicioTecnologíaPuerto externo sugeridoFunción
    Web PHPApache + PHP8080Servidor web principal
    MySQLMySQL3306 o solo internoBase de datos relacional
    phpMyAdminphpMyAdmin8081Administración de MySQL
    MongoDBMongoDB27017 o solo internoBase de datos NoSQL
    Mongo ExpressMongo Express8082Administración de MongoDB

    La aplicación PHP deberá mostrar una página web con información del alumno, conexión a MySQL y, opcionalmente, conexión a MongoDB.


    4. Normas de entrega

    El alumno deberá entregar:

    1. Carpeta completa del proyecto.
    2. Archivo docker-compose.yml.
    3. Archivo .env.
    4. Código fuente de la aplicación PHP.
    5. Capturas de pantalla.
    6. Documento de memoria en PDF.
    7. Comandos utilizados.
    8. Respuestas a las preguntas de reflexión.
    9. Evidencias de funcionamiento.
    10. Breve vídeo opcional de demostración.

    5. Estructura recomendada del proyecto

    Crea una carpeta llamada:

    practica-docker-servicios-nombre-apellido

    Dentro de ella deberás crear una estructura similar a esta:

    practica-docker-servicios-nombre-apellido/

    ├── docker-compose.yml
    ├── .env
    ├── README.md

    ├── web/
    │ ├── index.php
    │ ├── conexion_mysql.php
    │ ├── insertar.php
    │ ├── listar.php
    │ └── estilos.css

    ├── mysql/
    │ └── init.sql

    ├── evidencias/
    │ ├── captura_01_docker_version.png
    │ ├── captura_02_contenedores.png
    │ ├── captura_03_web_funcionando.png
    │ ├── captura_04_phpmyadmin.png
    │ ├── captura_05_mongo_express.png
    │ └── captura_06_logs.png

    └── memoria/
    └── memoria_practica.pdf

    6. Fase 1 — Preparación del entorno

    6.1. Comprobar Docker

    Ejecuta los siguientes comandos:

    docker --version
    docker compose version

    Guarda una captura de pantalla donde se vea el resultado.

    Preguntas de reflexión

    Responde en tu memoria:

    1. ¿Qué diferencia hay entre Docker y Docker Compose?
    2. ¿Qué problema soluciona Docker cuando trabajamos con varios servicios?
    3. ¿Por qué puede ser útil usar Docker en un entorno educativo o de laboratorio?

    6.2. Comprobar que Docker funciona

    Ejecuta:

    docker run hello-world

    Evidencia obligatoria

    Incluye una captura del resultado del comando.

    Pregunta de reflexión

    Explica con tus palabras qué ha ocurrido al ejecutar hello-world.

    Debes mencionar:

    • Si Docker ha usado una imagen local o la ha descargado.
    • Qué es un contenedor.
    • Por qué el contenedor termina después de ejecutarse.

    7. Fase 2 — Primer diseño de la infraestructura

    Antes de escribir el docker-compose.yml, realiza un pequeño esquema de la infraestructura.

    Puedes hacerlo con una tabla, un dibujo o un diagrama sencillo.

    Debe aparecer:

    • Servicio web.
    • Base de datos MySQL.
    • phpMyAdmin.
    • MongoDB.
    • Mongo Express.
    • Red interna.
    • Volúmenes de datos.
    • Puertos publicados.

    Ejemplo conceptual:

    Navegador
    |
    | puerto 8080
    v
    Apache + PHP
    |
    | red interna docker
    v
    MySQL

    Navegador
    |
    | puerto 8081
    v
    phpMyAdmin
    |
    v
    MySQL

    Navegador
    |
    | puerto 8082
    v
    Mongo Express
    |
    v
    MongoDB

    Preguntas de reflexión

    1. ¿Por qué no es recomendable instalar todos los servicios dentro del mismo contenedor?
    2. ¿Qué ventajas tiene separar web, base de datos y herramientas de administración?
    3. ¿Qué servicios deberían ser accesibles desde el navegador y cuáles deberían quedar solo dentro de la red Docker?

    8. Fase 3 — Creación del archivo .env

    Crea un archivo llamado .env en la raíz del proyecto.

    Ejemplo:

    MYSQL_ROOT_PASSWORD=rootpassword
    MYSQL_DATABASE=asir_db
    MYSQL_USER=asir_user
    MYSQL_PASSWORD=asir_password

    MONGO_INITDB_ROOT_USERNAME=admin
    MONGO_INITDB_ROOT_PASSWORD=adminpassword
    MONGO_DATABASE=asir_mongo

    WEB_PORT=8080
    PHPMYADMIN_PORT=8081
    MONGO_EXPRESS_PORT=8082

    Importante

    El archivo .env permite separar configuración del archivo docker-compose.yml.

    No deberías escribir directamente las contraseñas dentro del docker-compose.yml si puedes evitarlo.

    Preguntas de reflexión

    1. ¿Qué ventaja tiene usar variables de entorno?
    2. ¿Por qué puede ser peligroso subir un archivo .env real a un repositorio público?
    3. ¿Qué información sensible contiene este archivo?

    9. Fase 4 — Creación del docker-compose.yml

    Crea el archivo docker-compose.yml.

    Propuesta base:

    services:

    web:
    image: php:8.2-apache
    container_name: asir_web_php
    ports:
    - "${WEB_PORT}:80"
    volumes:
    - ./web:/var/www/html
    depends_on:
    - mysql
    - mongodb
    networks:
    - red_asir

    mysql:
    image: mysql:8.0
    container_name: asir_mysql
    restart: always
    environment:
    MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    MYSQL_DATABASE: ${MYSQL_DATABASE}
    MYSQL_USER: ${MYSQL_USER}
    MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
    - mysql_data:/var/lib/mysql
    - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
    - red_asir

    phpmyadmin:
    image: phpmyadmin:latest
    container_name: asir_phpmyadmin
    restart: always
    ports:
    - "${PHPMYADMIN_PORT}:80"
    environment:
    PMA_HOST: mysql
    PMA_PORT: 3306
    depends_on:
    - mysql
    networks:
    - red_asir

    mongodb:
    image: mongo:latest
    container_name: asir_mongodb
    restart: always
    environment:
    MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
    MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
    volumes:
    - mongo_data:/data/db
    networks:
    - red_asir

    mongo-express:
    image: mongo-express:latest
    container_name: asir_mongo_express
    restart: always
    ports:
    - "${MONGO_EXPRESS_PORT}:8081"
    environment:
    ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_INITDB_ROOT_USERNAME}
    ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
    ME_CONFIG_MONGODB_SERVER: mongodb
    depends_on:
    - mongodb
    networks:
    - red_asir

    volumes:
    mysql_data:
    mongo_data:

    networks:
    red_asir:
    driver: bridge

    9.1. Preguntas sobre el docker-compose.yml

    Responde en tu memoria:

    1. ¿Qué significa services?
    2. ¿Qué diferencia hay entre image y container_name?
    3. ¿Para qué sirve ports?
    4. ¿Qué diferencia hay entre el puerto de la izquierda y el de la derecha en esta línea?
    - "8080:80"
    1. ¿Para qué sirve volumes?
    2. ¿Qué pasaría con los datos de MySQL si no usásemos un volumen?
    3. ¿Qué hace depends_on?
    4. ¿Qué limitación tiene depends_on?
    5. ¿Para qué sirve definir una red propia llamada red_asir?
    6. ¿Por qué el servicio web puede conectarse a MySQL usando el nombre mysql?

    10. Fase 5 — Script inicial de MySQL

    Dentro de la carpeta mysql, crea el archivo init.sql.

    Ejemplo:

    CREATE TABLE IF NOT EXISTS alumnos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    apellido VARCHAR(100) NOT NULL,
    curso VARCHAR(100) NOT NULL,
    fecha_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );

    INSERT INTO alumnos (nombre, apellido, curso)
    VALUES
    ('Alumno', 'Ejemplo', 'ASIR'),
    ('Docker', 'Compose', 'Servicios');

    Este script se ejecutará automáticamente cuando se cree por primera vez el contenedor de MySQL y el volumen esté vacío.

    Preguntas de reflexión

    1. ¿Cuándo se ejecutan los scripts dentro de /docker-entrypoint-initdb.d/?
    2. Si modificas init.sql después de haber creado el volumen, ¿se vuelve a ejecutar automáticamente?
    3. ¿Qué tendrías que hacer para reiniciar completamente la base de datos desde cero?
    4. ¿Por qué hay que tener cuidado al borrar volúmenes?

    11. Fase 6 — Crear la aplicación PHP

    Dentro de la carpeta web, crea el archivo index.php.

    Ejemplo:

    <?php
    $host = 'mysql';
    $db = 'asir_db';
    $user = 'asir_user';
    $password = 'asir_password';

    $conexionCorrecta = false;
    $error = '';

    try {
    $pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $password);
    $conexionCorrecta = true;
    } catch (PDOException $e) {
    $error = $e->getMessage();
    }
    ?>

    <!DOCTYPE html>
    <html lang="es">
    <head>
    <meta charset="UTF-8">
    <title>Práctica Docker ASIR</title>
    <link rel="stylesheet" href="estilos.css">
    </head>
    <body>

    <main>
    <h1>Práctica Docker, Docker Compose y Servicios</h1>

    <section>
    <h2>Datos del alumno</h2>
    <p><strong>Nombre:</strong> Escribe aquí tu nombre</p>
    <p><strong>Curso:</strong> ASIR</p>
    <p><strong>Módulo:</strong> Implantación de aplicaciones / Servicios / Sistemas</p>
    </section>

    <section>
    <h2>Estado de la conexión con MySQL</h2>

    <?php if ($conexionCorrecta): ?>
    <p class="ok">Conexión correcta con MySQL.</p>
    <?php else: ?>
    <p class="error">Error de conexión con MySQL.</p>
    <pre><?php echo $error; ?></pre>
    <?php endif; ?>
    </section>

    <section>
    <h2>Listado de alumnos desde MySQL</h2>

    <?php
    if ($conexionCorrecta) {
    $consulta = $pdo->query("SELECT * FROM alumnos");

    echo "<table>";
    echo "<tr><th>ID</th><th>Nombre</th><th>Apellido</th><th>Curso</th><th>Fecha</th></tr>";

    while ($fila = $consulta->fetch(PDO::FETCH_ASSOC)) {
    echo "<tr>";
    echo "<td>" . htmlspecialchars($fila['id']) . "</td>";
    echo "<td>" . htmlspecialchars($fila['nombre']) . "</td>";
    echo "<td>" . htmlspecialchars($fila['apellido']) . "</td>";
    echo "<td>" . htmlspecialchars($fila['curso']) . "</td>";
    echo "<td>" . htmlspecialchars($fila['fecha_registro']) . "</td>";
    echo "</tr>";
    }

    echo "</table>";
    }
    ?>
    </section>
    </main>

    </body>
    </html>

    11.1. Archivo CSS

    Crea estilos.css:

    body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 0;
    }

    main {
    width: 80%;
    margin: 40px auto;
    background-color: white;
    padding: 30px;
    border-radius: 10px;
    }

    h1 {
    color: #333;
    }

    section {
    margin-bottom: 30px;
    }

    .ok {
    color: green;
    font-weight: bold;
    }

    .error {
    color: red;
    font-weight: bold;
    }

    table {
    width: 100%;
    border-collapse: collapse;
    }

    th {
    background-color: #333;
    color: white;
    }

    td, th {
    padding: 10px;
    border: 1px solid #ccc;
    }

    11.2. Problema intencionado

    Con la imagen oficial php:8.2-apache, puede ocurrir que PHP no tenga activada la extensión necesaria para conectarse a MySQL mediante PDO.

    Si al abrir la web aparece un error relacionado con MySQL o PDO, deberás investigarlo y solucionarlo.

    Pista

    Puede ser necesario crear un Dockerfile personalizado para el servicio web.


    12. Fase 7 — Crear una imagen personalizada para PHP

    Crea un archivo llamado Dockerfile dentro de la carpeta web.

    FROM php:8.2-apache

    RUN docker-php-ext-install pdo pdo_mysql mysqli

    Ahora modifica el servicio web en el docker-compose.yml.

    Antes:

    web:
    image: php:8.2-apache

    Después:

    web:
    build: ./web

    El servicio completo quedaría así:

    web:
    build: ./web
    container_name: asir_web_php
    ports:
    - "${WEB_PORT}:80"
    volumes:
    - ./web:/var/www/html
    depends_on:
    - mysql
    - mongodb
    networks:
    - red_asir

    Preguntas de reflexión

    1. ¿Qué diferencia hay entre usar image y usar build?
    2. ¿Para qué sirve un Dockerfile?
    3. ¿Qué hace esta línea?
    RUN docker-php-ext-install pdo pdo_mysql mysqli
    1. ¿Por qué necesitamos instalar extensiones de PHP?
    2. ¿Qué ventaja tiene crear nuestra propia imagen personalizada?

    13. Fase 8 — Levantar los servicios

    Ejecuta:

    docker compose up -d

    Después comprueba:

    docker compose ps

    También puedes usar:

    docker ps

    Evidencias obligatorias

    Incluye capturas donde se vea:

    • Los contenedores levantados.
    • Los puertos publicados.
    • El estado de cada contenedor.

    Preguntas de reflexión

    1. ¿Qué significa ejecutar Docker Compose con -d?
    2. ¿Qué diferencia hay entre docker compose ps y docker ps?
    3. ¿Qué significa que un contenedor esté en estado Up?
    4. ¿Qué harías si un contenedor aparece como Restarting?

    14. Fase 9 — Comprobación de servicios en navegador

    Accede desde el navegador a:

    http://localhost:8080

    Deberías ver la aplicación PHP.

    Accede también a:

    http://localhost:8081

    Deberías ver phpMyAdmin.

    Accede a:

    http://localhost:8082

    Deberías ver Mongo Express.

    Evidencias obligatorias

    Incluye capturas de:

    1. Página PHP funcionando.
    2. phpMyAdmin mostrando la base de datos.
    3. Tabla alumnos dentro de MySQL.
    4. Mongo Express funcionando.

    Preguntas de reflexión

    1. ¿Por qué accedemos a la web usando localhost:8080 y no localhost:80?
    2. ¿Qué usuario y contraseña has utilizado para entrar en phpMyAdmin?
    3. ¿Qué base de datos aparece creada automáticamente?
    4. ¿Qué diferencia hay entre administrar MySQL desde línea de comandos y hacerlo desde phpMyAdmin?
    5. ¿Qué riesgos tendría dejar phpMyAdmin abierto en un servidor real?

    15. Fase 10 — Insertar datos desde PHP

    Crea un archivo llamado insertar.php.

    <?php
    $host = 'mysql';
    $db = 'asir_db';
    $user = 'asir_user';
    $password = 'asir_password';

    try {
    $pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $password);

    $sql = "INSERT INTO alumnos (nombre, apellido, curso) VALUES (:nombre, :apellido, :curso)";
    $stmt = $pdo->prepare($sql);

    $stmt->execute([
    ':nombre' => 'NombreAlumno',
    ':apellido' => 'ApellidoAlumno',
    ':curso' => 'ASIR'
    ]);

    echo "Registro insertado correctamente.";
    } catch (PDOException $e) {
    echo "Error: " . $e->getMessage();
    }

    Accede a:

    http://localhost:8080/insertar.php

    Después vuelve a:

    http://localhost:8080/index.php

    Comprueba que el nuevo registro aparece en la tabla.

    Trabajo obligatorio

    Modifica el archivo para que inserte tus propios datos.

    Preguntas de reflexión

    1. ¿Qué ocurre cada vez que recargas insertar.php?
    2. ¿Por qué puede ser peligroso insertar datos directamente sin un formulario controlado?
    3. ¿Qué diferencia hay entre una consulta normal y una consulta preparada?
    4. ¿Qué problema de seguridad ayudan a evitar las consultas preparadas?

    16. Fase 11 — Crear formulario de inserción

    Crea una página formulario.php.

    <!DOCTYPE html>
    <html lang="es">
    <head>
    <meta charset="UTF-8">
    <title>Formulario de alumnos</title>
    <link rel="stylesheet" href="estilos.css">
    </head>
    <body>

    <main>
    <h1>Insertar alumno</h1>

    <form action="guardar.php" method="post">
    <label>Nombre:</label><br>
    <input type="text" name="nombre" required><br><br>

    <label>Apellido:</label><br>
    <input type="text" name="apellido" required><br><br>

    <label>Curso:</label><br>
    <input type="text" name="curso" required><br><br>

    <button type="submit">Guardar</button>
    </form>
    </main>

    </body>
    </html>

    Crea ahora guardar.php.

    <?php
    $host = 'mysql';
    $db = 'asir_db';
    $user = 'asir_user';
    $password = 'asir_password';

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nombre = $_POST['nombre'] ?? '';
    $apellido = $_POST['apellido'] ?? '';
    $curso = $_POST['curso'] ?? '';

    try {
    $pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8", $user, $password);

    $sql = "INSERT INTO alumnos (nombre, apellido, curso) VALUES (:nombre, :apellido, :curso)";
    $stmt = $pdo->prepare($sql);

    $stmt->execute([
    ':nombre' => $nombre,
    ':apellido' => $apellido,
    ':curso' => $curso
    ]);

    echo "Alumno guardado correctamente.";
    echo "<br><a href='index.php'>Volver al listado</a>";

    } catch (PDOException $e) {
    echo "Error: " . $e->getMessage();
    }
    }

    Evidencia obligatoria

    Incluye captura del formulario y del registro insertado.

    Preguntas de reflexión

    1. ¿Qué método HTTP utiliza el formulario?
    2. ¿Qué diferencia hay entre GET y POST?
    3. ¿Dónde llegan los datos enviados por el formulario?
    4. ¿Qué validaciones mínimas debería tener este formulario?
    5. ¿Por qué no deberíamos confiar nunca únicamente en la validación del navegador?

    17. Fase 12 — Trabajo con MongoDB

    MongoDB es una base de datos NoSQL. En lugar de trabajar con tablas y filas, trabaja con bases de datos, colecciones y documentos.

    Accede a Mongo Express:

    http://localhost:8082

    Crea una base de datos llamada:

    asir_mongo

    Crea una colección llamada:

    inventario

    Inserta al menos tres documentos similares a estos:

    {
    "nombre": "Servidor web",
    "tipo": "contenedor",
    "servicio": "Apache PHP",
    "puerto": 8080
    }
    {
    "nombre": "Base de datos relacional",
    "tipo": "contenedor",
    "servicio": "MySQL",
    "puerto": 3306
    }
    {
    "nombre": "Base de datos NoSQL",
    "tipo": "contenedor",
    "servicio": "MongoDB",
    "puerto": 27017
    }

    Evidencias obligatorias

    Incluye capturas de:

    • Base de datos creada.
    • Colección creada.
    • Documentos insertados.

    Preguntas de reflexión

    1. ¿Qué diferencia hay entre una tabla de MySQL y una colección de MongoDB?
    2. ¿Qué diferencia hay entre una fila y un documento?
    3. ¿Qué ventajas puede tener MongoDB frente a MySQL?
    4. ¿Qué ventajas puede tener MySQL frente a MongoDB?
    5. ¿En qué tipo de proyecto usarías cada uno?

    18. Fase 13 — Comandos de administración

    Ejecuta y documenta los siguientes comandos.

    Ver contenedores activos

    docker ps

    Ver todos los contenedores

    docker ps -a

    Ver imágenes

    docker images

    Ver redes

    docker network ls

    Ver volúmenes

    docker volume ls

    Ver logs del servicio web

    docker compose logs web

    Ver logs de MySQL

    docker compose logs mysql

    Entrar dentro del contenedor web

    docker exec -it asir_web_php bash

    Dentro del contenedor, ejecuta:

    ls -la /var/www/html

    Después sal:

    exit

    Evidencias obligatorias

    Incluye capturas de al menos cinco comandos anteriores.

    Preguntas de reflexión

    1. ¿Para qué sirve docker ps?
    2. ¿Qué información muestra docker images?
    3. ¿Qué diferencia hay entre una imagen y un contenedor?
    4. ¿Qué utilidad tienen los logs?
    5. ¿Para qué puede servir entrar dentro de un contenedor?
    6. ¿Por qué no deberíamos modificar manualmente demasiadas cosas dentro de un contenedor en producción?

    19. Fase 14 — Persistencia de datos

    Vas a comprobar que los datos sobreviven aunque los contenedores se detengan.

    Ejecuta:

    docker compose down

    Después vuelve a levantar:

    docker compose up -d

    Comprueba:

    • Que la web sigue funcionando.
    • Que los datos de MySQL siguen estando.
    • Que los documentos de MongoDB siguen estando.

    Ahora ejecuta:

    docker compose down -v

    Vuelve a levantar:

    docker compose up -d

    Comprueba qué ha ocurrido con los datos.

    Mucho cuidado

    El parámetro -v elimina los volúmenes asociados al proyecto.

    Preguntas de reflexión

    1. ¿Qué diferencia hay entre docker compose down y docker compose down -v?
    2. ¿Por qué se han conservado los datos en el primer caso?
    3. ¿Por qué se han perdido los datos en el segundo caso?
    4. ¿Qué papel tienen los volúmenes en Docker?
    5. ¿Por qué la persistencia es fundamental en bases de datos?

    20. Fase 15 — Copia de seguridad de MySQL

    Realiza una copia de seguridad de la base de datos MySQL.

    Ejemplo:

    docker exec asir_mysql mysqldump -u root -p asir_db > backup_asir_db.sql

    El sistema pedirá la contraseña de root.

    Después comprueba que se ha creado el archivo:

    ls -lh backup_asir_db.sql

    Evidencia obligatoria

    Incluye captura del archivo generado.

    Preguntas de reflexión

    1. ¿Qué es mysqldump?
    2. ¿Por qué es importante hacer copias de seguridad?
    3. ¿Dónde se ha guardado el archivo SQL?
    4. ¿Qué información contiene ese archivo?
    5. ¿Sería suficiente esta copia de seguridad en un entorno real? Justifica la respuesta.

    21. Fase 16 — Restauración básica de MySQL

    Para comprobar la restauración, puedes crear una nueva base de datos desde phpMyAdmin o desde consola e importar el archivo.

    Ejemplo orientativo:

    docker exec -i asir_mysql mysql -u root -p asir_db < backup_asir_db.sql

    Preguntas de reflexión

    1. ¿Qué diferencia hay entre exportar e importar una base de datos?
    2. ¿Qué riesgos existen al restaurar una copia sobre una base de datos que ya contiene información?
    3. ¿Qué medidas tomarías antes de restaurar una copia en producción?

    22. Fase 17 — Comprobación de red entre contenedores

    Entra en el contenedor web:

    docker exec -it asir_web_php bash

    Instala herramientas de red si fuera necesario:

    apt update
    apt install -y iputils-ping

    Prueba conectividad:

    ping mysql
    ping mongodb

    Sal del contenedor:

    exit

    Preguntas de reflexión

    1. ¿Por qué podemos hacer ping mysql y no necesitamos conocer la IP del contenedor?
    2. ¿Qué papel hace el DNS interno de Docker?
    3. ¿Cambiaría la IP del contenedor si lo eliminamos y lo volvemos a crear?
    4. ¿Por qué es mejor usar nombres de servicio que direcciones IP?

    23. Fase 18 — Seguridad básica

    Analiza la configuración actual.

    Responde:

    1. ¿Qué puertos están expuestos al equipo anfitrión?
    2. ¿Qué servicios son accesibles desde el navegador?
    3. ¿Tiene sentido exponer MySQL directamente al exterior?
    4. ¿Tiene sentido exponer MongoDB directamente al exterior?
    5. ¿Qué contraseñas son débiles?
    6. ¿Qué cambiarías si esto fuese un servidor real?
    7. ¿Qué riesgos tiene usar root para administrar la base de datos?
    8. ¿Qué riesgos tiene dejar herramientas como phpMyAdmin o Mongo Express accesibles públicamente?

    Mejora obligatoria

    Modifica el docker-compose.yml para que MySQL y MongoDB no publiquen puertos hacia el exterior.

    Es decir, evita configuraciones como:

    ports:
    - "3306:3306"

    o:

    ports:
    - "27017:27017"

    Los servicios internos deben comunicarse por la red Docker, no necesariamente por puertos públicos.

    Pregunta de reflexión

    Si MySQL no tiene puerto publicado, ¿por qué phpMyAdmin puede seguir conectando con él?


    24. Fase 19 — Personalización del proyecto

    El alumno deberá personalizar la práctica.

    Como mínimo deberá:

    1. Cambiar el nombre de los contenedores.
    2. Cambiar el nombre de la base de datos.
    3. Cambiar usuario y contraseña.
    4. Personalizar la página principal.
    5. Insertar datos propios.
    6. Añadir una nueva tabla en MySQL.
    7. Añadir una nueva colección en MongoDB.
    8. Documentar los cambios realizados.

    Nueva tabla obligatoria

    Crea una tabla llamada servicios.

    Ejemplo:

    CREATE TABLE IF NOT EXISTS servicios (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    imagen VARCHAR(100) NOT NULL,
    puerto VARCHAR(20),
    descripcion TEXT
    );

    Inserta al menos cinco servicios:

    • Apache/PHP.
    • MySQL.
    • phpMyAdmin.
    • MongoDB.
    • Mongo Express.

    Preguntas de reflexión

    1. ¿Qué campos has elegido para la tabla servicios?
    2. ¿Por qué has elegido esos campos?
    3. ¿Qué relación existe entre esta tabla y la infraestructura desplegada?
    4. ¿Cómo podrías mostrar esta tabla desde PHP?

    25. Fase 20 — Ampliación opcional

    Para subir nota, puedes realizar una o varias de estas mejoras.

    Opción A — Añadir Nginx como proxy inverso

    Añade un contenedor Nginx que actúe como punto de entrada.

    Debe permitir acceder a la aplicación PHP desde otro puerto o ruta.

    Opción B — Añadir Adminer

    Añade Adminer como alternativa a phpMyAdmin.

    Opción C — Añadir Redis

    Añade Redis como servicio adicional y explica para qué se utiliza.

    Opción D — Crear una aplicación PHP más completa

    La aplicación deberá permitir:

    • Insertar alumnos.
    • Listar alumnos.
    • Borrar alumnos.
    • Editar alumnos.