Llega el momento de la práctica seria. En esta lección no vas a leer teoría: vas a teclear, a equivocarte y a ver resultados en tu navegador. Empezarás con el contenedor más simple posible —uno que imprime algo y muere— para entender el ciclo más corto que existe. Después levantarás un servidor web real en segundo plano, publicarás su puerto y lo visitarás desde el navegador, aprendiendo por el camino qué significa exactamente -p 8080:80, que es probablemente la opción que más veces escribirás en tu carrera. Recorrerás el ciclo completo de ese contenedor: verlo, leer sus logs, pararlo, arrancarlo y borrarlo. Luego entrarás dentro de un contenedor con un shell interactivo para tocar su sistema de archivos aislado con tus propias manos. Y terminarás sirviendo la primera página web de Aurora Libros desde un contenedor, montando un fichero de tu máquina dentro de él.
Contenido
- Antes de empezar: comprobaciones
- Tu primer contenedor: en primer plano
- Qué ocurre cuando el proceso termina
- Un contenedor de servicio en segundo plano
- El mapeo de puertos explicado
- El ciclo completo: logs, stop, start y rm
- Un contenedor interactivo: entrar dentro
- Sirviendo la página de Aurora Libros con
-v - Limpieza final
- Antes de empezar: comprobaciones
Asegúrate de que todo está en orden:
El primero debe mostrar los bloques Client y Server. El segundo, una tabla (probablemente vacía) sin errores. Si algo falla, vuelve a la lección 01-02.
Comprueba también que el puerto que vamos a usar está libre:
ss -tln lista los puertos TCP en escucha (-t TCP, -l en escucha, -n sin resolver nombres). Si no hay coincidencias, grep falla y el || ejecuta el echo. Si algo estuviera escuchando en 8080, usa 8081 en todos los comandos de la lección.
- Tu primer contenedor: en primer plano
Empecemos por lo mínimo:
Parece trivial, pero han pasado muchas cosas. Desglose de la línea:
| Parte | Significado |
|---|---|
docker run |
Crea un contenedor nuevo y lo arranca |
alpine:3.20 |
La imagen a partir de la cual crearlo |
echo "Hola desde Aurora Libros" |
El comando a ejecutar dentro del contenedor, sustituyendo al comando por defecto de la imagen |
Y la secuencia interna, que ya conoces de la lección 01-03: el cliente llamó al daemon, el daemon comprobó que tenía alpine:3.20 en local, creó un contenedor apilando una capa de escritura sobre las capas de la imagen, pidió a containerd que arrancara el proceso echo, este escribió en su salida estándar, y esa salida se transmitió por el socket hasta tu terminal.
Como no has usado -d, el contenedor se ejecuta en primer plano: tu terminal queda conectada a su salida y no recupera el control hasta que el proceso termina.
Probemos algo un poco más largo:
docker run alpine:3.20 sh -c "echo 'Catálogo:'; echo '- Rayuela'; echo '- El jardín de senderos que se bifurcan'"Aquí el comando es sh -c "...": se lanza un shell dentro del contenedor y se le pasa una cadena con varias órdenes. Es el patrón habitual cuando necesitas encadenar comandos, porque docker run solo acepta un ejecutable con sus argumentos, no una línea de shell con ; o &&.
Y algo que dura un rato, para que veas el primer plano de verdad:
Las líneas salen de una en una, con un segundo de pausa. Tu terminal está bloqueada mientras tanto: eso es primer plano. Si pulsas Ctrl+C durante la ejecución, envías una señal de interrupción al proceso y el contenedor termina antes de tiempo.
- Qué ocurre cuando el proceso termina
Esta es la regla más importante de toda la lección:
Un contenedor vive exactamente lo que vive su proceso principal. Cuando ese proceso termina, el contenedor pasa a estado Exited. No hay nada más que "apagar".
Compruébalo:
Vacío: ninguno de los tres contenedores anteriores sigue vivo.
NAMES COMMAND STATUS
happy_bardeen "sh -c 'for i in 1 2…" Exited (0) 10 seconds ago
festive_curie "sh -c 'echo Catálog…" Exited (0) 1 minute ago
vibrant_torvalds "echo 'Hola desde Au…" Exited (0) 2 minutes agoLos tres están ahí, parados, con código de salida 0 (terminaron bien). Ocupan un poco de disco cada uno.
Este comportamiento explica un tropiezo clásico de principiantes:
El segundo comando no muestra nada, y la reacción típica es "el contenedor no arranca". Sí arrancó: la imagen alpine tiene como comando por defecto /bin/sh, un shell que, al no tener terminal interactiva ni entrada, no encuentra nada que hacer y termina inmediatamente. El contenedor hizo su trabajo y murió en milisegundos.
Para que un contenedor siga vivo necesita un proceso que no termine: un servidor web esperando peticiones, una base de datos, o un shell con una terminal interactiva conectada. Eso es lo que veremos ahora.
- Un contenedor de servicio en segundo plano
Vamos a levantar un servidor web Nginx, que es exactamente eso: un proceso que se queda escuchando indefinidamente.
Unable to find image 'nginx:alpine' locally
alpine: Pulling from library/nginx
f18232174bc9: Already exists
2c8d4f7e1a09: Pull complete
...
Status: Downloaded newer image for nginx:alpine
7c3e9a1f5b2d84e0a6c7d9f3b1e5a7c9d2f4b6e8a0c2d4f6b8e0a2c4d6f8b0e2Analicemos opción por opción, porque cada una resuelve un problema concreto:
| Opción | Nombre largo | Qué hace |
|---|---|---|
-d |
--detach |
Ejecuta el contenedor en segundo plano y te devuelve el control de la terminal inmediatamente, imprimiendo su ID |
--name aurora-web-demo |
Le da un nombre legible en lugar de uno aleatorio. Podrás referirte a él por ese nombre en todos los comandos | |
-p 8080:80 |
--publish |
Publica el puerto 80 del contenedor en el puerto 8080 de tu máquina |
nginx:alpine |
La imagen. No indicamos comando, así que se usa el de la imagen: arrancar Nginx |
Esa larga cadena hexadecimal de la última línea es el ID completo del contenedor. Como usaste --name, no te hará falta.
Ahora sí:
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
7c3e9a1f5b2d nginx:alpine "/docker-entrypoint.…" Up 8 seconds 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp aurora-web-demoUp 8 seconds: está vivo, y seguirá vivo porque Nginx no termina nunca por sí solo. Nota que docker ps sin -a ya lo muestra, a diferencia de los contenedores anteriores.
- El mapeo de puertos explicado
La columna PORTS merece un apartado propio porque es donde más gente se atasca.
Un contenedor tiene su propia pila de red: su propia interfaz, su propia dirección IP interna y sus propios puertos. Nginx escucha en el puerto 80 de esa red interna, que desde tu máquina no es accesible directamente. La opción -p crea un puente.
flowchart LR
NAV["Tu navegador<br/>http://localhost:8080"]
subgraph HOST["Tu máquina (host)"]
P8080["Puerto 8080<br/>(reservado por Docker)"]
subgraph NET["Red bridge de Docker · 172.17.0.0/16"]
subgraph CNT["Contenedor aurora-web-demo · 172.17.0.2"]
P80["Puerto 80<br/>Nginx escuchando"]
end
end
end
NAV --> P8080
P8080 -->|"-p 8080:80<br/>reenvío"| P80
La sintaxis se lee siempre de fuera hacia dentro:
- El primero es el de tu máquina: el que escribes en el navegador. Lo eliges tú y debe estar libre.
- El segundo es el del contenedor: en el que escucha la aplicación. No lo eliges tú, lo determina la aplicación (Nginx usa el 80, PostgreSQL el 5432, Redis el 6379, y la API de Aurora Libros usará el 3000).
Variantes útiles:
| Sintaxis | Efecto |
|---|---|
-p 8080:80 |
Puerto 8080 de todas las interfaces del host → 80 del contenedor |
-p 127.0.0.1:8080:80 |
Solo accesible desde tu propia máquina, no desde la red local. Más seguro |
-p 80:80 |
Puerto 80 del host (en Linux puede requerir privilegios) |
-p 8080:80 -p 8443:443 |
Varios puertos publicados a la vez |
-P |
Publica todos los puertos declarados por la imagen en puertos aleatorios altos |
Sin -p, el contenedor funciona perfectamente pero es inalcanzable desde fuera. Es lo correcto para servicios internos: en el módulo 4, aurora-db y aurora-cache no publicarán puertos, porque solo deben ser accesibles desde aurora-api, no desde Internet.
Comprobación con curl y con el navegador
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</html>Solo las cabeceras, para ver el estado HTTP:
-I pide solo las cabeceras (método HEAD). El 200 OK confirma que el servidor responde.
Abre ahora http://localhost:8080 en tu navegador: verás la página de bienvenida de Nginx. Acabas de servir una web desde un contenedor sin haber instalado Nginx en tu sistema. Si ejecutas nginx -v en tu terminal, lo más probable es que no exista el comando: todo está dentro del contenedor.
- El ciclo completo: logs, stop, start y rm
Ver los logs
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/08/04 10:15:03 [notice] 1#1: nginx/1.27.4
2026/08/04 10:15:03 [notice] 1#1: start worker processes
172.17.0.1 - - [04/Aug/2026:10:16:12 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.5.0"
172.17.0.1 - - [04/Aug/2026:10:16:45 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 ..."Ahí están tus dos visitas: la de curl y la del navegador, identificadas por su user agent. La IP 172.17.0.1 es la de tu máquina vista desde la red interna de Docker.
Opciones muy útiles:
docker logs -f aurora-web-demo # seguir en vivo (Ctrl+C para salir)
docker logs --tail 20 aurora-web-demo # solo las 20 últimas líneas
docker logs -t aurora-web-demo # con marca de tiempo de Docker
docker logs --since 5m aurora-web-demo # solo los últimos 5 minutosPrueba docker logs -f aurora-web-demo, recarga el navegador y verás aparecer la línea en tiempo real. Ctrl+C corta el seguimiento sin afectar al contenedor (una duda muy habitual: no, no lo paras).
Parar el contenedor
Devuelve el nombre de lo que ha parado. Verifica:
Dos cosas: docker ps ya no lo muestra, y en docker ps -a la columna PORTS está vacía (el mapeo del puerto 8080 se ha liberado). Confírmalo:
Nadie escucha. El contenedor sigue existiendo, pero no está en ejecución.
Arrancarlo de nuevo
Vuelve a funcionar, con la misma configuración. Y aquí hay un detalle conceptual importante: no tuviste que repetir -p 8080:80. La configuración se fijó al crear el contenedor y forma parte de él. docker start solo lo reanuda.
Corolario: no puedes cambiar el mapeo de puertos de un contenedor existente. Si necesitas otro puerto, hay que borrarlo y crear uno nuevo. Es una manifestación de la filosofía de contenedores desechables.
Verás los logs del primer arranque y los del segundo, uno detrás de otro: los logs se conservan mientras el contenedor exista.
Borrarlo
Si intentas docker rm sin parar primero:
Error response from daemon: cannot remove container "aurora-web-demo": container is running:
stop the container before removing or force removePuedes forzarlo con docker rm -f aurora-web-demo, que para y borra en un paso. Es cómodo en desarrollo, pero mata el proceso sin darle tiempo a cerrar ordenadamente.
Verifica:
El contenedor ha desaparecido de la lista; la imagen nginx:alpine sigue ahí. Una vez más: borrar un contenedor no borra su imagen.
- Un contenedor interactivo: entrar dentro
Hasta ahora ejecutabas comandos sueltos. Ahora vas a abrir un shell dentro de un contenedor y moverte por él.
Tres opciones nuevas:
| Opción | Nombre largo | Qué hace |
|---|---|---|
-i |
--interactive |
Mantiene abierta la entrada estándar, para que puedas escribir |
-t |
--tty |
Asigna una terminal virtual: prompt, colores, formato |
--rm |
Borra el contenedor automáticamente al salir |
-i y -t casi siempre van juntos (-it): con -i sin -t podrías escribir pero sin prompt ni formato; con -t sin -i verías el prompt pero no podrías escribir.
Tu prompt cambia:
Estás dentro del contenedor. Explora:
El nombre de host es el ID corto del contenedor, no el de tu máquina.
Un sistema de archivos Linux completo... que no es el tuyo. Compruébalo:
Alpine, aunque tu máquina sea Ubuntu, Windows o macOS.
Vacíos: tus documentos no están aquí. El aislamiento del sistema de archivos es real.
Esto es revelador: solo hay dos procesos, y el shell tiene el PID 1. En tu máquina hay cientos de procesos y el PID 1 es systemd. El contenedor tiene su propio espacio de nombres de procesos y no ve los del anfitrión (el mecanismo exacto, en la lección 05-07).
Y sin embargo:
El kernel es el de tu anfitrión. Espacio de usuario de Alpine, kernel de Ubuntu. Esa frase resume qué es un contenedor.
Instalar algo dentro
Alpine usa apk como gestor de paquetes:
No está instalado. Instalémoslo:
fetch https://dl-cdn.alpinelinux.org/alpine/v3.20/main/x86_64/APKINDEX.tar.gz
(1/8) Installing ca-certificates (20240705-r0)
...
(8/8) Installing curl (8.12.1-r0)
Executing busybox-1.36.1-r29.trigger
OK: 13 MiB in 23 packagesapk addinstala paquetes (el equivalente deapt install).--no-cacheevita guardar el índice de paquetes en disco; es la opción estándar en contenedores porque reduce el tamaño.
Funciona. Y algo importante: has instalado curl en el contenedor, no en tu máquina. Tu sistema no se ha tocado.
Crea también un fichero para la siguiente comprobación:
Ahora sal:
Vuelves a tu prompt normal.
La magia de --rm
No aparece. Gracias a --rm, el contenedor se eliminó automáticamente al terminar su proceso. Con él se fueron su capa de escritura, el curl que instalaste y el fichero /prueba.txt.
Confírmalo abriendo otro:
Es la lección 01-05 en carne y hueso: cada contenedor nace limpio de la imagen inmutable. Los cambios viven en su capa de escritura y mueren con él.
--rm es una opción excelente para pruebas y comandos puntuales: te ahorra la acumulación de contenedores parados. No la uses en servicios que quieras poder reanudar.
- Sirviendo la página de Aurora Libros con
-v
-vNginx sirviendo su página por defecto está bien para probar, pero queremos nuestro contenido. Vamos a crear la primera página de Aurora Libros.
Crear la página
Crea index.html con este contenido:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aurora Libros · Librería online</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 42rem; margin: 3rem auto; padding: 0 1rem; color: #222; }
h1 { color: #6b3fa0; }
li { margin-bottom: .6rem; }
footer { margin-top: 2rem; font-size: .85rem; color: #666; }
</style>
</head>
<body>
<h1>Aurora Libros</h1>
<p>Tu librería online. Catálogo destacado:</p>
<ul>
<li><strong>El jardín de senderos que se bifurcan</strong> — Jorge Luis Borges — 14,50 €</li>
<li><strong>Rayuela</strong> — Julio Cortázar — 19,90 €</li>
<li><strong>Cien años de soledad</strong> — Gabriel García Márquez — 17,95 €</li>
</ul>
<footer>Aurora Libros S.L. · Servido desde un contenedor Docker</footer>
</body>
</html>Montarla en el contenedor
docker run -d --name aurora-web-demo -p 8080:80 \
-v ~/aurora-libros/web/index.html:/usr/share/nginx/html/index.html:ro \
nginx:alpineLa opción nueva es -v (--volume), y su sintaxis se lee igual que -p, de fuera hacia dentro:
| Parte | Significado |
|---|---|
~/aurora-libros/web/index.html |
El fichero de tu máquina. Debe ser una ruta absoluta (~ la expande el shell) |
/usr/share/nginx/html/index.html |
Dónde aparecerá dentro del contenedor. Esta ruta es donde la imagen de Nginx busca sus ficheros |
:ro |
read-only: el contenedor puede leerlo pero no modificarlo. Buena práctica cuando no debe escribir |
Compruébalo:
Y en el navegador, en http://localhost:8080, verás la página de Aurora Libros con su catálogo. El fichero sigue estando en tu disco; el contenedor simplemente lo ve montado en su sistema de archivos.
La ventaja: edición en vivo
Edita index.html en tu editor y añade un libro:
Guarda y recarga el navegador. El cambio aparece al instante, sin reconstruir nada ni reiniciar el contenedor. El montaje es una ventana en vivo entre tu disco y el contenedor.
Este patrón —montar el código fuente dentro del contenedor para desarrollar sin reconstruir— es la base del flujo de desarrollo local que verás en la lección 04-07.
Nota importante. Este uso de
-ves puramente introductorio: es un bind mount, que monta una ruta de tu máquina. Existe otro tipo, los volúmenes gestionados por Docker, que es lo que necesitaráaurora-dbpara no perder el catálogo. Las diferencias, cuándo usar cada uno y las implicaciones de permisos y rendimiento son el contenido completo de la lección 03-06.
- Limpieza final
Dejemos el sistema como estaba:
- Los dos primeros paran y borran el contenedor de Nginx.
container prune -felimina cualquier otro contenedor parado que hayas dejado por el camino.
Comprueba:
Sin contenedores; las imágenes (alpine:3.20, nginx:alpine, hello-world) intactas, listas para reutilizarse sin descargarlas de nuevo. Tu fichero ~/aurora-libros/web/index.html también sigue en su sitio: lo necesitarás en las próximas lecciones.
Errores Comunes y Consejos
Bind for 0.0.0.0:8080 failed: port is already allocated. El puerto del host ya está ocupado, casi siempre por otro contenedor tuyo. Búscalo condocker psy páralo, o publica en otro puerto (-p 8081:80).- Invertir el orden de
-p.-p 80:8080con Nginx no funciona: estarías reenviando el puerto 80 del host al 8080 del contenedor, donde no escucha nadie. Recuerda: host:contenedor, de fuera hacia dentro. docker run -dcon un comando que termina.docker run -d alpine echo holaarranca y muere al instante.-dno mantiene vivo nada: solo desconecta tu terminal. Para seguir vivo hace falta un proceso que no termine.- Rutas relativas en
-v.-v ./web:/usr/share/nginx/htmlpuede fallar según la versión y el shell. Usa siempre rutas absolutas:$(pwd)/webo~/.... - Montar un directorio sobre uno que ya tiene contenido. El montaje oculta lo que hubiera en esa ruta dentro de la imagen. Si montas un directorio vacío sobre
/usr/share/nginx/html, Nginx devolverá 403 o 404 porque ya no ve suindex.html. - Confundir
Ctrl+Ccon parar el contenedor. Endocker logs -fodocker attach,Ctrl+Csolo corta tu conexión. En un contenedor en primer plano, sí interrumpe el proceso y lo termina. - Consejo: usa
--rmpara todo lo desechable. Pruebas, comandos puntuales, shells exploratorios. Te ahorrará limpiezas. - Consejo: nombra siempre con
--name.aurora-web-demoes infinitamente más manejable questupefied_swanson, y hace que los filtros por nombre funcionen. - Consejo: si un contenedor muere nada más arrancar,
docker logsprimero. Los logs se conservan aunque esté parado, y ahí está casi siempre la explicación.
Ejercicios
Ejercicio 1: dos escaparates a la vez
Levanta dos contenedores de Nginx simultáneamente a partir de la misma imagen: uno llamado aurora-web-es en el puerto 8080 y otro aurora-web-en en el 8081, cada uno sirviendo un index.html distinto (uno en español y otro en inglés). Comprueba con curl que cada puerto devuelve su página. Después responde: ¿cuántas copias de la imagen nginx:alpine hay en tu disco?, ¿cuánto ocupa la capa de escritura de cada contenedor (docker ps -as)? Al terminar, bórralos ambos en un único comando.
Ejercicio 2: el detective de logs
Ejecuta este contenedor, que está mal configurado a propósito:
docker run -d --name aurora-fallo -p 8080:80 \
-v /tmp/carpeta-que-no-uso:/usr/share/nginx/html:ro \
nginx:alpine(Crea antes /tmp/carpeta-que-no-uso vacía con mkdir -p /tmp/carpeta-que-no-uso.)
Visita http://localhost:8080 y observa el error. Después: (a) ¿está el contenedor en ejecución?, (b) ¿qué código HTTP devuelve y por qué?, (c) usa docker logs para encontrar el mensaje de error exacto de Nginx, y (d) explica en términos de montajes qué ha pasado y cómo lo arreglarías.
Ejercicio 3: exploración interactiva comparada
Abre un contenedor interactivo de alpine:3.20 y otro de nginx:alpine (en dos terminales, ambos con --rm). En cada uno, averigua: (a) el sistema operativo del espacio de usuario, (b) la versión del kernel, (c) el PID 1 y cuántos procesos hay, y (d) si existe el directorio /usr/share/nginx/html y qué contiene. Explica las diferencias y las coincidencias entre los dos contenedores, y qué te dice cada una sobre la naturaleza de los contenedores.
Soluciones
Solución al ejercicio 1
mkdir -p ~/aurora-libros/web-es ~/aurora-libros/web-en
echo '<h1>Aurora Libros</h1><p>Bienvenido a nuestra librería.</p>' > ~/aurora-libros/web-es/index.html
echo '<h1>Aurora Books</h1><p>Welcome to our bookstore.</p>' > ~/aurora-libros/web-en/index.html
docker run -d --name aurora-web-es -p 8080:80 \
-v ~/aurora-libros/web-es:/usr/share/nginx/html:ro nginx:alpine
docker run -d --name aurora-web-en -p 8081:80 \
-v ~/aurora-libros/web-en:/usr/share/nginx/html:ro nginx:alpine
curl -s http://localhost:8080
curl -s http://localhost:8081<h1>Aurora Libros</h1><p>Bienvenido a nuestra librería.</p>
<h1>Aurora Books</h1><p>Welcome to our bookstore.</p>Aquí se montan directorios en lugar de ficheros sueltos, que es lo habitual con Nginx. Los dos contenedores usan puertos distintos del host (8080 y 8081) pero ambos el 80 dentro: no hay conflicto, porque cada contenedor tiene su propia pila de red y su propio puerto 80.
Respuestas: hay una sola copia de la imagen en disco, compartida por los dos contenedores. Cada capa de escritura ocupa alrededor de 1 kB. El "virtual 52.5MB" incluye las capas compartidas de la imagen, que no se cuentan dos veces. Levantar el segundo contenedor costó prácticamente cero disco: es la densidad de la lección 01-01, medida.
rm -f para y borra en un paso, y acepta varios nombres.
Solución al ejercicio 2
(a) Sí, el contenedor está en ejecución:
CONTAINER ID IMAGE STATUS PORTS NAMES
b2c8f4a1e7d9 nginx:alpine Up 20 seconds 0.0.0.0:8080->80/tcp aurora-falloNginx arrancó correctamente. El problema no es el arranque, sino el contenido que sirve.
(b) Devuelve 403 Forbidden:
Es 403 y no 404 porque el directorio raíz existe (lo montaste) pero está vacío: no hay index.html que servir y Nginx no permite listar directorios por defecto.
(c) Los logs:
2026/08/04 11:02:41 [error] 30#30: *1 directory index of "/usr/share/nginx/html/" is forbidden,
client: 172.17.0.1, server: localhost, request: "GET / HTTP/1.1", host: "localhost:8080"El mensaje es explícito: directory index ... is forbidden.
(d) Qué ha pasado: el montaje oculta el contenido original de esa ruta en la imagen. La imagen nginx:alpine trae su propio index.html en /usr/share/nginx/html, pero al montar encima un directorio vacío de tu máquina, ese contenido deja de ser visible (no se borra: sigue en la capa de la imagen, simplemente queda tapado). Nginx encuentra un directorio vacío y responde 403.
Arreglos posibles: poner un index.html en /tmp/carpeta-que-no-uso, montar un directorio que sí tenga contenido, o no montar nada si querías la página por defecto.
echo '<h1>Aurora Libros</h1>' > /tmp/carpeta-que-no-uso/index.html
curl -s http://localhost:8080
docker rm -f aurora-falloNota que no hace falta reiniciar el contenedor: el montaje es en vivo, así que en cuanto el fichero existe en tu disco, Nginx lo sirve.
Solución al ejercicio 3
(La imagen de Nginx también trae un shell, así que se puede entrar en ella igual.)
En cada uno:
Resultados y su lectura:
| Comprobación | alpine:3.20 |
nginx:alpine |
Qué significa |
|---|---|---|---|
| (a) SO del espacio de usuario | Alpine Linux v3.20 | Alpine Linux (versión de la imagen base de Nginx) | Ambas se construyen sobre Alpine: comparten capas base, como viste en 01-05 |
| (b) Kernel | El de tu anfitrión (p. ej. 6.8.0-52-generic) |
El mismo | Confirma que ambos contenedores comparten el kernel del host. No hay SO invitado |
| (c) PID 1 y nº de procesos | sh como PID 1, 2 procesos |
sh como PID 1, 2 procesos |
Cada contenedor tiene su propio espacio de nombres de procesos, aislado. Y ojo: aquí Nginx no está corriendo, porque al pasar sh sustituiste el comando por defecto de la imagen |
(d) /usr/share/nginx/html |
No existe (ls: ... No such file or directory) |
Existe, con index.html y 50x.html |
El sistema de archivos lo aporta la imagen: son distintos porque las imágenes traen contenido distinto |
Las conclusiones importantes: los contenedores comparten el kernel (b) pero tienen sistemas de archivos y espacios de procesos aislados (c, d); dos imágenes pueden compartir su base y diferenciarse solo en las capas superiores (a); y pasar un comando a docker run sustituye el comando por defecto de la imagen, por lo que entrar con sh en la imagen de Nginx te da el sistema de archivos de Nginx sin Nginx ejecutándose.
Conclusión
Ya has creado, gestionado y destruido contenedores con tus propias manos. Te llevas la regla que gobierna todo lo demás: un contenedor vive lo que vive su proceso principal; si ese proceso termina, el contenedor pasa a Exited, y por eso docker run alpine "no hace nada" mientras docker run nginx:alpine se queda vivo indefinidamente.
Sabes lanzar contenedores en primer plano y en segundo plano con -d, nombrarlos con --name, y sobre todo publicar puertos con -p host:contenedor, leyendo la sintaxis siempre de fuera hacia dentro; has comprobado que el mapeo se fija al crear el contenedor y que docker start lo reanuda con la misma configuración. Has recorrido el ciclo completo —logs, stop, start, rm—, has entrado dentro de un contenedor con -it para ver con tus ojos el aislamiento del sistema de archivos y de los procesos junto al kernel compartido, y has confirmado con --rm que todo lo que escribes dentro muere con el contenedor. Y has servido la primera página de Aurora Libros montando un fichero de tu disco con -v, con edición en vivo incluida.
Tienes las tres piezas del rompecabezas —imágenes, contenedores y comandos— y una página web funcionando. Lo que aún no tienes es el problema real que justifica todo esto. En la última lección del módulo, El Proyecto del Curso: la Plataforma Aurora Libros, conocerás la empresa, su arquitectura objetivo y el código completo de su API, e intentarás arrancarla sin Docker para vivir en primera persona el calvario que el resto del curso va a resolver.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
