La opción -w hace que grep busque la palabra exacta linux, no partes de otras palabras.
Usar find + grep con borrado o acciones posteriores
A veces queremos localizar archivos cuyo contenido cumpla una condición y luego actuar sobre ellos. Aquí hay que ir con mucho cuidado, porque el pingüino no perdona los comandos mal puestos.
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.
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.
Crear contenedor MySQL.
Publicar puerto al host.
Conectarse desde MySQL Workbench.
Ver que el contenedor es “otro sistema”, pero accesible.
Arquitectura:
Workbench (Host) → localhost:3306 → Contenedor MySQL
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;
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:
Crea el directorio si no existe (/app en este caso).
Cambia el contexto: a partir de esa línea, todos los comandos (COPY, RUN, CMD, etc.) se ejecutan dentro de /app.
Así, COPY . . copia el contenido del proyecto local dentro del contenedor en /app, y python app.py se ejecutará desde ahí.
Sin WORKDIR
Si no la usas, Docker ejecuta todo desde / (la raíz del contenedor). Ejemplo:
FROM python:3.11-slim
COPY . .
CMD ["python", "app.py"]
Esto también funcionará, pero el código quedará desperdigado en / — algo poco limpio y poco profesional.
¿Qué son las variables de entorno en Docker?
Las variables de entorno (ENV) son valores que se inyectan en el entorno del contenedor y que las aplicaciones pueden usar para configurarse.
Por ejemplo:
Contraseña del root
Nombre de la base de datos
Usuario y contraseña de aplicación
Docker las pasa al contenedor con la instrucción ENV en el Dockerfile o con la opción -e en el docker run.
Ejemplo básico de Dockerfile con MySQL
# Imagen base oficial de MySQL
FROM mysql:8.0
# Variables de entorno
ENV MYSQL_ROOT_PASSWORD=supersegura
ENV MYSQL_DATABASE=tienda
ENV MYSQL_USER=usuario
ENV MYSQL_PASSWORD=1234
# Exponer el puerto de MySQL
EXPOSE 3306
Explicación:
MYSQL_ROOT_PASSWORD: establece la contraseña del usuario root.
MYSQL_DATABASE: crea automáticamente una base de datos llamada tienda.
MYSQL_USER y MYSQL_PASSWORD: crean un usuario adicional con permisos sobre esa base.
EXPOSE 3306: indica el puerto por defecto del servicio.
Construcción y ejecución
Construir la imagen:
docker build -t mysql-demo .
Ejecutar el contenedor:
docker run -d -p 3306:3306 --name contmysql mysql-demo
Con eso, MySQL arrancará con la configuración establecida en las variables del Dockerfile.
Alternativa más flexible (sin tocar el Dockerfile)
También se pueden pasar las variables en tiempo de ejecución, lo que se recomienda para producción (para no dejar contraseñas dentro del Dockerfile):
docker run -d --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
Servicio
Imagen base
Rol / Descripción
Puertos
nginx-web
nginx:latest
Página corporativa vulnerable (HTML + PHP)
8080
db-server
mysql:8.0
Base de datos de empleados con credenciales débiles
3306
ftp-server
delfer/alpine-ftp-server
Servidor FTP para intercambio de archivos
21
mail-server
maildev/maildev
Servidor SMTP simulado (visualización de correos en web)
1080
monitor
grafana/grafana
Panel de monitorización interno
3000
attacker
kalilinux/kali-rolling
Contenedor con herramientas ofensivas
shell interactiva
logger
fluentd
Centralizació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/
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)
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
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.
– 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
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.
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).
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
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:
Explicar qué es Docker y para qué se utiliza.
Diferenciar una imagen de un contenedor.
Crear y ejecutar contenedores mediante Docker Compose.
Configurar servicios conectados entre sí.
Usar volúmenes para conservar datos.
Usar redes Docker para aislar servicios.
Configurar variables de entorno.
Desplegar una aplicación PHP conectada a MySQL.
Desplegar y consultar una base de datos MongoDB.
Comprobar el estado de los servicios mediante comandos.
Interpretar errores básicos mediante logs.
Documentar una instalación técnica de forma clara.
Defender oralmente el trabajo realizado.
3. Resultado final esperado
Al terminar la práctica deberás tener funcionando una infraestructura compuesta por varios contenedores:
Servicio
Tecnología
Puerto externo sugerido
Función
Web PHP
Apache + PHP
8080
Servidor web principal
MySQL
MySQL
3306 o solo interno
Base de datos relacional
phpMyAdmin
phpMyAdmin
8081
Administración de MySQL
MongoDB
MongoDB
27017 o solo interno
Base de datos NoSQL
Mongo Express
Mongo Express
8082
Administració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:
Carpeta completa del proyecto.
Archivo docker-compose.yml.
Archivo .env.
Código fuente de la aplicación PHP.
Capturas de pantalla.
Documento de memoria en PDF.
Comandos utilizados.
Respuestas a las preguntas de reflexión.
Evidencias de funcionamiento.
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:
¿Qué diferencia hay entre el puerto de la izquierda y el de la derecha en esta línea?
- "8080:80"
¿Para qué sirve volumes?
¿Qué pasaría con los datos de MySQL si no usásemos un volumen?
¿Qué hace depends_on?
¿Qué limitación tiene depends_on?
¿Para qué sirve definir una red propia llamada red_asir?
¿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 );
¿Qué diferencia hay entre exportar e importar una base de datos?
¿Qué riesgos existen al restaurar una copia sobre una base de datos que ya contiene información?
¿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
¿Por qué podemos hacer ping mysql y no necesitamos conocer la IP del contenedor?
¿Qué papel hace el DNS interno de Docker?
¿Cambiaría la IP del contenedor si lo eliminamos y lo volvemos a crear?
¿Por qué es mejor usar nombres de servicio que direcciones IP?
23. Fase 18 — Seguridad básica
Analiza la configuración actual.
Responde:
¿Qué puertos están expuestos al equipo anfitrión?
¿Qué servicios son accesibles desde el navegador?
¿Tiene sentido exponer MySQL directamente al exterior?
¿Tiene sentido exponer MongoDB directamente al exterior?
¿Qué contraseñas son débiles?
¿Qué cambiarías si esto fuese un servidor real?
¿Qué riesgos tiene usar root para administrar la base de datos?
¿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á:
Cambiar el nombre de los contenedores.
Cambiar el nombre de la base de datos.
Cambiar usuario y contraseña.
Personalizar la página principal.
Insertar datos propios.
Añadir una nueva tabla en MySQL.
Añadir una nueva colección en MongoDB.
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
¿Qué campos has elegido para la tabla servicios?
¿Por qué has elegido esos campos?
¿Qué relación existe entre esta tabla y la infraestructura desplegada?
¿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.