44  33. Necesitamos servir esto a usuarios reales

uvicorn –reload es para tu máquina

Parte 8 — Deployment
NotaEn una frase

npm run dev, uvicorn --reload, go run — todo eso es para tu máquina: un proceso, sin HTTPS, que muere y nadie lo revive. Producción es otra arquitectura: workers para usar todos los núcleos, un reverse proxy que termina TLS, y health checks que le dicen al orquestador si estás vivo — y en Go, la mitad de eso ya viene en el binario.

44.1 El problema

La imagen de prod corre… y un solo proceso de Node/Python usa un solo núcleo de los 8 del servidor — estás pagando 8 y usando 1. Encima: ¿quién termina el HTTPS? ¿quién te revive si el proceso crashea? ¿cómo sabe el balanceador que el contenedor nuevo ya acepta tráfico? Servir a usuarios reales es una topología, no un comando.

HTTPS = HTTP viajando cifrado por TLS: nadie en el camino puede leer ni alterar el tráfico. El certificado es la credencial que prueba que el servidor es quien dice — lo emite una autoridad y hay que renovarlo.

Un proceso es un programa en ejecución: el sistema operativo le da memoria propia y un lugar en la fila del CPU. Tu API es un proceso; Postgres es otro; el navegador es varios. Que “se caiga el proceso” = el programa murió.

El CPU es el cerebro que ejecuta instrucciones; cada núcleo puede ejecutar una cosa a la vez (por eso importan la concurrencia y los procesos paralelos). “CPU-bound” = el límite es el cálculo, no la espera.

44.2 Cómo lo resuelve un equipo

Un equipo serio pone tres piezas delante de tu código:

  1. Workers/procesos: N procesos detrás del mismo puerto (o la plataforma escala N contenedores — misma idea) → los núcleos trabajan todos.
  2. Reverse proxy (nginx/Traefik/el balanceador de la nube): termina TLS (el candado), sirve estáticos si los hay, y reenvía a tu app — que solo habla HTTP interno.
  3. Health checks: el orquestador pregunta /health cada X segundos — si no respondes, te saca del tráfico y te reinicia.

Un puerto es una puerta numerada dentro de una computadora: la IP llega al edificio, el puerto al departamento. :3000 = “la app que escucha en el departamento 3000”. Postgres suele usar 5432, HTTP el 80, HTTPS el 443.

44.3 Conceptos nuevos

  • U Workers y procesos: por qué un proceso no usa tus 8 núcleos (GIL/single-thread, mencionado honesto)
  • TS PM2/cluster · Py gunicorn+uvicorn workers · Go un solo binario que ya usa todos los núcleos
  • Ops Reverse proxy y HTTPS: nginx/Traefik delante — TLS, X-Forwarded-*, COOKIE_SECURE
  • Ops Health checks reales: /health para el orquestador vs /health/deep para ti

44.4 La explicación visual

flowchart LR
  U[Usuarios HTTPS] --> LB[Reverse proxy<br/>TLS, X-Forwarded-For]
  LB --> W1[worker 1]
  LB --> W2[worker 2]
  LB --> W3[worker N...]
  LB -->|GET /health cada 10s| W1
  W1 & W2 & W3 --> DB[(Postgres)]

El proxy es la única cara pública: habla HTTPS afuera, HTTP adentro, y distribuye entre workers. Tu app nunca ve TLS — pero sí necesita saber que el request original era HTTPS (por eso X-Forwarded-*).

44.5 Implementación

Cómo cada stack usa todos los núcleos:

# PM2: process manager — N processes, restart on crash, logs
pm2 start dist/index.js -i max --name taskflow   # one process per core
pm2 save && pm2 startup                          # survive reboots
# nginx in front: TLS + proxy to the node processes
server {
  listen 443 ssl;
  server_name api.taskflow.dev;
  ssl_certificate     /etc/letsencrypt/live/api.taskflow.dev/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.taskflow.dev/privkey.pem;
  location / {
    proxy_pass http://localhost:3000;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto https;   # the app reads this for secure cookies
  }
}
# gunicorn manages N uvicorn workers — each is an event loop on its own process
gunicorn app.main:app -k uvicorn.workers.UvicornWorker \
  --workers 4 --bind 0.0.0.0:8000
# rule of thumb: workers ≈ (2 × cores) + 1 for I/O-bound APIs
Same nginx config — proxy_pass http://localhost:8000
Why workers: the GIL means ONE Python process uses ONE core;
N processes = N cores (cap. 20's honest concurrency table)
// nothing extra: ONE Go binary already uses all cores —
// the runtime schedules goroutines across OS threads (GOMAXPROCS = cores)
func main() {
    srv := &http.Server{
        Addr: ":8080", Handler: router,
        ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second,
        IdleTimeout: 60 * time.Second,          // production timeouts
    }
    log.Fatal(srv.ListenAndServe())              // that's the whole prod server
}
Same nginx in front if you want TLS/static — or terminate TLS at
the cloud load balancer and run the binary behind it.
Prepara mi API [Express/FastAPI/Go] para servir usuarios reales detrás
de nginx. Requisitos: N workers/procesos usando todos los núcleos
[PM2 -i max / gunicorn+uvicorn workers / nada extra en Go — explícame
por qué], config de nginx que termine TLS y haga proxy_pass con
X-Forwarded-For y X-Forwarded-Proto, la app leyendo el proto original
para cookies Secure, timeouts de servidor configurados, y /health
ligero para el balanceador + /health/deep con check de BD para
diagnóstico.

44.6 ¿Por qué cada stack lo hace así?

Lo único que necesitas llevarte: Node y Python escalan a núcleos multiplicando procesos (un proceso = un núcleo útil); Go ya paraleliza dentro de un proceso — por eso “un binario” es una respuesta completa, no una simplificación.

Stack Para usar 8 núcleos Por qué
Node pm2 -i 8 o 8 contenedores Un proceso = un event loop = un hilo
Python gunicorn --workers N El GIL: un proceso ejecuta un hilo de Python a la vez — N procesos = N núcleos
Go ./server — ya está Las goroutines se reparten en threads OS sobre todos los núcleos

En la nube esto se simplifica: el orquestador (ECS, Cloud Run, App Service) escala contenedores, no workers — corres 1 proceso por contenedor y escala horizontal. Los workers importan cuando tú operas la máquina (VPS, cap. nu-01) — y para entender qué estás pagando.

X-Forwarded-Proto y las cookies: tras el proxy, tu app ve HTTP — sin ese header, “¿el request original era HTTPS?” es imposible de saber y las cookies Secure se rompen o se desactivan. El proxy lo declara; la app lo confía solo desde el proxy (trust proxy en Express, el middleware correspondiente en los demás).

44.7 Errores comunes

Error Por qué pasa Fix
uvicorn --reload en prod “Es como lo corro en dev” Reload watches files + un proceso — prod es gunicorn/PM2/orquestador
Un proceso en máquina de 8 núcleos El default Workers × núcleos — o contenedores × réplicas
La app habla TLS ella misma “Pongo el cert en Express” TLS en el proxy — la app ve HTTP interno + X-Forwarded-Proto
/health que toca la BD “Chequeo todo” El balanceador pega cada 10s × N instancias — /health barato, /health/deep aparte
Confiar X-Forwarded-* de cualquiera trust proxy global Solo confiar cuando viene del proxy — el cliente puede falsificarlo directo

44.8 Buenas prácticas

  • Un proceso por contenedor en orquestadores: la plataforma escala réplicas; meter PM2 dentro del contenedor es orquestar dentro de la orquestación — señales y healthchecks se confunden.
  • Timeouts en el servidor (Read/Write/Idle): el servidor sin timeouts guarda conexiones muertas hasta morir él.
  • Graceful + healthchecks en el deploy: el orquestador marca “unhealthy” → te reinicia; marca “starting” → no te manda tráfico aún. Tu /health es el contrato con la plataforma.
  • El proxy también cachea/comprime: gzip y estáticos en nginx — tu app no paga esos ciclos.

EXTRA Los roadmaps piden conocer servidores web — el trio real:

  • Nginx — el reverse proxy estándar: sirve estáticos rapidísimo y reparte a tu app
  • Apache — el veterano; .htaccess y hosting clásico
  • Reverse proxy — el rol, no un producto: terminar TLS, servir estáticos, balancear; Nginx/Caddy/Traefik lo juegan

Tu app (Node/Python/Go) nunca mira internet directamente — el proxy es su fachada.

44.9 Ejercicio

  1. Tienes un VPS de 8 núcleos con FastAPI. ¿Cuántos procesos corren tu código en un setup serio y qué los levanta?
  2. ¿Por qué /health NO debe verificar la BD, si “salud real” suena mejor?
  3. Tras el proxy, Express ve req.secure === false aunque el usuario entró por HTTPS. ¿Qué falta y dónde?

Una base de datos es el programa que guarda datos de forma permanente y los responde rápido. Relacional (Postgres): tablas con relaciones. No relacional: documentos, clave-valor, grafos — cada una para una forma de dato distinta.

44.10 Mini reto

Explicar el camino completo de un request desde el navegador hasta tu handler en prod.

Ejercicio.

  1. ~(2×8)+1 ≈ 17 es la regla teórica para I/O-bound; en la práctica 4-8 workers + autoscale según medición — los levanta gunicorn -k uvicorn.workers.UvicornWorker. Si el orquestador es de contenedores: 1 proceso por contenedor × N réplicas.
  2. El balanceador pega cada ~10s a todas las instancias — si /health toca la BD, multiplicas carga y, peor: un flap de BD marca TODAS las instancias unhealthy y el orquestador las tumba a la vez — conviertes un problema de BD en una caída total. /health = “el proceso sirve”; /health/deep = diagnóstico para ti.
  3. app.set("trust proxy", ...) + el proxy enviando X-Forwarded-Proto: https. Sin lo primero, Express ignora el header (correcto — no confiar en headers del cliente); sin lo segundo, la cookie Secure nunca se setea.

Mini reto. DNS → IP del balanceador/proxy → handshake TLS (el candado) → nginx/balanceador termina TLS → proxy_pass a un worker sano (preguntó /health recién) → tu router → middleware (request_id, auth, rate limit) → handler → servicio → pool → Postgres. Nueve saltos, y cada uno es un capítulo del libro — por eso la cadena es repasable.

44.11 Vocabulario técnico del capítulo

Una función es una receta reutilizable: recibe ingredientes (parámetros), hace pasos y devuelve un plato (return). La escribes una vez y la llamas mil veces: add(2, 3) → 5.

Un bucle repite una acción por cada elemento o hasta cumplir una condición: for task in tasks hace algo con cada tarea. map y filter son bucles disfrazados de funciones — transforman/filtran colecciones sin for explícito.

Una clase es el molde de un objeto: define qué datos y qué métodos tiene. class Task es el molde; new Task() es una instancia concreta. Go no tiene clases — usa struct + métodos sueltos.

Una goroutine es el hilo ultraligero de Go: go f() lanza una función en paralelo gastando casi nada — se pueden tener millones. Es la superpotencia de Go: concurrencia como palabra del lenguaje.

Un log es el diario del programa: qué pasó, cuándo, con qué request. Estructurado = en JSON con campos (request_id, user_id), para filtrar por máquina y no con los ojos.

Un deploy es poner tu código a correr en el servidor: copiar la imagen, iniciar el proceso, verificar que responde. El objetivo es que sea aburrido — automático, repetible, con rollback.

Un servidor es una computadora que espera peticiones y las responde 24/7. Físicamente no es nada mágico: es una máquina (a veces una VM alquilada) corriendo tu programa, con la diferencia de que está siempre encendida y conectada.

El DNS es la agenda telefónica de internet: traduce nexus.com → 203.0.113.10. Tu dominio apunta vía DNS a tu load balancer o servidor — por eso “propagación de DNS” toma minutos.

Una IP es la dirección numérica de una máquina en la red: 203.0.113.10. Los servidores tienen IP pública; dentro de una VPC tienen IPs privadas que internet no ve.

El runtime es el motor que ejecuta tu código: Node.js es el runtime de JavaScript fuera del navegador; CPython es el de Python; Go compila a binario y el runtime va empaquetado dentro. Es “quien corre” lo que escribiste.

localhost significa “esta misma máquina” — es la dirección que tu computadora usa para hablarse a sí misma. Cuando desarrollas, el “servidor” y el “cliente” viven en tu laptop: por eso todo es localhost:3000.

Un pool es el staff de conexiones a la BD: abrir una conexión por request es caro, así que el pool mantiene ~10–20 abiertas y las presta. El request la usa, la devuelve, y la siguiente la reutiliza.

La autenticación responde “¿quién eres?” — login, contraseña, token. Se confunde con autorización (“¿qué puedes hacer?”), que es la pregunta siguiente. Primero te identificas, luego te dejan o no pasar.

Una cookie es un dato que el servidor pone en tu navegador y el navegador devuelve en cada request. httpOnly = JavaScript no puede leerla (el XSS no la roba); Secure = solo viaja por HTTPS.

Un middleware es un filtro en la cadena del request: pasa por él antes de llegar a tu handler. Auth es middleware — “verifica el token” vive una vez y protege todas las rutas, no se copia en cada una.

44.12 Lo que deberías saber hacer ahora