Saltar al contenido principal

Mi primer servidor en un contenedor

Si Nginx funciona dentro del contenedor, ¿cómo accedemos a su servicio desde nuestro ordenador? Esa es la pregunta que dejamos abierta. Vamos a probarlo antes de añadir nada nuevo al comando.

Trabajaremos con Docker preparado en nuestro equipo, no con un motor remoto. Utiliza el mismo acceso a Docker que en las unidades anteriores. Los nombres de las pruebas deben estar libres: compruébalo con docker ps -a y no elimines contenedores de otros trabajos para reutilizarlos.

Está ejecutándose, pero no vemos la web​

Comprueba que conservas la imagen de Nginx:

docker image ls

Si no aparece, recupérala con docker pull nginx. Después crea el primer contenedor, sin publicar ningún puerto:

docker run -d --name web-sin-puerto nginx
docker ps

Localiza su fila. Deberías ver Up ... en el estado y una indicación como 80/tcp en PORTS.

Predice: si abres http://localhost/ en el navegador, ¿llegarás automáticamente a ese Nginx? Pruébalo.

En un equipo sin otro servidor en ese punto, el navegador no podrá conectar. Si aparece una página, podría pertenecer a otra aplicación: no demuestra que este contenedor esté publicado.

localhost se refiere al propio equipo desde el que navegas. Al usar http://localhost/ sin indicar un puerto, el navegador intenta acceder al 80 del equipo. Nuestro Nginx escucha en el 80 del contenedor. Son lugares distintos.

Puedes comprobar los mensajes de arranque:

docker logs web-sin-puerto

El programa puede estar iniciado correctamente aunque todavía no hayamos configurado cómo llegar a él desde el navegador. Ejecutándose y accesible por una URL del host no son lo mismo.

Dos lados de una conexión​

Un puerto es un número que permite dirigir una conexión a un servicio. Decimos que un servidor escucha en un puerto cuando espera conexiones allí.

La imagen oficial de Nginx que estamos utilizando prepara su servicio HTTP en el puerto 80 del contenedor. Nuestro navegador, en cambio, está fuera de ese entorno:

Nuestro equipo                         Contenedor
┌───────────────────┐ ┌───────────────────┐
│ Navegador │ │ Nginx │
│ │ ¿Cómo llegar? │ escucha en :80 │
└───────────────────┘ └───────────────────┘

En esta práctica llamaremos puerto del host al puerto de nuestro equipo por el que entrará la conexión. Docker Desktop proporciona esa publicación hacia el equipo aunque el motor ejecute los contenedores en su entorno Linux.

Elegiremos el 8080 del host y lo relacionaremos con el 80 del contenedor. No necesitamos que los números coincidan.

Publicar el puerto del contenedor​

Publicar significa configurar por qué puerto del host podrán llegar conexiones al puerto correspondiente del contenedor. Lo indicaremos al crear el contenedor, con -p, abreviatura de --publish:

-p 8080:80
│ │
│ └── CONTENEDOR: donde escucha Nginx
└─────── HOST: por donde entraremos desde el navegador

HOST:CONTENEDOR

Primero retira solo nuestra prueba anterior:

docker stop web-sin-puerto
docker rm web-sin-puerto
docker ps -a

Comprueba que ya no existe. Vamos a crear otro contenedor con la publicación elegida; docker start no añadiría esa configuración al anterior.

Publica solo lo necesario. La forma -p 8080:80 publica por defecto en todas las interfaces del host, por lo que el servicio puede ser accesible desde otros equipos. Usaremos únicamente la página de prueba de Nginx, sin datos sensibles. Acceder mediante localhost no limita la publicación a esa dirección ni significa que la web esté automáticamente disponible en Internet: también intervienen la red, el firewall y la configuración del host y de la infraestructura.

Crea el servidor:

docker run -d --name mi-web -p 8080:80 nginx
docker ps

Ya conoces el resto del comando. La pieza nueva es -p 8080:80: publicamos el puerto 80 del contenedor a través del 8080 del host. Las conexiones que lleguen a ese puerto publicado se encaminarán al puerto del contenedor.

Si el comando informa de que 8080 está ocupado, no detengas aplicaciones a ciegas. Comprueba con docker ps -a si el intento dejó mi-web en estado Created; en ese caso, retira esa prueba con docker rm mi-web antes de repetir. Puedes elegir otro puerto libre del host, como 9000, y utilizarlo también en las URLs. Más adelante provocaremos este conflicto de forma controlada.

Comprobar la publicación​

En la fila de mi-web, busca una relación entre 8080 y 80 en la columna PORTS. Una salida orientativa sería:

0.0.0.0:8080->80/tcp

Puede aparecer también una dirección IPv6 u otra presentación según el entorno. Lo importante es identificar el puerto 8080 del host asociado al 80 del contenedor. No uses 0.0.0.0 como dirección de la web: aquí navegaremos a localhost.

Para consultar específicamente las publicaciones de este contenedor:

docker port mi-web

Este comando puede mostrar la relación en el otro sentido visual:

80/tcp -> 0.0.0.0:8080

Empieza por el puerto del contenedor y muestra dónde está publicado en el host. La sintaxis de -p sigue siendo HOST:CONTENEDOR.

80/tcp por sí solo no es una publicación. Informa de un puerto declarado por la imagen; no garantiza que el servicio esté escuchando ni que exista una conexión desde el host. Si encuentras el término EXPOSE en su documentación, no lo confundas con publicar: en nuestra práctica lo hacemos con -p.

Entrar desde el navegador​

Abre http://localhost:8080. Escribe http, no https: esta imagen de prueba sirve HTTP.

Deberías ver la página de bienvenida de Nginx. Esta es la conexión que acabas de realizar:

Navegador
│ http://localhost:8080
▼
Host :8080
│ publicación de Docker
▼
Contenedor mi-web :80
│
▼
Nginx recibe la petición y devuelve la página

La respuesta vuelve al navegador por esa conexión. Hemos comprobado que este contenedor está en ejecución, el servicio atiende en el puerto esperado y el navegador puede alcanzarlo mediante la publicación. No es una comprobación de todas las funciones posibles de Docker.

¿Por qué 8080 y no 80 o 9000?​

8080 es una elección para el host, no un número obligatorio de Docker. Podríamos elegir 9000 y publicar 9000:80: entraríamos por http://localhost:9000 y Nginx seguiría escuchando en el 80 del contenedor.

Cambiar el número de la izquierda cambia la entrada del host. Cambiar el de la derecha cambia el destino dentro del contenedor; no hace que Nginx empiece a escuchar allí. Por eso, invertir los números no resuelve el mismo problema.

Relacionar una petición con los logs​

Recarga la página y consulta:

docker logs mi-web

Además de los mensajes de arranque, busca una línea correspondiente a la petición de la página. Puede contener algo parecido a:

... "GET / HTTP/1.1" 200 ...

GET / corresponde a pedir la página inicial; 200 indica una respuesta HTTP satisfactoria. Los detalles y otras peticiones del navegador pueden variar. No necesitas interpretar toda la línea.

Para observar la relación mientras sucede, deja esta consulta abierta y recarga la web desde el navegador:

docker logs -f mi-web

Verás nuevas entradas cuando Nginx reciba las peticiones. Si el navegador reutiliza una página guardada, fuerza su recarga para enviar una petición nueva. Después termina el seguimiento con Ctrl + C; eso no detiene el servidor.

Navegador → publicación → Nginx → mensaje en los logs

Detener y volver a iniciar: ¿qué se conserva?​

Anota el ID de mi-web que muestra docker ps y su publicación. Predice qué ocurrirá con una petición nueva si detenemos el contenedor.

docker stop mi-web
docker ps -a

Comprueba que está detenido. Vuelve a cargar http://localhost:8080: el servicio de este contenedor ya no puede responder. Una pestaña que conserva la página anterior no demuestra que siga funcionando; comprueba una petición nueva, forzando la recarga si hace falta.

Recupéralo sin crear otro:

docker start mi-web
docker ps
docker port mi-web

Recarga la URL. La página vuelve a responder. Compara las pruebas: mismo ID, mismo nombre y misma publicación. La relación 8080:80 forma parte de la configuración del contenedor existente y vuelve a utilizarse al iniciarlo.

No tienes que repetir run ni configurar de nuevo el puerto. Si otro proceso hubiese ocupado el puerto del host durante la parada, podría impedir el inicio; en ese caso revisa el error antes de continuar.

Dos Nginx, el mismo puerto dentro​

Deja mi-web en marcha en 8080 y crea un segundo servidor con otro nombre y otra entrada en el host:

docker run -d --name otra-web -p 8081:80 nginx
docker ps

Abre ambas direcciones:

Dirección del navegadorDestino
http://localhost:8080Puerto 80 de mi-web.
http://localhost:8081Puerto 80 de otra-web.

Las páginas se parecen porque parten de la misma imagen. Sin embargo, los contenedores tienen IDs distintos y no comparten el mismo espacio de puertos interno:

Host :8080 ──→ mi-web   :80 ──→ Nginx A
Host :8081 ──→ otra-web :80 ──→ Nginx B

Ambos pueden escuchar en su propio 80. Las publicaciones del host son diferentes. Como comprobación adicional, recarga solo una URL y consulta los logs del contenedor correspondiente.

Provocar un conflicto controlado​

¿Qué ocurrirá si intentamos usar de nuevo el 8080 del host mientras mi-web lo ocupa? Con un nombre nuevo, prueba:

docker run -d --name web-conflicto -p 8080:80 nginx

Docker debe rechazar el inicio por un puerto ocupado. El mensaje puede hablar de una dirección en uso o de un puerto no disponible. El conflicto está en la publicación del host, no en que dos Nginx escuchen internamente en 80.

No podemos publicar dos contenedores a la vez sobre la misma combinación de dirección y puerto del host. Otra aplicación ajena a Docker también podría estar utilizando ese puerto.

Consulta lo que quedó después del intento:

docker ps -a

Puede existir web-conflicto en estado Created: se creó el contenedor, pero no llegó a iniciarse. Un run fallido puede dejar un contenedor y ocupar su nombre. Si esa fila existe y no está ejecutándose, elimina únicamente esa prueba:

docker rm web-conflicto
docker ps -a

Comprueba que mi-web y otra-web continúan funcionando. Ya has visto la alternativa al conflicto: elegir otra entrada libre en el host. También puedes liberar una publicación que ya no necesites, deteniendo conscientemente el contenedor que la utiliza.

Terminar el recorrido guiado​

Retira los dos servidores de esta práctica y comprueba el resultado:

docker stop mi-web otra-web
docker rm mi-web otra-web
docker ps -a
docker image ls

Sus filas desaparecen y esas URLs dejan de llegar a nuestros servidores. La imagen permanece para los retos siguientes. Hemos completado el recorrido desde la imagen hasta el navegador y la retirada de sus contenedores.

Si no aparece la página, busca la evidencia​

No empieces recreando el contenedor. Recorre estas preguntas con el nombre y los puertos de tu prueba:

PreguntaQué comprobarQué te indica
¿Existe el contenedor?docker ps -a.Si no está, puede que no se crease o que se eliminase.
¿Está ejecutándose?Su estado en docker ps y docker ps -a.Si está detenido o solo creado, revisa sus logs y el error de inicio.
¿Hay una publicación?PORTS y docker port mi-web.80/tcp solo no proporciona una entrada en el host. Sustituye mi-web por tu nombre si es otro.
¿La URL usa la entrada correcta?Dirección, http y puerto del host.Con 8080:80, el navegador debe usar localhost:8080.
¿Qué recibió o indicó Nginx?docker logs mi-web.Los mensajes pueden mostrar un problema de arranque o una petición recibida.
¿El host tiene ese puerto ocupado?Error de inicio y publicaciones de otros contenedores.El ocupante también puede ser una aplicación del host que no aparezca en docker ps.

Si falta la publicación, start no la añade. En estas pruebas sin datos, después de confirmar la causa puedes retirar tu contenedor y crear otro con la relación correcta. Si el problema es un puerto ocupado, elige uno libre o libera únicamente un recurso que conozcas y ya no necesites.

La ausencia de una entrada de petición en los logs no identifica por sí sola la causa: quizá la conexión no llegó, quizá el navegador reutilizó una respuesta anterior. Combina las evidencias.

Ideas que pueden llevarnos a error​

Si piensas…Recuerda…
«Nginx escucha en 80, así que basta con localhost:80».Ese es el puerto del host; necesitamos una publicación que conduzca al contenedor.
«80/tcp o EXPOSE significa que ya está publicado».Declarar un puerto no lo publica. En esta unidad la publicación se configura con -p.
«8080:80 significa contenedor:host».El orden es HOST:CONTENEDOR.
«Dos contenedores no pueden usar ambos el 80».Cada uno puede escuchar en su propio puerto interno.
«Ambos pueden publicar el mismo 8080 del host».Sus publicaciones simultáneas no pueden competir por la misma dirección y puerto.
«-p hace la web pública en Internet».La accesibilidad externa depende también de direcciones, red, firewall e infraestructura.
«Después de stop hay que volver a configurar los puertos».start conserva la configuración del mismo contenedor.
«Si falla el navegador, Docker está roto».Comprueba estado, publicación, URL y logs antes de concluir.

Mini reto: tres entradas, tres servidores​

Partiendo de la imagen local de Nginx y sin contenedores de las pruebas anteriores, consigue estas publicaciones:

ContenedorURL en el hostPuerto interno
web-ahttp://localhost:808180
web-bhttp://localhost:808280
web-chttp://localhost:808380

Elige los comandos. Si un nombre o puerto ya está ocupado por otro trabajo, utiliza uno libre y anota el cambio.

  1. Comprueba que los tres están en ejecución y visita sus URLs.
  2. Haz una petición a web-b y localízala en sus logs.
  3. Predice qué URLs responderán al detener solo web-b. Detenlo y compruébalo con peticiones nuevas.
  4. Vuelve a iniciarlo y comprueba de nuevo las tres páginas.
  5. Detén y elimina únicamente los tres contenedores del reto; verifica que ya no existen.

Explica por qué los tres podían escuchar en 80, qué significa 8082:80 y por qué no podrían utilizar todos la misma publicación 8080:80. ¿Qué conservaba web-b al estar detenido y qué perdió al ser eliminado?

Reto final de Docker 0: demuéstralo de principio a fin​

Prepara dos servidores independientes utilizando la imagen existente de Nginx, sin modificarla. No copies una secuencia completa: decide qué necesitas y registra tus comprobaciones.

Estado inicial: Docker funciona, no quedan contenedores de los ejercicios anteriores y puedes comprobar si la imagen está disponible. Usa nombres y puertos libres.

Resultado que debes conseguir:

servidor-a → localhost:8080 → su puerto interno 80
servidor-b → localhost:8081 → su puerto interno 80

Demuestra que:

  • has identificado la imagen y creado dos contenedores con nombres e IDs distintos;
  • ambas páginas responden y sus logs registran peticiones;
  • al detener uno, predices y compruebas qué página deja de responder;
  • al iniciarlo de nuevo, conserva identidad y publicación;
  • un intento con un tercer nombre sobre un puerto ya ocupado falla por la publicación del host; comprueba y retira lo que deje ese intento;
  • al terminar, no queda ningún contenedor del reto y la imagen sigue disponible.

Entrega un registro breve con la imagen, los nombres e IDs, las publicaciones, los comandos utilizados y las evidencias del navegador y los logs. Dibuja el recorrido de una petición y explica el conflicto sin limitarte a copiar el mensaje de error.

Estado final: únicamente la imagen conservada y tus anotaciones; ningún servidor de este reto sigue ejecutándose. Los recursos ajenos al ejercicio permanecen sin cambios.

De «en mi ordenador funciona» a un servicio accesible​

Empezamos preguntándonos por qué el mismo programa puede comportarse de forma distinta entre equipos. Ahora has recorrido este camino:

Problemas de entorno
↓
Contenedores y Docker
↓
Equipo preparado e imagen disponible
↓
Contenedor con identidad y ciclo de vida
↓
Servicio y puerto publicado
↓
Acceso desde el navegador, con estados y logs para comprobarlo

Ya puedes utilizar imágenes existentes, gestionar sus contenedores, publicar un servicio y distinguir problemas básicos de ejecución y acceso. No se trata de haber memorizado 8080:80, sino de poder explicar qué ocurre a cada lado y cómo lo has comprobado.

Hasta ahora hemos utilizado imágenes que otras personas han preparado. La siguiente necesidad es diferente: ¿cómo creo una imagen para mi propia aplicación? Ese será el punto de partida de Docker 1 · Fundamentos.