Hola mundo: el viaje de una petición

Escribimos una app de FastAPI que responde "Hola mundo" y seguimos su petición desde el navegador hasta una mini PC en una casa, y de regreso. Al final, el mismo viaje hasta un datacenter.

Escribes una dirección, das Enter y en menos de lo que tardas en parpadear aparece “Hola mundo”. En este artículo vamos a abrir ese parpadeo y a ver por dónde pasa la petición. Lo vamos a hacer tres veces: primero en tu propia computadora, luego hasta una mini PC en una casa y al final hasta un datacenter.

El programa

Es lo más pequeño que podemos escribir con FastAPI: una sola ruta que responde HTML plano. Lo guardamos como main.py:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/", response_class=HTMLResponse)
def hola() -> str:
    return "<!doctype html><html><body><h1>Hola mundo</h1></body></html>"

Para correrlo usamos uv, que se encarga de Python y de las dependencias:

uv run --with fastapi --with uvicorn uvicorn main:app --port 8000

Primer viaje: localhost:8000

Abres el navegador, escribes http://localhost:8000 y das Enter. Pasan cuatro cosas:

  1. El navegador busca qué IP es localhost. No le pregunta a nadie en internet: la respuesta ya está en tu propia máquina y siempre es 127.0.0.1.
  2. Abre una conexión TCP al puerto 8000 de esa dirección. El kernel ve que es la interfaz de loopback y se la entrega directo a uvicorn, que está escuchando ahí.
  3. El navegador manda la petición. Es texto plano, así:
GET / HTTP/1.1
Host: localhost:8000
Accept: text/html
  1. Uvicorn se la pasa a FastAPI, FastAPI ve GET /, ejecuta hola() y devuelve esto:
HTTP/1.1 200 OK
server: uvicorn
content-length: 60
content-type: text/html; charset=utf-8

<!doctype html><html><body><h1>Hola mundo</h1></body></html>

Son 60 bytes de HTML. El navegador los lee y pinta un título.

Ojo: en este viaje la petición nunca salió de tu computadora. No tocó tu tarjeta de red, ni tu módem, ni internet. localhost siempre eres tú mismo.

Por eso este primer viaje toma menos de un milisegundo, y por eso nadie más puede ver tu “Hola mundo”. Para que alguien más lo vea, la petición tiene que salir de casa.

Segundo viaje: hasta una casa en Monterrey

Ahora el mismo programa vive en una mini PC en una casa en Monterrey, y lo publicamos en https://hola.fierrosybits.com. Tú lo abres desde Guadalajara.

Lo que hay en la casa

  • Una mini PC con procesador Intel N100: cuatro núcleos, consume menos que un foco y cabe en la palma de la mano.
  • Ubuntu 26.04 LTS con Docker.
  • Dos contenedores: la app y cloudflared, el programa que abre un túnel de Cloudflare.

Las dependencias van en un pyproject.toml, y uv lock genera el uv.lock que fija sus versiones exactas:

[project]
name = "hola"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = ["fastapi", "uvicorn"]

La app se empaqueta con este Dockerfile:

FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --locked --no-dev
COPY main.py .
CMD ["/app/.venv/bin/uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Fíjate en --host 0.0.0.0. Adentro de un contenedor, localhost es el propio contenedor. Si uvicorn solo escuchara ahí, nadie de afuera del contenedor podría hablarle, ni siquiera cloudflared.

Y los dos contenedores se levantan juntos con compose.yaml:

services:
  app:
    build: .
  cloudflared:
    image: cloudflare/cloudflared:latest
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=${TUNNEL_TOKEN}
    depends_on:
      - app

En el panel de Cloudflare le decimos al túnel que hola.fierrosybits.com se atiende en http://app:8000. Y nota lo que no está: no hay ports:. La app no está expuesta ni en la mini PC ni en el módem.

Lo raro del túnel

Normalmente, para que alguien de afuera llegue a una computadora de tu casa, tendrías que abrir un puerto en el módem. Muchos proveedores en México ni siquiera te dan una IP propia (usan algo llamado CGNAT), así que ni abriendo puertos podrías.

El túnel le da la vuelta al problema: cloudflared es el que llama hacia afuera. Al arrancar, abre varias conexiones (por defecto cuatro) hacia al menos dos centros de datos de Cloudflare, y las deja abiertas. Para tu módem son conexiones que salieron de casa, como cualquier página que visitas. Cuando llega una petición, Cloudflare la manda de regreso por una de esas conexiones.

Paso a paso

  1. Tu navegador pregunta por DNS qué IP tiene hola.fierrosybits.com. La respuesta es una IP de Cloudflare, no de la casa.
  2. Tu proveedor de internet lleva la petición al centro de datos de Cloudflare más cercano a ti. Ahí se termina el cifrado HTTPS.
  3. Cloudflare revisa sus reglas, ve que ese nombre lo atiende un túnel y busca una de las conexiones que abrió cloudflared.
  4. El túnel lleva la petición, otra vez cifrada, hasta Monterrey.
  5. El módem de la casa la deja pasar sin preguntar: para él es la respuesta de una conexión que la mini PC abrió.
  6. La mini PC la recibe en su tarjeta de red y de ahí sube hasta el código. Lo vemos abajo.
Mapa de México con el recorrido de la petición: sale de Guadalajara, entra a la red de Cloudflare cerca de Querétaro y llega por el túnel a una casa en Monterrey. Tú, en Guadalajaraborde de Cloudflarecasa en Monterrey:la mini PCtúnel

Listo para enviar la petición.

0 ms

El borde de Cloudflare que te toca depende de tu proveedor de internet; Querétaro es ilustrativo. Los milisegundos son una estimación típica, no una medición.

Adentro de la mini PC

La petición entra como pulsos eléctricos por el cable de red. La tarjeta de red los convierte en paquetes y avisa al procesador. El kernel de Linux arma la conexión TCP y le entrega los datos a cloudflared, que corre en su contenedor.

cloudflared ve que la petición es para http://app:8000. Docker resuelve el nombre app a la IP del otro contenedor, y la petición cruza la red bridge de Docker: un cable virtual entre los dos contenedores que nunca sale de la mini PC.

En el contenedor de la app, uvicorn recibe exactamente el mismo texto que en el primer viaje: GET /. FastAPI ejecuta hola(). Al código le da lo mismo si la petición vino de Guadalajara o de la misma computadora.

Corte de la mini PC: la petición entra por la tarjeta de red, el kernel la entrega al contenedor de cloudflared, cruza la red bridge de Docker y llega al contenedor de la app, donde uvicorn y FastAPI ejecutan la función hola(). Mini PC N100 con Ubuntu 26.04 LTStarjeta de red2.5 GbECPU N1004 núcleosRAM16 GBSSDNVMeKernel Linuxdriver de red, TCP/IP, reglas de red de DockerDockercloudflaredel extremo del túnelappuvicorn :8000FastAPI → hola()red bridge de Dockerhttp://app:8000desdeel módemla petición

El regreso, rapidísimo

La respuesta hace el camino al revés y no se detiene en ningún lado. La conexión del túnel ya estaba abierta, así que no hay que saludar a nadie: los 60 bytes bajan por la red bridge, cloudflared los mete al túnel, Cloudflare los cifra para tu navegador y tu proveedor te los entrega. El navegador pinta el título.

Tramo Tiempo aproximado
De tu navegador al borde de Cloudflare 18 ms
Del borde a la mini PC, por el túnel 12 ms
Dentro de la mini PC: kernel, Docker, app 2 ms
Todo el regreso 30 ms
Total 62 ms

Son estimaciones típicas con la conexión ya abierta; la primera visita suma la consulta DNS y el saludo de HTTPS. En el video lo medimos de verdad.

Fíjate en el renglón de la mini PC: el código es la parte más rápida de todo el viaje. Casi todo el tiempo se va en la distancia.

Tercer viaje: el mismo hola mundo en un datacenter

Ahora llevemos la app a un datacenter corporativo. Lo interesante es lo poco que cambia por dentro: el mismo main.py, la misma imagen de Docker y, si quieres, hasta el mismo túnel de Cloudflare. La petición hace casi el mismo recorrido: borde, túnel, tarjeta de red, kernel, contenedor, hola().

Lo que cambia es todo lo que rodea al código.

Capas alrededor del código. Código, contenedor y sistema operativo son iguales en casa y en el datacenter; servidor, red, energía y edificio cambian. Edificio y seguridadEnergía y climaRedServidorSistema operativoContenedorhola()
Igual en los dos ladosCambia
Comparación por capa: homelab contra datacenter
CapaEn casaEn un datacenter
Códigomain.py, igual en los dos
ContenedorLa misma imagen de Docker, igual en los dos
Sistema operativoUbuntu 26.04 LTSUbuntu 26.04 LTS, casi siempre en una VM
ServidorMini PC N100: una fuente de poder, RAM sin ECCServidor de rack: dos fuentes de poder, RAM ECC y una tarjeta de administración remota (BMC)
RedEl módem del proveedor y una IP que cambia, o ni siquiera propia (CGNAT)Varios proveedores, IPs propias, BGP, switches y firewalls en pares
Energía y climaUn contacto de pared, con suerte un no-break, y el aire del cuartoDos alimentaciones (A y B), UPS, plantas de emergencia y aire de precisión
Edificio y seguridadLa puerta de tu casaControl de acceso, biometría, cámaras y guardia 24/7

En casa, si se va la luz, se cae tu “Hola mundo”. En un datacenter, si se va la luz, entran las baterías y después las plantas de emergencia, y nadie se entera. Si se cae un proveedor de internet, el tráfico se va por otro. Si falla una fuente de poder, el servidor sigue con la otra.

Casi nada de eso hace más rápido el viaje. Lo que compras en un datacenter no es velocidad, es que el viaje siga funcionando cuando algo falla.

Lo que viene

Esto fue el “Hola mundo” de fierrosybits. En la serie Del comando al fierro hacemos lo mismo con algo más grande: crear una máquina virtual, desde cómo se hacía antes hasta una nube privada.