[{"data":1,"prerenderedAt":12756},["ShallowReactive",2],{"home-recent-posts-es":3},[4,3396,4412,5380,6398,7514,8777,11790],{"id":5,"title":6,"author":7,"body":8,"category":3379,"cover":3380,"date":3381,"description":3382,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":3385,"navigation":411,"path":3386,"readingTime":3387,"seo":3388,"sitemap":3389,"stem":3390,"tags":3391,"__hash__":3395},"blog_es\u002Fes\u002Fblog\u002Fdeploy-cero-downtime-sin-kubernetes.md","Deploy cero-downtime sin Kubernetes: tutorial práctico en 2026","Equipo HeroCtl",{"type":9,"value":10,"toc":3354},"minimark",[11,15,18,23,39,42,56,59,63,66,88,91,95,102,105,108,111,115,118,205,208,211,213,217,220,223,275,286,289,314,317,339,346,350,357,367,372,916,920,1000,1004,1080,1094,1097,1101,1104,1140,1143,1208,1211,1236,1239,1243,1246,1249,1252,1266,1273,1363,1369,1372,1434,1440,1457,1460,1464,1467,2346,2357,2383,2386,2444,2447,2451,2454,2457,2506,2509,2520,2525,2533,2536,2538,2542,2545,2607,2611,2618,2624,2638,2710,2713,2727,2731,2734,2755,2758,2762,2765,2783,2786,2790,2793,2941,2944,2947,2968,2971,2975,3168,3171,3175,3223,3227,3232,3239,3244,3247,3252,3255,3260,3266,3271,3274,3279,3285,3290,3293,3298,3305,3307,3311,3314,3317,3333,3347,3350],[12,13,14],"p",{},"Hay un mito persistente de que el deploy cero-downtime es exclusividad de quien ejecutó Kubernetes en producción. No lo es. La técnica existe desde antes de que el coloso tuviera nombre — cualquier equipo que ejecutó un par de servidores físicos detrás de un balanceador en la década pasada ya hacía esto, con scripts de cincuenta líneas y ningún CRD en la vida. Lo que cambió fue el marketing alrededor de la práctica, no la práctica en sí.",[12,16,17],{},"Este post es un tutorial paso a paso para montar deploy sin downtime desde cero, en dos máquinas Linux, sin orquestador pesado, sin panel mágico. Al final vas a tener un script de bash que cambia una instancia por vez, espera a que la nueva esté saludable, y rota a la siguiente — exactamente el algoritmo que los orquestadores grandes implementan, solo que sin el boilerplate.",[19,20,22],"h2",{"id":21},"tldr","TL;DR",[12,24,25,26,30,31,34,35,38],{},"El deploy cero-downtime depende de tres ingredientes, no de una herramienta específica. Primero: ",[27,28,29],"strong",{},"dos o más instancias de la aplicación corriendo en paralelo",", detrás de un proxy básico. Segundo: ",[27,32,33],{},"un endpoint de health check confiable"," que valida dependencias reales (base, cache, cola), no solo responde 200 instantáneamente. Tercero: ",[27,36,37],{},"un script u orquestador que sustituya un contenedor por vez",", esperando a que el nuevo esté saludable antes de proseguir al siguiente.",[12,40,41],{},"Este tutorial monta el setup completo en dos VPS Linux con Docker, Caddy por delante como proxy + balanceador, y un script bash de cincuenta líneas que hace rolling update con health check activo, tiempo mínimo saludable, y rollback automático si falla. Resultado: deploy sin 5xx visible para el usuario, en menos de un minuto, sin ventana de mantenimiento.",[12,43,44,47,48,51,52,55],{},[27,45,46],{},"Prerequisitos:"," dos VPS Linux con Docker (Hetzner CPX11 a 5 € cada una), dominio con DNS controlable, app con health check decente. ",[27,49,50],{},"Tiempo de setup:"," dos a tres horas. ",[27,53,54],{},"Coste mensual:"," 10 € (12 € si quieres una tercera VPS dedicada al proxy). Al final mostramos la versión \"robusta\" vía HeroCtl para quien quiere parar de scriptear.",[57,58],"hr",{},[19,60,62],{"id":61},"los-tres-ingredientes-sin-esto-no-es-cero-downtime","Los tres ingredientes (sin esto, no es cero-downtime)",[12,64,65],{},"Antes de cualquier comando, vale la pena fijar la teoría — porque toda configuración más elaborada que vas a ver en internet es variación de estas tres piezas.",[67,68,69,76,82],"ol",{},[70,71,72,75],"li",{},[27,73,74],{},"Múltiples instancias de la app corriendo en paralelo."," Mínimo dos. Si solo tienes una, cualquier reinicio es ventana de error. No hay forma de evitar esto con truco de configuración.",[70,77,78,81],{},[27,79,80],{},"Un proxy\u002Fbalanceador por delante, haciendo health check."," El proxy decide a qué instancia mandar tráfico. Si una se cae (o fue retirada deliberadamente para el deploy), el proxy solo manda a las restantes.",[70,83,84,87],{},[27,85,86],{},"Un script que cambia instancias una por vez."," Nunca todas juntas. Espera a que la nueva esté saludable antes de tocar la siguiente. Si la nueva falla, para el deploy y mantiene las antiguas sirviendo.",[12,89,90],{},"Es solo eso. El resto — Kubernetes, paneles modernos, orquestadores ligeros — es embalaje alrededor de esos tres puntos.",[19,92,94],{"id":93},"por-que-single-server-nunca-es-cero-downtime-incluso-si-es-rapido","Por qué single-server NUNCA es cero-downtime (incluso si es rápido)",[12,96,97,98,101],{},"Veo esta pregunta toda semana en el Discord de la comunidad: \"¿puedo lograr cero-downtime con un solo servidor, si el deploy es lo suficientemente rápido?\". Respuesta corta: ",[27,99,100],{},"no",".",[12,103,104],{},"En una máquina única, el ciclo de deploy es: para el contenedor antiguo, sube el nuevo. Aunque todo suceda en tres segundos, esos tres segundos existen. Conexiones TCP en curso son cortadas. Peticiones que llegan en ese intervalo reciben connection refused o 502. Si tienes cinco requests por segundo, son quince usuarios viendo error en cada deploy.",[12,106,107],{},"Hay variación astuta — subir el nuevo en un puerto diferente, cambiar el proxy local, tirar el antiguo. Eso mejora, pero no elimina. Si la app tarda en cerrar conexiones en curso, el cutover aún genera errores. Si el health check es débil, el proxy apunta tráfico a app que aún no terminó de subir. Siempre hay una ventana.",[12,109,110],{},"La única forma confiable de eliminar la ventana es tener al menos una instancia siempre disponible durante todo el deploy. Eso exige dos máquinas. Punto.",[19,112,114],{"id":113},"el-setup-minimo-dos-vps-un-proxy","El setup mínimo (dos VPS + un proxy)",[12,116,117],{},"La topología más barata que entrega cero-downtime real:",[119,120,121,140],"table",{},[122,123,124],"thead",{},[125,126,127,131,134,137],"tr",{},[128,129,130],"th",{},"Componente",[128,132,133],{},"Tamaño",[128,135,136],{},"Coste",[128,138,139],{},"Función",[141,142,143,158,170,189],"tbody",{},[125,144,145,149,152,155],{},[146,147,148],"td",{},"VPS A",[146,150,151],{},"2 vCPU \u002F 2 GB RAM",[146,153,154],{},"5 €\u002Fmes",[146,156,157],{},"App instancia 1",[125,159,160,163,165,167],{},[146,161,162],{},"VPS B",[146,164,151],{},[146,166,154],{},[146,168,169],{},"App instancia 2",[125,171,172,175,183,186],{},[146,173,174],{},"Proxy",[146,176,177,178,182],{},"corriendo en VPS A ",[179,180,181],"em",{},"o"," tercera VPS",[146,184,185],{},"0 € (compartido) o 3 €\u002Fmes",[146,187,188],{},"Caddy\u002Fnginx haciendo balance",[125,190,191,194,199,202],{},[146,192,193],{},"Base",[146,195,196,197,182],{},"Postgres gestionado ",[179,198,181],{},[146,200,201],{},"varía",[146,203,204],{},"Estado compartido entre A y B",[12,206,207],{},"Tener el proxy compartido en una de las propias VPS economiza, pero tiene trade-off: si la VPS que hospeda el proxy se cae entera, el sitio se cae con ella (incluso con la otra VPS corriendo). Para equipo pequeño eso es aceptable. Cuando crezca, el proxy migra a VPS dedicada o se vuelve par redundante.",[12,209,210],{},"DNS A record de tu dominio apunta al IP del proxy. Apps en A y B se conectan a la misma base — sin esa parte compartida, las dos instancias divergen y el usuario ve resultado diferente dependiendo de cuál respondió.",[57,212],{},[19,214,216],{"id":215},"paso-1-provisionar-dos-vps-15-min","Paso 1 — Provisionar dos VPS (15 min)",[12,218,219],{},"Yo uso Hetzner CPX11 (€4,75) como referencia. DigitalOcean Droplet de US$6, Vultr Cloud Compute de US$6 o Linode Nanode de US$5 entregan algo parecido. Lo importante es Linux moderno (Ubuntu 24.04 LTS o Debian 12) con Docker.",[12,221,222],{},"Provisiona las dos máquinas con la misma clave SSH:",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-bash shiki shiki-themes github-dark-default","# desde tu laptop\nssh-keygen -t ed25519 -f ~\u002F.ssh\u002Fdeploy_key -C \"deploy@meudominio.com\"\n# añade ~\u002F.ssh\u002Fdeploy_key.pub en la consola del proveedor antes de crear la VPS\n","bash","",[231,232,233,242,269],"code",{"__ignoreMap":229},[234,235,238],"span",{"class":236,"line":237},"line",1,[234,239,241],{"class":240},"sH3jZ","# desde tu laptop\n",[234,243,245,249,253,257,260,263,266],{"class":236,"line":244},2,[234,246,248],{"class":247},"sQhOw","ssh-keygen",[234,250,252],{"class":251},"sFSAA"," -t",[234,254,256],{"class":255},"s9uIt"," ed25519",[234,258,259],{"class":251}," -f",[234,261,262],{"class":255}," ~\u002F.ssh\u002Fdeploy_key",[234,264,265],{"class":251}," -C",[234,267,268],{"class":255}," \"deploy@meudominio.com\"\n",[234,270,272],{"class":236,"line":271},3,[234,273,274],{"class":240},"# añade ~\u002F.ssh\u002Fdeploy_key.pub en la consola del proveedor antes de crear la VPS\n",[12,276,277,278,281,282,285],{},"Crea cada VPS, anota los IPs. Voy a usar ",[231,279,280],{},"203.0.113.10"," (VPS A) y ",[231,283,284],{},"203.0.113.20"," (VPS B) como placeholders en el resto del post.",[12,287,288],{},"Instala Docker en cada una:",[224,290,292],{"className":226,"code":291,"language":228,"meta":229,"style":229},"ssh root@203.0.113.10 \"curl -fsSL https:\u002F\u002Fget.docker.com | sh\"\nssh root@203.0.113.20 \"curl -fsSL https:\u002F\u002Fget.docker.com | sh\"\n",[231,293,294,305],{"__ignoreMap":229},[234,295,296,299,302],{"class":236,"line":237},[234,297,298],{"class":247},"ssh",[234,300,301],{"class":255}," root@203.0.113.10",[234,303,304],{"class":255}," \"curl -fsSL https:\u002F\u002Fget.docker.com | sh\"\n",[234,306,307,309,312],{"class":236,"line":244},[234,308,298],{"class":247},[234,310,311],{"class":255}," root@203.0.113.20",[234,313,304],{"class":255},[12,315,316],{},"Configura firewall para permitir solo 22 (SSH) y 8080 (puerto interno donde la app va a escuchar). Tráfico HTTP\u002FHTTPS llega solo al proxy:",[224,318,320],{"className":226,"code":319,"language":228,"meta":229,"style":229},"ssh root@203.0.113.10 \"ufw allow 22 && ufw allow 8080\u002Ftcp && ufw --force enable\"\nssh root@203.0.113.20 \"ufw allow 22 && ufw allow 8080\u002Ftcp && ufw --force enable\"\n",[231,321,322,331],{"__ignoreMap":229},[234,323,324,326,328],{"class":236,"line":237},[234,325,298],{"class":247},[234,327,301],{"class":255},[234,329,330],{"class":255}," \"ufw allow 22 && ufw allow 8080\u002Ftcp && ufw --force enable\"\n",[234,332,333,335,337],{"class":236,"line":244},[234,334,298],{"class":247},[234,336,311],{"class":255},[234,338,330],{"class":255},[12,340,341,342,345],{},"Validación: ",[231,343,344],{},"docker run --rm hello-world"," en cada máquina debe completar sin error.",[19,347,349],{"id":348},"paso-2-app-con-health-check-decente-30-min","Paso 2 — App con health check decente (30 min)",[12,351,352,353,356],{},"El endpoint ",[231,354,355],{},"\u002Fhealthz"," es el corazón del esquema. Si retorna 200 cuando la app no está realmente lista, el proxy manda tráfico a la instancia rota y el usuario ve error. Si retorna 500 cuando la app está saludable, el proxy retira la instancia buena del balanceo. O sea: el health check es la fuente de verdad del sistema entero.",[12,358,359,360,362,363,366],{},"Regla de oro: el ",[231,361,355],{}," valida ",[27,364,365],{},"dependencias reales que la app necesita para responder",". Mínimo: conexión con la base. Si tienes cache (Redis), incluye. Si tienes cola (SQS, RabbitMQ), incluye. NO retornes 200 nada más arrancar — espera a que assets compilen, cache caliente, conexiones abran.",[368,369,371],"h3",{"id":370},"nodejs-express","Node.js (Express)",[224,373,377],{"className":374,"code":375,"language":376,"meta":229,"style":229},"language-js shiki shiki-themes github-dark-default","import express from \"express\"\nimport { Pool } from \"pg\"\n\nconst app = express()\nconst pool = new Pool({ connectionString: process.env.DATABASE_URL })\n\nlet ready = false\n\n\u002F\u002F warm-up assíncrono — só fica ready quando dependencies validam\n;(async () => {\n  await pool.query(\"SELECT 1\")\n  \u002F\u002F outras inicializações: cache prime, etc.\n  ready = true\n})()\n\napp.get(\"\u002Fhealthz\", async (_req, res) => {\n  if (!ready) return res.status(503).send(\"warming up\")\n  try {\n    await pool.query(\"SELECT 1\")\n    res.status(200).send(\"ok\")\n  } catch (e) {\n    res.status(503).send(\"db down\")\n  }\n})\n\napp.get(\"\u002F\", (_req, res) => res.send(\"Hello v1\"))\n\nconst server = app.listen(8080, () => console.log(\"listening 8080\"))\n\n\u002F\u002F graceful shutdown — drena conexões antes de morrer\nprocess.on(\"SIGTERM\", () => {\n  ready = false  \u002F\u002F health check passa a falhar imediatamente\n  setTimeout(() => {\n    server.close(() => process.exit(0))\n  }, 5000)  \u002F\u002F 5s pro proxy notar e parar de mandar tráfego novo\n})\n","js",[231,378,379,395,407,413,432,457,462,477,482,488,506,527,533,544,550,555,592,633,641,657,681,693,715,721,727,732,769,774,813,818,824,844,857,870,896,911],{"__ignoreMap":229},[234,380,381,385,389,392],{"class":236,"line":237},[234,382,384],{"class":383},"suJrU","import",[234,386,388],{"class":387},"sZEs4"," express ",[234,390,391],{"class":383},"from",[234,393,394],{"class":255}," \"express\"\n",[234,396,397,399,402,404],{"class":236,"line":244},[234,398,384],{"class":383},[234,400,401],{"class":387}," { Pool } ",[234,403,391],{"class":383},[234,405,406],{"class":255}," \"pg\"\n",[234,408,409],{"class":236,"line":271},[234,410,412],{"emptyLinePlaceholder":411},true,"\n",[234,414,416,419,422,425,429],{"class":236,"line":415},4,[234,417,418],{"class":383},"const",[234,420,421],{"class":251}," app",[234,423,424],{"class":383}," =",[234,426,428],{"class":427},"sc3cj"," express",[234,430,431],{"class":387},"()\n",[234,433,435,437,440,442,445,448,451,454],{"class":236,"line":434},5,[234,436,418],{"class":383},[234,438,439],{"class":251}," pool",[234,441,424],{"class":383},[234,443,444],{"class":383}," new",[234,446,447],{"class":427}," Pool",[234,449,450],{"class":387},"({ connectionString: process.env.",[234,452,453],{"class":251},"DATABASE_URL",[234,455,456],{"class":387}," })\n",[234,458,460],{"class":236,"line":459},6,[234,461,412],{"emptyLinePlaceholder":411},[234,463,465,468,471,474],{"class":236,"line":464},7,[234,466,467],{"class":383},"let",[234,469,470],{"class":387}," ready ",[234,472,473],{"class":383},"=",[234,475,476],{"class":251}," false\n",[234,478,480],{"class":236,"line":479},8,[234,481,412],{"emptyLinePlaceholder":411},[234,483,485],{"class":236,"line":484},9,[234,486,487],{"class":240},"\u002F\u002F warm-up assíncrono — só fica ready quando dependencies validam\n",[234,489,491,494,497,500,503],{"class":236,"line":490},10,[234,492,493],{"class":387},";(",[234,495,496],{"class":383},"async",[234,498,499],{"class":387}," () ",[234,501,502],{"class":383},"=>",[234,504,505],{"class":387}," {\n",[234,507,509,512,515,518,521,524],{"class":236,"line":508},11,[234,510,511],{"class":383},"  await",[234,513,514],{"class":387}," pool.",[234,516,517],{"class":427},"query",[234,519,520],{"class":387},"(",[234,522,523],{"class":255},"\"SELECT 1\"",[234,525,526],{"class":387},")\n",[234,528,530],{"class":236,"line":529},12,[234,531,532],{"class":240},"  \u002F\u002F outras inicializações: cache prime, etc.\n",[234,534,536,539,541],{"class":236,"line":535},13,[234,537,538],{"class":387},"  ready ",[234,540,473],{"class":383},[234,542,543],{"class":251}," true\n",[234,545,547],{"class":236,"line":546},14,[234,548,549],{"class":387},"})()\n",[234,551,553],{"class":236,"line":552},15,[234,554,412],{"emptyLinePlaceholder":411},[234,556,558,561,564,566,569,572,574,577,580,582,585,588,590],{"class":236,"line":557},16,[234,559,560],{"class":387},"app.",[234,562,563],{"class":427},"get",[234,565,520],{"class":387},[234,567,568],{"class":255},"\"\u002Fhealthz\"",[234,570,571],{"class":387},", ",[234,573,496],{"class":383},[234,575,576],{"class":387}," (",[234,578,579],{"class":247},"_req",[234,581,571],{"class":387},[234,583,584],{"class":247},"res",[234,586,587],{"class":387},") ",[234,589,502],{"class":383},[234,591,505],{"class":387},[234,593,595,598,600,603,606,609,612,615,617,620,623,626,628,631],{"class":236,"line":594},17,[234,596,597],{"class":383},"  if",[234,599,576],{"class":387},[234,601,602],{"class":383},"!",[234,604,605],{"class":387},"ready) ",[234,607,608],{"class":383},"return",[234,610,611],{"class":387}," res.",[234,613,614],{"class":427},"status",[234,616,520],{"class":387},[234,618,619],{"class":251},"503",[234,621,622],{"class":387},").",[234,624,625],{"class":427},"send",[234,627,520],{"class":387},[234,629,630],{"class":255},"\"warming up\"",[234,632,526],{"class":387},[234,634,636,639],{"class":236,"line":635},18,[234,637,638],{"class":383},"  try",[234,640,505],{"class":387},[234,642,644,647,649,651,653,655],{"class":236,"line":643},19,[234,645,646],{"class":383},"    await",[234,648,514],{"class":387},[234,650,517],{"class":427},[234,652,520],{"class":387},[234,654,523],{"class":255},[234,656,526],{"class":387},[234,658,660,663,665,667,670,672,674,676,679],{"class":236,"line":659},20,[234,661,662],{"class":387},"    res.",[234,664,614],{"class":427},[234,666,520],{"class":387},[234,668,669],{"class":251},"200",[234,671,622],{"class":387},[234,673,625],{"class":427},[234,675,520],{"class":387},[234,677,678],{"class":255},"\"ok\"",[234,680,526],{"class":387},[234,682,684,687,690],{"class":236,"line":683},21,[234,685,686],{"class":387},"  } ",[234,688,689],{"class":383},"catch",[234,691,692],{"class":387}," (e) {\n",[234,694,696,698,700,702,704,706,708,710,713],{"class":236,"line":695},22,[234,697,662],{"class":387},[234,699,614],{"class":427},[234,701,520],{"class":387},[234,703,619],{"class":251},[234,705,622],{"class":387},[234,707,625],{"class":427},[234,709,520],{"class":387},[234,711,712],{"class":255},"\"db down\"",[234,714,526],{"class":387},[234,716,718],{"class":236,"line":717},23,[234,719,720],{"class":387},"  }\n",[234,722,724],{"class":236,"line":723},24,[234,725,726],{"class":387},"})\n",[234,728,730],{"class":236,"line":729},25,[234,731,412],{"emptyLinePlaceholder":411},[234,733,735,737,739,741,744,747,749,751,753,755,757,759,761,763,766],{"class":236,"line":734},26,[234,736,560],{"class":387},[234,738,563],{"class":427},[234,740,520],{"class":387},[234,742,743],{"class":255},"\"\u002F\"",[234,745,746],{"class":387},", (",[234,748,579],{"class":247},[234,750,571],{"class":387},[234,752,584],{"class":247},[234,754,587],{"class":387},[234,756,502],{"class":383},[234,758,611],{"class":387},[234,760,625],{"class":427},[234,762,520],{"class":387},[234,764,765],{"class":255},"\"Hello v1\"",[234,767,768],{"class":387},"))\n",[234,770,772],{"class":236,"line":771},27,[234,773,412],{"emptyLinePlaceholder":411},[234,775,777,779,782,784,787,790,792,795,798,800,803,806,808,811],{"class":236,"line":776},28,[234,778,418],{"class":383},[234,780,781],{"class":251}," server",[234,783,424],{"class":383},[234,785,786],{"class":387}," app.",[234,788,789],{"class":427},"listen",[234,791,520],{"class":387},[234,793,794],{"class":251},"8080",[234,796,797],{"class":387},", () ",[234,799,502],{"class":383},[234,801,802],{"class":387}," console.",[234,804,805],{"class":427},"log",[234,807,520],{"class":387},[234,809,810],{"class":255},"\"listening 8080\"",[234,812,768],{"class":387},[234,814,816],{"class":236,"line":815},29,[234,817,412],{"emptyLinePlaceholder":411},[234,819,821],{"class":236,"line":820},30,[234,822,823],{"class":240},"\u002F\u002F graceful shutdown — drena conexões antes de morrer\n",[234,825,827,830,833,835,838,840,842],{"class":236,"line":826},31,[234,828,829],{"class":387},"process.",[234,831,832],{"class":427},"on",[234,834,520],{"class":387},[234,836,837],{"class":255},"\"SIGTERM\"",[234,839,797],{"class":387},[234,841,502],{"class":383},[234,843,505],{"class":387},[234,845,847,849,851,854],{"class":236,"line":846},32,[234,848,538],{"class":387},[234,850,473],{"class":383},[234,852,853],{"class":251}," false",[234,855,856],{"class":240},"  \u002F\u002F health check passa a falhar imediatamente\n",[234,858,860,863,866,868],{"class":236,"line":859},33,[234,861,862],{"class":427},"  setTimeout",[234,864,865],{"class":387},"(() ",[234,867,502],{"class":383},[234,869,505],{"class":387},[234,871,873,876,879,881,883,886,889,891,894],{"class":236,"line":872},34,[234,874,875],{"class":387},"    server.",[234,877,878],{"class":427},"close",[234,880,865],{"class":387},[234,882,502],{"class":383},[234,884,885],{"class":387}," process.",[234,887,888],{"class":427},"exit",[234,890,520],{"class":387},[234,892,893],{"class":251},"0",[234,895,768],{"class":387},[234,897,899,902,905,908],{"class":236,"line":898},35,[234,900,901],{"class":387},"  }, ",[234,903,904],{"class":251},"5000",[234,906,907],{"class":387},")  ",[234,909,910],{"class":240},"\u002F\u002F 5s pro proxy notar e parar de mandar tráfego novo\n",[234,912,914],{"class":236,"line":913},36,[234,915,726],{"class":387},[368,917,919],{"id":918},"python-django-gunicorn","Python (Django + gunicorn)",[224,921,925],{"className":922,"code":923,"language":924,"meta":229,"style":229},"language-python shiki shiki-themes github-dark-default","# health\u002Fviews.py\nfrom django.db import connection\nfrom django.http import JsonResponse, HttpResponse\nimport redis, os\n\n_r = redis.from_url(os.environ[\"REDIS_URL\"])\n\ndef healthz(request):\n    try:\n        with connection.cursor() as c:\n            c.execute(\"SELECT 1\")\n        _r.ping()\n        return HttpResponse(\"ok\", status=200)\n    except Exception as e:\n        return HttpResponse(f\"unhealthy: {e}\", status=503)\n","python",[231,926,927,932,937,942,947,951,956,960,965,970,975,980,985,990,995],{"__ignoreMap":229},[234,928,929],{"class":236,"line":237},[234,930,931],{},"# health\u002Fviews.py\n",[234,933,934],{"class":236,"line":244},[234,935,936],{},"from django.db import connection\n",[234,938,939],{"class":236,"line":271},[234,940,941],{},"from django.http import JsonResponse, HttpResponse\n",[234,943,944],{"class":236,"line":415},[234,945,946],{},"import redis, os\n",[234,948,949],{"class":236,"line":434},[234,950,412],{"emptyLinePlaceholder":411},[234,952,953],{"class":236,"line":459},[234,954,955],{},"_r = redis.from_url(os.environ[\"REDIS_URL\"])\n",[234,957,958],{"class":236,"line":464},[234,959,412],{"emptyLinePlaceholder":411},[234,961,962],{"class":236,"line":479},[234,963,964],{},"def healthz(request):\n",[234,966,967],{"class":236,"line":484},[234,968,969],{},"    try:\n",[234,971,972],{"class":236,"line":490},[234,973,974],{},"        with connection.cursor() as c:\n",[234,976,977],{"class":236,"line":508},[234,978,979],{},"            c.execute(\"SELECT 1\")\n",[234,981,982],{"class":236,"line":529},[234,983,984],{},"        _r.ping()\n",[234,986,987],{"class":236,"line":535},[234,988,989],{},"        return HttpResponse(\"ok\", status=200)\n",[234,991,992],{"class":236,"line":546},[234,993,994],{},"    except Exception as e:\n",[234,996,997],{"class":236,"line":552},[234,998,999],{},"        return HttpResponse(f\"unhealthy: {e}\", status=503)\n",[368,1001,1003],{"id":1002},"ruby-rails","Ruby (Rails)",[224,1005,1009],{"className":1006,"code":1007,"language":1008,"meta":229,"style":229},"language-ruby shiki shiki-themes github-dark-default","# config\u002Froutes.rb\nget \"\u002Fhealthz\", to: \"health#show\"\n\n# app\u002Fcontrollers\u002Fhealth_controller.rb\nclass HealthController \u003C ApplicationController\n  def show\n    ActiveRecord::Base.connection.execute(\"SELECT 1\")\n    Rails.cache.read(\"__healthcheck__\")\n    head :ok\n  rescue => e\n    Rails.logger.warn(\"healthcheck failed: #{e.message}\")\n    head :service_unavailable\n  end\nend\n","ruby",[231,1010,1011,1016,1021,1025,1030,1035,1040,1045,1050,1055,1060,1065,1070,1075],{"__ignoreMap":229},[234,1012,1013],{"class":236,"line":237},[234,1014,1015],{},"# config\u002Froutes.rb\n",[234,1017,1018],{"class":236,"line":244},[234,1019,1020],{},"get \"\u002Fhealthz\", to: \"health#show\"\n",[234,1022,1023],{"class":236,"line":271},[234,1024,412],{"emptyLinePlaceholder":411},[234,1026,1027],{"class":236,"line":415},[234,1028,1029],{},"# app\u002Fcontrollers\u002Fhealth_controller.rb\n",[234,1031,1032],{"class":236,"line":434},[234,1033,1034],{},"class HealthController \u003C ApplicationController\n",[234,1036,1037],{"class":236,"line":459},[234,1038,1039],{},"  def show\n",[234,1041,1042],{"class":236,"line":464},[234,1043,1044],{},"    ActiveRecord::Base.connection.execute(\"SELECT 1\")\n",[234,1046,1047],{"class":236,"line":479},[234,1048,1049],{},"    Rails.cache.read(\"__healthcheck__\")\n",[234,1051,1052],{"class":236,"line":484},[234,1053,1054],{},"    head :ok\n",[234,1056,1057],{"class":236,"line":490},[234,1058,1059],{},"  rescue => e\n",[234,1061,1062],{"class":236,"line":508},[234,1063,1064],{},"    Rails.logger.warn(\"healthcheck failed: #{e.message}\")\n",[234,1066,1067],{"class":236,"line":529},[234,1068,1069],{},"    head :service_unavailable\n",[234,1071,1072],{"class":236,"line":535},[234,1073,1074],{},"  end\n",[234,1076,1077],{"class":236,"line":546},[234,1078,1079],{},"end\n",[12,1081,1082,1083,1086,1087,1090,1091,1093],{},"El detalle que diferencia health check amateur de profesional es el ",[27,1084,1085],{},"graceful shutdown",": al recibir ",[231,1088,1089],{},"SIGTERM",", la app pasa a retornar 503 en ",[231,1092,355],{}," inmediatamente, pero sigue aceptando conexiones en curso por algunos segundos más. El proxy nota el 503, deja de mandar tráfico nuevo, y cuando la app finalmente cierra ya no hay nadie esperando respuesta.",[12,1095,1096],{},"Sin esto, el cutover siempre filtra algunos errores incluso con todo lo demás correcto.",[19,1098,1100],{"id":1099},"paso-3-subir-dos-instancias-docker-15-min","Paso 3 — Subir dos instancias Docker (15 min)",[12,1102,1103],{},"Haz build de tu app en imagen Docker. Para el tutorial voy a usar una imagen genérica que sustituyes:",[224,1105,1107],{"className":226,"code":1106,"language":228,"meta":229,"style":229},"# en tu laptop, push al registry (Docker Hub, ECR, GHCR)\ndocker build -t meuusuario\u002Fmyapp:v1 .\ndocker push meuusuario\u002Fmyapp:v1\n",[231,1108,1109,1114,1130],{"__ignoreMap":229},[234,1110,1111],{"class":236,"line":237},[234,1112,1113],{"class":240},"# en tu laptop, push al registry (Docker Hub, ECR, GHCR)\n",[234,1115,1116,1119,1122,1124,1127],{"class":236,"line":244},[234,1117,1118],{"class":247},"docker",[234,1120,1121],{"class":255}," build",[234,1123,252],{"class":251},[234,1125,1126],{"class":255}," meuusuario\u002Fmyapp:v1",[234,1128,1129],{"class":255}," .\n",[234,1131,1132,1134,1137],{"class":236,"line":271},[234,1133,1118],{"class":247},[234,1135,1136],{"class":255}," push",[234,1138,1139],{"class":255}," meuusuario\u002Fmyapp:v1\n",[12,1141,1142],{},"Sube instancia en VPS A:",[224,1144,1146],{"className":226,"code":1145,"language":228,"meta":229,"style":229},"ssh root@203.0.113.10 \"\n  docker pull meuusuario\u002Fmyapp:v1 &&\n  docker run -d --name app --restart=unless-stopped \\\n    -p 8080:8080 \\\n    -e DATABASE_URL='postgres:\u002F\u002Fuser:pass@db.example.com:5432\u002Fapp' \\\n    --health-cmd='curl -f http:\u002F\u002Flocalhost:8080\u002Fhealthz || exit 1' \\\n    --health-interval=5s --health-timeout=2s --health-retries=3 \\\n    meuusuario\u002Fmyapp:v1\n\"\n",[231,1147,1148,1157,1162,1170,1177,1184,1191,1198,1203],{"__ignoreMap":229},[234,1149,1150,1152,1154],{"class":236,"line":237},[234,1151,298],{"class":247},[234,1153,301],{"class":255},[234,1155,1156],{"class":255}," \"\n",[234,1158,1159],{"class":236,"line":244},[234,1160,1161],{"class":255},"  docker pull meuusuario\u002Fmyapp:v1 &&\n",[234,1163,1164,1167],{"class":236,"line":271},[234,1165,1166],{"class":255},"  docker run -d --name app --restart=unless-stopped ",[234,1168,1169],{"class":383},"\\\n",[234,1171,1172,1175],{"class":236,"line":415},[234,1173,1174],{"class":255},"    -p 8080:8080 ",[234,1176,1169],{"class":383},[234,1178,1179,1182],{"class":236,"line":434},[234,1180,1181],{"class":255},"    -e DATABASE_URL='postgres:\u002F\u002Fuser:pass@db.example.com:5432\u002Fapp' ",[234,1183,1169],{"class":383},[234,1185,1186,1189],{"class":236,"line":459},[234,1187,1188],{"class":255},"    --health-cmd='curl -f http:\u002F\u002Flocalhost:8080\u002Fhealthz || exit 1' ",[234,1190,1169],{"class":383},[234,1192,1193,1196],{"class":236,"line":464},[234,1194,1195],{"class":255},"    --health-interval=5s --health-timeout=2s --health-retries=3 ",[234,1197,1169],{"class":383},[234,1199,1200],{"class":236,"line":479},[234,1201,1202],{"class":255},"    meuusuario\u002Fmyapp:v1\n",[234,1204,1205],{"class":236,"line":484},[234,1206,1207],{"class":255},"\"\n",[12,1209,1210],{},"Repite para VPS B cambiando el IP. Valida:",[224,1212,1214],{"className":226,"code":1213,"language":228,"meta":229,"style":229},"curl http:\u002F\u002F203.0.113.10:8080\u002Fhealthz   # debe retornar \"ok\"\ncurl http:\u002F\u002F203.0.113.20:8080\u002Fhealthz   # debe retornar \"ok\"\n",[231,1215,1216,1227],{"__ignoreMap":229},[234,1217,1218,1221,1224],{"class":236,"line":237},[234,1219,1220],{"class":247},"curl",[234,1222,1223],{"class":255}," http:\u002F\u002F203.0.113.10:8080\u002Fhealthz",[234,1225,1226],{"class":240},"   # debe retornar \"ok\"\n",[234,1228,1229,1231,1234],{"class":236,"line":244},[234,1230,1220],{"class":247},[234,1232,1233],{"class":255}," http:\u002F\u002F203.0.113.20:8080\u002Fhealthz",[234,1235,1226],{"class":240},[12,1237,1238],{},"Si las dos vuelven 200, la base está lista.",[19,1240,1242],{"id":1241},"paso-4-caddy-como-reverse-proxy-balanceador-30-min","Paso 4 — Caddy como reverse proxy + balanceador (30 min)",[12,1244,1245],{},"Caddy es más fácil de empezar que nginx por el TLS automático embebido — Let's Encrypt funciona out of the box, sin configurar bot externo. nginx es más flexible y tiene ecosistema mayor; Caddy es más simple para este caso. Para el tutorial voy con Caddy.",[12,1247,1248],{},"Voy a ejecutar Caddy en la VPS A, compartiendo la máquina con una de las instancias de la app. Si prefieres una tercera VPS dedicada, cambia el IP donde sea relevante.",[12,1250,1251],{},"Primero, libera puertos 80 y 443 en la VPS A:",[224,1253,1255],{"className":226,"code":1254,"language":228,"meta":229,"style":229},"ssh root@203.0.113.10 \"ufw allow 80 && ufw allow 443\"\n",[231,1256,1257],{"__ignoreMap":229},[234,1258,1259,1261,1263],{"class":236,"line":237},[234,1260,298],{"class":247},[234,1262,301],{"class":255},[234,1264,1265],{"class":255}," \"ufw allow 80 && ufw allow 443\"\n",[12,1267,1268,1269,1272],{},"Crea el ",[231,1270,1271],{},"Caddyfile",":",[224,1274,1278],{"className":1275,"code":1276,"language":1277,"meta":229,"style":229},"language-caddyfile shiki shiki-themes github-dark-default","meudominio.com {\n    reverse_proxy 203.0.113.10:8080 203.0.113.20:8080 {\n        lb_policy round_robin\n        health_uri \u002Fhealthz\n        health_interval 5s\n        health_timeout 2s\n        health_status 200\n\n        fail_duration 30s\n        max_fails 2\n        unhealthy_status 5xx\n\n        transport http {\n            dial_timeout 2s\n        }\n    }\n}\n","caddyfile",[231,1279,1280,1285,1290,1295,1300,1305,1310,1315,1319,1324,1329,1334,1338,1343,1348,1353,1358],{"__ignoreMap":229},[234,1281,1282],{"class":236,"line":237},[234,1283,1284],{},"meudominio.com {\n",[234,1286,1287],{"class":236,"line":244},[234,1288,1289],{},"    reverse_proxy 203.0.113.10:8080 203.0.113.20:8080 {\n",[234,1291,1292],{"class":236,"line":271},[234,1293,1294],{},"        lb_policy round_robin\n",[234,1296,1297],{"class":236,"line":415},[234,1298,1299],{},"        health_uri \u002Fhealthz\n",[234,1301,1302],{"class":236,"line":434},[234,1303,1304],{},"        health_interval 5s\n",[234,1306,1307],{"class":236,"line":459},[234,1308,1309],{},"        health_timeout 2s\n",[234,1311,1312],{"class":236,"line":464},[234,1313,1314],{},"        health_status 200\n",[234,1316,1317],{"class":236,"line":479},[234,1318,412],{"emptyLinePlaceholder":411},[234,1320,1321],{"class":236,"line":484},[234,1322,1323],{},"        fail_duration 30s\n",[234,1325,1326],{"class":236,"line":490},[234,1327,1328],{},"        max_fails 2\n",[234,1330,1331],{"class":236,"line":508},[234,1332,1333],{},"        unhealthy_status 5xx\n",[234,1335,1336],{"class":236,"line":529},[234,1337,412],{"emptyLinePlaceholder":411},[234,1339,1340],{"class":236,"line":535},[234,1341,1342],{},"        transport http {\n",[234,1344,1345],{"class":236,"line":546},[234,1346,1347],{},"            dial_timeout 2s\n",[234,1349,1350],{"class":236,"line":552},[234,1351,1352],{},"        }\n",[234,1354,1355],{"class":236,"line":557},[234,1356,1357],{},"    }\n",[234,1359,1360],{"class":236,"line":594},[234,1361,1362],{},"}\n",[12,1364,1365,1366,1368],{},"Quince líneas. Todo lo que importa está ahí: round-robin entre los dos IPs, health check activo cada cinco segundos en ",[231,1367,355],{},", marca como unhealthy después de dos fallos seguidos en 30s, timeout de dos segundos para abrir conexión.",[12,1370,1371],{},"Sube Caddy:",[224,1373,1375],{"className":226,"code":1374,"language":228,"meta":229,"style":229},"ssh root@203.0.113.10 \"\n  mkdir -p \u002Fetc\u002Fcaddy &&\n  docker run -d --name caddy --restart=unless-stopped \\\n    --network host \\\n    -v \u002Fetc\u002Fcaddy\u002FCaddyfile:\u002Fetc\u002Fcaddy\u002FCaddyfile \\\n    -v caddy_data:\u002Fdata \\\n    -v caddy_config:\u002Fconfig \\\n    caddy:2-alpine\n\"\n",[231,1376,1377,1385,1390,1397,1404,1411,1418,1425,1430],{"__ignoreMap":229},[234,1378,1379,1381,1383],{"class":236,"line":237},[234,1380,298],{"class":247},[234,1382,301],{"class":255},[234,1384,1156],{"class":255},[234,1386,1387],{"class":236,"line":244},[234,1388,1389],{"class":255},"  mkdir -p \u002Fetc\u002Fcaddy &&\n",[234,1391,1392,1395],{"class":236,"line":271},[234,1393,1394],{"class":255},"  docker run -d --name caddy --restart=unless-stopped ",[234,1396,1169],{"class":383},[234,1398,1399,1402],{"class":236,"line":415},[234,1400,1401],{"class":255},"    --network host ",[234,1403,1169],{"class":383},[234,1405,1406,1409],{"class":236,"line":434},[234,1407,1408],{"class":255},"    -v \u002Fetc\u002Fcaddy\u002FCaddyfile:\u002Fetc\u002Fcaddy\u002FCaddyfile ",[234,1410,1169],{"class":383},[234,1412,1413,1416],{"class":236,"line":459},[234,1414,1415],{"class":255},"    -v caddy_data:\u002Fdata ",[234,1417,1169],{"class":383},[234,1419,1420,1423],{"class":236,"line":464},[234,1421,1422],{"class":255},"    -v caddy_config:\u002Fconfig ",[234,1424,1169],{"class":383},[234,1426,1427],{"class":236,"line":479},[234,1428,1429],{"class":255},"    caddy:2-alpine\n",[234,1431,1432],{"class":236,"line":484},[234,1433,1207],{"class":255},[12,1435,1436,1437,1439],{},"Apunta el DNS A de tu dominio a ",[231,1438,280],{},". En unos minutos:",[224,1441,1443],{"className":226,"code":1442,"language":228,"meta":229,"style":229},"curl https:\u002F\u002Fmeudominio.com\u002F\n# debe retornar \"Hello v1\" (alternando entre las dos instancias)\n",[231,1444,1445,1452],{"__ignoreMap":229},[234,1446,1447,1449],{"class":236,"line":237},[234,1448,1220],{"class":247},[234,1450,1451],{"class":255}," https:\u002F\u002Fmeudominio.com\u002F\n",[234,1453,1454],{"class":236,"line":244},[234,1455,1456],{"class":240},"# debe retornar \"Hello v1\" (alternando entre las dos instancias)\n",[12,1458,1459],{},"Caddy emitió certificado Let's Encrypt automáticamente. Eso funciona porque el dominio resuelve al IP donde Caddy está escuchando en el puerto 80 (challenge HTTP-01).",[19,1461,1463],{"id":1462},"paso-5-script-bash-de-deploy-60-min","Paso 5 — Script bash de deploy (60 min)",[12,1465,1466],{},"Este es el corazón del tutorial. Un script que orquesta rolling update entre las dos VPS:",[224,1468,1470],{"className":226,"code":1469,"language":228,"meta":229,"style":229},"#!\u002Fusr\u002Fbin\u002Fenv bash\n# deploy.sh — rolling deploy zero-downtime entre duas VPS\nset -euo pipefail\n\nIMAGE=\"${1:?Uso: .\u002Fdeploy.sh meuusuario\u002Fmyapp:v2}\"\nHOSTS=(\"203.0.113.10\" \"203.0.113.20\")\nHEALTH_DEADLINE=300   # max segundos esperando health check\nMIN_HEALTHY_TIME=10   # segundos saudável sustentado antes de prosseguir\nSSH_OPTS=\"-o StrictHostKeyChecking=no -o ConnectTimeout=5\"\n\ndeploy_host() {\n  local host=$1\n  local image=$2\n  echo \"==> [${host}] pulling ${image}\"\n  ssh ${SSH_OPTS} \"root@${host}\" \"docker pull ${image}\"\n\n  # guarda imagem antiga pro caso de rollback\n  local old_image\n  old_image=$(ssh ${SSH_OPTS} \"root@${host}\" \"docker inspect app --format '{{.Config.Image}}' 2>\u002Fdev\u002Fnull || echo none\")\n  echo \"==> [${host}] versão atual: ${old_image}\"\n\n  echo \"==> [${host}] substituindo contêiner\"\n  ssh ${SSH_OPTS} \"root@${host}\" \"\n    docker stop app 2>\u002Fdev\u002Fnull || true\n    docker rm app 2>\u002Fdev\u002Fnull || true\n    docker run -d --name app --restart=unless-stopped \\\n      -p 8080:8080 \\\n      -e DATABASE_URL='${DATABASE_URL}' \\\n      --health-cmd='curl -f http:\u002F\u002Flocalhost:8080\u002Fhealthz || exit 1' \\\n      --health-interval=5s --health-timeout=2s --health-retries=3 \\\n      ${image}\n  \"\n\n  echo \"==> [${host}] esperando health check (max ${HEALTH_DEADLINE}s)\"\n  local start=$(date +%s)\n  local healthy_since=0\n  while true; do\n    local now=$(date +%s)\n    if (( now - start > HEALTH_DEADLINE )); then\n      echo \"!!  [${host}] healthy_deadline excedido — fazendo rollback pra ${old_image}\"\n      ssh ${SSH_OPTS} \"root@${host}\" \"\n        docker stop app && docker rm app &&\n        docker run -d --name app --restart=unless-stopped \\\n          -p 8080:8080 -e DATABASE_URL='${DATABASE_URL}' \\\n          ${old_image}\n      \"\n      return 1\n    fi\n\n    if curl -sf --max-time 2 \"http:\u002F\u002F${host}:8080\u002Fhealthz\" > \u002Fdev\u002Fnull; then\n      if (( healthy_since == 0 )); then\n        healthy_since=${now}\n        echo \"    [${host}] saudável — confirmando por ${MIN_HEALTHY_TIME}s\"\n      elif (( now - healthy_since >= MIN_HEALTHY_TIME )); then\n        echo \"==> [${host}] saudável sustentado — promovendo\"\n        return 0\n      fi\n    else\n      healthy_since=0\n    fi\n    sleep 2\n  done\n}\n\necho \"### Deploy ${IMAGE} em ${#HOSTS[@]} hosts (rolling, max_parallel=1)\"\nfor host in \"${HOSTS[@]}\"; do\n  if ! deploy_host \"${host}\" \"${IMAGE}\"; then\n    echo \"### Deploy abortado em ${host}. Hosts anteriores mantidos como estavam.\"\n    exit 1\n  fi\ndone\necho \"### Deploy completo: todos os hosts em ${IMAGE}\"\n",[231,1471,1472,1477,1482,1493,1497,1550,1567,1580,1593,1603,1607,1615,1628,1640,1660,1683,1687,1692,1699,1724,1740,1744,1755,1769,1774,1779,1786,1793,1805,1812,1819,1828,1833,1837,1853,1872,1884,1899,1918,1942,1960,1976,1982,1990,2002,2012,2018,2027,2033,2038,2073,2093,2104,2123,2144,2156,2165,2171,2177,2187,2192,2201,2207,2212,2217,2245,2273,2300,2314,2322,2328,2334],{"__ignoreMap":229},[234,1473,1474],{"class":236,"line":237},[234,1475,1476],{"class":240},"#!\u002Fusr\u002Fbin\u002Fenv bash\n",[234,1478,1479],{"class":236,"line":244},[234,1480,1481],{"class":240},"# deploy.sh — rolling deploy zero-downtime entre duas VPS\n",[234,1483,1484,1487,1490],{"class":236,"line":271},[234,1485,1486],{"class":251},"set",[234,1488,1489],{"class":251}," -euo",[234,1491,1492],{"class":255}," pipefail\n",[234,1494,1495],{"class":236,"line":415},[234,1496,412],{"emptyLinePlaceholder":411},[234,1498,1499,1502,1504,1507,1510,1513,1516,1518,1521,1524,1527,1529,1532,1535,1537,1540,1542,1545,1548],{"class":236,"line":434},[234,1500,1501],{"class":387},"IMAGE",[234,1503,473],{"class":383},[234,1505,1506],{"class":255},"\"",[234,1508,1509],{"class":251},"${1",[234,1511,1512],{"class":383},":?",[234,1514,1515],{"class":387},"Uso",[234,1517,1272],{"class":383},[234,1519,1520],{"class":255}," .",[234,1522,1523],{"class":383},"\u002F",[234,1525,1526],{"class":387},"deploy",[234,1528,101],{"class":255},[234,1530,1531],{"class":387},"sh",[234,1533,1534],{"class":387}," meuusuario",[234,1536,1523],{"class":383},[234,1538,1539],{"class":387},"myapp",[234,1541,1272],{"class":383},[234,1543,1544],{"class":387},"v2",[234,1546,1547],{"class":251},"}",[234,1549,1207],{"class":255},[234,1551,1552,1555,1557,1559,1562,1565],{"class":236,"line":459},[234,1553,1554],{"class":387},"HOSTS",[234,1556,473],{"class":383},[234,1558,520],{"class":387},[234,1560,1561],{"class":255},"\"203.0.113.10\"",[234,1563,1564],{"class":255}," \"203.0.113.20\"",[234,1566,526],{"class":387},[234,1568,1569,1572,1574,1577],{"class":236,"line":464},[234,1570,1571],{"class":387},"HEALTH_DEADLINE",[234,1573,473],{"class":383},[234,1575,1576],{"class":255},"300",[234,1578,1579],{"class":240},"   # max segundos esperando health check\n",[234,1581,1582,1585,1587,1590],{"class":236,"line":479},[234,1583,1584],{"class":387},"MIN_HEALTHY_TIME",[234,1586,473],{"class":383},[234,1588,1589],{"class":255},"10",[234,1591,1592],{"class":240},"   # segundos saudável sustentado antes de prosseguir\n",[234,1594,1595,1598,1600],{"class":236,"line":484},[234,1596,1597],{"class":387},"SSH_OPTS",[234,1599,473],{"class":383},[234,1601,1602],{"class":255},"\"-o StrictHostKeyChecking=no -o ConnectTimeout=5\"\n",[234,1604,1605],{"class":236,"line":490},[234,1606,412],{"emptyLinePlaceholder":411},[234,1608,1609,1612],{"class":236,"line":508},[234,1610,1611],{"class":427},"deploy_host",[234,1613,1614],{"class":387},"() {\n",[234,1616,1617,1620,1623,1625],{"class":236,"line":529},[234,1618,1619],{"class":383},"  local",[234,1621,1622],{"class":387}," host",[234,1624,473],{"class":383},[234,1626,1627],{"class":247},"$1\n",[234,1629,1630,1632,1635,1637],{"class":236,"line":535},[234,1631,1619],{"class":383},[234,1633,1634],{"class":387}," image",[234,1636,473],{"class":383},[234,1638,1639],{"class":247},"$2\n",[234,1641,1642,1645,1648,1651,1654,1657],{"class":236,"line":546},[234,1643,1644],{"class":251},"  echo",[234,1646,1647],{"class":255}," \"==> [${",[234,1649,1650],{"class":387},"host",[234,1652,1653],{"class":255},"}] pulling ${",[234,1655,1656],{"class":387},"image",[234,1658,1659],{"class":255},"}\"\n",[234,1661,1662,1665,1668,1671,1673,1676,1679,1681],{"class":236,"line":552},[234,1663,1664],{"class":247},"  ssh",[234,1666,1667],{"class":387}," ${SSH_OPTS} ",[234,1669,1670],{"class":255},"\"root@${",[234,1672,1650],{"class":387},[234,1674,1675],{"class":255},"}\"",[234,1677,1678],{"class":255}," \"docker pull ${",[234,1680,1656],{"class":387},[234,1682,1659],{"class":255},[234,1684,1685],{"class":236,"line":557},[234,1686,412],{"emptyLinePlaceholder":411},[234,1688,1689],{"class":236,"line":594},[234,1690,1691],{"class":240},"  # guarda imagem antiga pro caso de rollback\n",[234,1693,1694,1696],{"class":236,"line":635},[234,1695,1619],{"class":383},[234,1697,1698],{"class":387}," old_image\n",[234,1700,1701,1704,1706,1709,1711,1713,1715,1717,1719,1722],{"class":236,"line":643},[234,1702,1703],{"class":387},"  old_image",[234,1705,473],{"class":383},[234,1707,1708],{"class":387},"$(",[234,1710,298],{"class":247},[234,1712,1667],{"class":387},[234,1714,1670],{"class":255},[234,1716,1650],{"class":387},[234,1718,1675],{"class":255},[234,1720,1721],{"class":255}," \"docker inspect app --format '{{.Config.Image}}' 2>\u002Fdev\u002Fnull || echo none\"",[234,1723,526],{"class":387},[234,1725,1726,1728,1730,1732,1735,1738],{"class":236,"line":659},[234,1727,1644],{"class":251},[234,1729,1647],{"class":255},[234,1731,1650],{"class":387},[234,1733,1734],{"class":255},"}] versão atual: ${",[234,1736,1737],{"class":387},"old_image",[234,1739,1659],{"class":255},[234,1741,1742],{"class":236,"line":683},[234,1743,412],{"emptyLinePlaceholder":411},[234,1745,1746,1748,1750,1752],{"class":236,"line":695},[234,1747,1644],{"class":251},[234,1749,1647],{"class":255},[234,1751,1650],{"class":387},[234,1753,1754],{"class":255},"}] substituindo contêiner\"\n",[234,1756,1757,1759,1761,1763,1765,1767],{"class":236,"line":717},[234,1758,1664],{"class":247},[234,1760,1667],{"class":387},[234,1762,1670],{"class":255},[234,1764,1650],{"class":387},[234,1766,1675],{"class":255},[234,1768,1156],{"class":255},[234,1770,1771],{"class":236,"line":723},[234,1772,1773],{"class":255},"    docker stop app 2>\u002Fdev\u002Fnull || true\n",[234,1775,1776],{"class":236,"line":729},[234,1777,1778],{"class":255},"    docker rm app 2>\u002Fdev\u002Fnull || true\n",[234,1780,1781,1784],{"class":236,"line":734},[234,1782,1783],{"class":255},"    docker run -d --name app --restart=unless-stopped ",[234,1785,1169],{"class":383},[234,1787,1788,1791],{"class":236,"line":771},[234,1789,1790],{"class":255},"      -p 8080:8080 ",[234,1792,1169],{"class":383},[234,1794,1795,1798,1800,1803],{"class":236,"line":776},[234,1796,1797],{"class":255},"      -e DATABASE_URL='${",[234,1799,453],{"class":387},[234,1801,1802],{"class":255},"}' ",[234,1804,1169],{"class":383},[234,1806,1807,1810],{"class":236,"line":815},[234,1808,1809],{"class":255},"      --health-cmd='curl -f http:\u002F\u002Flocalhost:8080\u002Fhealthz || exit 1' ",[234,1811,1169],{"class":383},[234,1813,1814,1817],{"class":236,"line":820},[234,1815,1816],{"class":255},"      --health-interval=5s --health-timeout=2s --health-retries=3 ",[234,1818,1169],{"class":383},[234,1820,1821,1824,1826],{"class":236,"line":826},[234,1822,1823],{"class":255},"      ${",[234,1825,1656],{"class":387},[234,1827,1362],{"class":255},[234,1829,1830],{"class":236,"line":846},[234,1831,1832],{"class":255},"  \"\n",[234,1834,1835],{"class":236,"line":859},[234,1836,412],{"emptyLinePlaceholder":411},[234,1838,1839,1841,1843,1845,1848,1850],{"class":236,"line":872},[234,1840,1644],{"class":251},[234,1842,1647],{"class":255},[234,1844,1650],{"class":387},[234,1846,1847],{"class":255},"}] esperando health check (max ${",[234,1849,1571],{"class":387},[234,1851,1852],{"class":255},"}s)\"\n",[234,1854,1855,1857,1860,1862,1864,1867,1870],{"class":236,"line":898},[234,1856,1619],{"class":383},[234,1858,1859],{"class":387}," start",[234,1861,473],{"class":383},[234,1863,1708],{"class":387},[234,1865,1866],{"class":247},"date",[234,1868,1869],{"class":255}," +%s",[234,1871,526],{"class":387},[234,1873,1874,1876,1879,1881],{"class":236,"line":913},[234,1875,1619],{"class":383},[234,1877,1878],{"class":387}," healthy_since",[234,1880,473],{"class":383},[234,1882,1883],{"class":251},"0\n",[234,1885,1887,1890,1893,1896],{"class":236,"line":1886},37,[234,1888,1889],{"class":383},"  while",[234,1891,1892],{"class":251}," true",[234,1894,1895],{"class":387},"; ",[234,1897,1898],{"class":383},"do\n",[234,1900,1902,1905,1908,1910,1912,1914,1916],{"class":236,"line":1901},38,[234,1903,1904],{"class":383},"    local",[234,1906,1907],{"class":387}," now",[234,1909,473],{"class":383},[234,1911,1708],{"class":387},[234,1913,1866],{"class":247},[234,1915,1869],{"class":255},[234,1917,526],{"class":387},[234,1919,1921,1924,1927,1930,1933,1936,1939],{"class":236,"line":1920},39,[234,1922,1923],{"class":383},"    if",[234,1925,1926],{"class":387}," (( now ",[234,1928,1929],{"class":383},"-",[234,1931,1932],{"class":387}," start ",[234,1934,1935],{"class":383},">",[234,1937,1938],{"class":387}," HEALTH_DEADLINE )); ",[234,1940,1941],{"class":383},"then\n",[234,1943,1945,1948,1951,1953,1956,1958],{"class":236,"line":1944},40,[234,1946,1947],{"class":251},"      echo",[234,1949,1950],{"class":255}," \"!!  [${",[234,1952,1650],{"class":387},[234,1954,1955],{"class":255},"}] healthy_deadline excedido — fazendo rollback pra ${",[234,1957,1737],{"class":387},[234,1959,1659],{"class":255},[234,1961,1963,1966,1968,1970,1972,1974],{"class":236,"line":1962},41,[234,1964,1965],{"class":247},"      ssh",[234,1967,1667],{"class":387},[234,1969,1670],{"class":255},[234,1971,1650],{"class":387},[234,1973,1675],{"class":255},[234,1975,1156],{"class":255},[234,1977,1979],{"class":236,"line":1978},42,[234,1980,1981],{"class":255},"        docker stop app && docker rm app &&\n",[234,1983,1985,1988],{"class":236,"line":1984},43,[234,1986,1987],{"class":255},"        docker run -d --name app --restart=unless-stopped ",[234,1989,1169],{"class":383},[234,1991,1993,1996,1998,2000],{"class":236,"line":1992},44,[234,1994,1995],{"class":255},"          -p 8080:8080 -e DATABASE_URL='${",[234,1997,453],{"class":387},[234,1999,1802],{"class":255},[234,2001,1169],{"class":383},[234,2003,2005,2008,2010],{"class":236,"line":2004},45,[234,2006,2007],{"class":255},"          ${",[234,2009,1737],{"class":387},[234,2011,1362],{"class":255},[234,2013,2015],{"class":236,"line":2014},46,[234,2016,2017],{"class":255},"      \"\n",[234,2019,2021,2024],{"class":236,"line":2020},47,[234,2022,2023],{"class":383},"      return",[234,2025,2026],{"class":251}," 1\n",[234,2028,2030],{"class":236,"line":2029},48,[234,2031,2032],{"class":383},"    fi\n",[234,2034,2036],{"class":236,"line":2035},49,[234,2037,412],{"emptyLinePlaceholder":411},[234,2039,2041,2043,2046,2049,2052,2055,2058,2060,2063,2066,2069,2071],{"class":236,"line":2040},50,[234,2042,1923],{"class":383},[234,2044,2045],{"class":247}," curl",[234,2047,2048],{"class":251}," -sf",[234,2050,2051],{"class":251}," --max-time",[234,2053,2054],{"class":251}," 2",[234,2056,2057],{"class":255}," \"http:\u002F\u002F${",[234,2059,1650],{"class":387},[234,2061,2062],{"class":255},"}:8080\u002Fhealthz\"",[234,2064,2065],{"class":383}," >",[234,2067,2068],{"class":255}," \u002Fdev\u002Fnull",[234,2070,1895],{"class":387},[234,2072,1941],{"class":383},[234,2074,2076,2079,2082,2085,2088,2091],{"class":236,"line":2075},51,[234,2077,2078],{"class":383},"      if",[234,2080,2081],{"class":387}," (( healthy_since ",[234,2083,2084],{"class":383},"==",[234,2086,2087],{"class":251}," 0",[234,2089,2090],{"class":387}," )); ",[234,2092,1941],{"class":383},[234,2094,2096,2099,2101],{"class":236,"line":2095},52,[234,2097,2098],{"class":387},"        healthy_since",[234,2100,473],{"class":383},[234,2102,2103],{"class":387},"${now}\n",[234,2105,2107,2110,2113,2115,2118,2120],{"class":236,"line":2106},53,[234,2108,2109],{"class":251},"        echo",[234,2111,2112],{"class":255}," \"    [${",[234,2114,1650],{"class":387},[234,2116,2117],{"class":255},"}] saudável — confirmando por ${",[234,2119,1584],{"class":387},[234,2121,2122],{"class":255},"}s\"\n",[234,2124,2126,2129,2131,2133,2136,2139,2142],{"class":236,"line":2125},54,[234,2127,2128],{"class":383},"      elif",[234,2130,1926],{"class":387},[234,2132,1929],{"class":383},[234,2134,2135],{"class":387}," healthy_since ",[234,2137,2138],{"class":383},">=",[234,2140,2141],{"class":387}," MIN_HEALTHY_TIME )); ",[234,2143,1941],{"class":383},[234,2145,2147,2149,2151,2153],{"class":236,"line":2146},55,[234,2148,2109],{"class":251},[234,2150,1647],{"class":255},[234,2152,1650],{"class":387},[234,2154,2155],{"class":255},"}] saudável sustentado — promovendo\"\n",[234,2157,2159,2162],{"class":236,"line":2158},56,[234,2160,2161],{"class":383},"        return",[234,2163,2164],{"class":251}," 0\n",[234,2166,2168],{"class":236,"line":2167},57,[234,2169,2170],{"class":383},"      fi\n",[234,2172,2174],{"class":236,"line":2173},58,[234,2175,2176],{"class":383},"    else\n",[234,2178,2180,2183,2185],{"class":236,"line":2179},59,[234,2181,2182],{"class":387},"      healthy_since",[234,2184,473],{"class":383},[234,2186,1883],{"class":255},[234,2188,2190],{"class":236,"line":2189},60,[234,2191,2032],{"class":383},[234,2193,2195,2198],{"class":236,"line":2194},61,[234,2196,2197],{"class":247},"    sleep",[234,2199,2200],{"class":251}," 2\n",[234,2202,2204],{"class":236,"line":2203},62,[234,2205,2206],{"class":383},"  done\n",[234,2208,2210],{"class":236,"line":2209},63,[234,2211,1362],{"class":387},[234,2213,2215],{"class":236,"line":2214},64,[234,2216,412],{"emptyLinePlaceholder":411},[234,2218,2220,2223,2226,2228,2231,2234,2236,2239,2242],{"class":236,"line":2219},65,[234,2221,2222],{"class":251},"echo",[234,2224,2225],{"class":255}," \"### Deploy ${",[234,2227,1501],{"class":387},[234,2229,2230],{"class":255},"} em ${",[234,2232,2233],{"class":383},"#",[234,2235,1554],{"class":387},[234,2237,2238],{"class":255},"[",[234,2240,2241],{"class":383},"@",[234,2243,2244],{"class":255},"]} hosts (rolling, max_parallel=1)\"\n",[234,2246,2248,2251,2254,2257,2260,2262,2264,2266,2269,2271],{"class":236,"line":2247},66,[234,2249,2250],{"class":383},"for",[234,2252,2253],{"class":387}," host ",[234,2255,2256],{"class":383},"in",[234,2258,2259],{"class":255}," \"${",[234,2261,1554],{"class":387},[234,2263,2238],{"class":255},[234,2265,2241],{"class":383},[234,2267,2268],{"class":255},"]}\"",[234,2270,1895],{"class":387},[234,2272,1898],{"class":383},[234,2274,2276,2278,2281,2284,2286,2288,2290,2292,2294,2296,2298],{"class":236,"line":2275},67,[234,2277,597],{"class":383},[234,2279,2280],{"class":383}," !",[234,2282,2283],{"class":247}," deploy_host",[234,2285,2259],{"class":255},[234,2287,1650],{"class":387},[234,2289,1675],{"class":255},[234,2291,2259],{"class":255},[234,2293,1501],{"class":387},[234,2295,1675],{"class":255},[234,2297,1895],{"class":387},[234,2299,1941],{"class":383},[234,2301,2303,2306,2309,2311],{"class":236,"line":2302},68,[234,2304,2305],{"class":251},"    echo",[234,2307,2308],{"class":255}," \"### Deploy abortado em ${",[234,2310,1650],{"class":387},[234,2312,2313],{"class":255},"}. Hosts anteriores mantidos como estavam.\"\n",[234,2315,2317,2320],{"class":236,"line":2316},69,[234,2318,2319],{"class":251},"    exit",[234,2321,2026],{"class":251},[234,2323,2325],{"class":236,"line":2324},70,[234,2326,2327],{"class":383},"  fi\n",[234,2329,2331],{"class":236,"line":2330},71,[234,2332,2333],{"class":383},"done\n",[234,2335,2337,2339,2342,2344],{"class":236,"line":2336},72,[234,2338,2222],{"class":251},[234,2340,2341],{"class":255}," \"### Deploy completo: todos os hosts em ${",[234,2343,1501],{"class":387},[234,2345,1659],{"class":255},[12,2347,2348,2349,2352,2353,2356],{},"Guarda como ",[231,2350,2351],{},"deploy.sh",", dale ",[231,2354,2355],{},"chmod +x",", y:",[224,2358,2360],{"className":226,"code":2359,"language":228,"meta":229,"style":229},"export DATABASE_URL='postgres:\u002F\u002Fuser:pass@db.example.com:5432\u002Fapp'\n.\u002Fdeploy.sh meuusuario\u002Fmyapp:v2\n",[231,2361,2362,2375],{"__ignoreMap":229},[234,2363,2364,2367,2370,2372],{"class":236,"line":237},[234,2365,2366],{"class":383},"export",[234,2368,2369],{"class":387}," DATABASE_URL",[234,2371,473],{"class":383},[234,2373,2374],{"class":255},"'postgres:\u002F\u002Fuser:pass@db.example.com:5432\u002Fapp'\n",[234,2376,2377,2380],{"class":236,"line":244},[234,2378,2379],{"class":247},".\u002Fdeploy.sh",[234,2381,2382],{"class":255}," meuusuario\u002Fmyapp:v2\n",[12,2384,2385],{},"El algoritmo es literalmente lo que los orquestadores grandes hacen internamente:",[67,2387,2388,2394,2408,2414,2419,2425,2438],{},[70,2389,2390,2393],{},[27,2391,2392],{},"Para cada host, secuencialmente"," (max_parallel = 1)",[70,2395,2396,2399,2400,2403,2404,2407],{},[27,2397,2398],{},"Pull de la nueva imagen"," antes de tocar el contenedor — así el downtime entre ",[231,2401,2402],{},"docker stop"," y ",[231,2405,2406],{},"docker run"," es mínimo",[70,2409,2410,2413],{},[27,2411,2412],{},"Guarda referencia de la imagen antigua"," para rollback si algo sale mal",[70,2415,2416],{},[27,2417,2418],{},"Sustituye el contenedor",[70,2420,2421,2424],{},[27,2422,2423],{},"Loop esperando health check"," con deadline de cinco minutos",[70,2426,2427,2430,2431,2433,2434,2437],{},[27,2428,2429],{},"Min healthy time de diez segundos",": solo avanza cuando ",[231,2432,355],{}," retornó 200 ",[179,2435,2436],{},"sostenidamente"," por diez segundos (si cae en el medio, reinicia el conteo)",[70,2439,2440,2443],{},[27,2441,2442],{},"Rollback automático"," si pasa del deadline",[12,2445,2446],{},"Los números (max_parallel: 1, min_healthy_time: 10s, healthy_deadline: 300s) son exactamente los defaults que usamos en HeroCtl. No es coincidencia — son los valores que sobrevivieron a años de prueba y error. Min healthy time muy corto detecta síntomas transitorios como \"saludable\" y rompe; muy largo deja el deploy lento sin ganancia. Diez segundos es el punto donde ruido desaparece y el deploy aún termina rápido.",[19,2448,2450],{"id":2449},"paso-6-validar-con-test-de-carga-durante-deploy-15-min","Paso 6 — Validar con test de carga durante deploy (15 min)",[12,2452,2453],{},"Esta es la prueba de fuego: ejecutar carga sostenida y hacer deploy al mismo tiempo. Si algún 5xx aparece, alguna parte del esquema está rota.",[12,2455,2456],{},"En una máquina externa (tu laptop u otra VPS):",[224,2458,2460],{"className":226,"code":2459,"language":228,"meta":229,"style":229},"# instala hey\ngo install github.com\u002Frakyll\u002Fhey@latest\n\n# carga sustentada de 60s, 5 conexões concorrentes\nhey -z 60s -c 5 https:\u002F\u002Fmeudominio.com\u002F\n",[231,2461,2462,2467,2478,2482,2487],{"__ignoreMap":229},[234,2463,2464],{"class":236,"line":237},[234,2465,2466],{"class":240},"# instala hey\n",[234,2468,2469,2472,2475],{"class":236,"line":244},[234,2470,2471],{"class":247},"go",[234,2473,2474],{"class":255}," install",[234,2476,2477],{"class":255}," github.com\u002Frakyll\u002Fhey@latest\n",[234,2479,2480],{"class":236,"line":271},[234,2481,412],{"emptyLinePlaceholder":411},[234,2483,2484],{"class":236,"line":415},[234,2485,2486],{"class":240},"# carga sustentada de 60s, 5 conexões concorrentes\n",[234,2488,2489,2492,2495,2498,2501,2504],{"class":236,"line":434},[234,2490,2491],{"class":247},"hey",[234,2493,2494],{"class":251}," -z",[234,2496,2497],{"class":255}," 60s",[234,2499,2500],{"class":251}," -c",[234,2502,2503],{"class":251}," 5",[234,2505,1451],{"class":255},[12,2507,2508],{},"En otra ventana, simultáneamente:",[224,2510,2512],{"className":226,"code":2511,"language":228,"meta":229,"style":229},".\u002Fdeploy.sh meuusuario\u002Fmyapp:v2\n",[231,2513,2514],{"__ignoreMap":229},[234,2515,2516,2518],{"class":236,"line":237},[234,2517,2379],{"class":247},[234,2519,2382],{"class":255},[12,2521,2522,2523,1272],{},"Al final del ",[231,2524,2491],{},[224,2526,2531],{"className":2527,"code":2529,"language":2530},[2528],"language-text","Status code distribution:\n  [200] 1847 responses\n","text",[231,2532,2529],{"__ignoreMap":229},[12,2534,2535],{},"Solo 200. Si aparece un 502 o 503, alguna de las tres piezas está débil: health check retornando 200 demasiado pronto, graceful shutdown ausente, o min healthy time corto. Investiga y corrige.",[57,2537],{},[19,2539,2541],{"id":2540},"los-seis-detalles-que-separan-cero-downtime-real-de-aproximacion","Los seis detalles que separan cero-downtime real de aproximación",[12,2543,2544],{},"Cubrimos buena parte de ellos a lo largo del tutorial, pero vale la pena consolidar — porque uno solo de estos ausente convierte el esquema entero en \"mostly zero-downtime\", que es diferente.",[67,2546,2547,2556,2573,2589,2595,2601],{},[70,2548,2549,2552,2553,2555],{},[27,2550,2551],{},"Connection draining en SIGTERM."," Cuando el contenedor recibe señal de parada, la app marca ",[231,2554,355],{}," como fallando inmediatamente, pero sigue aceptando conexiones en curso por algunos segundos. Sin esto, conexiones abiertas en el momento del stop son cortadas.",[70,2557,2558,2561,2562,2565,2566,2569,2570,101],{},[27,2559,2560],{},"Pre-stop hook si tienes worker asíncrono."," Colas que procesan jobs en background necesitan pausa explícita antes de matar el proceso, o el job en ejecución queda huérfano. En Sidekiq, es el ",[231,2563,2564],{},":quiet"," antes del ",[231,2567,2568],{},":term",". En Celery, es ",[231,2571,2572],{},"--soft-time-limit",[70,2574,2575,2578,2579,2582,2583,2585,2586,2588],{},[27,2576,2577],{},"Health check ANTES de promover, no \"container running\"."," ",[231,2580,2581],{},"docker ps"," muestra \"running\" milisegundos después del ",[231,2584,2406],{},". No significa nada. Promueve solo después de que ",[231,2587,355],{}," retorne 200 sostenidamente.",[70,2590,2591,2594],{},[27,2592,2593],{},"Min healthy time de diez segundos sostenidos."," No vale ver un único 200 y seguir adelante — apps con warm-up irregular pasan un momento y vuelven a fallar.",[70,2596,2597,2600],{},[27,2598,2599],{},"Versión anterior pre-pulled para rollback rápido."," Si confiaste en \"mantener la imagen antigua en cache de Docker\", en algún momento es borrada por garbage collection y el rollback se vuelve lento. Mantén las últimas tres imágenes explícitamente.",[70,2602,2603,2606],{},[27,2604,2605],{},"Auto-revert al pasar del healthy deadline."," Sin esto, el deploy traba en un estado parcial — la mitad de los hosts en v2, la mitad en v1, sin nadie para decidir qué hacer.",[19,2608,2610],{"id":2609},"database-migrations-cero-downtime-la-parte-que-rompe-el-deploy-de-gente-experimentada","Database migrations + cero-downtime (la parte que rompe el deploy de gente experimentada)",[12,2612,2613,2614,2617],{},"Este es el tópico que más veo a desarrollador senior errar. Rolling update asume que ",[27,2615,2616],{},"las dos versiones de la app corren simultáneamente en producción por algún período",". Si la v2 espera schema incompatible con lo que la v1 entiende, alguna de las dos rompe durante la ventana de transición.",[12,2619,2620,2621,101],{},"Regla de oro innegociable: ",[27,2622,2623],{},"migrations son siempre backward-compatible",[12,2625,2626,2627,2630,2631,2634,2635,2637],{},"Caso clásico: quieres renombrar columna ",[231,2628,2629],{},"email"," a ",[231,2632,2633],{},"email_address",". Solución equivocada: haces la migration que renombra directo antes del deploy. Resultado: durante el rolling, instancias v1 aún escriben en ",[231,2636,2629],{}," (que ya no existe) y rompen. Solución correcta, en tres deploys:",[119,2639,2640,2653],{},[122,2641,2642],{},[125,2643,2644,2647,2650],{},[128,2645,2646],{},"Deploy",[128,2648,2649],{},"Migration",[128,2651,2652],{},"Código v*",[141,2654,2655,2677,2695],{},[125,2656,2657,2660,2666],{},[146,2658,2659],{},"1",[146,2661,2662,2663,2665],{},"Añade ",[231,2664,2633],{}," (nullable). Ninguna eliminación.",[146,2667,2668,2669,2671,2672,2674,2675,101],{},"App escribe en ",[231,2670,2629],{}," Y en ",[231,2673,2633],{},"; lee de ",[231,2676,2629],{},[125,2678,2679,2682,2689],{},[146,2680,2681],{},"2",[146,2683,2684,2685,2688],{},"Backfill: ",[231,2686,2687],{},"UPDATE users SET email_address = email WHERE email_address IS NULL",". NOT NULL constraint.",[146,2690,2691,2692,2694],{},"App lee de ",[231,2693,2633],{},"; aún escribe en las dos.",[125,2696,2697,2700,2705],{},[146,2698,2699],{},"3",[146,2701,2702,2703,101],{},"Drop ",[231,2704,2629],{},[146,2706,2707,2708,101],{},"App solo usa ",[231,2709,2633],{},[12,2711,2712],{},"Tres deploys, semanas de espaciamiento. Es tedioso, es la forma. Drop de columna directo siempre rompe. Cambio de tipo directo siempre rompe. Añadir NOT NULL sin default directo siempre rompe.",[12,2714,2715,2716,2403,2719,2722,2723,2726],{},"Herramientas que ayudan: ",[231,2717,2718],{},"pg-osc",[231,2720,2721],{},"pgroll"," (Postgres), ",[231,2724,2725],{},"gh-ost"," (MySQL) — hacen schema change online, sin lock largo. Para migrations ligeras, la forma manual en tres pasos resuelve.",[19,2728,2730],{"id":2729},"patrones-mas-alla-de-rolling","Patrones más allá de rolling",[12,2732,2733],{},"Rolling update es el patrón default y más económico. Hay otros que vale la pena conocer:",[2735,2736,2737,2743,2749],"ul",{},[70,2738,2739,2742],{},[27,2740,2741],{},"Blue-green."," Dos entornos paralelos completos — \"blue\" corriendo v1, \"green\" provisionado con v2 vacío. Subes v2 entero en green, validas, cambias DNS (o cutover del balanceador). Ventaja: rollback instantáneo (vuelve DNS a blue). Desventaja: cuesta el doble de recursos durante la ventana de deploy.",[70,2744,2745,2748],{},[27,2746,2747],{},"Canary."," Manda 5% del tráfico a v2, observa métricas (errores, latencia, tasa de conversión), decide si promueve a 100% o aborta. Detecta bugs sutiles que health check no agarra — tipo regression en conversión de checkout. Exige proxy con weighted routing y observabilidad decente.",[70,2750,2751,2754],{},[27,2752,2753],{},"Rainbow \u002F N+1."," Generalización del blue-green con N versiones coexistiendo. Útil cuando quieres A\u002FB test de larga duración entre versiones enteras.",[12,2756,2757],{},"Para el tutorial, rolling update es lo que tiene sentido. Los otros valen cuando el tamaño del tráfico justifica inversión extra.",[19,2759,2761],{"id":2760},"version-facil-coolify-o-dokploy","Versión \"fácil\" — Coolify o Dokploy",[12,2763,2764],{},"Si no quieres scriptear, dos paneles modernos hacen rolling update automáticamente:",[2735,2766,2767,2773],{},[70,2768,2769,2772],{},[27,2770,2771],{},"Coolify"," en modo multi-server hace rolling update con health check configurable. Multi-server fue añadido en las versiones más recientes — antes era single-server only. Vale la pena chequear la versión.",[70,2774,2775,2778,2779,2782],{},[27,2776,2777],{},"Dokploy"," sobre Docker Swarm hace rolling update con ",[231,2780,2781],{},"--update-parallelism 1 --update-delay",". Aprovecha lo que Swarm ya ofrece.",[12,2784,2785],{},"Trade-off: cambias el script de cincuenta líneas (que entiende todo lo que está sucediendo) por un panel (que es más rápido para subir, pero se vuelve caja-negra cuando algo sale mal). Para equipo pequeño donde una persona cuida operación parcialmente, el panel gana. Para equipo donde necesitas entender exactamente qué pasó a las 3h de la mañana, el script gana.",[19,2787,2789],{"id":2788},"version-robusta-heroctl","Versión \"robusta\" — HeroCtl",[12,2791,2792],{},"Para quien quiere parar de scriptear pero no quiere caja-negra, HeroCtl combina rolling update automático con plano de control replicado. Describes el servicio en archivo de configuración y el orquestador hace el resto:",[224,2794,2798],{"className":2795,"code":2796,"language":2797,"meta":229,"style":229},"language-hcl shiki shiki-themes github-dark-default","job \"minhaapp\" {\n  group \"web\" {\n    count = 2\n\n    task \"app\" {\n      driver = \"docker\"\n      config {\n        image = \"meuusuario\u002Fmyapp:v2\"\n        ports = [\"http\"]\n      }\n\n      service {\n        port = \"http\"\n        check {\n          type     = \"http\"\n          path     = \"\u002Fhealthz\"\n          interval = \"5s\"\n          timeout  = \"2s\"\n        }\n      }\n    }\n\n    update {\n      max_parallel      = 1\n      min_healthy_time  = \"10s\"\n      healthy_deadline  = \"5m\"\n      auto_revert       = true\n    }\n  }\n}\n","hcl",[231,2799,2800,2805,2810,2815,2819,2824,2829,2834,2839,2844,2849,2853,2858,2863,2868,2873,2878,2883,2888,2892,2896,2900,2904,2909,2914,2919,2924,2929,2933,2937],{"__ignoreMap":229},[234,2801,2802],{"class":236,"line":237},[234,2803,2804],{},"job \"minhaapp\" {\n",[234,2806,2807],{"class":236,"line":244},[234,2808,2809],{},"  group \"web\" {\n",[234,2811,2812],{"class":236,"line":271},[234,2813,2814],{},"    count = 2\n",[234,2816,2817],{"class":236,"line":415},[234,2818,412],{"emptyLinePlaceholder":411},[234,2820,2821],{"class":236,"line":434},[234,2822,2823],{},"    task \"app\" {\n",[234,2825,2826],{"class":236,"line":459},[234,2827,2828],{},"      driver = \"docker\"\n",[234,2830,2831],{"class":236,"line":464},[234,2832,2833],{},"      config {\n",[234,2835,2836],{"class":236,"line":479},[234,2837,2838],{},"        image = \"meuusuario\u002Fmyapp:v2\"\n",[234,2840,2841],{"class":236,"line":484},[234,2842,2843],{},"        ports = [\"http\"]\n",[234,2845,2846],{"class":236,"line":490},[234,2847,2848],{},"      }\n",[234,2850,2851],{"class":236,"line":508},[234,2852,412],{"emptyLinePlaceholder":411},[234,2854,2855],{"class":236,"line":529},[234,2856,2857],{},"      service {\n",[234,2859,2860],{"class":236,"line":535},[234,2861,2862],{},"        port = \"http\"\n",[234,2864,2865],{"class":236,"line":546},[234,2866,2867],{},"        check {\n",[234,2869,2870],{"class":236,"line":552},[234,2871,2872],{},"          type     = \"http\"\n",[234,2874,2875],{"class":236,"line":557},[234,2876,2877],{},"          path     = \"\u002Fhealthz\"\n",[234,2879,2880],{"class":236,"line":594},[234,2881,2882],{},"          interval = \"5s\"\n",[234,2884,2885],{"class":236,"line":635},[234,2886,2887],{},"          timeout  = \"2s\"\n",[234,2889,2890],{"class":236,"line":643},[234,2891,1352],{},[234,2893,2894],{"class":236,"line":659},[234,2895,2848],{},[234,2897,2898],{"class":236,"line":683},[234,2899,1357],{},[234,2901,2902],{"class":236,"line":695},[234,2903,412],{"emptyLinePlaceholder":411},[234,2905,2906],{"class":236,"line":717},[234,2907,2908],{},"    update {\n",[234,2910,2911],{"class":236,"line":723},[234,2912,2913],{},"      max_parallel      = 1\n",[234,2915,2916],{"class":236,"line":729},[234,2917,2918],{},"      min_healthy_time  = \"10s\"\n",[234,2920,2921],{"class":236,"line":734},[234,2922,2923],{},"      healthy_deadline  = \"5m\"\n",[234,2925,2926],{"class":236,"line":771},[234,2927,2928],{},"      auto_revert       = true\n",[234,2930,2931],{"class":236,"line":776},[234,2932,1357],{},[234,2934,2935],{"class":236,"line":815},[234,2936,720],{},[234,2938,2939],{"class":236,"line":820},[234,2940,1362],{},[12,2942,2943],{},"Los mismos parámetros del script bash, declarativos. La diferencia es que el orquestador coordina rolling update entre N servidores (no solo dos), hace elección automática de coordinador en alrededor de siete segundos si el nodo actual se cae, y mantiene el plano de control distribuido entre los primeros tres servidores. Cluster sobrevive a la pérdida de cualquier servidor único sin intervención humana.",[12,2945,2946],{},"Instalación:",[224,2948,2950],{"className":226,"code":2949,"language":228,"meta":229,"style":229},"curl -sSL https:\u002F\u002Fget.heroctl.com\u002Finstall.sh | sh\n",[231,2951,2952],{"__ignoreMap":229},[234,2953,2954,2956,2959,2962,2965],{"class":236,"line":237},[234,2955,1220],{"class":247},[234,2957,2958],{"class":251}," -sSL",[234,2960,2961],{"class":255}," https:\u002F\u002Fget.heroctl.com\u002Finstall.sh",[234,2963,2964],{"class":383}," |",[234,2966,2967],{"class":247}," sh\n",[12,2969,2970],{},"Plan Community es gratuito permanente — sin límite de servidores o jobs, con todas las features de orquestación descritas en el tutorial. Plan Business añade SSO\u002FSAML, RBAC granular, auditoría detallada y soporte con SLA, para equipos que tienen requisitos formales de plataforma. Plan Enterprise añade escrow de código fuente, contrato de continuidad y soporte 24×7. Los precios de Business y Enterprise están publicados en la página de planes — sin \"habla con ventas\" obligatorio.",[19,2972,2974],{"id":2973},"comparativo-cinco-caminos-lado-a-lado","Comparativo: cinco caminos lado a lado",[119,2976,2977,3002],{},[122,2978,2979],{},[125,2980,2981,2984,2987,2990,2993,2996,2999],{},[128,2982,2983],{},"Criterio",[128,2985,2986],{},"Script bash (2 servers)",[128,2988,2989],{},"Coolify multi-server",[128,2991,2992],{},"Dokploy + Swarm",[128,2994,2995],{},"HeroCtl",[128,2997,2998],{},"Kamal",[128,3000,3001],{},"Kubernetes",[141,3003,3004,3026,3049,3071,3089,3106,3128,3148],{},[125,3005,3006,3009,3012,3015,3018,3021,3023],{},[146,3007,3008],{},"Tiempo de setup",[146,3010,3011],{},"2-3h",[146,3013,3014],{},"30 min",[146,3016,3017],{},"1h",[146,3019,3020],{},"5 min",[146,3022,3017],{},[146,3024,3025],{},"4h-4 días",[125,3027,3028,3031,3034,3037,3040,3043,3046],{},[146,3029,3030],{},"Líneas de config",[146,3032,3033],{},"~50 (script)",[146,3035,3036],{},"UI",[146,3038,3039],{},"~20",[146,3041,3042],{},"~50",[146,3044,3045],{},"~40",[146,3047,3048],{},"300+",[125,3050,3051,3054,3057,3060,3063,3066,3068],{},[146,3052,3053],{},"HA del plano de control",[146,3055,3056],{},"N\u002FA",[146,3058,3059],{},"No",[146,3061,3062],{},"Limitado",[146,3064,3065],{},"Sí",[146,3067,3056],{},[146,3069,3070],{},"Sí (5+ componentes)",[125,3072,3073,3076,3079,3081,3083,3085,3087],{},[146,3074,3075],{},"Health check declarativo",[146,3077,3078],{},"Manual",[146,3080,3065],{},[146,3082,3065],{},[146,3084,3065],{},[146,3086,3065],{},[146,3088,3065],{},[125,3090,3091,3093,3096,3098,3100,3102,3104],{},[146,3092,2442],{},[146,3094,3095],{},"Manual en el script",[146,3097,3065],{},[146,3099,3065],{},[146,3101,3065],{},[146,3103,3065],{},[146,3105,3065],{},[125,3107,3108,3111,3114,3117,3120,3123,3125],{},[146,3109,3110],{},"Escala objetivo",[146,3112,3113],{},"1-3 servers",[146,3115,3116],{},"1-10 servers",[146,3118,3119],{},"1-20 servers",[146,3121,3122],{},"1-500 servers",[146,3124,3116],{},[146,3126,3127],{},"50+ servers",[125,3129,3130,3133,3136,3138,3141,3144,3146],{},[146,3131,3132],{},"¿Caja-negra?",[146,3134,3135],{},"No (lo escribiste tú)",[146,3137,3065],{},[146,3139,3140],{},"Parcial",[146,3142,3143],{},"No (declarativo corto)",[146,3145,3059],{},[146,3147,3065],{},[125,3149,3150,3153,3156,3158,3161,3163,3165],{},[146,3151,3152],{},"Curva de aprendizaje",[146,3154,3155],{},"Baja",[146,3157,3155],{},[146,3159,3160],{},"Media",[146,3162,3155],{},[146,3164,3155],{},[146,3166,3167],{},"Alta",[12,3169,3170],{},"Cada columna tiene su nicho. Script bash es insuperable cuando quieres entender cada línea. Coolify gana cuando solo quieres un panel. HeroCtl gana cuando necesitas HA real sin montar plano de control externo. Kubernetes gana en escala planetaria — donde la complejidad compensa.",[19,3172,3174],{"id":3173},"los-cinco-errores-mas-comunes","Los cinco errores más comunes",[67,3176,3177,3189,3195,3205,3217],{},[70,3178,3179,3185,3186,3188],{},[27,3180,3181,3182,3184],{},"Health check en ",[231,3183,1523],{}," retornando 200 sin validar dependencias."," La app retorna 200 antes de conectar a la base, el proxy promueve, y el usuario ve error 500 en las primeras requests. Solución: ",[231,3187,355],{}," valida base, cache, cola — cualquier cosa que la app necesite para responder de verdad.",[70,3190,3191,3194],{},[27,3192,3193],{},"Min healthy time de 1 segundo."," Apps con warm-up irregular pueden retornar 200 en un momento y 503 justo después (cache poblando, clase siendo lazy-loaded). El orquestador promueve en la primera ventana buena, y la próxima request golpea en estado malo. Diez segundos sostenidos eliminan noventa por ciento de esos casos.",[70,3196,3197,3200,3201,3204],{},[27,3198,3199],{},"Sin max_parallel (o max_parallel = N)."," Si cambias todas las instancias juntas, durante la ventana del cutover no hay nadie saludable sirviendo. Es single-server downtime disfrazado. Siempre ",[231,3202,3203],{},"max_parallel = 1"," para empezar.",[70,3206,3207,3210,3211,3213,3214,3216],{},[27,3208,3209],{},"Mix de versiones en producción sin schema compat."," v1 escribe en ",[231,3212,2629],{},", v2 lee de ",[231,3215,2633],{},", y durante el rolling de cinco minutos las dos conviven — usuarios que reciben v2 no ven datos que v1 acaba de grabar. Migration backward-compatible en tres pasos resuelve.",[70,3218,3219,3222],{},[27,3220,3221],{},"Cache stale en el cliente (CDN, browser, service worker)."," Backend ya es v2 pero el usuario tiene el JS de v1 en cache, y el JS antiguo llama a API que ya no existe. Solución: mantén endpoints antiguos por una ventana; versionado de API; cache-busting fuerte en assets críticos.",[19,3224,3226],{"id":3225},"faq","FAQ",[12,3228,3229],{},[27,3230,3231],{},"¿Puedo hacer cero-downtime con un solo servidor?",[12,3233,3234,3235,3238],{},"No. Toda variación que prometa eso tiene ventana de error mensurable cuando la mides con ",[231,3236,3237],{},"hey -c 20",". La única forma de tener cero-downtime real es mantener al menos una instancia siempre saludable durante todo el deploy — lo que exige dos máquinas como mínimo.",[12,3240,3241],{},[27,3242,3243],{},"¿DNS round-robin funciona como balanceador?",[12,3245,3246],{},"Funciona como balanceador básico, pero no como mecanismo de health check. DNS no retira IP muerto de la rotación rápidamente — TTLs cacheando en ISPs y clientes mantienen el IP equivocado en uso por minutos u horas. Para cero-downtime necesitas un proxy real (Caddy, nginx, HAProxy) que retira instancia unhealthy del balanceo en segundos.",[12,3248,3249],{},[27,3250,3251],{},"¿Caddy o Traefik — cuál es mejor para este setup?",[12,3253,3254],{},"Para dos servidores y un setup estático, Caddy es más simple — Caddyfile de quince líneas resuelve. Traefik brilla cuando tienes descubrimiento dinámico de servicios (tipo Docker labels o Consul) y muchos backends cambiando todo el tiempo. nginx queda en el medio: más flexible, sin TLS automático embebido (necesita certbot externo). Para este tutorial, Caddy.",[12,3256,3257],{},[27,3258,3259],{},"¿Conexiones WebSocket sobreviven durante rolling update?",[12,3261,3262,3263,3265],{},"Conexiones abiertas en instancia que está siendo tirada son cortadas. El cliente tiene que reconectar. Buena biblioteca de WebSocket (Socket.IO, Phoenix Channels) reconecta automáticamente — usuario ve un parpadeo de medio segundo en el estado. Connection draining ayuda: la instancia marca ",[231,3264,355],{}," fallando, el proxy deja de mandar conexiones nuevas, pero las existentes siguen hasta el pre-stop timer. Treinta segundos de drain suelen ser suficientes para que conexiones largas se vacíen naturalmente.",[12,3267,3268],{},[27,3269,3270],{},"Database migrations — ¿cuál es la regla de oro?",[12,3272,3273],{},"Toda migration tiene que ser backward-compatible. Drop de columna nunca directo. Rename nunca directo. Cambio de tipo nunca directo. En su lugar, tres deploys: añade estructura nueva, backfill, elimina la antigua. Lento, sí. Pero el rolling update depende de eso para no romper.",[12,3275,3276],{},[27,3277,3278],{},"Rollback automático — ¿cómo implementar?",[12,3280,3281,3282,101],{},"Dos piezas: deadline (tiempo máximo esperando health check) y referencia de la imagen anterior pre-pulled. Si pasa del deadline sin estar saludable, el script reinstala la versión anterior. El ejemplo en el Paso 5 hace exactamente eso. En orquestadores declarativos, se vuelve ",[231,3283,3284],{},"auto_revert = true",[12,3286,3287],{},[27,3288,3289],{},"¿Sticky sessions complican el cero-downtime?",[12,3291,3292],{},"Sí. Si la app guarda estado de sesión en memoria del proceso, tirar la instancia tira las sesiones de los usuarios conectados a ella. Solución: saca sesión de la memoria — Redis, Postgres o JWT signed. Ahí cualquier instancia sirve a cualquier usuario, y rolling update no corta ninguna sesión.",[12,3294,3295],{},[27,3296,3297],{},"¿Cuánto tiempo tarda un deploy completo?",[12,3299,3300,3301,3304],{},"Dos servidores, app que sube en quince segundos: cerca de un minuto. Detalle: pull de la imagen (5-15s, depende de la red y del tamaño), sustitución del contenedor (1s), warm-up + health check (10-30s), min healthy time de 10s, total unos 30-50s por host, multiplicado por dos hosts en secuencia = 1-2 min. Cuatro servidores quedan en torno a 2-4 min. Con cincuenta servidores, el deploy empieza a tomar diez o quince minutos — momento de aumentar ",[231,3302,3303],{},"max_parallel"," a dos o tres (manteniendo health check riguroso).",[57,3306],{},[19,3308,3310],{"id":3309},"cierre","Cierre",[12,3312,3313],{},"El deploy cero-downtime es arquitectura, no herramienta. Los tres ingredientes — múltiples instancias, proxy con health check, rolling update controlado — funcionan con bash y Caddy tanto como con orquestador grande. La diferencia está en cuánto de la operación quieres escribir a mano y cuánto delegar.",[12,3315,3316],{},"Para un SaaS pequeño, tres VPS y un script de cincuenta líneas resuelven indefinidamente. Cuando el cluster crece a decenas de servidores o el equipo necesita HA real del plano de control, vale la pena subir al orquestador declarativo:",[224,3318,3319],{"className":226,"code":2949,"language":228,"meta":229,"style":229},[231,3320,3321],{"__ignoreMap":229},[234,3322,3323,3325,3327,3329,3331],{"class":236,"line":237},[234,3324,1220],{"class":247},[234,3326,2958],{"class":251},[234,3328,2961],{"class":255},[234,3330,2964],{"class":383},[234,3332,2967],{"class":247},[12,3334,3335,3336,3341,3342,3346],{},"Más sobre el algoritmo de rolling update en ",[3337,3338,3340],"a",{"href":3339},"\u002Fes\u002Fblog\u002Frolling-deploy-seguro-por-que-el-tuyo-puede-no-serlo","Rolling update seguro: por qué el tuyo puede no serlo",". Para quien está saliendo de Compose a setup multi-servidor, ",[3337,3343,3345],{"href":3344},"\u002Fes\u002Fblog\u002Fdeploy-docker-produccion-de-compose-a-cluster","Deploy Docker en producción: de compose a cluster"," cubre el camino intermedio.",[12,3348,3349],{},"Orquestación de contenedores, sin ceremonia.",[3351,3352,3353],"style",{},"html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}",{"title":229,"searchDepth":244,"depth":244,"links":3355},[3356,3357,3358,3359,3360,3361,3366,3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378],{"id":21,"depth":244,"text":22},{"id":61,"depth":244,"text":62},{"id":93,"depth":244,"text":94},{"id":113,"depth":244,"text":114},{"id":215,"depth":244,"text":216},{"id":348,"depth":244,"text":349,"children":3362},[3363,3364,3365],{"id":370,"depth":271,"text":371},{"id":918,"depth":271,"text":919},{"id":1002,"depth":271,"text":1003},{"id":1099,"depth":244,"text":1100},{"id":1241,"depth":244,"text":1242},{"id":1462,"depth":244,"text":1463},{"id":2449,"depth":244,"text":2450},{"id":2540,"depth":244,"text":2541},{"id":2609,"depth":244,"text":2610},{"id":2729,"depth":244,"text":2730},{"id":2760,"depth":244,"text":2761},{"id":2788,"depth":244,"text":2789},{"id":2973,"depth":244,"text":2974},{"id":3173,"depth":244,"text":3174},{"id":3225,"depth":244,"text":3226},{"id":3309,"depth":244,"text":3310},"engineering",null,"2026-06-09","No necesitas Kubernetes para tener deploy sin downtime. Tutorial completo con 2 servidores, Caddy\u002FTraefik por delante, y rolling update vía script u orquestador ligero.",false,"md",{},"\u002Fes\u002Fblog\u002Fdeploy-cero-downtime-sin-kubernetes","15 min",{"title":6,"description":3382},{"loc":3386},"es\u002Fblog\u002Fdeploy-cero-downtime-sin-kubernetes",[1526,3392,3393,3394],"cero-downtime","tutorial","ingenieria","DIdopGCYte4Yw34SS-f9-YpuJ3SLOw5rKbvHsED4tPc",{"id":3397,"title":3398,"author":7,"body":3399,"category":3379,"cover":3380,"date":4398,"description":4399,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":4400,"navigation":411,"path":4401,"readingTime":4402,"seo":4403,"sitemap":4404,"stem":4405,"tags":4406,"__hash__":4411},"blog_es\u002Fes\u002Fblog\u002Fapi-gateway-auto-hospedado-cuando-instalar.md","API Gateway auto-hospedado: cuándo vale la pena instalar Kong, Traefik o similar",{"type":9,"value":3400,"toc":4378},[3401,3404,3407,3411,3418,3421,3428,3432,3435,3517,3523,3583,3586,3590,3593,3597,3600,3606,3612,3618,3622,3625,3630,3635,3640,3644,3647,3652,3657,3662,3666,3669,3674,3679,3684,3688,3691,3696,3701,3706,3710,3713,3751,3754,3758,3761,3813,3816,3820,3823,3829,3832,3835,3839,4125,4132,4136,4139,4145,4151,4157,4163,4169,4172,4176,4179,4185,4191,4197,4203,4207,4210,4242,4246,4256,4262,4268,4278,4284,4290,4296,4310,4316,4320,4323,4326,4329,4332,4348,4362,4372,4375],[12,3402,3403],{},"\"API gateway\" es una de las categorías más sobrecargadas de jerga en la arquitectura de back-end. El término se volvió paraguas para cosas que un reverse proxy simple ya hace hace veinte años — enrutar, terminar TLS, balancear entre instancias — mezcladas con cosas que de hecho exigen un componente dedicado: validación de clave de API por cliente, límite de peticiones por usuario, transformación del cuerpo de la petición, agregación de múltiples back-ends en una sola respuesta. La confusión vende mucho producto. Y hace que muchas startups instalen un componente crítico que no necesitaban — pagando después en latencia, RAM, complejidad operacional y superficie de fallo.",[12,3405,3406],{},"Este post separa lo que cada cosa cubre, lista los cinco jugadores principales con números honestos de consumo de recursos, y dibuja una regla práctica: cuándo el reverse proxy embebido en el orquestador resuelve, cuándo vale levantar un Traefik standalone, y cuándo de hecho necesitas un Kong con plug-ins de autenticación. La audiencia es el tech lead que está mirando el stack actual y tratando de decidir si el siguiente dolor merece un componente más en el camino crítico — o si el dolor es falso.",[19,3408,3410],{"id":3409},"tldr-instalar-gateway-dedicado-es-decision-cara-manten-la-regla-corta","TL;DR — instalar gateway dedicado es decisión cara, mantén la regla corta",[12,3412,3413,3414,3417],{},"Reverse proxy simple cubre el tronco del problema: HTTPS terminado, certificados Let's Encrypt automáticos, enrutamiento por host y ruta, balanceo entre back-ends, health check, compresión. Para un SaaS B2B típico con web app + algunos micro-servicios HTTP, ",[27,3415,3416],{},"eso basta",". No hace falta instalar Kong, no hace falta Tyk, no hace falta KrakenD.",[12,3419,3420],{},"API gateway dedicado se vuelve inversión defendible cuando aparecen tres señales simultáneas: publicas una API para que terceros la consuman (no solo tu propia web\u002Fmobile), necesitas límite de peticiones por clave de cliente (no por IP), y quieres documentación interactiva con tentar-aquí para los consumidores. En ese escenario, Kong, Tyk o Traefik con middlewares ricos pagan su propio coste. Fuera de ese escenario, estás añadiendo 100–300 MB de RAM en el camino crítico, de 1 a 3 milisegundos de latencia por petición, y un componente más que puede caer en producción — a cambio de funcionalidades que nadie va a usar.",[12,3422,3423,3424,3427],{},"La regla más simple que conocemos: ",[27,3425,3426],{},"si tu cliente final es una persona abriendo un navegador, basta con reverse proxy. Si tu cliente final es un desarrollador con clave de API, considera el gateway."," Todo lo demás es variación sobre esas dos líneas.",[19,3429,3431],{"id":3430},"lo-que-un-reverse-proxy-simple-ya-cubre-y-lo-que-aun-falta","Lo que un reverse proxy simple ya cubre, y lo que aún falta",[12,3433,3434],{},"Antes de comparar gateways, vale fijar el suelo. Un reverse proxy decente — Caddy, nginx, o el router integrado de un orquestador moderno — entrega bastante cosa gratis. Esta lista representa el estado del arte en 2026, no el mínimo histórico:",[2735,3436,3437,3443,3449,3466,3472,3478,3484,3490,3496,3502],{},[70,3438,3439,3442],{},[27,3440,3441],{},"Terminación HTTP\u002FHTTPS con HTTP\u002F2 y HTTP\u002F3."," El proxy habla con el cliente en cualquier protocolo moderno y habla con el back-end en HTTP\u002F1.1 limpio si es el caso.",[70,3444,3445,3448],{},[27,3446,3447],{},"Certificados Let's Encrypt automáticos."," Emisión, renovación a los 60 días, recuperación de error. Hoy eso es commodity — cualquier router serio lo hace.",[70,3450,3451,2578,3454,3457,3458,3461,3462,3465],{},[27,3452,3453],{},"Enrutamiento por host y ruta.",[231,3455,3456],{},"api.ejemplo.com"," va a un back-end, ",[231,3459,3460],{},"app.ejemplo.com"," va a otro, ",[231,3463,3464],{},"\u002Fv1\u002Fusuarios"," va a un tercero. Reglas con prefijo, regex y prioridad.",[70,3467,3468,3471],{},[27,3469,3470],{},"Balanceo entre instancias."," Round-robin, menos conexiones, hash por IP. Suficiente para distribuir carga entre las réplicas de un mismo servicio.",[70,3473,3474,3477],{},[27,3475,3476],{},"Health check activo y pasivo."," Saca instancia enferma del pool. La re-incluye cuando vuelve.",[70,3479,3480,3483],{},[27,3481,3482],{},"Compresión gzip y brotli."," Negocia con el cliente, comprime lo que vale la pena.",[70,3485,3486,3489],{},[27,3487,3488],{},"Caché de contenido estático."," Para archivos inmutables, evita la ida al back-end.",[70,3491,3492,3495],{},[27,3493,3494],{},"Límite básico por IP."," Treinta peticiones por segundo por dirección, por ejemplo. Cubre buena parte del abuso tonto.",[70,3497,3498,3501],{},[27,3499,3500],{},"Timeouts y reintentos."," Falla rápido, intenta de nuevo en un back-end alternativo si es el caso.",[70,3503,3504,2578,3507,571,3510,571,3513,3516],{},[27,3505,3506],{},"Cabeceras de proxy.",[231,3508,3509],{},"X-Forwarded-For",[231,3511,3512],{},"X-Real-IP",[231,3514,3515],{},"X-Forwarded-Proto",". El back-end ve al cliente real.",[12,3518,3519,3520,3522],{},"Eso es mucha cosa. Para 80% de las aplicaciones web de SaaS B2B, es todo lo que se necesita en el camino de entrada. Lo que el reverse proxy simple ",[27,3521,100],{}," cubre es lo que diferencia al gateway:",[2735,3524,3525,3531,3541,3547,3553,3559,3565,3571,3577],{},[70,3526,3527,3530],{},[27,3528,3529],{},"Validación de clave de API por cliente."," Cada consumidor recibe una clave, el gateway valida, identifica al cliente, y usa esa identidad para límites y auditoría.",[70,3532,3533,3536,3537,3540],{},[27,3534,3535],{},"Validación de token JWT con claves rotables."," El gateway baja las claves públicas del emisor, valida firma y tiempo, expone ",[231,3538,3539],{},"claims"," al back-end.",[70,3542,3543,3546],{},[27,3544,3545],{},"Límite de peticiones por clave\u002Fusuario\u002Fruta."," Cliente A puede hacer 1.000 llamadas\u002Fhora; cliente B, 100. Por ruta, por día, con ventana deslizante. Difícil de hacer en proxy simple.",[70,3548,3549,3552],{},[27,3550,3551],{},"Transformación de petición y respuesta."," Añadir\u002Fquitar cabeceras, reescribir cuerpo JSON, traducir entre versiones de la API.",[70,3554,3555,3558],{},[27,3556,3557],{},"Versionado por cabecera o ruta."," Convivir con clientes en v1 mientras v2 gana tracción. Política de descontinuación.",[70,3560,3561,3564],{},[27,3562,3563],{},"Agregación de back-ends."," Endpoint compuesto que llama a tres micro-servicios y devuelve una respuesta unificada (patrón back-end-for-frontend).",[70,3566,3567,3570],{},[27,3568,3569],{},"Validación de esquema de la petición."," Rechazar en el gateway lo que no encaja con el contrato OpenAPI antes de tocar al back-end.",[70,3572,3573,3576],{},[27,3574,3575],{},"Portal de documentación con tentar-aquí."," Página interactiva para que los desarrolladores exploren la API.",[70,3578,3579,3582],{},[27,3580,3581],{},"Métricas granulares por clave de API."," Quién llamó, cuánto, cuándo, con qué latencia. Vital si la API es el producto.",[12,3584,3585],{},"Cada item de esa segunda lista es una feature que cuesta caro hacer en código de aplicación esparcido. Si necesitas la mayoría, el gateway paga. Si no necesitas casi nada — que es el caso común en SaaS de producto — el gateway es peso muerto.",[19,3587,3589],{"id":3588},"los-cinco-jugadores-que-importan-en-2026","Los cinco jugadores que importan en 2026",[12,3591,3592],{},"El mercado se asentó. Hay cinco elecciones defendibles para gateway auto-hospedado, con perfiles razonablemente distintos. Los números de RAM y latencia abajo se miden con configuración por defecto y workload modesto (algunas decenas de llamadas por segundo); plug-ins pesados o volumen alto cambian todo.",[368,3594,3596],{"id":3595},"kong-basado-en-lua-sobre-openresty","Kong (basado en Lua, sobre OpenResty)",[12,3598,3599],{},"El nombre más conocido de la categoría. Kong empezó en 2015 y tiene el catálogo más grande de plug-ins del espacio — autenticación OAuth, validación JWT, transformación, log a Elasticsearch, integración con cofres externos, todo pre-listo. La versión de código abierto cubre la mayoría de los casos; la pagada añade portal de desarrollador más pulido, RBAC fino, y soporte con SLA.",[12,3601,3602,3605],{},[27,3603,3604],{},"Recursos:"," mínimo realista de 200 MB de RAM por instancia, más la base de datos si no usas modo sin-base. Latencia añadida de 1 a 2 milisegundos por petición en llamada simple. Plug-ins pesados (validación de esquema con OpenAPI grande, transformación JSON compleja) pueden duplicar eso.",[12,3607,3608,3611],{},[27,3609,3610],{},"Cuándo tiene sentido:"," API pública seria con varios consumidores externos, necesidad de plug-ins del catálogo, equipo dispuesto a aprender Lua si necesita customizar. Empresa de medio de pagos, plataforma de comunicación, cualquier negocio donde la API es el producto vendido.",[12,3613,3614,3617],{},[27,3615,3616],{},"Trampa:"," el modo con PostgreSQL coloca la base en el camino crítico. La base se cae, el gateway no actualiza configuración. Usa el modo sin-base (configuración declarativa vía archivo) siempre que sea posible — elimina esa dependencia.",[368,3619,3621],{"id":3620},"traefik-escrito-en-go-hablando-varios-proxies-de-orquestador","Traefik (escrito en Go, hablando varios proxies de orquestador)",[12,3623,3624],{},"Conocido como controlador de entrada de Kubernetes, pero tiene middlewares ricos lo suficiente para cubrir muchos casos de gateway. Límite de peticiones por cliente, validación básica de JWT, transformación de cabecera, redirecciones complejas, autenticación reenviada (delegando a servicio externo). Versión pagada añade plug-ins comerciales y dashboard más robusto.",[12,3626,3627,3629],{},[27,3628,3604],{}," 50 a 100 MB de RAM, latencia añadida de 0,5 a 1 milisegundo. Discovery automático de back-ends vía etiquetas de contenedor es el punto fuerte — no escribes configuración de ruta, ella aparece cuando el servicio sube.",[12,3631,3632,3634],{},[27,3633,3610],{}," ya estás usando Traefik como router de entrada y quieres evitar añadir un componente más; necesitas middlewares razonables pero no del catálogo gigante de Kong; valoras configuración declarativa por etiqueta en lugar de base de datos.",[12,3636,3637,3639],{},[27,3638,3616],{}," algunos patrones avanzados (agregación de llamadas, validación OpenAPI completa, portal de documentación interactivo) no caben en Traefik. Si necesitas eso, la tentación de \"estirar\" Traefik vía plug-ins customizados lleva a complejidad que Kong resolvería de forma más limpia.",[368,3641,3643],{"id":3642},"tyk-escrito-en-go-foco-en-portal-de-desarrollador","Tyk (escrito en Go, foco en portal de desarrollador)",[12,3645,3646],{},"La versión de código abierto entrega mucho más que la mayoría — límite de peticiones por clave, gestión de claves, portal de desarrollador, todos en el plan gratis. La versión pagada añade panel multi-tenant, replicación multi-región, y soporte.",[12,3648,3649,3651],{},[27,3650,3604],{}," 100 MB de RAM, latencia añadida de 1 a 2 milisegundos. Base de datos (Redis) es parte central de la arquitectura — límite de peticiones y contadores viven ahí.",[12,3653,3654,3656],{},[27,3655,3610],{}," API con muchos consumidores externos, portal de desarrollador es parte del producto, quieres pagar menos de lo que Kong cobra por el equivalente en recursos. Equipos pequeños publicando API para socios han encontrado buen encaje aquí.",[12,3658,3659,3661],{},[27,3660,3616],{}," menos plug-ins listos que Kong. Si tu integración esperada existe en la lista de Kong pero no en la de Tyk, el trade-off cambia.",[368,3663,3665],{"id":3664},"krakend-escrito-en-go-sin-base-de-datos-foco-en-agregacion","KrakenD (escrito en Go, sin base de datos, foco en agregación)",[12,3667,3668],{},"KrakenD es el gateway pequeño que se especializa en agregación. Configuración 100% en archivo, sin estado externo, diseñado para componer endpoints — el cliente hace una llamada, KrakenD llama a tres back-ends en paralelo y devuelve una respuesta combinada. Excelente para patrón back-end-for-frontend.",[12,3670,3671,3673],{},[27,3672,3604],{}," 50 MB de RAM, latencia añadida de 0,5 milisegundo. El más liviano de la categoría. Sin base, sin panel — todo es archivo de configuración estático.",[12,3675,3676,3678],{},[27,3677,3610],{}," tienes múltiples micro-servicios y quieres exponer una API más limpia al front-end mobile\u002Fweb. No necesitas gestión dinámica de claves ni portal de desarrollador. Te gusta la configuración inmutable: cambias archivo, haces deploy nuevo, listo.",[12,3680,3681,3683],{},[27,3682,3616],{}," todo es estático. Añadir una clave nueva es deploy. Para equipo pequeño eso es simplificación; para plataforma de API con terceros registrándose, se vuelve cuello de botella.",[368,3685,3687],{"id":3686},"envoy-gateway-cncf-sobre-proxy-envoy","Envoy Gateway (CNCF, sobre proxy Envoy)",[12,3689,3690],{},"El benjamín serio de la lista. Envoy es el proxy de altísimo rendimiento usado en mallas de servicio grandes. Envoy Gateway es el proyecto que empaqueta Envoy como gateway de API con configuración declarativa. Foco en Kubernetes, alta tasa de transferencia, integración con malla.",[12,3692,3693,3695],{},[27,3694,3604],{}," Envoy puro consume 50 a 100 MB en el proxy de datos; el plano de control pesa más. Latencia añadida baja (\u003C 1 milisegundo) en llamada simple. Pero la complejidad operacional es la más alta de la lista.",[12,3697,3698,3700],{},[27,3699,3610],{}," ya corres malla de servicio con Envoy (Istio, Consul, Linkerd con proxy compatible) y quieres consistencia de configuración entre malla y gateway. Operas en escala lo suficientemente alta para que la tasa de Envoy importe (decenas de miles de peticiones por segundo).",[12,3702,3703,3705],{},[27,3704,3616],{}," para startup con 4 servidores y algunas decenas de peticiones por segundo, Envoy Gateway es overkill por dos o tres tamaños. La complejidad de configuración no se paga.",[19,3707,3709],{"id":3708},"cuando-basta-un-reverse-proxy-simple","¿Cuándo basta un reverse proxy simple?",[12,3711,3712],{},"Esta es la pregunta que ahorra dinero. La respuesta honesta es: en la gran mayoría de los SaaS B2B que vemos corriendo, basta. Los criterios para \"basta\":",[2735,3714,3715,3721,3727,3733,3739,3745],{},[70,3716,3717,3720],{},[27,3718,3719],{},"Público de tu API es tu propia aplicación."," Web, mobile, integraciones internas. No hay terceros desconocidos llamando endpoints con claves emitidas por ti.",[70,3722,3723,3726],{},[27,3724,3725],{},"Autenticación ocurre en la aplicación, no en el camino."," Sesión por cookie, token JWT emitido por el propio back-end y validado por middleware de la aplicación, OAuth vía biblioteca dentro del código. El proxy no necesita ver al usuario.",[70,3728,3729,3732],{},[27,3730,3731],{},"Límite de peticiones es \"evita abuso tonto\"."," Treinta por segundo por IP, quizás. No hay plan comercial que limite Cliente A a 1.000 llamadas\u002Fdía y Cliente B a 10.000.",[70,3734,3735,3738],{},[27,3736,3737],{},"No necesitas combinar back-ends."," Cada llamada del front-end va a un endpoint, ese endpoint llama a lo que necesita internamente. Sin agregación en el camino.",[70,3740,3741,3744],{},[27,3742,3743],{},"Documentación de la API es interna o inexistente."," No hay portal de desarrollador con tentar-aquí para terceros.",[70,3746,3747,3750],{},[27,3748,3749],{},"Versionado, si existe, se gestiona en código."," El back-end enruta internamente entre v1 y v2 cuando es necesario. No hay política formal en el gateway.",[12,3752,3753],{},"Si cinco de los seis items de arriba son verdad, instalar gateway dedicado es caro por el beneficio real. Reverse proxy embebido en el orquestador, o Caddy\u002Fnginx standalone, cubre todo.",[19,3755,3757],{"id":3756},"cuando-vale-la-pena-un-gateway-dedicado","¿Cuándo vale la pena un gateway dedicado?",[12,3759,3760],{},"La inversión de la lista anterior. Gateway compensa cuando aparecen algunos de estos:",[2735,3762,3763,3769,3775,3781,3795,3801,3807],{},[70,3764,3765,3768],{},[27,3766,3767],{},"API pública es parte del producto."," Cobras (o planeas cobrar) por uso de API. Terceros se registran, reciben clave, consumen.",[70,3770,3771,3774],{},[27,3772,3773],{},"Límite por clave\u002Fusuario\u002Fruta es regla de negocio."," Plan gratis tiene techo, plan pagado tiene techo mayor, plan enterprise se negocia. Ese límite necesita vivir en algún lugar — gateway es el lugar correcto.",[70,3776,3777,3780],{},[27,3778,3779],{},"Múltiples back-ends necesitan combinarse en una respuesta."," Patrón back-end-for-frontend, agregación de micro-servicios, fan-out y fan-in. Costes altos en la aplicación, costes modestos en el gateway.",[70,3782,3783,3786,3787,3790,3791,3794],{},[27,3784,3785],{},"Versionado formal de API."," Soportas v1 y v2 simultáneamente, con fecha de descontinuación anunciada. Cabecera ",[231,3788,3789],{},"Accept-Version"," o ruta ",[231,3792,3793],{},"\u002Fv2\u002F",". Cliente legacy no puede romperse.",[70,3796,3797,3800],{},[27,3798,3799],{},"Autenticación compleja."," Validación de JWT emitido por tercero, con claves públicas bajadas y cacheadas, con rotación automática. OAuth con varios proveedores. Autenticación por certificado de cliente (mTLS) para integraciones entre empresas.",[70,3802,3803,3806],{},[27,3804,3805],{},"Portal de desarrollador con tentar-aquí."," Documentación interactiva, gestión de clave self-service, panel de uso para el consumidor.",[70,3808,3809,3812],{},[27,3810,3811],{},"Métricas por clave de API."," Quién llama qué, cuándo, latencia por consumidor. Dashboards comerciales, informes de uso, SLA por cliente.",[12,3814,3815],{},"Tres o más de esos criterios verdaderos, gateway es defendible. Uno o dos, aún se puede resolver de otras formas (autenticación en la aplicación, límite por aplicación, métricas estructuradas en el log).",[19,3817,3819],{"id":3818},"router-integrado-de-heroctl-donde-queda-en-esa-regla","Router integrado de HeroCtl — dónde queda en esa regla",[12,3821,3822],{},"El router embebido en HeroCtl no intenta ser gateway. Cubre el lado de reverse proxy bien hecho: HTTPS terminado, Let's Encrypt automático con renovación, enrutamiento por host y ruta, balanceo entre las réplicas que el orquestador subió, health check coordinado con el agente en cada nodo, compresión, cabeceras de proxy, límite básico por IP, política de reintento en caso de fallo de back-end.",[12,3824,3825,3826,3828],{},"Lo que el router integrado ",[27,3827,100],{}," hace: validación de clave de API por consumidor, límite por clave\u002Fusuario, transformación de cuerpo, agregación de back-ends, validación de esquema OpenAPI, portal de desarrollador. Para los 80% de los casos donde el cliente final es navegador o app mobile de la propia empresa, el router embebido cubre el camino de entrada entero — no instalas nada más al frente.",[12,3830,3831],{},"Para los 20% que necesitan gateway dedicado, el camino es directo: instala Kong, Traefik standalone, Tyk o KrakenD como un job más en el cluster, detrás del router embebido. El router termina TLS en la borda, el gateway hace el trabajo de gateway, los back-ends quedan detrás. Sin ceremonia, sin dependencia circular.",[12,3833,3834],{},"El plano de control de HeroCtl ocupa entre 200 y 400 MB por servidor — o sea, un Kong instalado añade prácticamente el mismo peso del plano de control entero. Vale recordar el orden de magnitud antes de \"solo instalar\".",[19,3836,3838],{"id":3837},"tabla-comparativa-12-criterios","Tabla comparativa — 12 criterios",[119,3840,3841,3868],{},[122,3842,3843],{},[125,3844,3845,3847,3850,3853,3856,3859,3862,3865],{},[128,3846,2983],{},[128,3848,3849],{},"Reverse proxy simple (Caddy\u002Fnginx)",[128,3851,3852],{},"Router HeroCtl",[128,3854,3855],{},"Traefik standalone",[128,3857,3858],{},"KrakenD",[128,3860,3861],{},"Tyk OSS",[128,3863,3864],{},"Kong OSS",[128,3866,3867],{},"Envoy Gateway",[141,3869,3870,3896,3920,3941,3960,3979,3998,4018,4037,4058,4079,4100],{},[125,3871,3872,3875,3878,3881,3884,3887,3890,3893],{},[146,3873,3874],{},"RAM mínima",[146,3876,3877],{},"20–50 MB",[146,3879,3880],{},"embebido en el plano de control",[146,3882,3883],{},"50–100 MB",[146,3885,3886],{},"~50 MB",[146,3888,3889],{},"~100 MB",[146,3891,3892],{},"~200 MB",[146,3894,3895],{},"~100 MB + plano de control",[125,3897,3898,3901,3904,3906,3909,3912,3915,3917],{},[146,3899,3900],{},"Latencia añadida",[146,3902,3903],{},"\u003C 0,5 ms",[146,3905,3903],{},[146,3907,3908],{},"0,5–1 ms",[146,3910,3911],{},"~0,5 ms",[146,3913,3914],{},"1–2 ms",[146,3916,3914],{},[146,3918,3919],{},"\u003C 1 ms (puede crecer)",[125,3921,3922,3925,3928,3930,3932,3935,3937,3939],{},[146,3923,3924],{},"Certificados automáticos",[146,3926,3927],{},"Sí (Caddy nativo)",[146,3929,3065],{},[146,3931,3065],{},[146,3933,3934],{},"No directo",[146,3936,3065],{},[146,3938,3065],{},[146,3940,3065],{},[125,3942,3943,3946,3948,3950,3952,3954,3956,3958],{},[146,3944,3945],{},"Enrutamiento host\u002Fruta",[146,3947,3065],{},[146,3949,3065],{},[146,3951,3065],{},[146,3953,3065],{},[146,3955,3065],{},[146,3957,3065],{},[146,3959,3065],{},[125,3961,3962,3965,3967,3969,3971,3973,3975,3977],{},[146,3963,3964],{},"Balanceo + health",[146,3966,3065],{},[146,3968,3065],{},[146,3970,3065],{},[146,3972,3065],{},[146,3974,3065],{},[146,3976,3065],{},[146,3978,3065],{},[125,3980,3981,3984,3986,3988,3990,3992,3994,3996],{},[146,3982,3983],{},"Límite por IP",[146,3985,3065],{},[146,3987,3065],{},[146,3989,3065],{},[146,3991,3065],{},[146,3993,3065],{},[146,3995,3065],{},[146,3997,3065],{},[125,3999,4000,4003,4005,4007,4010,4012,4014,4016],{},[146,4001,4002],{},"Límite por clave\u002Fusuario",[146,4004,3059],{},[146,4006,3059],{},[146,4008,4009],{},"Sí (con middleware)",[146,4011,3065],{},[146,4013,3065],{},[146,4015,3065],{},[146,4017,3065],{},[125,4019,4020,4023,4025,4027,4029,4031,4033,4035],{},[146,4021,4022],{},"Validación de JWT",[146,4024,3059],{},[146,4026,3059],{},[146,4028,3140],{},[146,4030,3065],{},[146,4032,3065],{},[146,4034,3065],{},[146,4036,3065],{},[125,4038,4039,4042,4044,4046,4048,4051,4053,4056],{},[146,4040,4041],{},"Agregación de back-ends",[146,4043,3059],{},[146,4045,3059],{},[146,4047,3059],{},[146,4049,4050],{},"Sí (foco)",[146,4052,3140],{},[146,4054,4055],{},"Sí (con plug-in)",[146,4057,3065],{},[125,4059,4060,4063,4065,4067,4069,4072,4074,4077],{},[146,4061,4062],{},"Validación OpenAPI",[146,4064,3059],{},[146,4066,3059],{},[146,4068,3059],{},[146,4070,4071],{},"Sí (suscriptor)",[146,4073,3065],{},[146,4075,4076],{},"Sí (plug-in)",[146,4078,3065],{},[125,4080,4081,4084,4086,4088,4090,4092,4095,4098],{},[146,4082,4083],{},"Portal de desarrollador",[146,4085,3059],{},[146,4087,3059],{},[146,4089,3059],{},[146,4091,3059],{},[146,4093,4094],{},"Sí (incluido)",[146,4096,4097],{},"Sí (pagado en el OSS robusto)",[146,4099,3059],{},[125,4101,4102,4105,4108,4111,4114,4116,4119,4122],{},[146,4103,4104],{},"Configuración",[146,4106,4107],{},"Archivo",[146,4109,4110],{},"Panel + API",[146,4112,4113],{},"Etiquetas\u002Farchivo",[146,4115,4107],{},[146,4117,4118],{},"Archivo + panel",[146,4120,4121],{},"Archivo + panel + base",[146,4123,4124],{},"Custom Resources",[12,4126,4127,4128,4131],{},"La tabla tiene zonas claras. Las tres primeras columnas resuelven el camino de entrada con peso bajo. Las cuatro últimas resuelven camino de entrada ",[27,4129,4130],{},"más"," trabajo de gateway, con peso y complejidad crecientes.",[19,4133,4135],{"id":4134},"stack-tipica-por-etapa-de-la-empresa","Stack típica por etapa de la empresa",[12,4137,4138],{},"Esta es la regla que recomendamos. No es prescripción rígida — es lo que vemos funcionar en equipos de SaaS.",[12,4140,4141,4144],{},[27,4142,4143],{},"MVP (1 back-end, 1 desarrollador)."," Caddy standalone en un servidor, o router embebido si ya estás en orquestador. No instalas nada. No piensas en gateway. Foco en el producto.",[12,4146,4147,4150],{},[27,4148,4149],{},"Indie hacker (3 a 5 back-ends, equipo de 1 a 3)."," Router embebido en el orquestador, punto. El camino de entrada ya cubre lo que importa. Autenticación en la aplicación, límite básico por IP en el proxy. Tiempo gastado con gateway es tiempo no gastado en feature del producto.",[12,4152,4153,4156],{},[27,4154,4155],{},"Startup early (10 a 20 back-ends, primeros consumidores externos de la API)."," Hora de evaluar. Si la API externa es experimento que aún puede morir, deja la autenticación en la aplicación y límite por clave en una biblioteca compartida. Si la API es parte central de la promesa del producto, instala Traefik standalone con middlewares de autenticación y límite, o Tyk OSS por el portal incluido. Kong en esta fase suele ser pesado de más.",[12,4158,4159,4162],{},[27,4160,4161],{},"Startup mid (50+ back-ends, plataforma de API pública se vuelve producto)."," Kong OSS o Tyk pagado. Necesitas plug-ins, portal robusto, gestión de clave self-service, métricas comerciales. El peso de Kong ahora se justifica — estás cobrando por uso de API y el gateway es ingreso, no coste.",[12,4164,4165,4168],{},[27,4166,4167],{},"Empresa grande (cientos de servicios, integraciones con socios serios)."," Kong Enterprise o Envoy Gateway, dependiendo del contexto. Equipo dedicado cuidando del gateway. Política formal de versionado, descontinuación, SLA por cliente.",[12,4170,4171],{},"La migración natural — reverse proxy → Traefik\u002FTyk → Kong — funciona porque cada peldaño resuelve dolor real del peldaño anterior. Saltar peldaños es caro: instalar Kong en la fase de MVP es poner un camión para entregar una pizza.",[19,4173,4175],{"id":4174},"los-4-errores-caros-mas-comunes-con-gateway","Los 4 errores caros más comunes con gateway",[12,4177,4178],{},"Los tropiezos que vemos en producción:",[12,4180,4181,4184],{},[27,4182,4183],{},"Instalar Kong con PostgreSQL en el camino crítico sin necesidad."," El modo sin-base existe y es perfecto para la mayoría de los casos. Configuración declarativa vía archivo, sin dependencia externa, sin punto extra de fallo. Muchos equipos caen en la configuración por defecto con base y descubren eso solo cuando la base queda indisponible y el gateway no consigue más propagar cambios. Si necesitas gestión dinámica de claves (consumidores registrándose self-service), base compensa. Si no, modo sin-base.",[12,4186,4187,4190],{},[27,4188,4189],{},"No monitorizar el gateway con la misma severidad que los back-ends."," Gateway al frente se vuelve caja-negra de fácil olvido. Latencia crece 5 ms, tasa de error va de 0,01% a 0,5%, nadie lo nota hasta que el cliente reclama. Métricas de gateway (latencia por ruta, tasa de error 4xx\u002F5xx, uso de memoria, lag de propagación de configuración) merecen dashboard propio y alertas, no solo inclusión tímida en el panel general.",[12,4192,4193,4196],{},[27,4194,4195],{},"Plug-in customizado en Lua\u002FJS corriendo en producción."," Kong permite plug-in customizado en Lua. Tyk en JavaScript. Tentación enorme para resolver \"solo esa transformación\" en el gateway. Bug en ese plug-in derriba el gateway entero — bug que tú creaste, sin test de carga, en el camino crítico de todo. Si necesitas transformación custom, hazla en micro-servicio detrás del gateway. Plug-in customizado solo con revisión seria, test de carga, y plan de rollback automático.",[12,4198,4199,4202],{},[27,4200,4201],{},"Versión del gateway desactualizada."," Kong, Envoy y Tyk reciben CVEs (vulnerabilidades de seguridad) con regularidad. Gateway está expuesto a internet — superficie de ataque relevante. Versión de hace 18 meses es vulnerabilidad conocida acumulando. Hazlo parte del ciclo de mantenimiento: actualizar el gateway es tan importante como actualizar el sistema operativo.",[19,4204,4206],{"id":4205},"escenarios-reales-en-los-que-no-instalar-gateway","Escenarios reales en los que NO instalar gateway",[12,4208,4209],{},"Lista fuerte. Si estás en alguno de estos, evita el gateway dedicado aunque el tema aparezca en el consejo:",[2735,4211,4212,4218,4224,4230,4236],{},[70,4213,4214,4217],{},[27,4215,4216],{},"SaaS web con 5 endpoints, sin API externa pública."," Cliente final es el navegador. Autenticación por sesión. Reverse proxy resuelve el camino de entrada entero. Añadir gateway aquí es vanidad arquitectónica.",[70,4219,4220,4223],{},[27,4221,4222],{},"Equipo pequeño (1 a 3 desarrolladores)."," Curva de aprendizaje de Kong cuesta de dos a cuatro semanas de productividad total del equipo. En equipo de 3 personas, eso es trimestre de feature parado. A no ser que el gateway resuelva dolor concreto hoy, posponlo.",[70,4225,4226,4229],{},[27,4227,4228],{},"Workload donde latencia sub-10ms es requisito duro."," Trading de baja latencia, subasta en tiempo real, juego multijugador. Cada milisegundo cuenta. Gateway añade 1 a 3 ms — en workload sensible, es caro. Coloca la inteligencia en la aplicación.",[70,4231,4232,4235],{},[27,4233,4234],{},"Aplicación monolítica sin agregación."," El monolito atiende al front-end directo, sin composición entre servicios. No hay qué agregar. Gateway es solución buscando problema.",[70,4237,4238,4241],{},[27,4239,4240],{},"Cumplimiento que prefiere superficie de ataque mínima."," Cada componente expuesto a internet es un item más para auditoría, un parche más a aplicar, un log más a guardar. Si la auditoría valora minimalismo, justifica cada componente — gateway que no cubre dolor concreto es minus.",[19,4243,4245],{"id":4244},"preguntas-frecuentes","Preguntas frecuentes",[12,4247,4248,4251,4252,4255],{},[27,4249,4250],{},"¿Kong sin base es estable en 2026?","\nSí. El modo declarativo (",[231,4253,4254],{},"db_less = on",") es maduro, recomendado por el propio Kong para gran parte de los casos, y elimina PostgreSQL como dependencia. Pierdes gestión dinámica de clave vía API admin (necesitas hacer deploy de configuración nueva), pero ganas simplicidad operacional enorme. Para equipo pequeño, el cambio casi siempre vale.",[12,4257,4258,4261],{},[27,4259,4260],{},"¿Traefik hace todo lo que Kong hace?","\nNo. Traefik con middlewares cubre la mayoría de los casos comunes — autenticación básica, límite por clave simple, transformación de cabecera, reenvío de autenticación. No cubre catálogo de plug-ins de Kong, validación OpenAPI nativa, portal de desarrollador robusto, integraciones comerciales listas. Si tu dolor cabe en middleware de Traefik, queda en Traefik (más liviano, más simple). Si necesitas cosa del catálogo de Kong, Kong.",[12,4263,4264,4267],{},[27,4265,4266],{},"¿Puedo tener dos gateways en serie?","\nTécnicamente sí, en la práctica casi siempre es síntoma de organización confusa. Dos gateways = dos configuraciones para mantener, dos latencias sumadas, dos puntos de fallo. El caso defendible es: router de borda haciendo TLS y enrutamiento básico, gateway dedicado detrás haciendo trabajo específico (validación de clave, agregación). Eso es diferente de \"dos gateways completos en serie\" — es división de responsabilidad.",[12,4269,4270,4273,4274,101],{},[27,4271,4272],{},"¿API gateway sustituye malla de servicio?","\nNo. Gateway cuida del tráfico norte-sur (cliente externo → tu sistema). Malla cuida del tráfico este-oeste (servicio interno → servicio interno). Funciones parecidas (autenticación, límite, observabilidad) pero alcance diferente. Para startup media, gateway resuelve la parte que importa; malla de servicio completa solo se vuelve inversión defendible en escala mayor. Tratamos ese límite en ",[3337,4275,4277],{"href":4276},"\u002Fes\u002Fblog\u002Fservice-mesh-cuando-vale-la-pena-en-saas-pequeno","service mesh: cuándo vale la pena en SaaS pequeño y mediano",[12,4279,4280,4283],{},[27,4281,4282],{},"¿Cuánta latencia añade Kong en llamada típica?","\nEn hardware moderno, con configuración por defecto y plug-ins livianos (validación de clave + límite por clave): 1 a 2 milisegundos por petición. Plug-ins pesados (validación OpenAPI completa en payload grande, transformación JSON compleja, log síncrono a servicio externo) pueden sumar 3 a 10 ms. Mide antes y después — no confíes en número de blog post genérico.",[12,4285,4286,4289],{},[27,4287,4288],{},"Proveedor de OAuth auto-hospedado — ¿Keycloak o Hydra?","\nKeycloak es el estándar para quien quiere panel de administración robusto, federación con LDAP\u002FSAML, gestión de usuario completa. Pesa más (1 GB de RAM mínimo, JVM). Hydra es minimalista, se enfoca solo en OAuth\u002FOIDC, sin gestión de usuario (integras con tu sistema de usuarios existente). Para equipo pequeño que ya tiene sistema de usuario propio, Hydra es más adecuado. Para empresa que quiere un único lugar para identidad, Keycloak. Los dos hablan protocolos estándar, así que el gateway no diferencia entre ellos.",[12,4291,4292,4295],{},[27,4293,4294],{},"Validación de esquema — ¿OpenAPI o JSON Schema?","\nOpenAPI (anteriormente Swagger) es el estándar para describir API HTTP — engloba rutas, métodos, petición y respuesta. Incluye JSON Schema para describir payloads. Kong, Tyk y validadores standalone hablan OpenAPI directo. JSON Schema puro es más portable (no atado a HTTP) pero exige más pegamento. Usa OpenAPI cuando el gateway lo soporte; vale tener el esquema del contrato vivo, no desactualizado.",[12,4297,4298,4301,4302,4305,4306,4309],{},[27,4299,4300],{},"¿Puedo hacer límite en la aplicación en lugar del gateway?","\nPuedes. Bibliotecas como ",[231,4303,4304],{},"golang.org\u002Fx\u002Ftime\u002Frate"," o Redis con ",[231,4307,4308],{},"INCR"," resuelven límite por usuario a nivel de la aplicación. La cuestión es dónde el límite es más barato: en el gateway, antes de que el back-end sea tocado (ahorra recursos del back-end, aplica antes del trabajo empezar) o en la aplicación, con regla de negocio más cerca del código (más fácil de razonar, más fácil de testear). Para límite simple, aplicación basta. Para límite por plan comercial con múltiples niveles y auditoría, gateway es el lugar correcto.",[12,4311,4312,4315],{},[27,4313,4314],{},"¿Puedo usar dos gateways diferentes en rutas distintas?","\nPuedes. Algunas empresas corren Kong para rutas \"producto\" (API pública vendida) y Traefik para rutas \"interno\" (admin, ops, cron). La justificación es que cada gateway resuelve un dolor diferente, y tener uno solo forzaría compromiso. Vale cuando los perfiles de uso de hecho divergen. No vale solo por el placer de tener variedad — dos piezas para mantener.",[19,4317,4319],{"id":4318},"cierre-empieza-por-el-minimo-sube-cuando-el-dolor-sea-real","Cierre — empieza por el mínimo, sube cuando el dolor sea real",[12,4321,4322],{},"La trampa de la categoría \"API gateway\" es tratar la decisión como binaria — instalar o no — cuando es gradual. Reverse proxy bien hecho cubre 80% de las aplicaciones. Router integrado en el orquestador cubre las mismas 80% sin componente separado. Gateway dedicado es inversión defendible cuando aparecen tres o cuatro señales simultáneas: API externa pública, límite por clave, agregación, portal de desarrollador.",[12,4324,4325],{},"La regla honesta: instala el mínimo hasta que el dolor concreto fuerce el siguiente peldaño. Saltar peldaños cuesta caro en latencia, RAM, complejidad, área de fallo. Equipo pequeño que instala Kong \"porque va a necesitar\" pasa tres semanas configurando algo que no usa, y aún así tiene un componente más para monitorizar.",[12,4327,4328],{},"HeroCtl entrega el peldaño más bajo embebido — router integrado con TLS automático, balanceo, health check, límite por IP. Cuando el dolor de gateway aparezca de verdad, subes Kong, Traefik standalone, Tyk o KrakenD como un job más en el cluster. Sin migración dolorosa, sin ceremonia.",[12,4330,4331],{},"Para subir un cluster y probar:",[224,4333,4334],{"className":226,"code":2949,"language":228,"meta":229,"style":229},[231,4335,4336],{"__ignoreMap":229},[234,4337,4338,4340,4342,4344,4346],{"class":236,"line":237},[234,4339,1220],{"class":247},[234,4341,2958],{"class":251},[234,4343,2961],{"class":255},[234,4345,2964],{"class":383},[234,4347,2967],{"class":247},[12,4349,4350,4353,4354,4357,4358,4361],{},[27,4351,4352],{},"Community"," es gratuito para siempre, sin techo de servidores, sin techo de jobs, sin feature gate. ",[27,4355,4356],{},"Business"," añade SSO\u002FSAML, RBAC granular, auditoría detallada y soporte con SLA. ",[27,4359,4360],{},"Enterprise"," suma escrow de código fuente, contrato de continuidad y soporte 24×7.",[12,4363,4364,4365,2403,4367,4371],{},"Posts próximos: ",[3337,4366,4277],{"href":4276},[3337,4368,4370],{"href":4369},"\u002Fes\u002Fblog\u002Fmulti-tenant-saas-aislamiento-real","multi-tenant SaaS: aislamiento real entre clientes",". Los tres temas juntos cubren la mayor parte de las decisiones de plataforma para startup en la franja de 1 a 500 servidores.",[12,4373,4374],{},"Orquestación de contenedores, sin ceremonia. Gateway solo cuando el dolor lo pida.",[3351,4376,4377],{},"html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":229,"searchDepth":244,"depth":244,"links":4379},[4380,4381,4382,4389,4390,4391,4392,4393,4394,4395,4396,4397],{"id":3409,"depth":244,"text":3410},{"id":3430,"depth":244,"text":3431},{"id":3588,"depth":244,"text":3589,"children":4383},[4384,4385,4386,4387,4388],{"id":3595,"depth":271,"text":3596},{"id":3620,"depth":271,"text":3621},{"id":3642,"depth":271,"text":3643},{"id":3664,"depth":271,"text":3665},{"id":3686,"depth":271,"text":3687},{"id":3708,"depth":244,"text":3709},{"id":3756,"depth":244,"text":3757},{"id":3818,"depth":244,"text":3819},{"id":3837,"depth":244,"text":3838},{"id":4134,"depth":244,"text":4135},{"id":4174,"depth":244,"text":4175},{"id":4205,"depth":244,"text":4206},{"id":4244,"depth":244,"text":4245},{"id":4318,"depth":244,"text":4319},"2026-06-03","El API gateway resuelve auth, rate limit, transformaciones y observabilidad — a cambio de un componente crítico más. Cuándo basta un reverse proxy simple vs cuándo vale el gateway dedicado.",{},"\u002Fes\u002Fblog\u002Fapi-gateway-auto-hospedado-cuando-instalar","13 min",{"title":3398,"description":4399},{"loc":4401},"es\u002Fblog\u002Fapi-gateway-auto-hospedado-cuando-instalar",[4407,4408,4409,3379,4410],"api-gateway","kong","traefik","arquitectura","DQw030gmc7XmXLeSlClU41twoCW00I44K4a7nhuqWGQ",{"id":4413,"title":4414,"author":7,"body":4415,"category":3379,"cover":3380,"date":5369,"description":5370,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":5371,"navigation":411,"path":4276,"readingTime":4402,"seo":5372,"sitemap":5373,"stem":5374,"tags":5375,"__hash__":5379},"blog_es\u002Fes\u002Fblog\u002Fservice-mesh-cuando-vale-la-pena-en-saas-pequeno.md","¿Service mesh es overkill en SaaS pequeño? Cuándo vale instalar Istio\u002FLinkerd",{"type":9,"value":4416,"toc":5351},[4417,4420,4424,4427,4430,4434,4437,4527,4530,4534,4537,4576,4579,4583,4586,4620,4623,4627,4630,4706,4709,4735,4738,4742,4745,4762,4765,4772,4776,4779,4782,4802,4805,4819,4822,4826,4829,5027,5034,5038,5041,5067,5070,5074,5077,5109,5113,5116,5142,5145,5149,5152,5172,5175,5179,5182,5202,5205,5209,5212,5249,5251,5257,5266,5272,5278,5284,5290,5296,5302,5308,5310,5313,5316,5334,5346,5349],[12,4418,4419],{},"La pregunta llega siempre con el mismo formato. Un tech lead de un SaaS con seis u ocho servicios corriendo lee tres posts en inglés sobre malla de servicio, ve a toda la industria americana usando Istio, y abre el terminal para instalar — junto con la duda: \"¿esto no es demasiado para el tamaño de mi empresa?\". Probablemente lo es. Pero la respuesta honesta exige separar cuatro problemas que la malla de servicio resuelve, mostrar el coste en RAM y CPU por servidor, y describir el punto exacto en que el beneficio pasa a superar el overhead.",[19,4421,4423],{"id":4422},"tldr-service-mesh-vale-la-pena-en-saas-pequenomediano","TL;DR — ¿Service mesh vale la pena en SaaS pequeño\u002Fmediano?",[12,4425,4426],{},"Malla de servicio (Istio, Linkerd, Cilium Service Mesh, Consul Connect) resuelve cuatro problemas reales entre servicios de una arquitectura de microservicios: cifrado automático (llamada entre pods sin TLS por default filtra tráfico en texto puro), retries y circuit breakers (resiliencia configurable), observabilidad granular (qué servicio llama a cuál, con qué latencia), y traffic shaping para canary releases. A cambio, añade un proxy paralelo en cada pod (normalmente Envoy) que consume entre 50 y 100 MB de RAM y añade 5 a 10 ms de latencia por llamada interna.",[12,4428,4429],{},"Para startup con menos de diez servicios activos y menos de cincuenta pods, malla de servicio es overkill — el overhead operacional supera el beneficio, y el equipo gasta semanas estudiando una capa que resuelve un problema que aún no tiene. Para empresa con cincuenta o más microservicios donde diagnosticar \"¿qué servicio está atrasando la llamada?\" lleva horas, malla paga en productividad. El término medio son clusters con cifrado entre servicios embebido en el propio plano de control — cubren cerca de 60% de lo que malla ofrece sin el sidecar paralelo, y atienden la mayoría de los casos hasta el rango de los treinta servicios.",[19,4431,4433],{"id":4432},"lo-que-service-mesh-resuelve-en-una-frase","Lo que service mesh resuelve, en una frase",[12,4435,4436],{},"Antes de discutir coste, es necesario ser claro sobre lo que está siendo comprado. Malla de servicio es una capa de red que se entromete en cada llamada entre servicios y añade seis comportamientos:",[2735,4438,4439,4452,4464,4476,4503,4512],{},[70,4440,4441,4444,4445,2630,4448,4451],{},[27,4442,4443],{},"Cifrado automático entre pods."," Sin malla, una llamada de ",[231,4446,4447],{},"pedidos",[231,4449,4450],{},"usuarios"," dentro del cluster transita en HTTP simple. Cualquier agente con acceso a la red del nodo ve el contenido. Con malla, cada llamada es cifrada con certificados emitidos automáticamente, sin alteración en el código de la aplicación.",[70,4453,4454,4457,4458,4460,4461,4463],{},[27,4455,4456],{},"Retries automáticos en llamadas internas."," Cuando ",[231,4459,4447],{}," llama a ",[231,4462,4450],{}," y el primer intento falla por una flap de red de 200 ms, la malla reenvía. Sin malla, la aplicación necesita implementar esa lógica en cada cliente HTTP que crea.",[70,4465,4466,4469,4470,4472,4473,4475],{},[27,4467,4468],{},"Circuit breakers configurables."," Si ",[231,4471,4450],{}," empieza a responder con latencia de cinco segundos, la malla abre el circuito y hace que ",[231,4474,4447],{}," falle rápido en lugar de apilar conexiones. Sin malla, el equipo necesita añadir una biblioteca en cada servicio.",[70,4477,4478,4481,4482,571,4485,4488,4489,4491,4492,4494,4495,2403,4497,4500,4501,101],{},[27,4479,4480],{},"Distributed tracing automático."," La malla propaga cabeceras de correlación (",[231,4483,4484],{},"x-request-id",[231,4486,4487],{},"traceparent",") por toda la cadena de llamadas. El equipo consigue ver, en un panel, que un request entró en el ",[231,4490,4407],{},", pasó por ",[231,4493,4447],{},", llamó a ",[231,4496,4450],{},[231,4498,4499],{},"stock",", y gastó la mayor parte del tiempo en ",[231,4502,4499],{},[70,4504,4505,4508,4509,4511],{},[27,4506,4507],{},"Traffic shaping fino."," Encaminar 5% del tráfico de ",[231,4510,4447],{}," a una versión nueva (canary), espejar 100% a una versión de test sin afectar al cliente (mirror), o alternar entre dos versiones enteras (blue-green) — todo configurado declarativamente, sin código.",[70,4513,4514,4517,4518,2403,4520,4523,4524,4526],{},[27,4515,4516],{},"Authorization policies entre servicios."," Declarar que solo ",[231,4519,4447],{},[231,4521,4522],{},"reportes"," pueden llamar a ",[231,4525,4450],{},", y cualquier otro servicio recibe 403. Es la base de la llamada \"red zero-trust\" entre pods.",[12,4528,4529],{},"Esos seis comportamientos son reales y el valor es mensurable. La pregunta es si tu cluster de hoy tiene volumen y complejidad suficientes para justificar pagar por ellos.",[19,4531,4533],{"id":4532},"lo-que-no-es-problema-de-service-mesh","Lo que NO es problema de service mesh",[12,4535,4536],{},"Antes de avanzar, vale la pena eliminar cuatro problemas que muchos equipos confunden con motivo para instalar malla — y que orquestador moderno ya resuelve solo:",[2735,4538,4539,4551,4557,4566],{},[70,4540,4541,4544,4545,4547,4548,4550],{},[27,4542,4543],{},"Enrutamiento de entrada (ingress HTTP)."," Recibir tráfico externo, terminar TLS, rutear ",[231,4546,3456],{}," a un servicio y ",[231,4549,3460],{}," a otro. Eso es trabajo de router integrado al orquestador, no de malla.",[70,4552,4553,4556],{},[27,4554,4555],{},"Balanceo de carga simple."," Distribuir requests entre tres réplicas de un mismo servicio con round-robin. Orquestador hace eso con DNS interno y health checks. Malla agrega solo cuando la política de balanceo necesita ser sofisticada (peso por región, sticky sessions complejas).",[70,4558,4559,4562,4563,4565],{},[27,4560,4561],{},"Service discovery."," Encontrar dónde ",[231,4564,4450],{}," está corriendo. DNS interno del cluster resuelve. Malla no trae nada nuevo aquí.",[70,4567,4568,4571,4572,4575],{},[27,4569,4570],{},"Terminación HTTP\u002FHTTPS en la borda."," Ingress controller resuelve. Malla cuida del tráfico ",[179,4573,4574],{},"entre"," servicios, no de la entrada.",[12,4577,4578],{},"Quien instala malla esperando que resuelva esos cuatro está pagando dos veces por el mismo trabajo.",[19,4580,4582],{"id":4581},"los-cuatro-jugadores-principales","Los cuatro jugadores principales",[12,4584,4585],{},"Cuatro productos dominan esa categoría en 2026. Las diferencias importan cuando el tradeoff es overhead vs features.",[2735,4587,4588,4598,4608,4614],{},[70,4589,4590,4593,4594,4597],{},[27,4591,4592],{},"Istio."," El más antiguo, más completo, más documentado — y más pesado. Usa Envoy como sidecar en cada pod. Patrón de facto en empresas grandes que adoptaron malla entre 2019 y 2022. La versión Ambient Mode (sin sidecar, con ",[231,4595,4596],{},"ztunnel"," por nodo) reduce overhead, pero aún está estabilizando en producción.",[70,4599,4600,4603,4604,4607],{},[27,4601,4602],{},"Linkerd."," Foco en simplicidad. Proxy propio escrito en Rust (",[231,4605,4606],{},"linkerd2-proxy","), bien más ligero que Envoy. Curva de aprendizaje corta — instalación cabe en un par de comandos. CNCF graduado, pero con comunidad menor que Istio.",[70,4609,4610,4613],{},[27,4611,4612],{},"Cilium Service Mesh."," Aprovecha eBPF en el kernel para implementar buena parte de la malla sin sidecar. Overhead por pod cerca de cero. A cambio, el setup del cluster necesita kernel reciente y CNI compatible, y algunas features avanzadas (como autorización L7 sofisticada) aún dependen de proxy auxiliar.",[70,4615,4616,4619],{},[27,4617,4618],{},"Consul Connect."," De Hashicorp. Integra con la caja fuerte de secretos de la propia casa, y funciona bien en ambientes mixtos (VMs + contenedores). Comunidad menor que Istio\u002FLinkerd.",[12,4621,4622],{},"Existen otros (Kuma, Open Service Mesh, AWS App Mesh), pero concentrar en el cuarteto arriba cubre 95% de las decisiones reales que un tech lead va a enfrentar.",[19,4624,4626],{"id":4625},"cuanto-cuesta-en-ram-y-cpu","¿Cuánto cuesta en RAM y CPU?",[12,4628,4629],{},"La pregunta que decide la discusión.",[119,4631,4632,4648],{},[122,4633,4634],{},[125,4635,4636,4639,4642,4645],{},[128,4637,4638],{},"Malla",[128,4640,4641],{},"RAM por pod",[128,4643,4644],{},"CPU por pod",[128,4646,4647],{},"Latencia adicional",[141,4649,4650,4664,4678,4692],{},[125,4651,4652,4655,4658,4661],{},[146,4653,4654],{},"Istio (Envoy sidecar)",[146,4656,4657],{},"+80–120 MB",[146,4659,4660],{},"+10–15%",[146,4662,4663],{},"5–10 ms",[125,4665,4666,4669,4672,4675],{},[146,4667,4668],{},"Linkerd (linkerd2-proxy Rust)",[146,4670,4671],{},"+20–40 MB",[146,4673,4674],{},"+3–6%",[146,4676,4677],{},"1–3 ms",[125,4679,4680,4683,4686,4689],{},[146,4681,4682],{},"Cilium Service Mesh (eBPF)",[146,4684,4685],{},"~0 MB por pod",[146,4687,4688],{},"~2% en el nodo",[146,4690,4691],{},"\u003C1 ms",[125,4693,4694,4697,4700,4703],{},[146,4695,4696],{},"Consul Connect (Envoy sidecar)",[146,4698,4699],{},"+70–110 MB",[146,4701,4702],{},"+8–12%",[146,4704,4705],{},"4–8 ms",[12,4707,4708],{},"En cluster con cien pods activos:",[2735,4710,4711,4717,4723,4729],{},[70,4712,4713,4716],{},[27,4714,4715],{},"Istio"," consume cerca de 10 GB de RAM solo en proxies paralelos, antes de cualquier aplicación.",[70,4718,4719,4722],{},[27,4720,4721],{},"Linkerd"," consume cerca de 3 GB.",[70,4724,4725,4728],{},[27,4726,4727],{},"Cilium"," consume casi nada por pod, pero exige un agente por nodo (cerca de 200–400 MB cada).",[70,4730,4731,4734],{},[27,4732,4733],{},"Consul Connect"," queda cerca de Istio.",[12,4736,4737],{},"Para cluster típico de startup — cuatro servidores con 4 GB de RAM cada, totalizando 16 GB — Istio solo ocupa un tercio de la memoria del cluster antes de que ninguna línea de código corra. Linkerd ocupa un quinto. Cilium ocupa casi nada por pod, pero exige planificación de CNI.",[19,4739,4741],{"id":4740},"mi-startup-necesita-esto","¿Mi startup necesita esto?",[12,4743,4744],{},"Respuesta directa: probablemente no. Los criterios honestos para \"necesita\":",[2735,4746,4747,4750,4753,4756,4759],{},[70,4748,4749],{},"Treinta o más microservicios activos en producción.",[70,4751,4752],{},"Tráfico entre servicios es más de 50% del volumen HTTP total del cluster.",[70,4754,4755],{},"Más de un incidente al mes relacionado con \"qué servicio cayó, atrasó o está reventando timeout\".",[70,4757,4758],{},"Compliance formal exige red zero-trust entre pods (PCI-DSS nivel 1, ciertos contratos con Banco Central, frameworks de salud).",[70,4760,4761],{},"Equipo tiene al menos una persona dedicada a la plataforma, con tiempo para estudiar y operar la malla.",[12,4763,4764],{},"Si no pegas en al menos tres de esos cinco criterios, malla es overkill. La complejidad agregada no retorna en valor — retorna en llamadas en la guardia intentando entender por qué el sidecar está reciclando.",[12,4766,4767,4768,4771],{},"Criterio más importante y menos discutido: ",[27,4769,4770],{},"¿cuánto del tráfico es interno?",". Aplicación que recibe request en la borda, hace una única consulta en la base y responde, gasta 95% del tiempo entre cliente externo y base — no entre servicios. Aplicación que recibe request en la borda, llama a diez servicios internos para montar la respuesta, gasta la mayor parte del tiempo en el tráfico interno. Para la primera, malla no añade nada perceptible. Para la segunda, malla puede cortar horas de debugging por mes.",[19,4773,4775],{"id":4774},"el-sustituto-cluster-native","El sustituto cluster-native",[12,4777,4778],{},"Aquí vive la parte que el discurso americano subestima. En 2026, varios orquestadores modernos — incluyendo HeroCtl y algunas distribuciones del coloso ortodoxo — vienen con cifrado entre servicios embebido en el plano de control. Sin sidecar, sin proxy paralelo, sin instalar producto adicional.",[12,4780,4781],{},"Lo que eso cubre:",[2735,4783,4784,4790,4796],{},[70,4785,4786,4789],{},[27,4787,4788],{},"Cifrado entre servicios."," Cada servicio recibe certificado emitido automáticamente por el cluster. Llamada interna es cifrada por defecto.",[70,4791,4792,4795],{},[27,4793,4794],{},"Identidad de servicio."," Cada servicio se autentica por el certificado, no por IP o DNS.",[70,4797,4798,4801],{},[27,4799,4800],{},"Authorization básica."," Listas de quién puede llamar a quién, declarativas en el archivo de configuración del servicio.",[12,4803,4804],{},"Lo que eso NO cubre:",[2735,4806,4807,4810,4813,4816],{},[70,4808,4809],{},"Traffic shaping fino (canary con 5% de tráfico, mirror).",[70,4811,4812],{},"Distributed tracing completamente automático.",[70,4814,4815],{},"Circuit breakers configurables por llamada.",[70,4817,4818],{},"Políticas de retry sofisticadas.",[12,4820,4821],{},"Para startup mediana que estaba pensando en instalar malla solo para tener \"cifrado entre servicios\", el cluster-native ya basta. Cubre el tópico de auditoría más común sin costar 10 GB de RAM.",[19,4823,4825],{"id":4824},"lado-a-lado-sin-adornos","Lado a lado, sin adornos",[12,4827,4828],{},"La tabla compara Istio, Linkerd, Cilium y la opción de no instalar malla (con cifrado cluster-native activo) en doce criterios. No hay columna sin matiz.",[119,4830,4831,4847],{},[122,4832,4833],{},[125,4834,4835,4837,4839,4841,4844],{},[128,4836,2983],{},[128,4838,4715],{},[128,4840,4721],{},[128,4842,4843],{},"Cilium SM",[128,4845,4846],{},"Sin malla + cluster-native",[141,4848,4849,4863,4876,4891,4908,4924,4940,4953,4967,4981,4994,5010],{},[125,4850,4851,4854,4856,4858,4861],{},[146,4852,4853],{},"RAM overhead por pod",[146,4855,4657],{},[146,4857,4671],{},[146,4859,4860],{},"~0",[146,4862,4860],{},[125,4864,4865,4868,4870,4872,4874],{},[146,4866,4867],{},"CPU overhead por pod",[146,4869,4660],{},[146,4871,4674],{},[146,4873,4688],{},[146,4875,4860],{},[125,4877,4878,4881,4883,4885,4888],{},[146,4879,4880],{},"Complejidad de setup",[146,4882,3167],{},[146,4884,3155],{},[146,4886,4887],{},"Media (kernel)",[146,4889,4890],{},"Mínima",[125,4892,4893,4896,4899,4902,4905],{},[146,4894,4895],{},"Documentación en español",[146,4897,4898],{},"Buena",[146,4900,4901],{},"Razonable",[146,4903,4904],{},"Poca",[146,4906,4907],{},"Embebida en el orquestador",[125,4909,4910,4913,4916,4918,4921],{},[146,4911,4912],{},"Comunidad local",[146,4914,4915],{},"Grande",[146,4917,3160],{},[146,4919,4920],{},"Pequeña",[146,4922,4923],{},"Crece con el orquestador",[125,4925,4926,4929,4932,4935,4938],{},[146,4927,4928],{},"Sidecar paralelo",[146,4930,4931],{},"Sí (Envoy)",[146,4933,4934],{},"Sí (Rust)",[146,4936,4937],{},"No (eBPF)",[146,4939,3059],{},[125,4941,4942,4945,4947,4949,4951],{},[146,4943,4944],{},"Cifrado entre servicios automático",[146,4946,3065],{},[146,4948,3065],{},[146,4950,3065],{},[146,4952,3065],{},[125,4954,4955,4958,4960,4962,4964],{},[146,4956,4957],{},"Distributed tracing automático",[146,4959,3065],{},[146,4961,3065],{},[146,4963,3140],{},[146,4965,4966],{},"No (necesita OpenTelemetry)",[125,4968,4969,4972,4974,4976,4978],{},[146,4970,4971],{},"Traffic shaping fino (canary 5%)",[146,4973,3065],{},[146,4975,3065],{},[146,4977,3140],{},[146,4979,4980],{},"Básico (rolling, blue-green)",[125,4982,4983,4986,4988,4990,4992],{},[146,4984,4985],{},"Circuit breakers configurables",[146,4987,3065],{},[146,4989,3065],{},[146,4991,3062],{},[146,4993,3059],{},[125,4995,4996,4998,5001,5004,5007],{},[146,4997,3152],{},[146,4999,5000],{},"6–10 semanas",[146,5002,5003],{},"2–4 semanas",[146,5005,5006],{},"4–6 semanas",[146,5008,5009],{},"Días",[125,5011,5012,5015,5018,5021,5024],{},[146,5013,5014],{},"Rango de aplicación ideal",[146,5016,5017],{},"50+ servicios",[146,5019,5020],{},"10–50 servicios",[146,5022,5023],{},"30+ servicios con kernel nuevo",[146,5025,5026],{},"1–30 servicios",[12,5028,5029,5030,5033],{},"La columna que importa es la última línea — ",[27,5031,5032],{},"rango de aplicación ideal",". Quien está debajo del rango, paga overhead sin retorno. Quien está por encima, siente que falta feature.",[19,5035,5037],{"id":5036},"cuando-service-mesh-paga-el-precio","Cuándo service mesh paga el precio",[12,5039,5040],{},"Cuatro escenarios en que la inversión se justifica:",[2735,5042,5043,5049,5055,5061],{},[70,5044,5045,5048],{},[27,5046,5047],{},"Treinta o más microservicios activos."," La complejidad operacional sin malla se vuelve peor que con malla — diagnosticar una cadena de seis llamadas internas en tres equipos distintos es caro sin tracing automático.",[70,5050,5051,5054],{},[27,5052,5053],{},"Compliance enterprise con requisitos de zero-trust."," Algunos frameworks de auditoría piden que el stack tenga \"red zero-trust nominalmente\". Malla resuelve la checkbox formalmente.",[70,5056,5057,5060],{},[27,5058,5059],{},"Federación multi-cluster."," Enrutamiento de servicio entre dos o tres clusters en regiones distintas, con failover automático. Malla facilita ese escenario; cluster-native resuelve mal.",[70,5062,5063,5066],{},[27,5064,5065],{},"Equipo de plataforma con cinco o más personas dedicadas."," Tienes capacidad para extraer valor de la malla — operar, evolucionar, dimensionar el plano de control de ella. Sin ese equipo, la malla se vuelve pasivo.",[12,5068,5069],{},"Si pegas en dos o más de esos, empieza a evaluar. Empieza por Linkerd — es el que da menos dolor por menos retorno relativo perdido.",[19,5071,5073],{"id":5072},"cuando-no-instalar-la-mayoria-de-los-casos","Cuándo NO instalar (la mayoría de los casos)",[12,5075,5076],{},"Cinco escenarios en que instalar malla hoy cuesta más de lo que retorna:",[2735,5078,5079,5085,5091,5097,5103],{},[70,5080,5081,5084],{},[27,5082,5083],{},"Monolito con cinco a diez microservicios auxiliares."," Cero ganancia, coste grande. El overhead en RAM cae directamente en cuenta de servidor.",[70,5086,5087,5090],{},[27,5088,5089],{},"Equipo pequeño, menos de tres personas en plataforma."," Operar malla exige guardia dedicada para ella. Equipo pequeño absorbe ese coste a costa de feature de producto.",[70,5092,5093,5096],{},[27,5094,5095],{},"Cluster con menos de treinta pods totales."," Gestionar treinta pods es trabajo humano, no exige tracing automático. El coste de aprender la malla no retorna.",[70,5098,5099,5102],{},[27,5100,5101],{},"Workload HTTP simple sin requisitos de canary."," Si nunca necesitaste liberar 5% del tráfico a una versión nueva porque rolling update siempre sirvió, malla es solución para problema que no existe.",[70,5104,5105,5108],{},[27,5106,5107],{},"Coste de cluster bajo presión."," Si cada gigabyte de RAM está siendo contado, gastar 10 GB en sidecars es decisión difícil de defender al inversor.",[19,5110,5112],{"id":5111},"decision-evolutiva-por-etapa","Decisión evolutiva, por etapa",[12,5114,5115],{},"La decisión correcta cambia con el tamaño del sistema. Cuatro etapas:",[2735,5117,5118,5124,5130,5136],{},[70,5119,5120,5123],{},[27,5121,5122],{},"Etapa 1 — 1 a 10 servicios."," Sin malla. Si necesitas cifrado entre servicios, haz TLS en el código (la mayoría de los lenguajes tiene cliente HTTPS listo). No vale la pena el aprendizaje. Foca en entregar producto.",[70,5125,5126,5129],{},[27,5127,5128],{},"Etapa 2 — 10 a 30 servicios."," Cluster con cifrado embebido en el plano de control (HeroCtl, algunos presets del coloso). Resuelve cifrado + identidad + descubrimiento de servicio sin sidecar. Cubre la mayor parte de lo que malla ofrece, sin el coste.",[70,5131,5132,5135],{},[27,5133,5134],{},"Etapa 3 — 30 a 50 servicios con equipo de plataforma."," Evalúa Linkerd primero. Curva corta, overhead bajo, resuelve tracing y circuit breakers. Istio solo si features avanzadas (authorization L7 sofisticada, federación multi-cluster real) son requisito inmediato.",[70,5137,5138,5141],{},[27,5139,5140],{},"Etapa 4 — 50+ servicios, compliance enterprise."," Istio o Cilium Service Mesh. Compliance va a pedir una de las dos; resto son detalles.",[12,5143,5144],{},"Salir de una etapa a la próxima es decisión deliberada, no gradual. Añade el componente cuando el equipo acepta el aprendizaje y el cluster acepta el overhead. No antes.",[19,5146,5148],{"id":5147},"la-trampa-del-vamos-a-instalar-ahora-para-estar-preparado","La trampa del \"vamos a instalar ahora para estar preparado\"",[12,5150,5151],{},"Argumento que aparece en toda discusión: \"si voy a crecer a cincuenta servicios el año que viene, mejor instalar ahora y aprender\". La trampa tiene tres caras:",[2735,5153,5154,5160,5166],{},[70,5155,5156,5159],{},[27,5157,5158],{},"Aprender malla cuesta de cuatro a ocho semanas por persona del equipo."," En equipo de cinco, son veinte a cuarenta semanas-persona. Multiplicado por R$ 200\u002Fhora, da entre R$ 160 mil y R$ 320 mil solo en aprendizaje. Ese dinero adquiere feature o compra plazo de runway.",[70,5161,5162,5165],{},[27,5163,5164],{},"Cada componente nuevo es un punto de fallo crítico más."," Plano de control de la malla (Istio Pilot, Linkerd controller, Cilium operator) puede fallar y llevarse conectividad interna junto. Más componentes en quórum, más superficie de incidente. Añade solo cuando la ganancia compensa ese riesgo.",[70,5167,5168,5171],{},[27,5169,5170],{},"Cuando lo necesites, instalar lleva una semana, no un mes."," Linkerd en particular es instalable en un par de comandos. Cilium en algunas horas si el cluster acepta kernel reciente. Aplazar la decisión no es deuda técnica — es deuda aplazada con intereses menores.",[12,5173,5174],{},"No funciona \"anticipar para estar preparado\". Lo que funciona es monitorear los criterios objetivos de la sección anterior e instalar cuando dos o más se vuelvan realidad.",[19,5176,5178],{"id":5177},"como-heroctl-encara-el-problema","Cómo HeroCtl encara el problema",[12,5180,5181],{},"Nuestra posición es deliberada: malla de servicio, en la mayoría de los casos, es decisión para etapa tres o cuatro. Para cubrir las etapas uno y dos, HeroCtl trae embebido en el plano de control:",[2735,5183,5184,5190,5196],{},[70,5185,5186,5189],{},[27,5187,5188],{},"Cifrado entre servicios automático."," Cada servicio enviado recibe identidad propia. Llamada interna entre dos servicios es cifrada por defecto, sin alteración en el código de la aplicación y sin sidecar paralelo.",[70,5191,5192,5195],{},[27,5193,5194],{},"Distributed tracing vía OpenTelemetry exporter integrado."," El cluster propaga cabeceras de correlación y exporta a cualquier collector que entienda OTLP. No es tan rico como malla completa (que inyecta tracing automáticamente en los sidecars), pero cubre 80% del uso real.",[70,5197,5198,5201],{},[27,5199,5200],{},"Traffic shaping básico embebido."," Rolling update, canary con porcentaje fijo de tráfico, blue-green. Suficiente para startup que hace diez deploys al día. No cubre mirror ni canary con peso por header — para eso, necesita instalar malla.",[12,5203,5204],{},"Para startup hasta el rango de los treinta servicios, eso cubre cerca de 80% de lo que una malla completa entrega — sin el sidecar, sin las cuatro semanas de aprendizaje, sin los 10 GB de RAM. Cuando el sistema crece más allá, instalar Linkerd encima de HeroCtl es camino documentado.",[19,5206,5208],{"id":5207},"los-cuatro-errores-mas-caros-instalando-service-mesh","Los cuatro errores más caros instalando service mesh",[12,5210,5211],{},"Para equipo que decidió por el paso, cuatro trampas que cuestan de dos semanas a tres meses de retrabajo:",[2735,5213,5214,5220,5237,5243],{},[70,5215,5216,5219],{},[27,5217,5218],{},"Instalar antes de necesitar."," Cobertura innecesaria se vuelve pasivo. Componente nuevo en el quórum, coste de RAM, tiempo de aprendizaje — sin retorno equivalente.",[70,5221,5222,5225,5226,5229,5230,5233,5234,5236],{},[27,5223,5224],{},"Configurar cifrado estricto en el día uno sin pensar en legacy."," Modo ",[231,5227,5228],{},"STRICT"," rompe cualquier servicio que aún no fue migrado. La migración correcta es gradual: modo ",[231,5231,5232],{},"PERMISSIVE"," al inicio (acepta tráfico cifrado y no-cifrado), solo se vuelve ",[231,5235,5228],{}," cuando todos los servicios están dentro de la malla.",[70,5238,5239,5242],{},[27,5240,5241],{},"No dimensionar el plano de control."," Istio Pilot y similares necesitan RAM y CPU suficientes para distribuir configuración a todos los sidecars. En cluster creciendo, volverse cuello de botella del plano de control es incidente clásico de quien no planificó.",[70,5244,5245,5248],{},[27,5246,5247],{},"Saltar Linkerd a Istio \"porque es más popular\"."," Linkerd resuelve 80% de los casos con 30% del overhead. La elección por Istio solo se justifica cuando una feature específica (authorization L7 sofisticada, integración con servicio de identidad externo, multi-cluster federation) sea requisito real, no preferencia de currículum.",[19,5250,4245],{"id":4244},[12,5252,5253,5256],{},[27,5254,5255],{},"¿Linkerd es lo bastante ligero para cluster pequeño?","\nMás ligero que Istio en un orden de magnitud, pero aún así es sidecar paralelo en cada pod. Para cluster con veinte pods y cuatro nodos de 4 GB, Linkerd come cerca de 600 MB de RAM total — significativo pero tolerable. Para cluster con diez pods, aún es exagero. Linkerd entra en escena en la etapa tres (10–50 servicios), no antes.",[12,5258,5259,5262,5263,5265],{},[27,5260,5261],{},"¿Istio Ambient Mode (sin sidecar) cambia esa decisión?","\nReduce el overhead por pod (va a un agente por nodo, ",[231,5264,4596],{},"), pero aún exige operar el plano de control de Istio entero. En producción estable desde 2024, pero la comunidad aún es pequeña — esperar más algunos trimestres para adopción en proyecto crítico es prudente.",[12,5267,5268,5271],{},[27,5269,5270],{},"¿Cilium eBPF realmente tiene cero overhead?","\nPor pod, sí — no tiene sidecar paralelo. Pero el agente Cilium en cada nodo consume de 200 a 400 MB y añade carga en el kernel. Para cluster con kernel Linux moderno y CNI compatible, es la opción más eficiente. Para cluster que aún corre kernel antiguo o usa CNI específico, el setup se vuelve proyecto.",[12,5273,5274,5277],{},[27,5275,5276],{},"¿Cómo hago cifrado entre servicios sin service mesh?","\nTres caminos. Primero, TLS en el código de la aplicación — cada servicio expone HTTPS, cada cliente confía en CA interna. Funciona, pero exige distribuir certificados manualmente (o vía caja fuerte de secretos). Segundo, plano de control del orquestador emitir certificados automáticamente — HeroCtl y algunas distribuciones del coloso hacen eso, es el camino más limpio. Tercero, VPN o red overlay cifrada (WireGuard) entre nodos — protege el tráfico dentro del cluster, pero no la identidad servicio-a-servicio.",[12,5279,5280,5283],{},[27,5281,5282],{},"¿Distributed tracing necesita malla?","\nNo. OpenTelemetry SDK en cada servicio, exportando a un collector central (Tempo, Jaeger, o servicio gestionado), cubre 90% del uso. Malla automatiza la inyección sin cambiar código, lo que es cómodo — pero no es requisito. Para startup, empezar con OpenTelemetry en el código es más barato.",[12,5285,5286,5289],{},[27,5287,5288],{},"¿Service mesh en cluster gestionado es más fácil?","\nMás fácil de instalar, sí — buena parte de los proveedores ofrece add-on de Istio o Linkerd con un clic. Más fácil de operar, no — aún necesitas entender el plano de control, dimensionar, debugar cuando una sidecar reciclar. No ganes tiempo de instalación a costa de despreparo operacional.",[12,5291,5292,5295],{},[27,5293,5294],{},"¿Cuál malla es más usada en startup?","\nPor experiencia de comunidad, Istio domina en las empresas que adoptaron entre 2020 y 2022 (efecto moda CNCF). Linkerd crece desde 2024 entre quienes migraron o empezaron nuevo, especialmente fintechs de tamaño mediano. Cilium aparece en casos específicos (clusters muy grandes, optimización de coste). Consul Connect rarísimo.",[12,5297,5298,5301],{},[27,5299,5300],{},"¿Vale la pena para monolito + 3 microservicios?","\nNo. Monolito + tres microservicios no tiene complejidad interna que malla ayude a domar. TLS en el código resuelve cifrado. Logs centralizados resuelven visibilidad. Rolling update del orquestador resuelve deploy seguro. Instalar malla en ese escenario es traer un problema para resolver otro problema que no existe.",[12,5303,5304,5307],{},[27,5305,5306],{},"¿HeroCtl sustituye completamente una service mesh?","\nPara etapas uno y dos (hasta treinta servicios), sustituye en cerca de 80% del uso real. Para etapas tres y cuatro (por encima de treinta servicios, o compliance específico), HeroCtl convive con Linkerd o Istio corriendo como jobs encima. El cifrado entre servicios del plano de control de HeroCtl coexiste con la malla — la malla cuida del tráfico entre tus pods, HeroCtl cuida de la identidad de los servicios y de la comunicación con el plano de control.",[19,5309,3310],{"id":3309},[12,5311,5312],{},"La regla práctica que recomendamos al tech lead: instala malla cuando dos o más de los criterios objetivos se vuelvan realidad — treinta servicios activos, más de un incidente al mes relacionado con llamadas internas, compliance formal pidiendo zero-trust, equipo de plataforma de cinco personas, federación multi-cluster real. Antes de eso, cluster con cifrado embebido en el plano de control resuelve la mayor parte de lo que ibas a comprar con malla, sin los 10 GB de RAM y sin las ocho semanas de aprendizaje.",[12,5314,5315],{},"Para empezar a explorar esa vía — orquestador con cifrado entre servicios ya incluido, sin sidecar paralelo, plano de control ocupando entre 200 y 400 MB por servidor y elección de coordinador en cerca de siete segundos cuando algo cae — instala en un servidor Linux cualquiera y abre el panel:",[224,5317,5319],{"className":226,"code":5318,"language":228,"meta":229,"style":229},"curl -sSL get.heroctl.com\u002Finstall.sh | sh\n",[231,5320,5321],{"__ignoreMap":229},[234,5322,5323,5325,5327,5330,5332],{"class":236,"line":237},[234,5324,1220],{"class":247},[234,5326,2958],{"class":251},[234,5328,5329],{"class":255}," get.heroctl.com\u002Finstall.sh",[234,5331,2964],{"class":383},[234,5333,2967],{"class":247},[12,5335,5336,5337,5340,5341,5345],{},"Para continuar en esa línea, dos posts directos. En ",[3337,5338,5339],{"href":4369},"Multi-tenant SaaS — ¿aislamiento real o solo namespace?"," tratamos del problema vecino — separar clientes dentro del mismo cluster sin romper el presupuesto. En ",[3337,5342,5344],{"href":5343},"\u002Fes\u002Fblog\u002Fk3s-vs-heroctl-cuando-cada-uno-tiene-sentido","K3s vs HeroCtl — cuándo cada uno tiene sentido"," comparamos la alternativa más común cuando el equipo ya decidió que el coloso ortodoxo es exagero.",[12,5347,5348],{},"La elección por malla de servicio es, en el fondo, una elección de cuándo absorber complejidad. La pregunta correcta no es \"¿necesito Istio?\" — es \"¿cuál es el menor sistema que aún resuelve mi problema actual?\". Para gran parte de las startups, la respuesta es más simple de lo que la industria americana sugiere.",[3351,5350,4377],{},{"title":229,"searchDepth":244,"depth":244,"links":5352},[5353,5354,5355,5356,5357,5358,5359,5360,5361,5362,5363,5364,5365,5366,5367,5368],{"id":4422,"depth":244,"text":4423},{"id":4432,"depth":244,"text":4433},{"id":4532,"depth":244,"text":4533},{"id":4581,"depth":244,"text":4582},{"id":4625,"depth":244,"text":4626},{"id":4740,"depth":244,"text":4741},{"id":4774,"depth":244,"text":4775},{"id":4824,"depth":244,"text":4825},{"id":5036,"depth":244,"text":5037},{"id":5072,"depth":244,"text":5073},{"id":5111,"depth":244,"text":5112},{"id":5147,"depth":244,"text":5148},{"id":5177,"depth":244,"text":5178},{"id":5207,"depth":244,"text":5208},{"id":4244,"depth":244,"text":4245},{"id":3309,"depth":244,"text":3310},"2026-05-29","Service mesh resuelve problemas reales (mTLS, observabilidad entre servicios, traffic shaping). Pero añade 30-50% overhead de RAM\u002FCPU y complejidad. Cuándo vale la pena y cuándo es overkill.",{},{"title":4414,"description":5370},{"loc":4276},"es\u002Fblog\u002Fservice-mesh-cuando-vale-la-pena-en-saas-pequeno",[5376,5377,5378,3394,4410],"service-mesh","istio","linkerd","WjLsoEL_PvEf75FpHRjW2Wfu2YOhmhW3jd7R5Rf_YDU",{"id":5381,"title":5382,"author":7,"body":5383,"category":6382,"cover":3380,"date":6383,"description":6384,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":6385,"navigation":411,"path":6386,"readingTime":6387,"seo":6388,"sitemap":6389,"stem":6390,"tags":6391,"__hash__":6397},"blog_es\u002Fes\u002Fblog\u002Fsalir-de-aws-sin-reescribir-el-stack.md","Cómo salir de AWS sin reescribir todo el stack: guía práctica 2026",{"type":9,"value":5384,"toc":6344},[5385,5388,5392,5395,5398,5404,5407,5411,5414,5417,5420,5424,5427,5501,5504,5508,5511,5714,5717,5721,5724,5727,5731,5738,5741,5744,5750,5754,5757,5760,5764,5767,5774,5777,5781,5784,5787,5791,5794,5797,5801,5804,5808,5811,5814,5818,5821,5824,5827,5831,5834,5844,5847,5851,5854,5857,5861,5864,5870,5876,5879,5883,5886,5892,5898,5908,5914,5920,5926,5932,5938,5942,5945,5977,5981,5984,5989,6026,6031,6062,6065,6068,6072,6075,6078,6095,6098,6101,6105,6108,6114,6120,6126,6132,6136,6139,6153,6156,6160,6163,6166,6191,6194,6206,6209,6211,6215,6219,6222,6226,6229,6233,6236,6240,6246,6250,6269,6273,6276,6280,6283,6287,6290,6294,6297,6299,6303,6306,6309,6325,6328,6339,6342],[12,5386,5387],{},"La mayoría de los equipos que piensa en salir de AWS lo posterga indefinidamente porque cree estar ante un proyecto de \"reescribir todo el stack\". No lo es. Es un proyecto de mapeo, no de reescritura. Y el mapeo cabe en una hoja de cálculo de doce líneas.",[19,5389,5391],{"id":5390},"tldr-lo-que-vas-a-leer-en-tres-minutos","TL;DR — lo que vas a leer en tres minutos",[12,5393,5394],{},"Stack típico de SaaS usa cerca de doce servicios AWS, y cada uno de ellos tiene alternativa portable que cuesta entre tres y siete veces menos. EC2 se vuelve VPS en cualquier proveedor (Hetzner, DigitalOcean, Magalu Cloud). RDS se vuelve Postgres en VPS dedicado, Neon o Supabase. ElastiCache se vuelve Valkey auto-hospedado. S3 se vuelve CloudFlare R2 o Backblaze B2 — ambos con API S3-compatible, así que el código ni cambia. SQS se vuelve cola basada en Redis o RabbitMQ. Lambda se vuelve endpoint en el app server tradicional o CloudFlare Workers. ALB se vuelve el router integrado del orquestador. CloudFront se vuelve CloudFlare gratis. IAM se vuelve inyección de secretos en el orquestador.",[12,5396,5397],{},"Cronograma realista para startup con cinco a diez aplicaciones: seis a ocho semanas, ochenta a ciento sesenta horas de desarrollo. Ahorro típico: tres a siete veces en la cuenta de infra, con retorno en menos de un mes de salario sénior.",[12,5399,5400,5403],{},[27,5401,5402],{},"No migres si"," tu cumplimiento exige AWS nominalmente, si el equipo es único y se enfoca en producto, o si el stack usa lock-in profundo (DynamoDB con features específicas, Aurora Serverless v2, IAM cross-account complejo).",[12,5405,5406],{},"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",[19,5408,5410],{"id":5409},"por-que-tantos-equipos-posponen-la-salida-de-aws","¿Por qué tantos equipos posponen la salida de AWS?",[12,5412,5413],{},"La respuesta honesta es confusión entre dos proyectos diferentes. \"Salir de AWS\" se volvió sinónimo mental de \"reescribir la aplicación\". No es la misma cosa.",[12,5415,5416],{},"Reescribir la aplicación es cambiar tecnología central — base relacional por NoSQL, framework síncrono por reactivo, monolito por microservicios. Eso sí toma trimestres. Salir de AWS es cambiar la infra que sostiene la aplicación que ya tienes. El código de dominio queda idéntico. Cambian endpoints de base, credenciales, algunos SDKs y la forma de declarar deploy.",[12,5418,5419],{},"La confusión dura porque el equipo mira la consola de AWS y ve doscientos servicios. Nadie usa doscientos. La mayor parte usa doce. Mapea esos doce, encuentra alternativa para cada uno, y lo que queda es trabajo de ejecución — no de investigación.",[19,5421,5423],{"id":5422},"los-doce-servicios-aws-que-tu-stack-probablemente-usa","Los doce servicios AWS que tu stack probablemente usa",[12,5425,5426],{},"La hoja de cálculo de inicio es esa. Todo lo que está fuera de ella en tu cuenta probablemente es satélite — alarma del CloudWatch que nadie mira, bucket S3 olvidado, función Lambda muerta. Enfócate en los doce:",[67,5428,5429,5435,5441,5447,5453,5459,5465,5471,5477,5483,5489,5495],{},[70,5430,5431,5434],{},[27,5432,5433],{},"EC2"," — máquinas virtuales que corren app server y workers",[70,5436,5437,5440],{},[27,5438,5439],{},"RDS"," — base relacional gestionada (Postgres o MySQL)",[70,5442,5443,5446],{},[27,5444,5445],{},"ElastiCache"," — Redis para caché y sesión",[70,5448,5449,5452],{},[27,5450,5451],{},"S3"," — almacenamiento de objetos (uploads, backups, assets)",[70,5454,5455,5458],{},[27,5456,5457],{},"ALB \u002F NLB"," — balanceador de carga al frente de las EC2",[70,5460,5461,5464],{},[27,5462,5463],{},"CloudFront"," — CDN para assets estáticos",[70,5466,5467,5470],{},[27,5468,5469],{},"Route 53"," — DNS autoritativo",[70,5472,5473,5476],{},[27,5474,5475],{},"SES"," — email transaccional",[70,5478,5479,5482],{},[27,5480,5481],{},"SQS \u002F SNS"," — cola y pub-sub",[70,5484,5485,5488],{},[27,5486,5487],{},"IAM"," — credenciales y roles para que servicios conversen",[70,5490,5491,5494],{},[27,5492,5493],{},"CloudWatch"," — métricas y logs",[70,5496,5497,5500],{},[27,5498,5499],{},"Lambda"," — funciones serverless",[12,5502,5503],{},"Si tu cuenta tiene los doce, felicitaciones: eres el stack mediano. Si tiene ocho o nueve, mejor — menos cosas para migrar. Si tiene cinco servicios muy específicos (Aurora Global, DynamoDB con Streams, EventBridge complejo), estás en un camino diferente — lee la sección de lock-ins antes de continuar.",[19,5505,5507],{"id":5506},"mapeo-servicio-por-servicio-alternativa-coste-y-complejidad","Mapeo servicio por servicio — alternativa, coste y complejidad",[12,5509,5510],{},"La tabla abajo es el atajo. Cada línea tiene detalle expandido después.",[119,5512,5513,5532],{},[122,5514,5515],{},[125,5516,5517,5520,5523,5526,5529],{},[128,5518,5519],{},"Servicio AWS",[128,5521,5522],{},"Alternativa portable",[128,5524,5525],{},"Coste antes (US$\u002Fmes)",[128,5527,5528],{},"Coste después (US$\u002Fmes)",[128,5530,5531],{},"Complejidad migración",[141,5533,5534,5550,5566,5582,5597,5612,5626,5641,5655,5671,5684,5698],{},[125,5535,5536,5539,5542,5545,5548],{},[146,5537,5538],{},"EC2 t3.medium",[146,5540,5541],{},"VPS Hetzner CPX21",[146,5543,5544],{},"30",[146,5546,5547],{},"9",[146,5549,3155],{},[125,5551,5552,5555,5558,5561,5564],{},[146,5553,5554],{},"RDS db.t4g.large",[146,5556,5557],{},"Postgres auto-hospedado o Neon",[146,5559,5560],{},"140",[146,5562,5563],{},"10–50",[146,5565,3160],{},[125,5567,5568,5571,5574,5577,5580],{},[146,5569,5570],{},"ElastiCache cache.t4g.micro",[146,5572,5573],{},"Valkey auto-hospedado",[146,5575,5576],{},"15",[146,5578,5579],{},"6",[146,5581,3160],{},[125,5583,5584,5587,5590,5593,5595],{},[146,5585,5586],{},"S3 (1TB + egress)",[146,5588,5589],{},"CloudFlare R2",[146,5591,5592],{},"120",[146,5594,5576],{},[146,5596,3155],{},[125,5598,5599,5602,5605,5608,5610],{},[146,5600,5601],{},"ALB",[146,5603,5604],{},"Router integrado del orquestador",[146,5606,5607],{},"22",[146,5609,893],{},[146,5611,3160],{},[125,5613,5614,5616,5619,5622,5624],{},[146,5615,5463],{},[146,5617,5618],{},"CloudFlare gratis",[146,5620,5621],{},"80",[146,5623,893],{},[146,5625,3155],{},[125,5627,5628,5630,5633,5636,5638],{},[146,5629,5469],{},[146,5631,5632],{},"CloudFlare DNS",[146,5634,5635],{},"5",[146,5637,893],{},[146,5639,5640],{},"Trivial",[125,5642,5643,5645,5648,5650,5653],{},[146,5644,5475],{},[146,5646,5647],{},"Resend o Postmark",[146,5649,1589],{},[146,5651,5652],{},"15–20",[146,5654,5640],{},[125,5656,5657,5659,5662,5665,5668],{},[146,5658,5481],{},[146,5660,5661],{},"Redis Streams o RabbitMQ",[146,5663,5664],{},"16",[146,5666,5667],{},"0 (misma VPS)",[146,5669,5670],{},"Media–alta",[125,5672,5673,5675,5678,5680,5682],{},[146,5674,5487],{},[146,5676,5677],{},"Secretos del orquestador",[146,5679,893],{},[146,5681,893],{},[146,5683,3160],{},[125,5685,5686,5688,5691,5694,5696],{},[146,5687,5493],{},[146,5689,5690],{},"Prometheus + Loki",[146,5692,5693],{},"50",[146,5695,5667],{},[146,5697,3160],{},[125,5699,5700,5702,5705,5708,5711],{},[146,5701,5499],{},[146,5703,5704],{},"App server o CloudFlare Workers",[146,5706,5707],{},"40",[146,5709,5710],{},"0–12",[146,5712,5713],{},"Variable",[12,5715,5716],{},"Costes de antes asumen stack de SaaS pequeño-medio con cinco a diez aplicaciones activas.",[368,5718,5720],{"id":5719},"ec2-se-vuelve-vps-en-cualquier-proveedor","EC2 se vuelve VPS en cualquier proveedor",[12,5722,5723],{},"La migración más obvia. EC2 t3.medium cuesta cerca de treinta dólares mensuales. Hetzner CPX21 con la misma clase de CPU y más RAM cuesta siete euros y noventa y nueve. DigitalOcean queda en el medio. Magalu Cloud es competitivo para quien prioriza factura en moneda local y dato en territorio nacional.",[12,5725,5726],{},"El camino técnico es provisionar la VPS, correr tu Ansible existente (o un script de bootstrap simple), copiar el snapshot del EC2 o subir la imagen desde cero. Para cada servidor, cuenta de dos a cuatro horas. No es la parte demorada de la migración.",[368,5728,5730],{"id":5729},"rds-se-vuelve-postgres-auto-hospedado-o-neonsupabase","RDS se vuelve Postgres auto-hospedado o Neon\u002FSupabase",[12,5732,5733,5734,5737],{},"Aquí hay tres caminos honestos. El primero es Postgres corriendo en una VPS dedicada, con backup automatizado vía ",[231,5735,5736],{},"pg_dump"," en cron y replicación física a un secundario en otra región. Cuesta el precio de la VPS — diez a veinte dólares mensuales — para sustituir un RDS de ciento cuarenta.",[12,5739,5740],{},"El segundo es Neon. Postgres serverless con branching, ramp-up automático, plan gratuito generoso, planes pagados a partir de cinco dólares. Útil para quien quiere abandonar AWS sin asumir operación directa de base.",[12,5742,5743],{},"El tercero es Supabase, que entrega Postgres con APIs adicionales (auth, realtime, storage) y tier gratuito permanente. Tiene sentido para startups que toleran acoplamiento a Supabase a cambio de simplicidad.",[12,5745,5746,5747,5749],{},"La migración en sí es ",[231,5748,5736],{}," seguido de restore en el destino, con ventana de mantenimiento corta — o replicación lógica con cutover casi sin downtime si tu Postgres es versión 13 o superior. Cuatro a ocho horas dependiendo del tamaño de la base.",[368,5751,5753],{"id":5752},"elasticache-se-vuelve-valkey-auto-hospedado","ElastiCache se vuelve Valkey auto-hospedado",[12,5755,5756],{},"Redis se volvió Valkey después del cambio de licencia en 2024 — fork mantenido por la Linux Foundation. Corre en cualquier VPS con dos clics. Seis dólares mensuales sustituyen el ElastiCache de quince.",[12,5758,5759],{},"La migración tiene dos etapas. Primera, levantar el cluster Valkey con Sentinel para failover automático. Segunda, poblar la caché — script que lee de AWS y graba en el destino, o simplemente dejar que la aplicación lo llene orgánicamente después del cutover (caché cold start de pocos minutos). Tres a seis horas de trabajo.",[368,5761,5763],{"id":5762},"s3-se-vuelve-cloudflare-r2-o-backblaze-b2","S3 se vuelve CloudFlare R2 (o Backblaze B2)",[12,5765,5766],{},"Esa es la ganancia más inmediata. CloudFlare R2 cobra cero por el egreso — la rebanada más cara del S3 cuando sirves assets a usuarios. Quince centavos de dólar por GB almacenado, contra veintitrés centavos del S3 estándar. Backblaze B2 es una alternativa casi idéntica, con integración aún más barata para workloads de backup pesado.",[12,5768,5769,5770,5773],{},"La migración técnica es trivial: ",[231,5771,5772],{},"rclone copy s3:mi-bucket r2:mi-bucket"," en paralelo. Un terabyte transfiere alrededor de doce horas dependiendo de la banda. El código de la aplicación cambia exactamente una línea — el endpoint del cliente S3. Toda biblioteca AWS SDK acepta configuración de endpoint custom; R2 y B2 implementan el protocolo S3 idéntico.",[12,5775,5776],{},"Volumen típico de SaaS medio (cincuenta GB de uploads de usuario): US$15 mensuales en R2 contra US$120 en S3 con egreso activo. El ahorro paga una semana de trabajo de migración en el primer mes.",[368,5778,5780],{"id":5779},"alb-se-vuelve-router-integrado-del-orquestador","ALB se vuelve router integrado del orquestador",[12,5782,5783],{},"Si estás usando ALB, es porque tienes varias EC2 detrás de él. La alternativa es el router embebido en el orquestador elegido — HeroCtl, Caddy, o el router embebido en otros stacks auto-hospedados. El orquestador descubre los contenedores corriendo, abre puerto, terminan TLS vía Let's Encrypt automático, distribuye tráfico.",[12,5785,5786],{},"La migración cambia la definición de target group AWS por una definición de ingress en el manifiesto del orquestador. Cuatro a ocho horas para entender las reglas correctas. Veintidós dólares mensuales ahorrados por balanceador, y el orquestador acepta cuantos hosts quieras sin cobro adicional.",[368,5788,5790],{"id":5789},"cloudfront-se-vuelve-cloudflare-gratis","CloudFront se vuelve CloudFlare gratis",[12,5792,5793],{},"Ese merece una mención destacada. CloudFront cobra por GB transferido — quien sirve vídeo o descargas pesadas sangra. CloudFlare ofrece CDN global gratuita en el plan free, con caché configurable, mitigación básica de DDoS y WAF rudimentario. Para la mayor parte de los casos de SaaS, es más que suficiente.",[12,5795,5796],{},"La migración es cambiar los nameservers del dominio a CloudFlare y configurar reglas de caché. Dos a cuatro horas. El ahorro puede ser masivo — ochenta dólares mensuales para quien tiene volumen medio de tráfico, miles para quien tiene volumen alto.",[368,5798,5800],{"id":5799},"route-53-se-vuelve-cloudflare-dns","Route 53 se vuelve CloudFlare DNS",[12,5802,5803],{},"DNS en CloudFlare es gratis y más rápido que Route 53 en la mayoría de las mediciones públicas. Migración es exportar la zone file, importar en CloudFlare, validar registros, cambiar nameservers en el registrar. Treinta minutos. Cinco dólares mensuales que vuelven a la caja.",[368,5805,5807],{"id":5806},"ses-se-vuelve-resend-postmark-o-mailgun","SES se vuelve Resend, Postmark o Mailgun",[12,5809,5810],{},"AWS es barato para envío en volumen, pero la entregabilidad del SES exige calentamiento de IP y configuración de reputación que toma tiempo. Resend cobra veinte dólares por cincuenta mil emails mensuales y tiene entregabilidad superior fuera de la caja. Postmark cobra quince por diez mil. Mailgun cubre el caso de quien manda mucho volumen no-transaccional.",[12,5812,5813],{},"La migración es cambiar credenciales SMTP en el app — una hora de trabajo.",[368,5815,5817],{"id":5816},"sqs-y-sns-se-vuelven-redis-streams-o-rabbitmq","SQS y SNS se vuelven Redis Streams o RabbitMQ",[12,5819,5820],{},"La migración más delicada. SQS es un servicio que hace una cosa y la hace bien; sustituir exige elegir tecnología de cola y refactorizar productor y consumidor.",[12,5822,5823],{},"El camino más corto es Redis Streams, principalmente si ya estás corriendo Valkey para caché. Bibliotecas como Sidekiq (Ruby), BullMQ (Node), RQ (Python) y Asynq (Go) consumen Redis nativamente. RabbitMQ es más robusto para escenarios de enrutamiento complejo. NATS es alternativa moderna para pub-sub.",[12,5825,5826],{},"Para cada cola, cuenta uno a tres días dependiendo de la complejidad. Colas simples de jobs background son triviales. Colas con fan-out, dead letter queues y visibility timeout customizado exigen más cuidado. Dieciséis dólares mensuales ahorrados, y la cola corre en la misma VPS que la caché — cero adicional en la infra.",[368,5828,5830],{"id":5829},"iam-se-vuelve-secretos-del-orquestador","IAM se vuelve secretos del orquestador",[12,5832,5833],{},"Aquí está la migración no-obvia que pilla a muchos equipos experimentados en AWS desprevenidos. En AWS, la aplicación accede a S3 y RDS sin credenciales explícitas en el código — la EC2 hereda un IAM role y el SDK busca tokens automáticamente. Fuera de AWS, eso desaparece.",[12,5835,5836,5837,5839,5840,5843],{},"La solución es inyección de secretos por el orquestador. HeroCtl, k3s y similares aceptan secretos como recursos de primera clase — declaras ",[231,5838,453],{}," o ",[231,5841,5842],{},"S3_ACCESS_KEY"," en el manifiesto del job y el orquestador inyecta como variable de entorno en el contenedor. Para escenarios más sofisticados, HashiCorp Vault auto-hospedado hace rotación automática.",[12,5845,5846],{},"La migración es refactorizar cada role IAM en un conjunto de credenciales explícitas, creadas en el proveedor de destino (CloudFlare API token, Postgres user específico, etc), y declaradas como secretos. Cuatro a ocho horas para un stack medio.",[368,5848,5850],{"id":5849},"cloudwatch-se-vuelve-prometheus-loki","CloudWatch se vuelve Prometheus + Loki",[12,5852,5853],{},"Métricas se vuelven Prometheus + Grafana. Logs se vuelven Loki + Grafana. Todo corre en contenedores en el mismo cluster. Cincuenta dólares mensuales de CloudWatch se vuelven cero adicional.",[12,5855,5856],{},"La configuración inicial toma cerca de cuatro horas para volverse productiva: Prometheus con service discovery apuntando a los agentes del orquestador, Loki recibiendo vía Promtail o directamente del runtime de contenedor, Grafana con dashboards básicos. Hay posts dedicados a esa migración en el blog.",[368,5858,5860],{"id":5859},"lambda-la-parte-mas-dificil","Lambda — la parte más difícil",[12,5862,5863],{},"Lambda es el servicio con la mayor varianza de complejidad en la migración. Depende totalmente de cómo la estés usando.",[12,5865,5866,5869],{},[27,5867,5868],{},"Lambda HTTP simple"," (API Gateway → Lambda → respuesta) es trivial. Se vuelve endpoint en tu app server. El código de la función cambia poco — handler del framework en lugar del handler del Lambda. Una a dos horas por función.",[12,5871,5872,5875],{},[27,5873,5874],{},"Lambda event-driven"," (S3 dispara Lambda, SQS dispara Lambda, EventBridge agenda Lambda) es la parte cara. Para eventos de S3, R2 ofrece eventos vía CloudFlare Workers — reescribes la Lambda como Worker y mantienes el patrón. Para SQS, se vuelve consumer en el app server. Para EventBridge agendado, se vuelve cron en el orquestador.",[12,5877,5878],{},"Escenario peor: Lambda compleja con EventBridge, Step Functions y dead letter queues encadenados. Aquí es redesign. Reserva una semana o dos y dibuja un modelo de eventos más simple — generalmente el sistema queda mejor.",[19,5880,5882],{"id":5881},"cronograma-realista-de-seis-a-ocho-semanas","Cronograma realista de seis a ocho semanas",[12,5884,5885],{},"El orden importa. Empezar por la base es tentación y trampa — base es la última en migrar, no la primera.",[12,5887,5888,5891],{},[27,5889,5890],{},"Semana 1 — Inventario y decisión."," Lista los doce servicios, anota coste actual, identifica integraciones entre ellos. Elige alternativa para cada uno. Documento de una página con la tabla de mapeo. Sin código aún.",[12,5893,5894,5897],{},[27,5895,5896],{},"Semana 2 — Provisión del destino en paralelo."," Levanta las VPS, instala el orquestador (HeroCtl o similar), configura DNS de prueba apuntando a un subdominio. Sube Postgres, Valkey, CloudFlare R2. Todo vacío. Smoke test: un \"hello world\" corriendo.",[12,5899,5900,5903,5904,5907],{},[27,5901,5902],{},"Semana 3 — Migración de storage."," S3 a R2 con ",[231,5905,5906],{},"rclone",". Suele ser lenta (volumen) pero bajísimo riesgo. Aplicación aún lee de S3, pero validas que R2 esté sincronizado. Al final de la semana, dual-write — aplicación escribe en los dos.",[12,5909,5910,5913],{},[27,5911,5912],{},"Semana 4 — Migración de la base."," Réplica lógica de Postgres del RDS al destino. Cutover en una ventana de mantenimiento corta — suele ser minutos, no horas, con replicación lógica funcionando. Aplicación apunta a la base nueva. RDS queda como hot standby por una semana.",[12,5915,5916,5919],{},[27,5917,5918],{},"Semana 5 — Migración de aplicaciones web."," Apps que corren en EC2 se vuelven jobs en el orquestador. Router integrado hace el papel del ALB. DNS apunta al orquestador (o a CloudFlare al frente). Cutover gradual usando weighted DNS.",[12,5921,5922,5925],{},[27,5923,5924],{},"Semana 6 — Colas y jobs asíncronos."," SQS sale, Redis Streams o RabbitMQ entra. Workers corren en el orquestador. Período de dual-consume para garantizar que ningún mensaje cae.",[12,5927,5928,5931],{},[27,5929,5930],{},"Semana 7 — Lambdas y workloads event-driven."," La semana más variable. Lambdas HTTP migran rápidamente. Lambdas event-driven exigen el redesign discutido arriba. Si tienes más de diez Lambdas complejas, considera extender a dos semanas.",[12,5933,5934,5937],{},[27,5935,5936],{},"Semana 8 — Cutover final, monitoreo intensivo, decommission."," CloudFlare al frente sustituye CloudFront. Route 53 se vuelve CloudFlare DNS. CloudWatch va a Prometheus + Loki. Última cosa: apaga las EC2 antiguas y cierra la cuenta AWS — o deja un saldo mínimo si aún mantienes algún servicio residual.",[19,5939,5941],{"id":5940},"los-cinco-lock-ins-que-mas-duelen-en-la-migracion","Los cinco lock-ins que más duelen en la migración",[12,5943,5944],{},"La honestidad es importante: no todo migra fácil. Cinco cosas exigen trabajo extra y a veces cambian la viabilidad del proyecto:",[67,5946,5947,5953,5959,5965,5971],{},[70,5948,5949,5952],{},[27,5950,5951],{},"DynamoDB con features específicas."," GSI, Streams, scan limits, TTL. No hay equivalente directo. El camino realista es rediseñar a Postgres con JSONB, o a un NoSQL auto-hospedado (FoundationDB, ScyllaDB) — re-arquitectura, no migración.",[70,5954,5955,5958],{},[27,5956,5957],{},"Aurora-only features."," Aurora Serverless v2 con auto-scaling de connections, Aurora Global Database, Aurora I\u002FO optimized. Postgres auto-hospedado hace casi todo, pero no tiene el auto-scaling instantáneo. Para workloads spiky, considera Neon (que ofrece patrón similar).",[70,5960,5961,5964],{},[27,5962,5963],{},"IAM cross-service complejo."," Equipos que usan IAM roles cross-account, Service Control Policies y organización jerárquica de cuentas tienen control de acceso embebido en la arquitectura. Migrar exige reimplementar la jerarquía en otro lugar — Vault, CloudFlare Access, o inyección de secretos del orquestador. Cuenta días, no horas.",[70,5966,5967,5970],{},[27,5968,5969],{},"Lambda + EventBridge complejo."," Pipelines de eventos con varios hops, retries, dead letter queues. No migra como está. Rediseña en torno de colas (RabbitMQ, NATS) y workers persistentes. Generalmente el sistema queda más simple — pero toma tiempo.",[70,5972,5973,5976],{},[27,5974,5975],{},"S3 events disparando Lambda."," Patrón muy común, y R2 con CloudFlare Workers cubre la mayoría de los casos. Para workloads que necesitan garantía exactamente-una-vez o ordering fuerte, cambia a patrón de cola — productor escribe evento en la cola cuando archivo es confirmado, worker consume.",[19,5978,5980],{"id":5979},"la-cuenta-de-ahorro-sin-optimismo","La cuenta de ahorro, sin optimismo",[12,5982,5983],{},"Escenario típico de SaaS con cinco aplicaciones:",[12,5985,5986],{},[27,5987,5988],{},"Antes en AWS:",[2735,5990,5991,5994,5997,6000,6003,6006,6009,6012,6015,6018,6021],{},[70,5992,5993],{},"Cinco EC2 t3.medium: US$150",[70,5995,5996],{},"RDS db.t4g.large Multi-AZ: US$280",[70,5998,5999],{},"ElastiCache cache.t4g.micro: US$15",[70,6001,6002],{},"S3 con 100GB y egreso medio: US$60",[70,6004,6005],{},"ALB: US$22",[70,6007,6008],{},"CloudFront con volumen medio: US$80",[70,6010,6011],{},"Route 53 + SES: US$15",[70,6013,6014],{},"CloudWatch logs\u002Fmétricas: US$50",[70,6016,6017],{},"Lambda con volumen medio: US$40",[70,6019,6020],{},"NAT Gateway: US$40",[70,6022,6023],{},[27,6024,6025],{},"Total: US$752\u002Fmes = US$9.024\u002Faño",[12,6027,6028],{},[27,6029,6030],{},"Después auto-hospedado:",[2735,6032,6033,6036,6039,6042,6045,6048,6051,6054,6057],{},[70,6034,6035],{},"Cuatro VPS Hetzner CPX21 con orquestador: US$36",[70,6037,6038],{},"Postgres auto-hospedado (incluido en las VPS): US$0",[70,6040,6041],{},"Valkey (incluido): US$0",[70,6043,6044],{},"CloudFlare R2 50GB con egreso ilimitado: US$15",[70,6046,6047],{},"CloudFlare CDN + DNS: US$0",[70,6049,6050],{},"Resend para email: US$20",[70,6052,6053],{},"Prometheus + Loki (incluido): US$0",[70,6055,6056],{},"Workers de cola (incluidos): US$0",[70,6058,6059],{},[27,6060,6061],{},"Total: US$71\u002Fmes = US$852\u002Faño",[12,6063,6064],{},"Ahorro: US$681\u002Fmes, US$8.172\u002Faño. Aproximadamente un mes de salario de ingeniero sénior.",[12,6066,6067],{},"La migración consume ochenta a ciento sesenta horas. En tiempo de dev sénior interno, son entre tres y seis mil dólares. Retorno en cinco a diez meses, con ahorro perpetuo después.",[19,6069,6071],{"id":6070},"la-migracion-mas-no-obvia-secretos-y-credenciales","La migración más no-obvia: secretos y credenciales",[12,6073,6074],{},"Vale repetir, porque es lo que más sorprende a equipos experimentados en AWS. En AWS accedes a S3 sin credentials en el código — IAM role del EC2 resuelve. Accedes a RDS vía IAM authentication. Accedes a parameter store vía IAM. El equipo pierde la noción de que esa \"magia\" existe.",[12,6076,6077],{},"Fuera de AWS, toda credencial es explícita. Aplicación necesita:",[2735,6079,6080,6083,6086,6089,6092],{},[70,6081,6082],{},"Access key y secret para R2 (creada en el panel CloudFlare)",[70,6084,6085],{},"Connection string con user y contraseña para el Postgres",[70,6087,6088],{},"URL del Valkey con contraseña",[70,6090,6091],{},"API key para Resend",[70,6093,6094],{},"Token para CloudFlare API si automatizas DNS",[12,6096,6097],{},"La solución del orquestador es declarar todo eso como secretos inyectados en el contenedor como variables de entorno. El secreto es cifrado en reposo en el orquestador y nunca aparece en los logs. Para rotación automática y auditoría sofisticada, Vault auto-hospedado entra en juego — pero la mayoría de los equipos no lo necesita.",[12,6099,6100],{},"Plan: haz una hoja de cálculo con todas las credenciales que cada app necesita, crea cada una en el proveedor de destino, declara como secreto en el orquestador, inyecta en el contenedor. Cuatro a ocho horas para un stack medio.",[19,6102,6104],{"id":6103},"cuando-no-migrar-perfiles-honestos","Cuándo NO migrar (perfiles honestos)",[12,6106,6107],{},"Cuatro situaciones en que salir de AWS es decisión equivocada:",[12,6109,6110,6113],{},[27,6111,6112],{},"Cumplimiento que lista AWS nominalmente."," FedRAMP, ITAR, ciertos contratos de gobierno americano y algunas certificaciones financieras exigen que la infra corra sobre componentes pre-aprobados — y la mayoría de las listas incluyen AWS, GCP, Azure, y pocos proveedores adicionales. Si tu cliente es una agencia federal americana, AWS resuelve una rebanada del cumplimiento que costaría meses replicar en otro lugar.",[12,6115,6116,6119],{},[27,6117,6118],{},"Equipo único enfocado en producto."," Si eres el único dev y estás construyendo el producto, ocho semanas redirigidas a la migración matan roadmap. Hazlo cuando tengas el segundo dev, o cuando los costes AWS pasen a representar rebanada significativa del MRR. Antes de eso, AWS es caro pero comprable.",[12,6121,6122,6125],{},[27,6123,6124],{},"Costes AWS por debajo del 2% del MRR."," Cuenta de doscientos dólares mensuales para startup que factura veinte mil. El ahorro es real pero el esfuerzo no vale el foco. Migra cuando la factura pase del cinco al diez por ciento del MRR — ahí la ganancia cubre la oportunidad perdida.",[12,6127,6128,6131],{},[27,6129,6130],{},"Lock-in profundo en DynamoDB o Aurora Serverless v2."," Ya tratado arriba. Si la mitad de tu arquitectura es DynamoDB con Streams, no migras — re-arquitectas. Ese es proyecto diferente, con alcance diferente, decisión diferente.",[19,6133,6135],{"id":6134},"estrategia-hibrida-alternativa-para-quien-no-quiere-migrar-todo","Estrategia híbrida — alternativa para quien no quiere migrar todo",[12,6137,6138],{},"Equipos con cincuenta o más aplicaciones en AWS raramente migran en bloque. La estrategia híbrida funciona mejor:",[2735,6140,6141,6144,6147,6150],{},[70,6142,6143],{},"Mantiene en AWS lo que es caro de mover (Aurora con features específicas, Lambda crítica, DynamoDB)",[70,6145,6146],{},"Mueve lo que es barato de mover y caro de mantener (S3 → R2, CloudFront → CloudFlare, EC2 no-críticas → VPS)",[70,6148,6149],{},"Establece VPN o conexión privada entre los dos extremos",[70,6151,6152],{},"Ahorro parcial pero cero riesgo de migración radical",[12,6154,6155],{},"Resultado típico: corte del cuarenta al sesenta por ciento de la factura AWS, sin tocar las piezas críticas. Para empresa que paga diez mil mensuales, eso es cuatro a seis mil de vuelta — y el resto migra orgánicamente en los doce meses siguientes, conforme equipos reescriben componentes por otras razones.",[19,6157,6159],{"id":6158},"heroctl-como-destino-lo-que-cambia-en-la-practica","HeroCtl como destino — lo que cambia en la práctica",[12,6161,6162],{},"HeroCtl es orquestador de contenedores que corre en cualquier servidor Linux con Docker. Cuatro VPS corriendo HeroCtl entregan experiencia operacional cercana a la que tendrías con ECS gestionado — sin cobro gestionado, sin lock-in.",[12,6164,6165],{},"Lo que sustituye:",[2735,6167,6168,6173,6179,6185],{},[70,6169,6170,6172],{},[27,6171,5601],{}," se vuelve el router integrado de HeroCtl, con TLS Let's Encrypt automático",[70,6174,6175,6178],{},[27,6176,6177],{},"CloudWatch parcial"," se vuelve métricas embebidas y logs centralizados nativos",[70,6180,6181,6184],{},[27,6182,6183],{},"RDS automated backups"," se vuelve backup gestionado en el Business Edition",[70,6186,6187,6190],{},[27,6188,6189],{},"IAM roles en apps"," se vuelve inyección de secretos en el manifiesto de job",[12,6192,6193],{},"Lo que sigue igual: Docker corriendo tu app exactamente como corre en ECS. Variables de entorno, healthchecks, rolling deploys, multi-replicas. La aplicación no percibe la diferencia.",[12,6195,6196,6197,6199,6200,6202,6203,6205],{},"Hay tres planes. ",[27,6198,4352],{}," es gratuito permanente, sin límite de servidores o jobs — corre toda la stack descrita arriba incluyendo alta disponibilidad real, router, certificados, métricas y logs. ",[27,6201,4356],{}," añade SSO, RBAC granular, auditoría detallada, backup gestionado y soporte con SLA — útil para quien ya tiene requisitos formales de plataforma. ",[27,6204,4360],{}," añade escrow de código fuente, soporte 24×7 y desarrollo dedicado. Los precios de Business y Enterprise están publicados en la página de planes, sin \"habla con ventas\" obligatorio.",[12,6207,6208],{},"El cluster público de demostración corre en cuatro servidores y la elección de coordinador ocurre en cerca de siete segundos cuando el nodo actual cae — número medido, no estimado.",[12,6210,5406],{},[19,6212,6214],{"id":6213},"preguntas-que-recibimos-sobre-salida-de-aws","Preguntas que recibimos sobre salida de AWS",[368,6216,6218],{"id":6217},"cuanto-tiempo-realmente-toma-migrar-un-stack-medio","¿Cuánto tiempo realmente toma migrar un stack medio?",[12,6220,6221],{},"Para startup con cinco a diez aplicaciones, sin lock-ins profundos: seis a ocho semanas con un dev sénior dedicando medio tiempo, o tres a cuatro semanas con dedicación total. Stacks mayores o con Lambdas event-driven complejas: tres a cuatro meses. Stacks con DynamoDB o Aurora Serverless v2 críticos: vuélvelo proyecto de re-arquitectura, plazo de seis meses o más.",[368,6223,6225],{"id":6224},"dynamodb-tiene-alternativa-buena","¿DynamoDB tiene alternativa buena?",[12,6227,6228],{},"No hay sustituto idéntico. Las opciones honestas son: Postgres con JSONB para la mayoría de los casos (resuelve ochenta por ciento de los usos de DynamoDB con performance excelente), ScyllaDB o Cassandra auto-hospedado para workloads que realmente necesitan NoSQL distribuido, FoundationDB para quien necesita transacciones distribuidas. Ninguno de esos es \"cambia la connection string y listo\" — exigen cambio en el modelo de datos.",[368,6230,6232],{"id":6231},"puedo-mantener-aws-para-la-base-y-mover-compute","¿Puedo mantener AWS para la base y mover compute?",[12,6234,6235],{},"Sí, y es la estrategia híbrida más común. Aurora o RDS sigue en AWS, EC2 se vuelven VPS Hetzner o DigitalOcean, S3 se vuelve R2. Abres VPN entre los dos extremos y el app sigue accediendo RDS vía endpoint privado. Ahorro típicamente del cincuenta al setenta por ciento de la factura AWS.",[368,6237,6239],{"id":6238},"s3-r2-cuanto-cuesta-transferir-1tb","S3 → R2: ¿cuánto cuesta transferir 1TB?",[12,6241,6242,6243,6245],{},"R2 cobra cero de ingreso. AWS cobra la salida de S3 — aproximadamente nueve centavos de dólar por GB en los primeros 10 TB. Un terabyte cuesta cerca de noventa dólares para salir de AWS. Tiempo de transferencia: doce a veinticuatro horas con ",[231,6244,5906],{}," paralelizado, dependiendo de la banda. Tras la migración, US$15 mensuales almacenando 50GB con egreso ilimitado, contra US$120 por lo mismo en S3 con tráfico activo.",[368,6247,6249],{"id":6248},"lambda-como-migrar-event-driven","Lambda — ¿cómo migrar event-driven?",[12,6251,6252,6253,6256,6257,6260,6261,6264,6265,6268],{},"Depende del disparador. ",[27,6254,6255],{},"S3 disparando Lambda"," se vuelve R2 con CloudFlare Workers (mismo patrón, sin cambio radical). ",[27,6258,6259],{},"SQS disparando Lambda"," se vuelve worker persistente en el app server, consumiendo de la cola — generalmente código más simple que la Lambda original. ",[27,6262,6263],{},"EventBridge agendado"," se vuelve cron en el orquestador. ",[27,6266,6267],{},"EventBridge con reglas complejas y Step Functions encadenados"," exige redesign — dibuja el flujo en torno a una cola central con workers consumidores, queda más auditable.",[368,6270,6272],{"id":6271},"rds-multi-az-postgres-auto-hospedado-es-confiable","RDS Multi-AZ → Postgres auto-hospedado, ¿es confiable?",[12,6274,6275],{},"Postgres con replicación física streaming y failover vía Patroni alcanza confiabilidad cercana a la del RDS Multi-AZ — siempre que el equipo sepa operar. Si nadie en el equipo domina Postgres en producción, el camino más seguro es Neon o Supabase, que entregan Postgres gestionado con tier gratuito. Para equipos con SRE o DBA, auto-hospedado es viable y ahorra substancial. Para equipos sin esa competencia, el ahorro no compensa el riesgo — paga por el gestionado.",[368,6277,6279],{"id":6278},"email-ses-quien-es-mas-barato","Email SES — ¿quién es más barato?",[12,6281,6282],{},"Depende del volumen. Hasta 10 mil emails mensuales, Postmark a US$15 entrega mucho más (entregabilidad superior, dashboard mejor, soporte responsivo). Entre 50 mil y 100 mil mensuales, Resend a US$20 es el mejor coste-beneficio. Por encima de 500 mil mensuales, Mailgun o Amazon SES compiten en precio — y SES quizás tiene sentido mantener incluso después de migrar el resto. Email es de los pocos servicios AWS que puede ser racional mantener.",[368,6284,6286],{"id":6285},"dns-todo-cloudflare-o-mezclar","DNS — ¿todo CloudFlare o mezclar?",[12,6288,6289],{},"CloudFlare resuelve DNS, CDN, DDoS, WAF y workers en el plan gratis. Para la mayoría de los stacks, concentrar todo ahí simplifica operación y corta coste. La excepción es cumplimiento que exige separación geográfica de proveedor — algunos frameworks de gobernanza piden que DNS y CDN sean de proveedores distintos. En ese caso, CloudFlare DNS + Bunny CDN (o Fastly) cumple la separación.",[368,6291,6293],{"id":6292},"cumplimiento-lgpd-cambia-algo","¿Cumplimiento LGPD cambia algo?",[12,6295,6296],{},"LGPD no exige hospedaje en territorio brasileño. Exige que sepas dónde están los datos y que tengas contrato adecuado con el operador. Hetzner (Alemania), DigitalOcean (varias regiones), CloudFlare R2 (multi-región) y Magalu Cloud (Brasil) son todos compatibles con LGPD desde que el contrato esté en orden. Para quien prefiere dato en territorio nacional por preferencia de cliente, Magalu Cloud es la alternativa directa.",[12,6298,5406],{},[19,6300,6302],{"id":6301},"proximo-paso-concreto","Próximo paso concreto",[12,6304,6305],{},"Si llegaste hasta aquí, el próximo paso es la hoja de cálculo. Lista los doce servicios, marca cuáles tu stack usa, anota coste actual de cada uno, elige alternativa. En una tarde sabes si la migración vale el esfuerzo.",[12,6307,6308],{},"Cuando estés listo para provisionar el destino:",[224,6310,6311],{"className":226,"code":5318,"language":228,"meta":229,"style":229},[231,6312,6313],{"__ignoreMap":229},[234,6314,6315,6317,6319,6321,6323],{"class":236,"line":237},[234,6316,1220],{"class":247},[234,6318,2958],{"class":251},[234,6320,5329],{"class":255},[234,6322,2964],{"class":383},[234,6324,2967],{"class":247},[12,6326,6327],{},"Corre en cualquier servidor Linux con Docker. Los primeros tres se vuelven quórum para el plano de control replicado. Envías jobs vía CLI, API o panel web embebido. El cluster decide dónde correr, hace health check, gestiona rolling deploys, emite certificados Let's Encrypt automáticamente.",[12,6329,6330,6331,2403,6335,101],{},"Para contexto adicional sobre costes y arquitectura, lee también ",[3337,6332,6334],{"href":6333},"\u002Fes\u002Fblog\u002Faws-ecs-vs-kubernetes-vs-auto-hospedado","AWS ECS vs Kubernetes vs auto-hospedado",[3337,6336,6338],{"href":6337},"\u002Fes\u002Fblog\u002Fcuanto-cuesta-alojar-un-saas-2026","Cuánto cuesta alojar un SaaS en 2026",[12,6340,6341],{},"La migración es más aburrida que difícil. Lo difícil es decidir empezar.",[3351,6343,4377],{},{"title":229,"searchDepth":244,"depth":244,"links":6345},[6346,6347,6348,6349,6363,6364,6365,6366,6367,6368,6369,6370,6381],{"id":5390,"depth":244,"text":5391},{"id":5409,"depth":244,"text":5410},{"id":5422,"depth":244,"text":5423},{"id":5506,"depth":244,"text":5507,"children":6350},[6351,6352,6353,6354,6355,6356,6357,6358,6359,6360,6361,6362],{"id":5719,"depth":271,"text":5720},{"id":5729,"depth":271,"text":5730},{"id":5752,"depth":271,"text":5753},{"id":5762,"depth":271,"text":5763},{"id":5779,"depth":271,"text":5780},{"id":5789,"depth":271,"text":5790},{"id":5799,"depth":271,"text":5800},{"id":5806,"depth":271,"text":5807},{"id":5816,"depth":271,"text":5817},{"id":5829,"depth":271,"text":5830},{"id":5849,"depth":271,"text":5850},{"id":5859,"depth":271,"text":5860},{"id":5881,"depth":244,"text":5882},{"id":5940,"depth":244,"text":5941},{"id":5979,"depth":244,"text":5980},{"id":6070,"depth":244,"text":6071},{"id":6103,"depth":244,"text":6104},{"id":6134,"depth":244,"text":6135},{"id":6158,"depth":244,"text":6159},{"id":6213,"depth":244,"text":6214,"children":6371},[6372,6373,6374,6375,6376,6377,6378,6379,6380],{"id":6217,"depth":271,"text":6218},{"id":6224,"depth":271,"text":6225},{"id":6231,"depth":271,"text":6232},{"id":6238,"depth":271,"text":6239},{"id":6248,"depth":271,"text":6249},{"id":6271,"depth":271,"text":6272},{"id":6278,"depth":271,"text":6279},{"id":6285,"depth":271,"text":6286},{"id":6292,"depth":271,"text":6293},{"id":6301,"depth":244,"text":6302},"case-study","2026-05-26","Migrar de AWS a cloud más barato (Hetzner\u002FDO) o auto-hospedado parece proyecto de 1 año. En la práctica, se puede hacer en 6-8 semanas si mapeas los 12 servicios AWS-only que tu stack usa de verdad.",{},"\u002Fes\u002Fblog\u002Fsalir-de-aws-sin-reescribir-el-stack","16 min",{"title":5382,"description":6384},{"loc":6386},"es\u002Fblog\u002Fsalir-de-aws-sin-reescribir-el-stack",[6392,6393,6394,6395,6396],"aws","migracion","costo","salida","guia","7t3Io1_05IyDD4uz6Hub49VPeOsK60ryeoLqblj3EKc",{"id":6399,"title":6400,"author":7,"body":6401,"category":3379,"cover":3380,"date":7502,"description":7503,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":7504,"navigation":411,"path":7505,"readingTime":4402,"seo":7506,"sitemap":7507,"stem":7508,"tags":7509,"__hash__":7513},"blog_es\u002Fes\u002Fblog\u002Fredis-en-produccion-gestionado-vs-auto-hospedado.md","Redis (y Valkey) en producción: gestionado vs auto-hospedado en 2026",{"type":9,"value":6402,"toc":7474},[6403,6417,6419,6438,6442,6445,6448,6454,6461,6465,6468,6474,6480,6486,6489,6495,6500,6505,6508,6514,6519,6524,6527,6533,6538,6548,6552,6555,6599,6603,6606,6610,6613,6645,6648,6652,6655,6669,6676,6680,6694,6697,6701,6734,6737,6741,6744,6758,6765,6769,6772,6853,6856,6860,6863,6869,6875,6882,6885,6889,6892,6924,6927,6931,6938,6941,6944,7240,7244,7247,7273,7277,7280,7306,7310,7313,7334,7343,7346,7349,7353,7378,7392,7398,7407,7413,7419,7425,7431,7433,7436,7442,7445,7461,7472],[12,6404,6405,6406,6409,6410,2403,6413,6416],{},"La pregunta \"¿Redis gestionado o auto-hospedado?\" se volvió otra pregunta a finales de marzo de 2024. Fue cuando la empresa detrás de Redis cambió la licencia de Apache 2.0 \u002F BSD a una combinación de RSAL con SSPL — un par de licencias \"source available\" diseñadas para impedir que proveedores de nube ofrecieran Redis como servicio sin licenciamiento comercial. La reacción fue rápida: la Linux Foundation lanzó ",[27,6407,6408],{},"Valkey"," como fork directo de la última versión BSD, con AWS, Google y Oracle financiando el desarrollo. En paralelo, proyectos que ya existían — ",[27,6411,6412],{},"KeyDB",[27,6414,6415],{},"Dragonfly"," — pasaron a aparecer con más frecuencia en benchmarks de empresas que estaban reevaluando el stack.",[19,6418,22],{"id":21},[12,6420,6421,6422,6425,6426,6428,6429,6431,6432,6434,6435,6437],{},"En 2026, \"Redis en producción\" se volvió una categoría con cuatro implementaciones disputando el mismo protocolo: ",[27,6423,6424],{},"Redis OSS"," (BSD pre-2024 o RSAL post), ",[27,6427,6408],{}," (BSD, drop-in por el fork), ",[27,6430,6412],{}," (multi-thread, fork antiguo) y ",[27,6433,6415],{}," (BSL, reescritura desde cero en C++). Auto-hospedar cualquiera de los cuatro cuesta entre R$30 y R$130 al mes en VPS Hetzner. El camino gestionado cuesta desde R$75 (ElastiCache micro) hasta R$1.000\u002Fmes (instancia de 13 GB), más Upstash con cobro serverless variando US$0–100\u002Fmes. Para startup con MRR por debajo de R$200k, ",[27,6436,5573],{}," en un cluster propio ahorra entre R$300 y R$1.500 al mes comparado al gestionado, elimina exposición a la licencia RSAL y mantiene compatibilidad total con clientes Redis. Cambiar el stack después de adoptar la versión comercial es dolor real — empezar con la versión amigable a OSS es la apuesta con menor coste de salida. Este post compara los cuatro productos, los tres caminos gestionados (ElastiCache, Upstash, Redis Cloud) y la configuración mínima para correr Valkey en producción sin perder noches de sueño.",[19,6439,6441],{"id":6440},"la-historia-corta-del-cambio-de-licencia","La historia corta del cambio de licencia",[12,6443,6444],{},"Antes de marzo de 2024, \"Redis\" era el cache OSS dominante: BSD, ecosistema gigante, presente en cualquier stack que hubiera cabido la palabra \"Rails\" o \"Node\" en el currículum. El proveedor comercial — Redis Inc, antigua Redis Labs — vivía bien del producto gestionado y de los módulos de pago (Search, JSON, TimeSeries).",[12,6446,6447],{},"Llegó el anuncio: la versión 7.4 en adelante saldría bajo RSAL + SSPL, no más BSD. En términos prácticos, el cambio apuntó directamente a AWS, Google y Azure. La lectura interna de quien produce software open source fue otra: \"si pasó con Redis, puede pasar con cualquier proyecto VC-funded\". Fue el tercer caso reciente — después de Elastic en 2021 y MongoDB en 2018 — en que un proyecto que parecía consolidado cambió las reglas.",[12,6449,6450,6451,6453],{},"La Linux Foundation fue rápida. Cinco días después del anuncio, ",[27,6452,6408],{}," fue formado como fork de la última versión BSD (7.2.4), con governanza independiente y backers de peso: AWS, Google Cloud, Oracle, Ericsson, Snap. En poco más de un año, AWS ya había migrado el motor por defecto de ElastiCache a Valkey. Google Memorystore siguió. En 2026, Valkey dejó de ser \"fork experimental\" para volverse referencia creciente — con versiones 7.x y 8.x ya incorporando optimizaciones propias que ni siquiera fueron ofrecidas a Redis OSS.",[12,6455,6456,6457,6460],{},"La lección operacional para quien está eligiendo cache hoy: ",[27,6458,6459],{},"el mainstream se movió",". Ya no hay la inercia de \"nadie fue despedido por elegir Redis\" — la pregunta en la entrevista de arquitectura se volvió \"¿por qué Redis y no Valkey?\". Y la respuesta honesta, en la mayoría de los casos, es \"costumbre\".",[19,6462,6464],{"id":6463},"cuales-son-los-cuatro-productos-disputando-ese-mercado","¿Cuáles son los cuatro productos disputando ese mercado?",[368,6466,6424],{"id":6467},"redis-oss",[12,6469,6470,6473],{},[27,6471,6472],{},"El original."," Versiones anteriores a 7.4 aún están bajo BSD y siguen usables indefinidamente — nadie revoca licencia retroactivamente. Versiones 7.4 en adelante salen bajo RSAL\u002FSSPL.",[12,6475,6476,6479],{},[27,6477,6478],{},"Pros",": comunidad aún enorme, batter-tested en producción desde hace más de una década, ecosistema más rico (módulos, integraciones, libros, talks). Casi toda client library testeó primero contra Redis OSS.",[12,6481,6482,6485],{},[27,6483,6484],{},"Cons",": la RSAL impide ofrecer-as-a-service sin licenciamiento comercial. Para quien opera Redis para uso interno, eso es irrelevante — la restricción es sobre reventa. El riesgo real es estratégico: si el proveedor cambió la licencia una vez, puede cambiar de nuevo. Adoptar Redis OSS en 2026 significa apostar que la próxima feature crítica va a bajar a rama abierta, y no quedarse en el producto comercial.",[368,6487,6408],{"id":6488},"valkey",[12,6490,6491,6494],{},[27,6492,6493],{},"El fork de la Linux Foundation."," Tomó el código de la 7.2.4 BSD y siguió desarrollando. Drop-in replacement a nivel de protocolo: ningún cliente necesita cambiar una línea de código para cambiar Redis por Valkey.",[12,6496,6497,6499],{},[27,6498,6478],{},": BSD permanente garantizado por governanza neutra (no es una empresa, es fundación). Backers grandes alinean incentivos para mantener el proyecto saludable. Paridad técnica con Redis 7.x y velocidad de desarrollo creciente.",[12,6501,6502,6504],{},[27,6503,6484],{},": la marca aún se está construyendo — algunos plugins de terceros y SDKs muy específicos aún solo listan \"Redis\" en el README. En 2026 eso es cada vez más cosmético, pero puede aparecer en integraciones antiguas que necesitan pequeña adaptación.",[368,6506,6412],{"id":6507},"keydb",[12,6509,6510,6513],{},[27,6511,6512],{},"El fork multi-thread."," Existe desde 2019, fue adquirido por Snap en 2022, hoy vive como proyecto Snap-Telemetry. La diferencia arquitectural es fundamental: Redis OSS y Valkey son single-thread por diseño (un thread principal procesa todos los comandos). KeyDB corre multi-thread por defecto.",[12,6515,6516,6518],{},[27,6517,6478],{},": en CPUs con 4+ cores, KeyDB entrega 2 a 3 veces más throughput que Redis single-thread en el mismo hardware. API es compatible, así que el cliente no cambia. Para workloads CPU-bound con volumen alto, es la elección obvia.",[12,6520,6521,6523],{},[27,6522,6484],{},": comunidad menor, ritmo de adopción de nuevas features Redis suele quedar trimestres atrás. Algunas features nuevas de Redis (Functions, ciertas extensiones) tardan en aparecer en KeyDB.",[368,6525,6415],{"id":6526},"dragonfly",[12,6528,6529,6532],{},[27,6530,6531],{},"La reescritura."," No es fork — es implementación nueva en C++ moderno, con hash table diseñada para cache (no la estructura genérica de Redis), usando io_uring en Linux para I\u002FO asíncrono. Compatibilidad a nivel del protocolo, no a nivel del código.",[12,6534,6535,6537],{},[27,6536,6478],{},": claims de 25× throughput en benchmarks específicos (pipelines pesados en hardware moderno). Memory efficiency real — 2 a 3 veces más datos en la misma RAM que Redis. Sin GIL implícito de single-thread; escala vertical en una máquina con 32+ cores.",[12,6539,6540,6542,6543,6547],{},[27,6541,6484],{},": licencia BSL (Business Source License) — queda cerrada por 4 años antes de volverse Apache 2.0. Es exactamente el mismo patrón de licencia que pegó a otros proyectos de la industria de orquestación por sorpresa, y que tratamos en nuestro post sobre ",[3337,6544,6546],{"href":6545},"\u002Fes\u002Fblog\u002Fpor-que-creamos-heroctl","por qué creamos HeroCtl",". Algunos commands aún incompatibles con Redis en casos de borde (scripts Lua complejos, ciertas operaciones de cluster).",[19,6549,6551],{"id":6550},"cual-elegir-para-nuevo-proyecto-en-2026","¿Cuál elegir para nuevo proyecto en 2026?",[12,6553,6554],{},"El árbol de decisión corto:",[2735,6556,6557,6566,6574,6582,6591],{},[70,6558,6559,6562,6563,6565],{},[27,6560,6561],{},"Default sensato",": ",[27,6564,6408],{},". BSD permanente, paridad Redis, cliente no necesita cambiar, futuro garantizado por backers grandes. No hay razón técnica para preferir Redis OSS para proyecto nuevo en 2026.",[70,6567,6568,6562,6571,6573],{},[27,6569,6570],{},"Performance crítica",[27,6572,6415],{},", si la aplicación sostiene por encima de 100 mil operaciones por segundo y el equipo acepta el riesgo de licencia BSL.",[70,6575,6576,6562,6579,6581],{},[27,6577,6578],{},"Multi-thread sin reescritura",[27,6580,6412],{},", si el cuello de botella es CPU en hardware grande y el equipo prefiere no migrar a Dragonfly.",[70,6583,6584,6562,6587,6590],{},[27,6585,6586],{},"Simplicidad extrema (1 VPS, bajo volumen)",[27,6588,6589],{},"Redis OSS 7.2.4 BSD"," aún funciona perfectamente. Cristalizó como versión estable; va a correr en cualquier Debian\u002FAlpine los próximos cinco años sin quejarse.",[70,6592,6593,6562,6596,6598],{},[27,6594,6595],{},"Migrando de Redis Labs gestionado",[27,6597,6408],{}," es drop-in. Cero código cambiando. La migración es solo operacional — replicación, swap de DNS, rollback si necesario.",[19,6600,6602],{"id":6601},"gestionado-vs-auto-hospedado-la-cuenta-sin-adornos","Gestionado vs auto-hospedado: la cuenta sin adornos",[12,6604,6605],{},"Los números abajo son precio de tabla en mayo de 2026, cambio R$5\u002FUSD.",[368,6607,6609],{"id":6608},"aws-elasticache","AWS ElastiCache",[12,6611,6612],{},"Crece en escalones por instancia:",[2735,6614,6615,6624,6630,6639],{},[70,6616,6617,6620,6621],{},[231,6618,6619],{},"cache.t4g.micro"," (1 GB): cerca de US$15\u002Fmes = ",[27,6622,6623],{},"R$75\u002Fmes",[70,6625,6626,6629],{},[231,6627,6628],{},"cache.t4g.small"," (2 GB): US$30\u002Fmes = R$150\u002Fmes",[70,6631,6632,6635,6636],{},[231,6633,6634],{},"cache.r6g.large"," (13 GB): cerca de US$200\u002Fmes = ",[27,6637,6638],{},"R$1.000\u002Fmes",[70,6640,6641,6644],{},[231,6642,6643],{},"cache.r6g.xlarge"," (26 GB): cerca de US$400\u002Fmes = R$2.000\u002Fmes",[12,6646,6647],{},"Multi-AZ duplica el precio (réplica en otra zona). Backup automático está incluido. Multi-AZ failover real es el argumento principal — pagas para no tener que pensar en eso.",[368,6649,6651],{"id":6650},"upstash","Upstash",[12,6653,6654],{},"Cobro serverless por comando:",[2735,6656,6657,6660,6663,6666],{},[70,6658,6659],{},"Free tier: 256 MB, 500k commands\u002Fdía",[70,6661,6662],{},"Pay-as-you-go: US$0,2 por 100k commands",[70,6664,6665],{},"Para startup con volumen medio (10M commands\u002Fdía): cerca de US$60\u002Fmes = R$300\u002Fmes",[70,6667,6668],{},"Para app con pico bajo: puede quedar entre US$0 y US$10\u002Fmes",[12,6670,6671,6672,6675],{},"La ventaja operacional es única: ",[27,6673,6674],{},"cero capacidad pre-asignada",". Si la app duerme, la cuenta duerme. Para Vercel\u002FCloudflare Workers, es el complemento natural. Para carga sostenida y predecible, queda más caro que ElastiCache.",[368,6677,6679],{"id":6678},"redis-cloud-oferta-directa-de-redis-inc","Redis Cloud (oferta directa de Redis Inc)",[2735,6681,6682,6685,6691],{},[70,6683,6684],{},"Plan Essentials 30MB: free",[70,6686,6687,6688],{},"Plan Pro 5GB single-region: cerca de US$50\u002Fmes = ",[27,6689,6690],{},"R$250\u002Fmes",[70,6692,6693],{},"Plan Pro 10GB multi-AZ: cerca de US$120\u002Fmes = R$600\u002Fmes",[12,6695,6696],{},"Incluye módulos comerciales (Search, JSON, TimeSeries) que no existen en Valkey ni en Redis OSS. Si usas esos módulos, no hay alternativa directa — es Redis Cloud o compras licencia comercial y auto-hospedas.",[368,6698,6700],{"id":6699},"auto-hospedado-en-hetzner","Auto-hospedado en Hetzner",[2735,6702,6703,6713,6719,6728],{},[70,6704,6705,6708,6709,6712],{},[27,6706,6707],{},"CPX21"," (3 vCPU, 4 GB RAM, 80 GB SSD): €7,99 = ",[27,6710,6711],{},"R$44\u002Fmes",". Cabe Valkey de 2 GB con holgura.",[70,6714,6715,6718],{},[27,6716,6717],{},"CPX31"," (4 vCPU, 8 GB RAM, 160 GB SSD): €13,99 = R$78\u002Fmes.",[70,6720,6721,6724,6725,101],{},[27,6722,6723],{},"Cluster de 3 CPX21 para Valkey + Sentinel HA",": 3 × €7,99 = €24\u002Fmes = ",[27,6726,6727],{},"R$130\u002Fmes",[70,6729,6730,6733],{},[27,6731,6732],{},"Cluster de 3 CPX31 para datos serios",": €42\u002Fmes = R$230\u002Fmes.",[12,6735,6736],{},"Para DigitalOcean, Linode, Vultr, multiplica por aproximadamente 1,5×. Para AWS EC2, multiplica por 2×. Pero en cualquier caso queda más barato que el gestionado equivalente.",[368,6738,6740],{"id":6739},"diferencia-practica","Diferencia práctica",[12,6742,6743],{},"Para workload de cache de 8 GB con replicación:",[2735,6745,6746,6749,6752,6755],{},[70,6747,6748],{},"ElastiCache Multi-AZ: ~R$1.000\u002Fmes",[70,6750,6751],{},"Redis Cloud Pro Multi-AZ: ~R$600\u002Fmes",[70,6753,6754],{},"Valkey self-hosted en 3× Hetzner CPX31: R$230\u002Fmes",[70,6756,6757],{},"Valkey single-node en 1× Hetzner CPX31 + backup S3: R$80\u002Fmes",[12,6759,6760,6761,6764],{},"Quien elige el camino gestionado paga ",[27,6762,6763],{},"3 a 10 veces más"," por el mismo throughput. La diferencia es lo que compras con eso: SLA contractual, multi-AZ failover automático, ausencia de pager a las 3 de la mañana. Para equipo pequeño, eso puede valer el precio. Para equipo que ya opera servidores Linux en producción, generalmente no vale la pena.",[19,6766,6768],{"id":6767},"stack-minimo-de-valkey-production-grade","Stack mínimo de Valkey production-grade",[12,6770,6771],{},"Configuración que aguanta producción real sin teatro:",[2735,6773,6774,6780,6789,6804,6810,6816,6822,6828],{},[70,6775,6776,6779],{},[27,6777,6778],{},"Container o systemd service en VPS dedicado."," No comparte máquina con la aplicación — cache y app compiten por RAM, y cuando da error da error para los dos al mismo tiempo.",[70,6781,6782,6788],{},[27,6783,6784,6787],{},[231,6785,6786],{},"maxmemory"," configurado"," entre 50 y 70% de la RAM disponible. Sobrar memoria para el sistema y para los buffers de red es más importante que tener los últimos megabytes para cache.",[70,6790,6791,6562,6796,6799,6800,6803],{},[27,6792,6793],{},[231,6794,6795],{},"maxmemory-policy",[231,6797,6798],{},"allkeys-lru"," si modo cache puro (tirar claves antiguas cuando llene). ",[231,6801,6802],{},"noeviction"," si modo storage (queue, sesiones) — ahí prefiere error de write a perder datos silenciosamente.",[70,6805,6806,6809],{},[27,6807,6808],{},"AOF persistence"," si la carga es cola de jobs (Sidekiq, BullMQ, Resque). Sin AOF, un restart pierde cualquier job que estaba enfilado pero no procesado. RDB es insuficiente en ese escenario porque snapshot es periódico.",[70,6811,6812,6815],{},[27,6813,6814],{},"RDB suficiente"," si la carga es cache puro (Rails cache, Django cache). Si reiniciar perdiendo cache solo significa \"request lento por algunos segundos mientras recalienta\", AOF es overhead innecesario.",[70,6817,6818,6821],{},[27,6819,6820],{},"Replicación async para standby"," en un segundo nodo. Failover manual con swap de DNS interno es aceptable para mucho caso. Failover automático cuesta Sentinel o Cluster.",[70,6823,6824,6827],{},[27,6825,6826],{},"Backup AOF + RDB para S3"," o compatible, diariamente. Restic o rclone resuelven bien.",[70,6829,6830,6833,6834,6837,6838,571,6841,571,6844,571,6847,571,6850,101],{},[27,6831,6832],{},"Monitoring"," con ",[231,6835,6836],{},"redis_exporter"," exportando para Prometheus + alertas en Grafana o similar. Métricas críticas: ",[231,6839,6840],{},"connected_clients",[231,6842,6843],{},"used_memory",[231,6845,6846],{},"evicted_keys",[231,6848,6849],{},"keyspace_hits\u002Fmisses",[231,6851,6852],{},"latency_percentiles",[12,6854,6855],{},"Ese setup corre cómodo en CPX21 (R$44\u002Fmes) sirviendo 50k+ ops\u002Fs sostenidas para app medio.",[19,6857,6859],{"id":6858},"sentinel-o-cluster","¿Sentinel o Cluster?",[12,6861,6862],{},"Pregunta que confunde a mucho equipo viniendo de Redis por primera vez.",[12,6864,6865,6868],{},[27,6866,6867],{},"Sentinel",": 1 master + N réplicas + 3+ procesos sentinel monitoreando. Failover automático cuando el master cae — un sentinel detecta, los sentinels votan, una réplica se vuelve master, clientes reciben nuevo endpoint vía discovery. Todo en un único shard — todo el dataset cabe en un nodo.",[12,6870,6871,6874],{},[27,6872,6873],{},"Cluster",": dataset particionado en 16384 slots distribuidos en 3+ masters. Cada master tiene sus propias réplicas. Multi-shard, escala horizontal de capacidad — puedes tener 100 GB total con ningún nodo individual sosteniendo más de 20 GB.",[12,6876,6877,6878,6881],{},"La regla práctica: ",[27,6879,6880],{},"Sentinel basta hasta dataset de ~100 GB",". Por encima, Cluster es necesario. Para mayoría de las startups, Sentinel es la elección correcta por simplicidad — Cluster añade complejidad real (clave necesita hashtag para operaciones multi-key, scripts Lua quedan restringidos a un slot, algunos clientes tienen bugs en modo cluster).",[12,6883,6884],{},"No uses Cluster por status. Usa Sentinel hasta que la métrica fuerce.",[19,6886,6888],{"id":6887},"patrones-de-sidekiq-bullmq-y-compania","Patrones de Sidekiq, BullMQ y compañía",[12,6890,6891],{},"Uso real, no diagrama de marketing:",[2735,6893,6894,6900,6906,6912,6918],{},[70,6895,6896,6899],{},[27,6897,6898],{},"Sidekiq Ruby",": Redis necesita AOF. Sin AOF, cualquier crash pierde los jobs enfilados que aún no fueron retirados. Sidekiq Pro añade \"reliable fetch\" que mejora — pero el backstop sigue siendo AOF.",[70,6901,6902,6905],{},[27,6903,6904],{},"BullMQ Node",": similar. AOF essential para durability. BullMQ usa estructuras de datos que dependen de atomicidad transaccional de Redis — restart sin AOF puede dejar cola en estado inconsistente.",[70,6907,6908,6911],{},[27,6909,6910],{},"Resque Ruby",": el padre de todos. AOF necesario por las mismas razones.",[70,6913,6914,6917],{},[27,6915,6916],{},"Cache puro (Rails.cache, Django cache, Laravel cache)",": puede correr sin AOF, RDB suficiente. Perder cache en un restart es aceptable.",[70,6919,6920,6923],{},[27,6921,6922],{},"Pub\u002Fsub puro",": ni siquiera necesita persistencia. Pub\u002Fsub es fire-and-forget por diseño.",[12,6925,6926],{},"Mezclar uso de cache y queue en el mismo Redis funciona — basta configurar AOF (la carga \"peor caso\" determina). Pero para workload serio, separar en dos instancias (una para cache sin AOF, otra para queue con AOF) es más limpio. Operacionalmente baratísimo si ya hay orquestador corriendo.",[19,6928,6930],{"id":6929},"elasticache-sao-paulo-es-confiable","¿ElastiCache São Paulo es confiable?",[12,6932,6933,6934,6937],{},"Sí — 99.99% uptime SLA contractual, multi-AZ en la región São Paulo (",[231,6935,6936],{},"sa-east-1","), backup automático, failover testeado. Latencia de la app desde São Paulo a ElastiCache São Paulo queda en 1-3ms, indistinguible de Redis local para mayoría de los workloads.",[12,6939,6940],{},"El punto débil no es confiabilidad técnica, es coste y lock-in. AWS Brazil cobra cerca de 30% más caro que regiones norteamericanas por el mismo recurso. Y migrar de ElastiCache a otro proveedor después implica dump\u002Frestore + cutover coordinado — no es apocalipsis, pero es trabajo de fin de semana.",[19,6942,6943],{"id":3837},"Tabla comparativa: 12 criterios",[119,6945,6946,6967],{},[122,6947,6948],{},[125,6949,6950,6952,6954,6956,6958,6960,6962,6964],{},[128,6951,2983],{},[128,6953,6424],{},[128,6955,6408],{},[128,6957,6412],{},[128,6959,6415],{},[128,6961,5445],{},[128,6963,6651],{},[128,6965,6966],{},"Self-hosted Valkey",[141,6968,6969,6994,7018,7040,7065,7086,7108,7129,7152,7176,7197,7218],{},[125,6970,6971,6974,6977,6980,6982,6985,6988,6991],{},[146,6972,6973],{},"Licencia",[146,6975,6976],{},"RSAL\u002FSSPL (7.4+)",[146,6978,6979],{},"BSD",[146,6981,6979],{},[146,6983,6984],{},"BSL → Apache 4 años",[146,6986,6987],{},"Comercial AWS",[146,6989,6990],{},"Comercial Upstash",[146,6992,6993],{},"BSD permanente",[125,6995,6996,6999,7002,7004,7007,7009,7012,7015],{},[146,6997,6998],{},"Threading",[146,7000,7001],{},"Single",[146,7003,7001],{},[146,7005,7006],{},"Multi",[146,7008,7006],{},[146,7010,7011],{},"Single (engine 7)",[146,7013,7014],{},"Serverless",[146,7016,7017],{},"Configurable",[125,7019,7020,7023,7026,7028,7030,7033,7035,7038],{},[146,7021,7022],{},"Compat. cliente Redis",[146,7024,7025],{},"100%",[146,7027,7025],{},[146,7029,7025],{},[146,7031,7032],{},"95%+",[146,7034,7025],{},[146,7036,7037],{},"100% (subset comandos)",[146,7039,7025],{},[125,7041,7042,7045,7048,7050,7053,7056,7059,7062],{},[146,7043,7044],{},"Throughput baseline",[146,7046,7047],{},"100k ops\u002Fs",[146,7049,7047],{},[146,7051,7052],{},"250k ops\u002Fs",[146,7054,7055],{},"1M+ ops\u002Fs",[146,7057,7058],{},"depende inst.",[146,7060,7061],{},"depende plan",[146,7063,7064],{},"100-250k ops\u002Fs",[125,7066,7067,7070,7072,7074,7076,7078,7081,7084],{},[146,7068,7069],{},"Persistencia AOF",[146,7071,3065],{},[146,7073,3065],{},[146,7075,3065],{},[146,7077,3065],{},[146,7079,7080],{},"Sí (snapshot)",[146,7082,7083],{},"Gestionada",[146,7085,3065],{},[125,7087,7088,7091,7093,7095,7097,7099,7102,7105],{},[146,7089,7090],{},"Replicación",[146,7092,3065],{},[146,7094,3065],{},[146,7096,3065],{},[146,7098,3065],{},[146,7100,7101],{},"Multi-AZ",[146,7103,7104],{},"Multi-region",[146,7106,7107],{},"Sí (config manual)",[125,7109,7110,7113,7116,7118,7120,7122,7125,7127],{},[146,7111,7112],{},"Failover automático",[146,7114,7115],{},"Sentinel\u002FCluster",[146,7117,7115],{},[146,7119,7115],{},[146,7121,6873],{},[146,7123,7124],{},"Built-in",[146,7126,7124],{},[146,7128,7115],{},[125,7130,7131,7134,7137,7139,7141,7143,7146,7149],{},[146,7132,7133],{},"Coste 8GB\u002Fmes (R$)",[146,7135,7136],{},"80 (VPS)",[146,7138,7136],{},[146,7140,7136],{},[146,7142,7136],{},[146,7144,7145],{},"1000 (Multi-AZ)",[146,7147,7148],{},"300-500",[146,7150,7151],{},"80-230",[125,7153,7154,7157,7160,7163,7165,7168,7171,7174],{},[146,7155,7156],{},"Lock-in",[146,7158,7159],{},"Medio (licencia)",[146,7161,7162],{},"Bajo",[146,7164,7162],{},[146,7166,7167],{},"Medio (BSL)",[146,7169,7170],{},"Alto (AWS)",[146,7172,7173],{},"Alto (Upstash API)",[146,7175,7162],{},[125,7177,7178,7181,7184,7186,7188,7190,7193,7195],{},[146,7179,7180],{},"Módulos premium",[146,7182,7183],{},"Pagos",[146,7185,3056],{},[146,7187,3056],{},[146,7189,3056],{},[146,7191,7192],{},"Add-on $$",[146,7194,3062],{},[146,7196,3056],{},[125,7198,7199,7202,7205,7207,7209,7211,7214,7216],{},[146,7200,7201],{},"Operacional",[146,7203,7204],{},"Tú",[146,7206,7204],{},[146,7208,7204],{},[146,7210,7204],{},[146,7212,7213],{},"AWS",[146,7215,6651],{},[146,7217,7204],{},[125,7219,7220,7223,7226,7229,7231,7233,7236,7238],{},[146,7221,7222],{},"Soporte SLA",[146,7224,7225],{},"Pago",[146,7227,7228],{},"Comunidad",[146,7230,7228],{},[146,7232,7225],{},[146,7234,7235],{},"Incluido",[146,7237,7235],{},[146,7239,7204],{},[19,7241,7243],{"id":7242},"cuando-aun-gestionado-tiene-sentido","Cuándo aún gestionado tiene sentido",[12,7245,7246],{},"La honestidad es mecanismo de defensa de cualquier recomendación técnica. Hay cuatro perfiles en que pagar por gestionado es la elección correcta:",[2735,7248,7249,7255,7261,7267],{},[70,7250,7251,7254],{},[27,7252,7253],{},"Equipo sin capacidad operacional para Redis cluster."," Si nadie en la empresa sabe debugar un master que ya no responde, o interpretar latencia de fork RDB, o cuidar de backup AOF — pagar AWS para hacer eso es racional. No es excusa, es división de trabajo.",[70,7256,7257,7260],{},[27,7258,7259],{},"Compliance que exige proveedor SOC2\u002FISO certificado."," Auditoría que pide \"proveedor certificado X\" no acepta \"corremos Valkey en un VPS Hetzner\". El camino es ElastiCache, Redis Cloud o similar con certificaciones en el contrato.",[70,7262,7263,7266],{},[27,7264,7265],{},"Volumen que necesita escalar instantáneo."," Aplicación que va de 100 req\u002Fs a 100k req\u002Fs en 5 minutos por una campaña viral — el camino serverless de Upstash es donde brilla. Auto-hospedado necesita capacidad reservada antes; serverless crece al instante.",[70,7268,7269,7272],{},[27,7270,7271],{},"Aplicación totalmente serverless."," Si la app corre en Vercel o Cloudflare Workers y Redis también necesita ser serverless por modelo de cobro, Upstash es prácticamente la única opción sana. Conectar funciones edge a un Redis en VPS implica cold start malo.",[19,7274,7276],{"id":7275},"cuando-auto-hospedar-es-obvio","Cuándo auto-hospedar es obvio",[12,7278,7279],{},"Y cuatro perfiles en que pagar gestionado es desperdicio:",[2735,7281,7282,7288,7294,7300],{},[70,7283,7284,7287],{},[27,7285,7286],{},"Startup con R$10k–R$200k MRR optimizando coste."," La diferencia entre R$80\u002Fmes y R$1.000\u002Fmes de cache es 1% del coste total de un SaaS pequeño; también son 11 horas de salario persona-hora de dev. Vale la pena hacer la cuenta.",[70,7289,7290,7293],{},[27,7291,7292],{},"Workload predecible."," Si el volumen de cache crece 10% al mes, no hay ventaja en escalar serverless. Capacidad reservada en VPS es más barata y más predecible.",[70,7295,7296,7299],{},[27,7297,7298],{},"Equipo tiene 1+ persona cómoda con Linux\u002FDocker."," Si ya tiene a alguien que opera Postgres, nginx, Docker — Redis\u002FValkey es más fácil que cualquiera de ellos. Curva de aprendizaje son días, no semanas.",[70,7301,7302,7305],{},[27,7303,7304],{},"Ya existe cluster propio."," Si la empresa corre orquestador (HeroCtl, Coolify, plataforma similar) con nodos sobrando, Valkey se vuelve solo otro job. Coste marginal cerca de cero — ya pagas por los nodos.",[19,7307,7309],{"id":7308},"heroctl-como-infraestructura-para-valkey","HeroCtl como infraestructura para Valkey",[12,7311,7312],{},"Para quien opera HeroCtl, correr Valkey en producción es ejercicio de configuración corta. Un archivo de ~30 líneas describe job con:",[2735,7314,7315,7318,7321,7324,7327],{},[70,7316,7317],{},"Container Valkey 8.x oficial",[70,7319,7320],{},"Volumen nombrado replicado entre nodos (datos sobreviven a kill -9 de servidor)",[70,7322,7323],{},"Recursos reservados (RAM y CPU) con límites duros",[70,7325,7326],{},"Health check en el ping Valkey",[70,7328,7329,7330,7333],{},"Enrutamiento interno entre servicios (la app habla con ",[231,7331,7332],{},"valkey.servicio.local"," sin exponer puerto a internet)",[12,7335,7336,7337,7339,7340,7342],{},"Backup automatizado AOF + RDB para S3-compatible está disponible en el plan ",[27,7338,4356],{}," — sin montar restic externo, sin cron manual en el host. Métricas Valkey salen por el ",[231,7341,6836],{}," corriendo como sidecar y aparecen en el Prometheus interno (ya incluido como job del propio cluster, sin stack externo).",[12,7344,7345],{},"Failover Sentinel está integrado al plano de control del orquestador: si el nodo del master Valkey cae, el cluster detecta en torno de 7 segundos y la réplica es promovida. La configuración de la app se actualiza vía descubrimiento de servicio — ningún redeploy manual.",[12,7347,7348],{},"Para startup con 4 servidores corriendo el orquestador, ese setup sustituye ElastiCache Multi-AZ entero a coste marginal cero (los servidores ya están allí). La diferencia mensual real es el salario-equivalente de una persona, dependiendo del tamaño de la operación.",[19,7350,7352],{"id":7351},"preguntas-que-recibimos","Preguntas que recibimos",[12,7354,7355,7358,7359,571,7362,571,7365,571,7368,571,7371,571,7374,7377],{},[27,7356,7357],{},"¿Valkey es compatible con client libraries Redis?","\nSí, en 100% de los casos prácticos. El protocolo es idéntico — ",[231,7360,7361],{},"redis-cli",[231,7363,7364],{},"node-redis",[231,7366,7367],{},"ioredis",[231,7369,7370],{},"redis-rb",[231,7372,7373],{},"redis-py",[231,7375,7376],{},"go-redis",", todos funcionan sin cambiar una línea. Lo que se cambia es solo el endpoint. En 2026, varias bibliotecas ya anuncian soporte explícito a Valkey en el README, pero eso es cosmético — el protocolo es el mismo.",[12,7379,7380,7383,7384,7387,7388,7391],{},[27,7381,7382],{},"¿Puedo migrar de Redis Labs gestionado a Valkey self-hosted sin downtime?","\nSí, con replicación. Configuras Valkey como réplica del Redis Labs (",[231,7385,7386],{},"REPLICAOF host port","), esperas sincronizar (algunos minutos a horas dependiendo del dataset), promueves Valkey a master (",[231,7389,7390],{},"REPLICAOF NO ONE","), haces cutover de DNS interno, decomisionas Redis Labs después de período de observación. Ventana de error real es de segundos durante el swap.",[12,7393,7394,7397],{},[27,7395,7396],{},"¿Dragonfly vale el riesgo de BSL?","\nDepende del horizonte de la empresa. BSL convierte a Apache 2.0 después de 4 años por el modelo estándar — entonces código de hoy será abierto hasta 2030. El riesgo es que la empresa detrás (DragonflyDB Inc) siga el camino de Redis Inc y haga la conversión menos amigable. Para workloads que exigen performance que Valkey no entrega (por encima de 500k ops\u002Fs sostenido), Dragonfly puede ser la elección correcta a pesar del riesgo. Para el resto, Valkey es más conservador.",[12,7399,7400,7403,7404,7406],{},[27,7401,7402],{},"¿Cuánto de RAM consume un Redis con 1 GB de datos útiles?","\nCuenta práctica: dataset de 1 GB ocupa entre 1,3 y 2 GB reales de RAM (overhead de estructura, fragmentación, buffers de cliente, replication backlog). Configurar ",[231,7405,6786],{}," en 60% de la RAM disponible es regla segura — instancia de 4 GB cabe ~2,5 GB de datos útiles con holgura.",[12,7408,7409,7412],{},[27,7410,7411],{},"¿Sidekiq necesita AOF de verdad? Los docs de Sidekiq dicen que se puede correr sin.","\nLos docs dicen que técnicamente corre. En producción, sin AOF, cualquier restart inesperado pierde jobs enfilados que estaban en el buffer. Para cola de \"envío de e-mail bienvenida\", lo descubres cuando el cliente reclama. Para cola de \"cobro recurrente\", lo descubres cuando contable reclama. AOF es barato (incremento de 5-10% de I\u002FO), el coste de no tenerlo es grande.",[12,7414,7415,7418],{},[27,7416,7417],{},"¿Cluster vs Sentinel para app procesando 50k jobs\u002Fdía?","\nSentinel. 50k jobs\u002Fdía son 0,6 ops\u002Fs media — caben en 100 MB de RAM Redis. Cluster es overkill por orden de magnitud. Sentinel resuelve failover automático con 1 master + 1 réplica + 3 sentinels (3 procesos sentinel en VPSs separados, pueden cohabitar con otras cosas).",[12,7420,7421,7424],{},[27,7422,7423],{},"¿ElastiCache São Paulo tiene latencia buena para app corriendo en São Paulo?","\nSí, 1-3ms p99 dentro de la misma AZ. El problema no es latencia — es coste y lock-in. Latencia solo se vuelve tema si la app está en otro proveedor (Hetzner FSN, DigitalOcean NYC) intentando hablar con ElastiCache São Paulo — ahí sube a 130-200ms y el argumento desaparece.",[12,7426,7427,7430],{},[27,7428,7429],{},"¿Cómo hacer backup de Valkey self-hosted que sobreviva a desastre?","\nTres capas. Primera: AOF persistente en disco local (sobrevive a restart). Segunda: snapshot RDB diario copiado para storage S3-compatible (Wasabi, Backblaze B2, Cloudflare R2 — todos más baratos que S3 de AWS para ese caso). Tercera: snapshot semanal copiado para otro proveedor de storage (segunda región, segundo proveedor). Restic o rclone hacen el trabajo. Coste total de almacenamiento para backup de Valkey de 4 GB: cerca de US$1\u002Fmes.",[19,7432,3310],{"id":3309},[12,7434,7435],{},"En 2026, \"Redis en producción\" se volvió pregunta con más matiz de lo que tenía en 2023. La licencia del producto original cambió, el fork Linux Foundation maduró, alternativas multi-thread están de pie, la oferta serverless tiene caso de uso real. Elegir entre las cuatro implementaciones y los tres caminos gestionados es ejercicio honesto — no hay respuesta única.",[12,7437,7438,7439,7441],{},"Nuestra recomendación default para startup en 2026: ",[27,7440,5573],{}," en un cluster propio, modo Sentinel, AOF activado si hay queue, monitoring con Prometheus. Coste en el rango de R$80–R$230\u002Fmes, contra R$600–R$2.000\u002Fmes de las alternativas gestionadas equivalentes. Compatibilidad total con cualquier biblioteca Redis. Sin exposición a la licencia RSAL. Migración reversible si se vuelve un problema.",[12,7443,7444],{},"Para poner ese stack de pie:",[224,7446,7447],{"className":226,"code":2949,"language":228,"meta":229,"style":229},[231,7448,7449],{"__ignoreMap":229},[234,7450,7451,7453,7455,7457,7459],{"class":236,"line":237},[234,7452,1220],{"class":247},[234,7454,2958],{"class":251},[234,7456,2961],{"class":255},[234,7458,2964],{"class":383},[234,7460,2967],{"class":247},[12,7462,7463,7464,7468,7469,7471],{},"Y leer en paralelo: ",[3337,7465,7467],{"href":7466},"\u002Fes\u002Fblog\u002Fpostgres-en-produccion-gestionado-vs-auto-hospedado","Postgres en producción: gestionado vs auto-hospedado"," (mismo análisis para la base de datos) y ",[3337,7470,6338],{"href":6337}," (la cuenta consolidada de todo el stack).",[3351,7473,4377],{},{"title":229,"searchDepth":244,"depth":244,"links":7475},[7476,7477,7478,7484,7485,7492,7493,7494,7495,7496,7497,7498,7499,7500,7501],{"id":21,"depth":244,"text":22},{"id":6440,"depth":244,"text":6441},{"id":6463,"depth":244,"text":6464,"children":7479},[7480,7481,7482,7483],{"id":6467,"depth":271,"text":6424},{"id":6488,"depth":271,"text":6408},{"id":6507,"depth":271,"text":6412},{"id":6526,"depth":271,"text":6415},{"id":6550,"depth":244,"text":6551},{"id":6601,"depth":244,"text":6602,"children":7486},[7487,7488,7489,7490,7491],{"id":6608,"depth":271,"text":6609},{"id":6650,"depth":271,"text":6651},{"id":6678,"depth":271,"text":6679},{"id":6699,"depth":271,"text":6700},{"id":6739,"depth":271,"text":6740},{"id":6767,"depth":244,"text":6768},{"id":6858,"depth":244,"text":6859},{"id":6887,"depth":244,"text":6888},{"id":6929,"depth":244,"text":6930},{"id":3837,"depth":244,"text":6943},{"id":7242,"depth":244,"text":7243},{"id":7275,"depth":244,"text":7276},{"id":7308,"depth":244,"text":7309},{"id":7351,"depth":244,"text":7352},{"id":3309,"depth":244,"text":3310},"2026-05-20","Redis cambió de licencia en 2024, Valkey nació como fork OSS, Dragonfly bate benchmarks. En 2026, elegir cache ya no es elegir Redis — es elegir entre 4 productos. Análisis honesto con costes.",{},"\u002Fes\u002Fblog\u002Fredis-en-produccion-gestionado-vs-auto-hospedado",{"title":6400,"description":7503},{"loc":7505},"es\u002Fblog\u002Fredis-en-produccion-gestionado-vs-auto-hospedado",[7510,6488,7511,7512,3394],"redis","cache","self-hosted","N3mLPFFjt2L-yR8G9IKJ-p0pd4dVOinPYOpCKt1ukTI",{"id":7515,"title":7516,"author":7,"body":7517,"category":8761,"cover":3380,"date":8762,"description":8763,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":8764,"navigation":411,"path":8765,"readingTime":8766,"seo":8767,"sitemap":8768,"stem":8769,"tags":8770,"__hash__":8776},"blog_es\u002Fes\u002Fblog\u002Fgithub-actions-vs-gitlab-ci-vs-drone.md","GitHub Actions vs GitLab CI vs Drone: qué CI\u002FCD elegir para startup",{"type":9,"value":7518,"toc":8724},[7519,7526,7529,7533,7548,7554,7560,7566,7569,7572,7576,7579,7582,7606,7609,7613,7620,7624,7656,7660,7663,7708,7711,7714,7717,7721,7724,7727,7747,7750,7754,7757,7761,7797,7801,7804,7809,7858,7861,7867,7881,7884,7888,7891,7895,7938,7942,7968,7972,7975,7982,7986,7989,8109,8116,8119,8123,8126,8187,8190,8192,8196,8375,8378,8382,8385,8389,8395,8399,8405,8408,8412,8418,8421,8425,8431,8435,8441,8445,8448,8451,8477,8487,8490,8494,8498,8501,8517,8521,8524,8535,8538,8542,8545,8548,8552,8555,8573,8576,8578,8583,8586,8591,8594,8599,8602,8607,8610,8615,8618,8623,8626,8631,8638,8643,8646,8651,8654,8656,8660,8663,8695,8698,8703,8706,8717],[12,7520,7521,7522,7525],{},"La elección de CI\u002FCD en 2026 ya no es sobre \"qué herramienta tiene más features\". Las tres serias — GitHub Actions, GitLab CI, Drone (y su fork Woodpecker) — hacen lo básico bien. La elección real es sobre ",[27,7523,7524],{},"dónde tu dolor va a aparecer primero",": en la factura a fin de mes, en la complejidad del workflow cuando el monorepo crezca, o a la hora de subir un runner que nadie entiende cuando el dev senior se va de vacaciones.",[12,7527,7528],{},"Este post es una comparación honesta para tech leads decidiendo CI\u002FCD en 2026. Sin ranking artificial, sin columna en la que una herramienta sea \"campeona\" en todo. Tradeoffs explícitos, números en euros, y una recomendación por perfil al final.",[19,7530,7532],{"id":7531},"tldr-200-palabras","TL;DR (200 palabras)",[12,7534,7535,7536,571,7539,571,7542,2403,7545,101],{},"La decisión de CI\u002FCD en 2026 sigue cuatro fuerzas: ",[27,7537,7538],{},"dónde está hospedado el código",[27,7540,7541],{},"coste de minutos",[27,7543,7544],{},"complejidad de workflow",[27,7546,7547],{},"disposición a operar self-hosted",[12,7549,7550,7553],{},[27,7551,7552],{},"GitHub Actions"," ganó mindshare absoluto para proyectos en GitHub. Es gratis hasta 2000 minutos\u002Fmes en repos públicos; después cuesta US$0,008\u002Fmin en runner Linux — entre US$5 y US$30\u002Fmes para startup típica (5 € a 30 €). Marketplace tiene 10 mil acciones listas. El talón es el pricing de minutos cuando el volumen crece.",[12,7555,7556,7559],{},[27,7557,7558],{},"GitLab CI"," es más completo: grafo de dependencias entre jobs nativo, parent-child pipelines, monorepo handling mejor, registry de imágenes incluido, scanning de seguridad embebido. Self-hosted (Community Edition) es gratuito pero exige 4 a 8 GB de RAM y operación activa. SaaS Premium es US$29\u002Fusuario\u002Fmes — caro para equipo grande.",[12,7561,7562,7565],{},[27,7563,7564],{},"Drone\u002FWoodpecker"," auto-hospedado es la opción para reducir coste a cero variable. Un servidor de 5 € a 15 €\u002Fmes corre CI para cinco a diez proyectos. Cuesta en ops: tú operas los runners.",[12,7567,7568],{},"Para startup ES pequeña en GitHub, empieza en el plan gratuito de Actions. Cuando pase de US$30\u002Fmes, considera Woodpecker self-hosted. Para empresa que valora CI + issue tracker + registry en un producto solo, GitLab self-hosted.",[12,7570,7571],{},"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",[19,7573,7575],{"id":7574},"por-que-esa-decision-importa-mas-de-lo-que-parece","¿Por qué esa decisión importa más de lo que parece?",[12,7577,7578],{},"CI\u002FCD es la infraestructura más usada de cualquier equipo de producto: cada commit toca el sistema, cada PR depende de él, cada deploy pasa por él. Una elección equivocada no te rompe en el primer mes — te rompe en el tercer año, cuando migrar cuesta cuatro semanas de dos personas y pagas eso mientras el roadmap queda parado.",[12,7580,7581],{},"Los tres síntomas que indican que la elección fue equivocada:",[67,7583,7584,7590,7600],{},[70,7585,7586,7589],{},[27,7587,7588],{},"La factura crece más rápido que el equipo."," Si el coste de CI se duplica cada seis meses sin que el volumen de deploys lo justifique, el pricing model no es el tuyo.",[70,7591,7592,7595,7596,7599],{},[27,7593,7594],{},"Los workflows se vuelven copy-paste."," Si cada nuevo proyecto empieza con ",[231,7597,7598],{},"cp -r .github\u002Fworkflows\u002F",", la herramienta no tiene composición decente.",[70,7601,7602,7605],{},[27,7603,7604],{},"Fallos en CI tardan más de una hora en debuguearse."," Si reproducir el error local exige correr una imagen Docker que nadie sabe montar, el build no es portable.",[12,7607,7608],{},"Los tres competidores principales resuelven esos síntomas de formas diferentes. Vamos por partes.",[19,7610,7612],{"id":7611},"github-actions-el-estandar-de-facto-vale-el-precio","GitHub Actions: el estándar de facto — ¿vale el precio?",[12,7614,7615,7616,7619],{},"Si tu código está en GitHub, Actions tiene una ventaja estructural que no se puede ignorar: cero fricción de integración. Creas ",[231,7617,7618],{},".github\u002Fworkflows\u002Fci.yml",", haces push, y está corriendo. Sin registro separado, sin token cruzado, sin webhook que configurar.",[368,7621,7623],{"id":7622},"lo-que-actions-hace-bien","Lo que Actions hace bien",[2735,7625,7626,7632,7638,7644,7650],{},[70,7627,7628,7631],{},[27,7629,7630],{},"Marketplace gigante."," Más de diez mil acciones listas para tareas comunes: setup de Node, Python, Go; deploy a AWS, GCP, Azure; firma de imágenes; scanning de seguridad. La mayoría es mantenida por el propio proveedor de la tecnología (HashiCorp publica la suya, AWS publica la suya, etc).",[70,7633,7634,7637],{},[27,7635,7636],{},"Matrix builds."," Correr la misma suite contra cinco versiones de Node o tres sistemas operativos es una clave en tres líneas.",[70,7639,7640,7643],{},[27,7641,7642],{},"Reusable workflows."," Desde 2021 puedes extraer workflows compartidos entre repos de la misma organización — soluciona el problema de \"copy-paste entre proyectos\" para equipos medianos.",[70,7645,7646,7649],{},[27,7647,7648],{},"Deployment protection rules."," Approvals manuales, ventanas de horario, restricción por branch — todo configurable sin plugin.",[70,7651,7652,7655],{},[27,7653,7654],{},"Self-hosted runners."," Puedes correr el agente en tu propia infra y usar la UI de Actions solo como orquestador. Resuelve el problema de minutos para equipos con volumen alto.",[368,7657,7659],{"id":7658},"lo-que-actions-cobra-caro","Lo que Actions cobra caro",[12,7661,7662],{},"El modelo de cobro es por minuto, y los números importan:",[119,7664,7665,7674],{},[122,7666,7667],{},[125,7668,7669,7672],{},[128,7670,7671],{},"Tipo de runner",[128,7673,136],{},[141,7675,7676,7684,7692,7700],{},[125,7677,7678,7681],{},[146,7679,7680],{},"Linux 2 vCPU (estándar)",[146,7682,7683],{},"US$0,008\u002Fmin (0,007 €)",[125,7685,7686,7689],{},[146,7687,7688],{},"Windows 2 vCPU",[146,7690,7691],{},"US$0,016\u002Fmin (0,014 €)",[125,7693,7694,7697],{},[146,7695,7696],{},"macOS (necesario para builds iOS)",[146,7698,7699],{},"US$0,08\u002Fmin (0,07 €)",[125,7701,7702,7705],{},[146,7703,7704],{},"Larger Linux (4 vCPU+)",[146,7706,7707],{},"US$0,016\u002Fmin y arriba",[12,7709,7710],{},"Para una startup en GitHub con cinco devs y workflow razonable (build + test + lint en cada PR), el consumo típico es 800 a 2500 minutos\u002Fmes en Linux. Eso da US$6 a US$20\u002Fmes — o sea, entre 5 € y 18 €. Cabe en la línea de \"herramientas de dev\" sin dolor.",[12,7712,7713],{},"Cuando duele: workflows pesados (E2E con Playwright, builds Rust, tests que suben Postgres + Redis en cada job) fácilmente pasan de 10 mil minutos\u002Fmes. A US$0,008\u002Fmin eso se vuelve US$80\u002Fmes — 70 €. Multiplica por 12 y estás pagando 850 €\u002Faño en CI.",[12,7715,7716],{},"Builds macOS son el peor caso: US$0,40\u002Fmin es diez veces más que Linux. Equipos que mantienen apps iOS gastan tres a cuatro veces más en CI que en infra de producción.",[368,7718,7720],{"id":7719},"self-hosted-runners-de-actions-resuelven","¿Self-hosted runners de Actions resuelven?",[12,7722,7723],{},"Parcialmente. Corres el binario del runner en una máquina tuya, lo registras en el repo o en la organización, y los jobs van ahí en lugar del pool gestionado. Coste de minuto va a cero — pagas solo la máquina.",[12,7725,7726],{},"Pero tres trampas:",[67,7728,7729,7735,7741],{},[70,7730,7731,7734],{},[27,7732,7733],{},"Mantenimiento del runner."," La versión actualiza con frecuencia; runners desactualizados empiezan a fallar silenciosamente. Sin automatización, se vuelve tarea de operación manual.",[70,7736,7737,7740],{},[27,7738,7739],{},"Scaling manual."," Si el equipo tiene cinco devs abriendo 20 PRs simultáneos, un runner serializa todo. Necesitas N runners — y provisionar\u002Fdesprovisionar conforme demanda exige tooling adicional.",[70,7742,7743,7746],{},[27,7744,7745],{},"Seguridad en repos públicos."," Self-hosted runners en repo público son una puerta abierta para que cualquier fork malicioso corra código arbitrario en tu máquina. Siempre restrito a repos privados u organización confiable.",[12,7748,7749],{},"La solución madura es Actions Runner Controller (ARC): un operador que sube runners on-demand en un cluster Kubernetes o similar. Resuelve scaling, pero añade una capa entera de infraestructura — no es trivial.",[19,7751,7753],{"id":7752},"gitlab-ci-el-competidor-pesado-aun-tiene-sentido","GitLab CI: el competidor \"pesado\" ¿aún tiene sentido?",[12,7755,7756],{},"GitLab CI es más antiguo que Actions, más completo en features, y menos popular fuera de los equipos que ya están en la plataforma GitLab. La pregunta correcta no es \"¿GitLab CI es mejor que Actions?\", es \"¿vale la pena migrar a GitLab para usar GitLab CI?\"",[368,7758,7760],{"id":7759},"lo-que-gitlab-ci-hace-mejor","Lo que GitLab CI hace mejor",[2735,7762,7763,7773,7779,7785,7791],{},[70,7764,7765,7768,7769,7772],{},[27,7766,7767],{},"Grafo de dependencias (DAG)."," Nativo, sin tooling externo. Declaras ",[231,7770,7771],{},"needs: [job_a, job_b]"," y los jobs corren en paralelo respetando dependencias. Para workflows con 30+ jobs (monorepo grande, varios lenguajes, deploy multi-entorno), eso es la diferencia entre 8 minutos y 25 minutos por pipeline.",[70,7774,7775,7778],{},[27,7776,7777],{},"Parent-child pipelines."," Pipeline grande puede disparar pipelines hijos con lógica condicional — útil para monorepo donde solo servicios alterados necesitan buildar.",[70,7780,7781,7784],{},[27,7782,7783],{},"Registry de imágenes incluido."," Cada proyecto viene con un registry de container privado nativo. Sin configurar secretos para Amazon ECR, Docker Hub o similar.",[70,7786,7787,7790],{},[27,7788,7789],{},"Pages, security scanning, code quality, dependency scanning"," — todo embebido en la plataforma. En Actions cada uno es una acción separada del marketplace.",[70,7792,7793,7796],{},[27,7794,7795],{},"Merge request integration profundo."," Pipelines aparecen dentro del MR con diff de cobertura, comparación de bundle size, comparación de tiempo de build. En Actions los checks aparecen como links — en GitLab son datos estructurados.",[368,7798,7800],{"id":7799},"donde-gitlab-ci-cobra-caro","Donde GitLab CI cobra caro",[12,7802,7803],{},"Dos dimensiones.",[12,7805,7806],{},[27,7807,7808],{},"Pricing SaaS:",[119,7810,7811,7823],{},[122,7812,7813],{},[125,7814,7815,7818,7820],{},[128,7816,7817],{},"Plan",[128,7819,136],{},[128,7821,7822],{},"Límite mensual de minutos",[141,7824,7825,7836,7847],{},[125,7826,7827,7830,7833],{},[146,7828,7829],{},"Free",[146,7831,7832],{},"US$0\u002Fusuario",[146,7834,7835],{},"400 minutos",[125,7837,7838,7841,7844],{},[146,7839,7840],{},"Premium",[146,7842,7843],{},"US$29\u002Fusuario\u002Fmes (26 €)",[146,7845,7846],{},"10.000 minutos",[125,7848,7849,7852,7855],{},[146,7850,7851],{},"Ultimate",[146,7853,7854],{},"US$99\u002Fusuario\u002Fmes (88 €)",[146,7856,7857],{},"50.000 minutos",[12,7859,7860],{},"Para equipo de cinco devs en Premium, son US$145\u002Fmes — 130 €. Ese es solo el ticket de entrada; minutos extra cuestan aparte. Para equipo de 20, US$580\u002Fmes = 520 € solo en suscripción.",[12,7862,7863,7866],{},[27,7864,7865],{},"Self-hosted Community Edition"," es gratuito y elimina ese coste de licencia — pero:",[2735,7868,7869,7872,7875,7878],{},[70,7870,7871],{},"Mínimo realista: 4 vCPU, 8 GB RAM (16 GB si vas a usar registry + pages + scanning).",[70,7873,7874],{},"VPS adecuado: 20 € a 45 €\u002Fmes.",[70,7876,7877],{},"Ops: 2 a 4 horas\u002Fmes en actualización, backup, monitoreo.",[70,7879,7880],{},"Actualizaciones mensuales. GitLab tiene cadencia rígida; quedarse tres versiones atrás abre brechas de seguridad documentadas.",[12,7882,7883],{},"En producción real, self-hosted GitLab da menos trabajo que Kubernetes pero más que Actions SaaS. Es un servidor real que tú operas.",[19,7885,7887],{"id":7886},"drone-ci-y-woodpecker-la-alternativa-minimalista","Drone CI y Woodpecker: la alternativa minimalista",[12,7889,7890],{},"Drone CI nació en 2014 como el \"CI nativo de container\": cada step del pipeline es un container, sin magia. En 2020 la empresa detrás (Drone Inc.) fue adquirida por Harness, y el producto ganó una versión Cloud comercial. El fork comunitario Woodpecker continúa 100% open-source, con API compatible con Drone.",[368,7892,7894],{"id":7893},"lo-que-dronewoodpecker-hacen-bien","Lo que Drone\u002FWoodpecker hacen bien",[2735,7896,7897,7906,7920,7926,7932],{},[70,7898,7899,7902,7903,7905],{},[27,7900,7901],{},"YAML simple."," Cada step declara una imagen y un comando. Sin DSL, sin actions reutilizables con semántica propia. Lo que ejecutas localmente con ",[231,7904,2406],{}," es lo que ejecuta en CI.",[70,7907,7908,7911,7912,7915,7916,7919],{},[27,7909,7910],{},"Container-native."," No hay \"executor\" Java, no hay agente Python ejecutando steps. Cada step es un container aislado. Reproducir el error localmente es literal: copia el ",[231,7913,7914],{},"image:"," y los ",[231,7917,7918],{},"commands:"," del YAML y lo corres en el terminal.",[70,7921,7922,7925],{},[27,7923,7924],{},"Self-hosted desde el día uno."," No hay \"Drone Cloud gratis\" pulling features hacia la versión paga. El servidor + los runners son el producto entero.",[70,7927,7928,7931],{},[27,7929,7930],{},"Plugins vía container."," Cada plugin (deploy SSH, Slack, Docker push, AWS) es una imagen publicada. Versionado igual a cualquier otra dependencia.",[70,7933,7934,7937],{},[27,7935,7936],{},"Soporta múltiples hosts de código."," GitHub, GitLab, Bitbucket, Gitea, Forgejo — todo en el mismo servidor de Drone.",[368,7939,7941],{"id":7940},"donde-dronewoodpecker-cobran","Donde Drone\u002FWoodpecker cobran",[2735,7943,7944,7950,7956,7962],{},[70,7945,7946,7949],{},[27,7947,7948],{},"Comunidad menor."," Cuando golpeas en un bug oscuro, Stack Overflow tiene cinco respuestas, no cincuenta. Github issues son tu fuente principal.",[70,7951,7952,7955],{},[27,7953,7954],{},"Operación no trivial en escala."," Un servidor + un runner es fácil. Cinco runners autoescalando detrás de una cola es tooling que tú montas — auto-scaling no es built-in.",[70,7957,7958,7961],{},[27,7959,7960],{},"Drone Cloud es pago."," Si quieres SaaS, vas a Harness; el tier gratis es limitado. Por eso la recomendación es siempre self-hosted.",[70,7963,7964,7967],{},[27,7965,7966],{},"Documentación modesta."," Cubre el camino feliz; casos de borde los descubres leyendo código.",[368,7969,7971],{"id":7970},"por-que-woodpecker-en-lugar-de-drone-en-2026","Por qué Woodpecker en lugar de Drone en 2026",[12,7973,7974],{},"Drone vanilla aún funciona, pero Harness ha priorizado la versión cloud comercial. Woodpecker es el fork comunitario del Drone original — 100% open-source, sin versión paga jalando features, releases mensuales activas, comunidad comprometida. API y YAML compatibles con Drone, así que la migración es trivial: cambias la URL del servidor.",[12,7976,7977,7978,7981],{},"Para cualquier equipo pequeño auto-hospedando en 2026, ",[27,7979,7980],{},"Woodpecker es la elección mejor que Drone vanilla",". Misma arquitectura, sin el overhead de una empresa controlando el roadmap.",[19,7983,7985],{"id":7984},"cual-es-mas-barata-en-2026","¿Cuál es más barata en 2026?",[12,7987,7988],{},"Coste total mensual real, considerando equipo de cinco devs con volumen medio (300 builds\u002Fmes, builds de 8 minutos medios en Linux):",[119,7990,7991,8007],{},[122,7992,7993],{},[125,7994,7995,7998,8001,8004],{},[128,7996,7997],{},"Opción",[128,7999,8000],{},"Coste fijo",[128,8002,8003],{},"Coste variable",[128,8005,8006],{},"Total estimado\u002Fmes",[141,8008,8009,8024,8037,8051,8065,8079,8093],{},[125,8010,8011,8014,8017,8020],{},[146,8012,8013],{},"Woodpecker self-hosted (VPS 15 €)",[146,8015,8016],{},"15 €",[146,8018,8019],{},"0 €",[146,8021,8022],{},[27,8023,8016],{},[125,8025,8026,8029,8031,8033],{},[146,8027,8028],{},"Actions repos públicos (open-source)",[146,8030,8019],{},[146,8032,8019],{},[146,8034,8035],{},[27,8036,8019],{},[125,8038,8039,8042,8044,8047],{},[146,8040,8041],{},"Actions repos privados (free tier 2000 min)",[146,8043,8019],{},[146,8045,8046],{},"0 € a 10 €",[146,8048,8049],{},[27,8050,8046],{},[125,8052,8053,8056,8058,8061],{},[146,8054,8055],{},"Actions Linux pago (volumen medio)",[146,8057,8019],{},[146,8059,8060],{},"10 € a 30 €",[146,8062,8063],{},[27,8064,8060],{},[125,8066,8067,8070,8073,8075],{},[146,8068,8069],{},"GitLab CI self-hosted (VPS 35 €)",[146,8071,8072],{},"35 €",[146,8074,8019],{},[146,8076,8077],{},[27,8078,8072],{},[125,8080,8081,8084,8086,8089],{},[146,8082,8083],{},"Actions con builds macOS pesados",[146,8085,8019],{},[146,8087,8088],{},"60 € a 280 €",[146,8090,8091],{},[27,8092,8088],{},[125,8094,8095,8098,8101,8104],{},[146,8096,8097],{},"GitLab CI SaaS Premium (5 devs)",[146,8099,8100],{},"130 €",[146,8102,8103],{},"0 € a 35 €",[146,8105,8106],{},[27,8107,8108],{},"130 € a 165 €",[12,8110,8111,8112,8115],{},"Ganador absoluto en coste: ",[27,8113,8114],{},"Woodpecker self-hosted"," para equipo dispuesto a operar una VPS. Cuesta lo mismo que un almuerzo al mes y corre CI para diez proyectos sin sentir.",[12,8117,8118],{},"Si ops no está disponible, Actions plan gratuito es la siguiente opción. Cabe en un equipo pequeño con workflows ligeros; cuando pasa de US$30\u002Fmes variable, vale la pena al menos evaluar runners self-hosted.",[19,8120,8122],{"id":8121},"cual-tiene-mejor-experiencia-de-desarrollador","¿Cuál tiene mejor experiencia de desarrollador?",[12,8124,8125],{},"DX en CI\u002FCD se mide en tres dimensiones: tiempo del \"yml en blanco\" hasta el \"primer build pasando\", capacidad de debug cuando sale mal, y capacidad de evolucionar el workflow cuando crece.",[119,8127,8128,8138],{},[122,8129,8130],{},[125,8131,8132,8135],{},[128,8133,8134],{},"Dimensión",[128,8136,8137],{},"Ganador",[141,8139,8140,8148,8156,8164,8172,8180],{},[125,8141,8142,8145],{},[146,8143,8144],{},"Templates listas \u002F accesibilidad",[146,8146,8147],{},"GitHub Actions (marketplace + onboarding)",[125,8149,8150,8153],{},[146,8151,8152],{},"Workflows complejos \u002F DAG \u002F monorepo",[146,8154,8155],{},"GitLab CI (parent-child + needs nativo)",[125,8157,8158,8161],{},[146,8159,8160],{},"Reproducción local \u002F simplicidad conceptual",[146,8162,8163],{},"Drone\u002FWoodpecker (cada step = container)",[125,8165,8166,8169],{},[146,8167,8168],{},"Debug de fallo intermitente",[146,8170,8171],{},"Drone\u002FWoodpecker (re-correr step aislado es trivial)",[125,8173,8174,8177],{},[146,8175,8176],{},"Composición entre proyectos",[146,8178,8179],{},"GitHub Actions (reusable workflows + composite actions)",[125,8181,8182,8185],{},[146,8183,8184],{},"Time-to-first-pipeline (cero a hello world)",[146,8186,7552],{},[12,8188,8189],{},"No hay ganador absoluto. Para equipo que valora empezar rápido, Actions. Para equipo que tiene workflow complejo desde el día uno (monorepo, varios lenguajes), GitLab CI. Para equipo que quiere entender exactamente lo que está sucediendo, Drone\u002FWoodpecker.",[12,8191,7571],{},[19,8193,8195],{"id":8194},"tabla-comparativa-12-criterios-honestos","Tabla comparativa: 12 criterios honestos",[119,8197,8198,8210],{},[122,8199,8200],{},[125,8201,8202,8204,8206,8208],{},[128,8203,2983],{},[128,8205,7552],{},[128,8207,7558],{},[128,8209,7564],{},[141,8211,8212,8225,8239,8253,8267,8281,8295,8309,8323,8335,8347,8361],{},[125,8213,8214,8217,8220,8223],{},[146,8215,8216],{},"Coste mensual startup ES (5 devs, volumen medio)",[146,8218,8219],{},"0 € a 30 €",[146,8221,8222],{},"15 € a 165 €",[146,8224,8016],{},[125,8226,8227,8230,8233,8236],{},[146,8228,8229],{},"Free tier real (2026)",[146,8231,8232],{},"2000 min\u002Fmes privados, ilimitado público",[146,8234,8235],{},"400 min\u002Fmes SaaS",[146,8237,8238],{},"Ilimitado self-hosted",[125,8240,8241,8244,8247,8250],{},[146,8242,8243],{},"Self-hosted disponible",[146,8245,8246],{},"Sí (runners), SaaS UI",[146,8248,8249],{},"Sí (CE completa)",[146,8251,8252],{},"Sí (es la única forma sensata)",[125,8254,8255,8258,8261,8264],{},[146,8256,8257],{},"Complejidad de workflow grande",[146,8259,8260],{},"Buena (reusable workflows)",[146,8262,8263],{},"Excelente (DAG + parent-child)",[146,8265,8266],{},"Modesta (lineal + matrix)",[125,8268,8269,8272,8275,8278],{},[146,8270,8271],{},"Soporte a monorepo",[146,8273,8274],{},"Medio (paths filter)",[146,8276,8277],{},"Excelente (rules + parent-child)",[146,8279,8280],{},"Medio (when filter)",[125,8282,8283,8286,8289,8292],{},[146,8284,8285],{},"Registry de containers integrado",[146,8287,8288],{},"No (necesita GHCR aparte)",[146,8290,8291],{},"Sí, nativo",[146,8293,8294],{},"No (usa registry externo)",[125,8296,8297,8300,8303,8306],{},[146,8298,8299],{},"Gestión de secretos",[146,8301,8302],{},"Repo + org + environment",[146,8304,8305],{},"Project + group + instance",[146,8307,8308],{},"Server + repo",[125,8310,8311,8314,8317,8320],{},[146,8312,8313],{},"Jobs paralelos out-of-box",[146,8315,8316],{},"Sí (matrix)",[146,8318,8319],{},"Sí (parallel + DAG)",[146,8321,8322],{},"Sí (depends_on)",[125,8324,8325,8328,8331,8333],{},[146,8326,8327],{},"Comunidad ES \u002F material en español",[146,8329,8330],{},"Mediana",[146,8332,8330],{},[146,8334,4920],{},[125,8336,8337,8339,8342,8344],{},[146,8338,4895],{},[146,8340,8341],{},"Parcial (oficial en inglés)",[146,8343,3140],{},[146,8345,8346],{},"Prácticamente cero",[125,8348,8349,8352,8355,8358],{},[146,8350,8351],{},"Integración con GitHub\u002FGitLab\u002FGitea",[146,8353,8354],{},"Solo GitHub",[146,8356,8357],{},"Solo GitLab (mirror externo es workaround)",[146,8359,8360],{},"Los tres + Bitbucket",[125,8362,8363,8366,8369,8372],{},[146,8364,8365],{},"Franja ideal de uso",[146,8367,8368],{},"1 a 50 devs en GitHub",[146,8370,8371],{},"5 a 500 devs en una sola plataforma",[146,8373,8374],{},"1 a 30 devs con ops disponible",[12,8376,8377],{},"Ningún competidor tiene columna sin reservas. La herramienta correcta depende del perfil del equipo.",[19,8379,8381],{"id":8380},"decision-por-perfil-de-equipo","Decisión por perfil de equipo",[12,8383,8384],{},"Cuatro recomendaciones concretas, sin \"depende\".",[368,8386,8388],{"id":8387},"indie-hacker-o-repo-publico-en-github","Indie hacker o repo público en GitHub",[12,8390,8391,8394],{},[27,8392,8393],{},"Usa GitHub Actions plan gratuito."," Repos públicos tienen minutos ilimitados. No tienes motivo para buscar alternativa. Si dentro de un año el proyecto crece, reevalúas.",[368,8396,8398],{"id":8397},"startup-early-en-github-repos-privados-10-mil-a-50-mil-mrr","Startup early en GitHub, repos privados, 10 mil a 50 mil € MRR",[12,8400,8401,8404],{},[27,8402,8403],{},"Continúa en Actions plan gratuito."," El free tier de 2000 minutos cabe en un equipo de dos a tres devs con workflows razonables. Cuando empiece a pasar de eso, primero reduce desperdicio (paths filter para no correr todo en todo PR, cache de dependencias decente) antes de migrar.",[12,8406,8407],{},"Si pasa consistentemente de US$30\u002Fmes variable, considera migrar a runners self-hosted o Woodpecker en paralelo.",[368,8409,8411],{"id":8410},"startup-con-50-mil-a-200-mil-mrr-en-github-volumen-ci-alto","Startup con 50 mil a 200 mil € MRR en GitHub, volumen CI alto",[12,8413,8414,8417],{},[27,8415,8416],{},"Híbrido."," Usa Actions para workflows ligeros (lint, tests unitarios) y self-hosted runners (vía ARC) o Woodpecker para workflows pesados (E2E, builds largos, deploys). Pagas por minuto donde compensa y cero donde duele.",[12,8419,8420],{},"Para equipos con builds macOS regulares, considera una máquina Mac mini dedicada como self-hosted runner. Inversión de 1.000 € paga en tres meses si gastas US$200\u002Fmes en macOS Actions hoy.",[368,8422,8424],{"id":8423},"empresa-es-en-gitlab-self-hosted","Empresa ES en GitLab self-hosted",[12,8426,8427,8430],{},[27,8428,8429],{},"Usa GitLab CI nativo."," Ya estás pagando el coste de operar GitLab; CI viene junto sin coste adicional. Migrar a otra herramienta significaría operar dos sistemas en paralelo — no vale.",[368,8432,8434],{"id":8433},"equipo-pequeno-controlando-coste-agresivamente","Equipo pequeño controlando coste agresivamente",[12,8436,8437,8440],{},[27,8438,8439],{},"Woodpecker self-hosted en VPS 15 €."," Corre CI para diez proyectos sin sudar. Cuesta en ops 1 a 2 horas\u002Fmes. Si el equipo tiene alguien con afinidad por herramientas Unix, es la opción más económica y más previsible en cuenta — sabes exactamente el coste cada mes.",[19,8442,8444],{"id":8443},"donde-heroctl-entra-como-infraestructura-para-runners","Donde HeroCtl entra como infraestructura para runners",[12,8446,8447],{},"Self-hosted CI\u002FCD es exactamente el tipo de carga de trabajo que HeroCtl orquesta bien: servicios largos (servidor de CI, base que mantiene historial de builds), servicios que escalan horizontalmente (runners que suben y bajan con la cola), servicios con necesidad de persistencia (cache de artefactos).",[12,8449,8450],{},"En lugar de operar Docker Compose en un servidor único — single point of failure — describes el setup como una configuración de jobs:",[2735,8452,8453,8459,8465,8471],{},[70,8454,8455,8458],{},[27,8456,8457],{},"Servidor de Drone\u002FWoodpecker como job largo",", con réplica única y volumen persistente para la base de historial.",[70,8460,8461,8464],{},[27,8462,8463],{},"N runners como job replicable",", escalando horizontalmente. El orquestador distribuye los runners entre nodos; si un servidor muere, los runners migran a los otros.",[70,8466,8467,8470],{},[27,8468,8469],{},"Backup integrado"," para el estado del CI (base del servidor + cache de artefactos), sin montar tooling externo.",[70,8472,8473,8476],{},[27,8474,8475],{},"Métricas y logs integrados"," — ves uso de CPU, memoria, tiempo de build sin subir un stack de observabilidad separado.",[12,8478,8479,8480,2630,8483,8486],{},"La diferencia práctica: en lugar de operar un stack de CI en paralelo a tu cluster de producción, se vuelve parte del mismo cluster, con las mismas garantías de alta disponibilidad. Si un servidor se cae, los runners migran. Si quieres duplicar capacidad para una sprint pesada, es cambiar ",[231,8481,8482],{},"replicas: 4",[231,8484,8485],{},"replicas: 8"," en el archivo de configuración.",[12,8488,8489],{},"Para quien está en la frontera \"empiezo simple pero voy a crecer\", eso resuelve la transición sin necesitar cambiar de herramienta a mitad de camino.",[19,8491,8493],{"id":8492},"los-4-errores-caros-en-cicd-self-hosted-y-como-evitarlos","Los 4 errores caros en CI\u002FCD self-hosted (y cómo evitarlos)",[368,8495,8497],{"id":8496},"error-1-cache-stale-silencioso","Error 1: cache stale silencioso",[12,8499,8500],{},"El síntoma: build pasa local, falla en CI por una dependencia que existe en la máquina del dev pero no en la imagen fresca. Peor caso: pasa en CI también porque cache anterior contiene la dependencia, pero falla en producción cuando la imagen es construida sin cache.",[12,8502,8503,8504,571,8507,571,8510,571,8513,8516],{},"La corrección: cache decente asume que puede ser invalidado en cualquier momento. Siempre que cambies archivos de manifiesto de dependencias (",[231,8505,8506],{},"package.json",[231,8508,8509],{},"go.mod",[231,8511,8512],{},"requirements.txt",[231,8514,8515],{},"Cargo.toml","), inclúyelos en la clave de cache. Periódicamente (semanal), forzar build sin cache para detectar drift.",[368,8518,8520],{"id":8519},"error-2-secret-commiteado-por-accidente","Error 2: secret commiteado por accidente",[12,8522,8523],{},"El síntoma: alguien pegó un token en la configuración de CI \"solo para probar\", commiteó, olvidó. El repo es público; en 12 horas el token está en uso por quien no debía.",[12,8525,8526,8527,8530,8531,8534],{},"La corrección: dos mecanismos en capas. ",[27,8528,8529],{},"Pre-commit hook"," que escanea patrones de claves comunes (AWS, Stripe, GitHub PAT). ",[27,8532,8533],{},"Rotación automática"," de tokens críticos (90 días máximo). Si un token se filtra, la ventana de exposición es finita.",[12,8536,8537],{},"En GitLab CI, usa variables con flag \"masked\" y \"protected\". En Actions, usa environment-scoped secrets con approval rules. En Drone\u002FWoodpecker, secretos son escopados por repo y nunca aparecen en logs por defecto.",[368,8539,8541],{"id":8540},"error-3-runner-corriendo-en-el-mismo-servidor-de-produccion","Error 3: runner corriendo en el mismo servidor de producción",[12,8543,8544],{},"El síntoma: build pesado consume CPU\u002FRAM, producción se vuelve lenta, latencia sube, alarma dispara, on-call despierta. Caso real común en equipos pequeños que intentan ahorrar máquina.",[12,8546,8547],{},"La corrección: runners en servidor separado de producción, siempre. Si el presupuesto aprieta, runner en VPS de 5 €\u002Fmes aún es más barato que un incidente de producción en horario comercial.",[368,8549,8551],{"id":8550},"error-4-workflow-que-no-corre-fuera-de-ci","Error 4: workflow que no corre fuera de CI",[12,8553,8554],{},"El síntoma: el build de CI es un script de 200 líneas inline en el YAML, con 15 variables de entorno que el sistema inyecta. Cuando algo sale mal, nadie logra reproducir local sin hacer ingeniería inversa del YAML.",[12,8556,8557,8558,571,8561,8564,8565,8568,8569,8572],{},"La corrección: el CI debe llamar comandos que existen como ",[231,8559,8560],{},"Makefile",[231,8562,8563],{},"script\u002Fbuild",", o ",[231,8566,8567],{},"package.json scripts",". El YAML del CI orquesta; la lógica vive en scripts versionados que corren en cualquier terminal. Si no logras correr ",[231,8570,8571],{},"make ci"," localmente y ver el mismo resultado, tu CI no es portable.",[12,8574,8575],{},"Drone\u002FWoodpecker fuerza esa disciplina por diseño (cada step es un container). Actions y GitLab CI permiten el anti-patrón; cabe al equipo evitar.",[19,8577,4245],{"id":4244},[12,8579,8580],{},[27,8581,8582],{},"¿GitHub Actions es más rápido que Drone?",[12,8584,8585],{},"En build crudo, depende del runner: el pool gestionado de Actions usa máquinas de 2 vCPU; un runner self-hosted en una máquina de 4 vCPU es más rápido. En tiempo total de pipeline (incluyendo cola), Actions gana cuando hay volumen — tienen capacidad ociosa enorme. Self-hosted (cualquier herramienta) tiene cola proporcional al número de runners que provisionas.",[12,8587,8588],{},[27,8589,8590],{},"¿Puedo usar GitLab CI con repo en GitHub?",[12,8592,8593],{},"Técnicamente sí, vía \"pull mirror\" (GitLab espeja el GitHub y corre CI en él). En la práctica es frágil: webhooks atrasan, status checks no vuelven al GitHub de la forma que el equipo espera, MRs quedan confusos. No vale la pena. Si estás en GitHub, usa Actions o Drone\u002FWoodpecker (que aceptan GitHub como fuente nativa).",[12,8595,8596],{},[27,8597,8598],{},"¿Self-hosted runners de GitHub Actions valen la pena?",[12,8600,8601],{},"Para repos privados con volumen alto (más de 5000 minutos\u002Fmes), sí. Ahorras minutos pagados a cambio de operar máquinas. Para repos públicos, no — riesgo de seguridad (forks maliciosos corriendo código en tu máquina) supera el beneficio. ARC (Actions Runner Controller) ayuda en escala, pero añade una capa de Kubernetes; solo tiene sentido para equipos que ya operan K8s.",[12,8603,8604],{},[27,8605,8606],{},"¿Woodpecker es estable lo suficiente en 2026?",[12,8608,8609],{},"Sí. Releases mensuales, base de código sólida (forkado de Drone, que tenía cinco años de producción), comunidad activa. En producción en cientos de empresas pequeñas y medianas. No es la apuesta segura \"nadie es despedido por elegir\" — esa es Actions o GitLab — pero en tres años de fork no hubo incidente comunitario grave. Para equipo pequeño self-hosted, es la elección sensata.",[12,8611,8612],{},[27,8613,8614],{},"¿ArgoCD y FluxCD entran en esa decisión?",[12,8616,8617],{},"No directamente. ArgoCD\u002FFluxCD son herramientas de GitOps para Kubernetes, no CI. Asisten un repo Git y aplican cambios en cluster. CI sigue siendo Actions\u002FGitLab\u002FDrone generando imágenes; ArgoCD\u002FFlux aplican el deploy. Si no estás en Kubernetes, ArgoCD\u002FFlux no son para ti. Equipos en otros orquestadores hacen deploy directo desde el CI o vía APIs del orquestador.",[12,8619,8620],{},[27,8621,8622],{},"¿Cuántos runners simultáneos para equipo de 5 devs?",[12,8624,8625],{},"Regla práctica: un runner por dos desarrolladores activos, más un runner extra para builds largos no bloquear PRs rápidos. Equipo de cinco devs: tres runners es cómodo. En horas pico (release day), sube a cinco temporalmente. Cada runner consume 1 a 2 GB de RAM en workload típica; un servidor de 8 GB corre cuatro runners sin dolor.",[12,8627,8628],{},[27,8629,8630],{},"Cache de dependencias — ¿qué herramienta lidia mejor?",[12,8632,8633,8634,8637],{},"GitLab CI tiene cache nativo por clave\u002Fpath, integrado al registry propio. GitHub Actions tiene ",[231,8635,8636],{},"actions\u002Fcache"," (gratuito, 10 GB por repo). Drone\u002FWoodpecker dependen de plugin de cache externo (S3, MinIO local) — más setup pero más flexible. En volumen moderado, todos resuelven; en volumen alto (monorepo grande), GitLab tiene ventaja por integración con el registry.",[12,8639,8640],{},[27,8641,8642],{},"Migrar de GitHub Actions a Drone — ¿cuánto trabajo?",[12,8644,8645],{},"Para workflows simples (build + test + push), 1 a 2 días. Para workflows que dependen de muchas acciones del marketplace, 1 a 2 semanas (necesita reescribir cada acción como container). El mayor dolor son secretos y entornos — exporta y reimporta con cuidado. Recomendación: haz migración proyecto a proyecto, no todo de una vez.",[12,8647,8648],{},[27,8649,8650],{},"¿Puedo correr runners de Actions y Drone\u002FWoodpecker en el mismo servidor?",[12,8652,8653],{},"Técnicamente sí, ambos son containers. En la práctica, aislamiento mejora: runners en servidores separados evitan que un build pesado afecte al otro. Si el presupuesto es ajustado, dos servidores de 7 €\u002Fmes son mejores que uno de 15 €\u002Fmes con todo junto.",[12,8655,7571],{},[19,8657,8659],{"id":8658},"en-resumen","En resumen",[12,8661,8662],{},"CI\u002FCD en 2026 no tiene herramienta ganadora. Tiene perfiles de uso y tradeoffs honestos:",[2735,8664,8665,8671,8677,8683,8689],{},[70,8666,8667,8670],{},[27,8668,8669],{},"¿Estás en GitHub y el volumen es ligero a medio?"," Actions, plan gratuito. No busques problema donde no hay.",[70,8672,8673,8676],{},[27,8674,8675],{},"¿Estás en GitLab self-hosted?"," GitLab CI nativo. Ya está pagado.",[70,8678,8679,8682],{},[27,8680,8681],{},"¿Quieres coste previsible y tienes 1-2h\u002Fmes de ops disponible?"," Woodpecker self-hosted en VPS 15 €. La elección más económica.",[70,8684,8685,8688],{},[27,8686,8687],{},"¿Tienes monorepo grande con workflow complejo?"," GitLab CI (DAG nativo) o Actions con reusable workflows.",[70,8690,8691,8694],{},[27,8692,8693],{},"¿Tienes volumen alto y dolor de pricing de minutos?"," Híbrido: Actions para workflows ligeros, runners self-hosted para pesados.",[12,8696,8697],{},"Si estás pensando en correr la herramienta de CI como parte del mismo cluster que sirve producción — con alta disponibilidad real, métricas integradas y backup sin montar stack aparte — instala HeroCtl en un servidor:",[224,8699,8701],{"className":8700,"code":2949,"language":2530},[2528],[231,8702,2949],{"__ignoreMap":229},[12,8704,8705],{},"A partir de ahí, describir un servidor de Woodpecker con tres runners autoescalables es un archivo de configuración de cincuenta líneas. El cluster cuida del resto: distribuye los runners por los nodos, mantiene el servidor disponible incluso con pérdida de máquina, hace backup del estado, expone métricas en el panel embebido.",[12,8707,8708,8709,8711,8712,8716],{},"Para quien quiere más contexto, vale la pena leer también ",[3337,8710,3345],{"href":3344}," — discute cuándo tiene sentido salir de docker-compose para un plano de control replicado, con los mismos criterios honestos de este post. Y para equipos pensando en simplificar la stack de orquestación entera, ",[3337,8713,8715],{"href":8714},"\u002Fes\u002Fblog\u002Fmigrar-de-kubernetes-a-stack-mas-simple","Migrar de Kubernetes a una stack más simple — case real"," tiene números de una migración real, con ganancias y dolores.",[12,8718,8719,8720,8723],{},"La elección de CI\u002FCD es una de las decisiones más duraderas del equipo. Vale algunos días de comparación honesta antes de copiar el ",[231,8721,8722],{},".github\u002Fworkflows\u002F"," del proyecto anterior — porque tres años después, migrar cuesta caro.",{"title":229,"searchDepth":244,"depth":244,"links":8725},[8726,8727,8728,8733,8737,8742,8743,8744,8745,8752,8753,8759,8760],{"id":7531,"depth":244,"text":7532},{"id":7574,"depth":244,"text":7575},{"id":7611,"depth":244,"text":7612,"children":8729},[8730,8731,8732],{"id":7622,"depth":271,"text":7623},{"id":7658,"depth":271,"text":7659},{"id":7719,"depth":271,"text":7720},{"id":7752,"depth":244,"text":7753,"children":8734},[8735,8736],{"id":7759,"depth":271,"text":7760},{"id":7799,"depth":271,"text":7800},{"id":7886,"depth":244,"text":7887,"children":8738},[8739,8740,8741],{"id":7893,"depth":271,"text":7894},{"id":7940,"depth":271,"text":7941},{"id":7970,"depth":271,"text":7971},{"id":7984,"depth":244,"text":7985},{"id":8121,"depth":244,"text":8122},{"id":8194,"depth":244,"text":8195},{"id":8380,"depth":244,"text":8381,"children":8746},[8747,8748,8749,8750,8751],{"id":8387,"depth":271,"text":8388},{"id":8397,"depth":271,"text":8398},{"id":8410,"depth":271,"text":8411},{"id":8423,"depth":271,"text":8424},{"id":8433,"depth":271,"text":8434},{"id":8443,"depth":244,"text":8444},{"id":8492,"depth":244,"text":8493,"children":8754},[8755,8756,8757,8758],{"id":8496,"depth":271,"text":8497},{"id":8519,"depth":271,"text":8520},{"id":8540,"depth":271,"text":8541},{"id":8550,"depth":271,"text":8551},{"id":4244,"depth":244,"text":4245},{"id":8658,"depth":244,"text":8659},"comparison","2026-05-15","GitHub Actions ganó mindshare pero tiene costes de minutos. GitLab CI es más completo pero pesa más. Drone (y Woodpecker) auto-hospedado corre en VPS pequeño. Comparación práctica.",{},"\u002Fes\u002Fblog\u002Fgithub-actions-vs-gitlab-ci-vs-drone","14 min",{"title":7516,"description":8763},{"loc":8765},"es\u002Fblog\u002Fgithub-actions-vs-gitlab-ci-vs-drone",[8771,8772,8773,8774,8775],"github-actions","gitlab-ci","drone","ci-cd","comparativo","SA7FLRIt6kzCrSWq_4TfgiVd00Hhv5nBRA60Zs17xcA",{"id":8778,"title":8779,"author":7,"body":8780,"category":3379,"cover":3380,"date":11777,"description":11778,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":11779,"navigation":411,"path":11780,"readingTime":6387,"seo":11781,"sitemap":11782,"stem":11783,"tags":11784,"__hash__":11789},"blog_es\u002Fes\u002Fblog\u002Fstack-monitoring-prometheus-grafana-loki.md","Stack de monitoring completa en 2026: Prometheus + Grafana + Loki paso a paso",{"type":9,"value":8781,"toc":11757},[8782,8795,8798,8800,8807,8818,8821,8824,8828,8831,8869,8884,8888,8891,8924,8927,8931,8937,8940,8943,8970,8984,8988,8993,9000,9006,9013,9361,9380,9383,9421,9444,9448,9453,9459,9739,9749,9768,9772,9778,9781,9860,9880,9886,9924,9934,9938,9942,9948,10202,10205,10208,10395,10405,10412,10416,10421,10430,10436,10545,10552,10559,10579,10586,10590,10594,10597,10607,10945,10952,11170,11181,11195,11199,11203,11209,11232,11239,11243,11248,11251,11254,11287,11290,11322,11331,11342,11346,11349,11352,11378,11381,11385,11435,11438,11441,11445,11576,11579,11583,11586,11596,11606,11618,11624,11626,11632,11638,11644,11650,11659,11673,11679,11685,11689,11692,11720,11732,11735,11751,11754],[12,8783,8784,8785,571,8788,571,8791,8794],{},"La primera vez que tu sitio caiga a las tres de la mañana, vas a descubrir una cosa incómoda: no tienes cómo saber qué pasó. No hay gráfico de CPU, no hay log del contenedor que murió, no hay alerta que avisó antes. Vas a abrir una terminal, conectar a los servidores uno por uno, correr ",[231,8786,8787],{},"top",[231,8789,8790],{},"df",[231,8792,8793],{},"journalctl",", e intentar reconstituir una escena de crimen que ya se enfrió.",[12,8796,8797],{},"Este post es el atajo para que no pases por eso. En cuatro horas, con R$80 a R$120 al mes de hardware, se puede montar la stack de observabilidad open-source que sustituye Datadog, New Relic y CloudWatch en 95% de los casos para startup. Las herramientas son las mismas que corren dentro de empresas con decenas de miles de servidores — y caben confortablemente en una VPS pequeña para el equipo que está empezando.",[19,8799,22],{"id":21},[12,8801,8802,8803,8806],{},"La stack de monitoring open-source estándar en 2026 — ",[27,8804,8805],{},"Prometheus + Grafana + Loki + Alertmanager"," — cabe en una única VPS de 4 GB de RAM y cubre métricas, logs centralizados, dashboards y alertas. Este tutorial muestra setup paso a paso para un cluster de 4 a 5 servidores en aproximadamente cuatro horas, usando docker-compose o job specs del orquestador.",[12,8808,8809,8810,8813,8814,8817],{},"Para startup brasileña, eso significa ",[27,8811,8812],{},"R$80 a R$120 al mes de hardware"," contra ",[27,8815,8816],{},"R$1.000 a R$2.000 al mes"," de SaaS de observabilidad equivalente. El costo de tiempo es honesto: cuatro horas de setup inicial más dos a cuatro horas al mes de mantenimiento continuo.",[12,8819,8820],{},"Resultado entregable al final del tutorial: dashboards de CPU, RAM, disco, red y métricas HTTP; logs buscables con retención de 30 días; alertas ruteadas a Slack, Discord o e-mail. Prerrequisitos: 1 VPS Linux con 4 GB de RAM y 50 GB de SSD, Docker instalado, y un dominio con DNS controlado por ti.",[12,8822,8823],{},"La elección entre correr esta stack en una VPS dedicada fuera del cluster de producción o como job dentro del propio orquestador es una decisión arquitectural — cubrimos las dos opciones en el paso 8 y en \"Cómo correr esto dentro de HeroCtl\".",[19,8825,8827],{"id":8826},"que-hace-cada-componente-en-una-frase","Qué hace cada componente, en una frase",[12,8829,8830],{},"Antes de instalar cualquier cosa, vale la pena entender el papel de cada pieza. La stack tiene seis componentes; la confusión generalmente viene de pensar que alguno de ellos es \"el sistema de monitoring\". No lo es. Cada uno hace una cosa.",[2735,8832,8833,8839,8845,8851,8857,8863],{},[70,8834,8835,8838],{},[27,8836,8837],{},"Prometheus"," es una base de datos de series temporales (TSDB) que recolecta métricas vía HTTP scrape — él jala los números, nadie los empuja. Retiene 15 días por default.",[70,8840,8841,8844],{},[27,8842,8843],{},"Grafana"," es la capa de visualización. Conecta a Prometheus, a Loki, a Postgres, a casi cualquier fuente estructurada, y dibuja gráficos.",[70,8846,8847,8850],{},[27,8848,8849],{},"Loki"," es la pieza de logs. Sintaxis similar a la de Prometheus, indexa solo labels (no el contenido de los logs), y por eso queda cerca de diez veces más barato que ELK para correr.",[70,8852,8853,8856],{},[27,8854,8855],{},"Promtail"," (o el Grafana Agent, que está sustituyendo al Promtail en 2026) es el recolector que lee los archivos de log de cada servidor y envía a Loki.",[70,8858,8859,8862],{},[27,8860,8861],{},"node_exporter"," corre en cada nodo monitoreado y expone un endpoint HTTP con CPU, RAM, disco y red en formato Prometheus.",[70,8864,8865,8868],{},[27,8866,8867],{},"Alertmanager"," recibe reglas de alerta de Prometheus y cuida del ruteo — Slack, e-mail, PagerDuty, webhook arbitrario.",[12,8870,8871,8872,571,8875,571,8878,571,8881,101],{},"Quien diseña la primera stack suele confundir Prometheus con \"monitoring\" y Grafana con \"dashboards bonitos\". La separación real es: ",[27,8873,8874],{},"Prometheus guarda números",[27,8876,8877],{},"Loki guarda texto",[27,8879,8880],{},"Grafana muestra ambos",[27,8882,8883],{},"Alertmanager grita cuando algún número queda mal",[19,8885,8887],{"id":8886},"cual-es-la-arquitectura-recomendada","¿Cuál es la arquitectura recomendada?",[12,8889,8890],{},"Para un cluster de 3 a 5 servidores corriendo aplicaciones de producción, la topología que viene funcionando en la práctica es separar el servidor de observabilidad del resto. Un nodo dedicado, fuera del cluster que él monitorea, con dos objetivos: no morir junto cuando el cluster muera, y no competir por CPU\u002FRAM con la aplicación real.",[2735,8892,8893,8899,8905,8915],{},[70,8894,8895,8898],{},[27,8896,8897],{},"1 servidor \"observability\" dedicado",", 4 GB de RAM, 50 GB de SSD. Corre Prometheus, Grafana, Loki, Alertmanager.",[70,8900,8901,8904],{},[27,8902,8903],{},"Cada servidor monitoreado"," corre solo dos procesos livianos: node_exporter (métricas de sistema) y Promtail (envío de logs).",[70,8906,8907,8910,8911,8914],{},[27,8908,8909],{},"Tus aplicaciones"," exponen un endpoint ",[231,8912,8913],{},"\u002Fmetrics"," en formato Prometheus. Si usas un framework popular, existe un cliente listo. Si no, es una biblioteca de pocas decenas de líneas.",[70,8916,8917,8919,8920,8923],{},[27,8918,8843],{}," queda accesible vía subdominio (",[231,8921,8922],{},"monitor.tudominio.com",") con TLS automático y autenticación básica al frente.",[12,8925,8926],{},"Esa separación tiene un costo: pagas por una VPS más. A cambio, cuando el cluster principal caiga, todavía logras mirar los gráficos para entender qué pasó. Para startup, ese trade-off compensa casi siempre — el peor escenario en monitoring es descubrir que la única cosa que paró junto con el sitio fue el sistema que iba a avisarte que el sitio paró.",[19,8928,8930],{"id":8929},"paso-1-como-provisionar-la-vps-de-observabilidad","Paso 1 — ¿Cómo provisionar la VPS de observabilidad?",[12,8932,8933,8934,101],{},"Tiempo estimado: ",[27,8935,8936],{},"10 minutos",[12,8938,8939],{},"Cualquier proveedor barato sirve. Los dos con mejor costo-beneficio para el caso brasileño hoy son Hetzner (CPX21 a 7,99 EUR al mes con 3 vCPUs y 4 GB de RAM, datacenter en Alemania) y DigitalOcean (Basic Droplet de US$24 al mes con la misma configuración, datacenters más cercanos a Brasil). Para workload de monitoring, latencia de scrape en datacenter europeo no causa problema — Prometheus jala cada 15 segundos por default, entonces 200ms de RTT entre Hetzner y tus servidores no estorba.",[12,8941,8942],{},"Provisionando:",[67,8944,8945,8948,8951,8957,8964],{},[70,8946,8947],{},"Crea la VPS con Ubuntu 24.04 LTS o Debian 12.",[70,8949,8950],{},"Agrega tu clave SSH pública en la creación. Deshabilita login por contraseña.",[70,8952,8953,8954,101],{},"Instala Docker y el plugin de compose: ",[231,8955,8956],{},"curl -fsSL https:\u002F\u002Fget.docker.com | sh && apt install docker-compose-plugin",[70,8958,8959,8960,8963],{},"Configura el firewall: puerto 22 (SSH) abierto, puerto 443 (HTTPS) abierto, todos los demás cerrados. Los puertos internos (3000, 9090, 3100, 9093) solo quedan accesibles por el ",[231,8961,8962],{},"localhost"," de la propia VPS — el reverse proxy expone Grafana vía 443.",[70,8965,8966,8967,8969],{},"Apunta el DNS: crea un registro A ",[231,8968,8922],{}," para la IP de la VPS.",[12,8971,341,8972,8975,8976,8979,8980,8983],{},[231,8973,8974],{},"docker --version"," retorna 26.x o superior; ",[231,8977,8978],{},"dig monitor.tudominio.com"," retorna la IP correcta; ",[231,8981,8982],{},"ssh root@monitor.tudominio.com"," conecta sin pedir contraseña.",[19,8985,8987],{"id":8986},"paso-2-como-subir-la-stack-via-docker-compose","Paso 2 — ¿Cómo subir la stack vía docker-compose?",[12,8989,8933,8990,101],{},[27,8991,8992],{},"45 minutos",[12,8994,8995,8996,8999],{},"Crea el directorio de trabajo en ",[231,8997,8998],{},"\u002Fopt\u002Fobservability\u002F"," con la siguiente estructura:",[224,9001,9004],{"className":9002,"code":9003,"language":2530},[2528],"\u002Fopt\u002Fobservability\u002F\n├── docker-compose.yml\n├── prometheus\u002F\n│   ├── prometheus.yml\n│   └── alerts.yml\n├── alertmanager\u002F\n│   └── alertmanager.yml\n├── loki\u002F\n│   └── loki-config.yml\n└── grafana\u002F\n    └── provisioning\u002F\n        └── datasources\u002F\n            └── datasources.yml\n",[231,9005,9003],{"__ignoreMap":229},[12,9007,9008,9009,9012],{},"El ",[231,9010,9011],{},"docker-compose.yml"," abreviado pero funcional:",[224,9014,9018],{"className":9015,"code":9016,"language":9017,"meta":229,"style":229},"language-yaml shiki shiki-themes github-dark-default","services:\n  prometheus:\n    image: prom\u002Fprometheus:v2.55.0\n    volumes:\n      - .\u002Fprometheus:\u002Fetc\u002Fprometheus\n      - prometheus-data:\u002Fprometheus\n    command:\n      - '--config.file=\u002Fetc\u002Fprometheus\u002Fprometheus.yml'\n      - '--storage.tsdb.retention.time=30d'\n      - '--web.enable-lifecycle'  # permite reload via HTTP POST\n    ports:\n      - '127.0.0.1:9090:9090'\n    restart: unless-stopped\n\n  grafana:\n    image: grafana\u002Fgrafana:11.3.0\n    volumes:\n      - grafana-data:\u002Fvar\u002Flib\u002Fgrafana\n      - .\u002Fgrafana\u002Fprovisioning:\u002Fetc\u002Fgrafana\u002Fprovisioning\n    environment:\n      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}\n      - GF_USERS_ALLOW_SIGN_UP=false\n    ports:\n      - '127.0.0.1:3000:3000'\n    restart: unless-stopped\n\n  loki:\n    image: grafana\u002Floki:3.2.0\n    volumes:\n      - .\u002Floki\u002Floki-config.yml:\u002Fetc\u002Floki\u002Fconfig.yml\n      - loki-data:\u002Floki\n    command: -config.file=\u002Fetc\u002Floki\u002Fconfig.yml\n    ports:\n      - '127.0.0.1:3100:3100'\n    restart: unless-stopped\n\n  alertmanager:\n    image: prom\u002Falertmanager:v0.27.0\n    volumes:\n      - .\u002Falertmanager:\u002Fetc\u002Falertmanager\n    ports:\n      - '127.0.0.1:9093:9093'\n    restart: unless-stopped\n\nvolumes:\n  prometheus-data:\n  grafana-data:\n  loki-data:\n","yaml",[231,9019,9020,9029,9036,9046,9053,9061,9068,9075,9082,9089,9099,9106,9113,9123,9127,9134,9143,9149,9156,9163,9170,9177,9184,9190,9197,9205,9209,9216,9225,9231,9238,9245,9254,9260,9267,9275,9279,9286,9295,9301,9308,9314,9321,9329,9333,9340,9347,9354],{"__ignoreMap":229},[234,9021,9022,9026],{"class":236,"line":237},[234,9023,9025],{"class":9024},"sPWt5","services",[234,9027,9028],{"class":387},":\n",[234,9030,9031,9034],{"class":236,"line":244},[234,9032,9033],{"class":9024},"  prometheus",[234,9035,9028],{"class":387},[234,9037,9038,9041,9043],{"class":236,"line":271},[234,9039,9040],{"class":9024},"    image",[234,9042,6562],{"class":387},[234,9044,9045],{"class":255},"prom\u002Fprometheus:v2.55.0\n",[234,9047,9048,9051],{"class":236,"line":415},[234,9049,9050],{"class":9024},"    volumes",[234,9052,9028],{"class":387},[234,9054,9055,9058],{"class":236,"line":434},[234,9056,9057],{"class":387},"      - ",[234,9059,9060],{"class":255},".\u002Fprometheus:\u002Fetc\u002Fprometheus\n",[234,9062,9063,9065],{"class":236,"line":459},[234,9064,9057],{"class":387},[234,9066,9067],{"class":255},"prometheus-data:\u002Fprometheus\n",[234,9069,9070,9073],{"class":236,"line":464},[234,9071,9072],{"class":9024},"    command",[234,9074,9028],{"class":387},[234,9076,9077,9079],{"class":236,"line":479},[234,9078,9057],{"class":387},[234,9080,9081],{"class":255},"'--config.file=\u002Fetc\u002Fprometheus\u002Fprometheus.yml'\n",[234,9083,9084,9086],{"class":236,"line":484},[234,9085,9057],{"class":387},[234,9087,9088],{"class":255},"'--storage.tsdb.retention.time=30d'\n",[234,9090,9091,9093,9096],{"class":236,"line":490},[234,9092,9057],{"class":387},[234,9094,9095],{"class":255},"'--web.enable-lifecycle'",[234,9097,9098],{"class":240},"  # permite reload via HTTP POST\n",[234,9100,9101,9104],{"class":236,"line":508},[234,9102,9103],{"class":9024},"    ports",[234,9105,9028],{"class":387},[234,9107,9108,9110],{"class":236,"line":529},[234,9109,9057],{"class":387},[234,9111,9112],{"class":255},"'127.0.0.1:9090:9090'\n",[234,9114,9115,9118,9120],{"class":236,"line":535},[234,9116,9117],{"class":9024},"    restart",[234,9119,6562],{"class":387},[234,9121,9122],{"class":255},"unless-stopped\n",[234,9124,9125],{"class":236,"line":546},[234,9126,412],{"emptyLinePlaceholder":411},[234,9128,9129,9132],{"class":236,"line":552},[234,9130,9131],{"class":9024},"  grafana",[234,9133,9028],{"class":387},[234,9135,9136,9138,9140],{"class":236,"line":557},[234,9137,9040],{"class":9024},[234,9139,6562],{"class":387},[234,9141,9142],{"class":255},"grafana\u002Fgrafana:11.3.0\n",[234,9144,9145,9147],{"class":236,"line":594},[234,9146,9050],{"class":9024},[234,9148,9028],{"class":387},[234,9150,9151,9153],{"class":236,"line":635},[234,9152,9057],{"class":387},[234,9154,9155],{"class":255},"grafana-data:\u002Fvar\u002Flib\u002Fgrafana\n",[234,9157,9158,9160],{"class":236,"line":643},[234,9159,9057],{"class":387},[234,9161,9162],{"class":255},".\u002Fgrafana\u002Fprovisioning:\u002Fetc\u002Fgrafana\u002Fprovisioning\n",[234,9164,9165,9168],{"class":236,"line":659},[234,9166,9167],{"class":9024},"    environment",[234,9169,9028],{"class":387},[234,9171,9172,9174],{"class":236,"line":683},[234,9173,9057],{"class":387},[234,9175,9176],{"class":255},"GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}\n",[234,9178,9179,9181],{"class":236,"line":695},[234,9180,9057],{"class":387},[234,9182,9183],{"class":255},"GF_USERS_ALLOW_SIGN_UP=false\n",[234,9185,9186,9188],{"class":236,"line":717},[234,9187,9103],{"class":9024},[234,9189,9028],{"class":387},[234,9191,9192,9194],{"class":236,"line":723},[234,9193,9057],{"class":387},[234,9195,9196],{"class":255},"'127.0.0.1:3000:3000'\n",[234,9198,9199,9201,9203],{"class":236,"line":729},[234,9200,9117],{"class":9024},[234,9202,6562],{"class":387},[234,9204,9122],{"class":255},[234,9206,9207],{"class":236,"line":734},[234,9208,412],{"emptyLinePlaceholder":411},[234,9210,9211,9214],{"class":236,"line":771},[234,9212,9213],{"class":9024},"  loki",[234,9215,9028],{"class":387},[234,9217,9218,9220,9222],{"class":236,"line":776},[234,9219,9040],{"class":9024},[234,9221,6562],{"class":387},[234,9223,9224],{"class":255},"grafana\u002Floki:3.2.0\n",[234,9226,9227,9229],{"class":236,"line":815},[234,9228,9050],{"class":9024},[234,9230,9028],{"class":387},[234,9232,9233,9235],{"class":236,"line":820},[234,9234,9057],{"class":387},[234,9236,9237],{"class":255},".\u002Floki\u002Floki-config.yml:\u002Fetc\u002Floki\u002Fconfig.yml\n",[234,9239,9240,9242],{"class":236,"line":826},[234,9241,9057],{"class":387},[234,9243,9244],{"class":255},"loki-data:\u002Floki\n",[234,9246,9247,9249,9251],{"class":236,"line":846},[234,9248,9072],{"class":9024},[234,9250,6562],{"class":387},[234,9252,9253],{"class":255},"-config.file=\u002Fetc\u002Floki\u002Fconfig.yml\n",[234,9255,9256,9258],{"class":236,"line":859},[234,9257,9103],{"class":9024},[234,9259,9028],{"class":387},[234,9261,9262,9264],{"class":236,"line":872},[234,9263,9057],{"class":387},[234,9265,9266],{"class":255},"'127.0.0.1:3100:3100'\n",[234,9268,9269,9271,9273],{"class":236,"line":898},[234,9270,9117],{"class":9024},[234,9272,6562],{"class":387},[234,9274,9122],{"class":255},[234,9276,9277],{"class":236,"line":913},[234,9278,412],{"emptyLinePlaceholder":411},[234,9280,9281,9284],{"class":236,"line":1886},[234,9282,9283],{"class":9024},"  alertmanager",[234,9285,9028],{"class":387},[234,9287,9288,9290,9292],{"class":236,"line":1901},[234,9289,9040],{"class":9024},[234,9291,6562],{"class":387},[234,9293,9294],{"class":255},"prom\u002Falertmanager:v0.27.0\n",[234,9296,9297,9299],{"class":236,"line":1920},[234,9298,9050],{"class":9024},[234,9300,9028],{"class":387},[234,9302,9303,9305],{"class":236,"line":1944},[234,9304,9057],{"class":387},[234,9306,9307],{"class":255},".\u002Falertmanager:\u002Fetc\u002Falertmanager\n",[234,9309,9310,9312],{"class":236,"line":1962},[234,9311,9103],{"class":9024},[234,9313,9028],{"class":387},[234,9315,9316,9318],{"class":236,"line":1978},[234,9317,9057],{"class":387},[234,9319,9320],{"class":255},"'127.0.0.1:9093:9093'\n",[234,9322,9323,9325,9327],{"class":236,"line":1984},[234,9324,9117],{"class":9024},[234,9326,6562],{"class":387},[234,9328,9122],{"class":255},[234,9330,9331],{"class":236,"line":1992},[234,9332,412],{"emptyLinePlaceholder":411},[234,9334,9335,9338],{"class":236,"line":2004},[234,9336,9337],{"class":9024},"volumes",[234,9339,9028],{"class":387},[234,9341,9342,9345],{"class":236,"line":2014},[234,9343,9344],{"class":9024},"  prometheus-data",[234,9346,9028],{"class":387},[234,9348,9349,9352],{"class":236,"line":2020},[234,9350,9351],{"class":9024},"  grafana-data",[234,9353,9028],{"class":387},[234,9355,9356,9359],{"class":236,"line":2029},[234,9357,9358],{"class":9024},"  loki-data",[234,9360,9028],{"class":387},[12,9362,9363,9364,9367,9368,9371,9372,9375,9376,9379],{},"Tres puntos importantes en ese archivo. Primero, todos los puertos están atados a ",[231,9365,9366],{},"127.0.0.1"," — ninguno de los servicios es accesible directamente de internet. Segundo, los volúmenes son nombrados (no bind mounts), así que sobreviven a ",[231,9369,9370],{},"docker-compose down",". Tercero, la contraseña de Grafana viene de variable de ambiente: crea un ",[231,9373,9374],{},".env"," al lado del compose con ",[231,9377,9378],{},"GRAFANA_PASSWORD=algo_largo_aleatorio"," y nunca commitees eso.",[12,9381,9382],{},"Sube la stack:",[224,9384,9386],{"className":226,"code":9385,"language":228,"meta":229,"style":229},"cd \u002Fopt\u002Fobservability\ndocker compose up -d\ndocker compose ps  # todos deben estar \"Up\" \u002F healthy\n",[231,9387,9388,9396,9409],{"__ignoreMap":229},[234,9389,9390,9393],{"class":236,"line":237},[234,9391,9392],{"class":251},"cd",[234,9394,9395],{"class":255}," \u002Fopt\u002Fobservability\n",[234,9397,9398,9400,9403,9406],{"class":236,"line":244},[234,9399,1118],{"class":247},[234,9401,9402],{"class":255}," compose",[234,9404,9405],{"class":255}," up",[234,9407,9408],{"class":251}," -d\n",[234,9410,9411,9413,9415,9418],{"class":236,"line":271},[234,9412,1118],{"class":247},[234,9414,9402],{"class":255},[234,9416,9417],{"class":255}," ps",[234,9419,9420],{"class":240},"  # todos deben estar \"Up\" \u002F healthy\n",[12,9422,9423,9424,9427,9428,1895,9431,9427,9434,1895,9437,9440,9441,101],{},"Validación rápida: ",[231,9425,9426],{},"curl localhost:9090\u002F-\u002Fready"," retorna ",[231,9429,9430],{},"Prometheus Server is Ready",[231,9432,9433],{},"curl localhost:3100\u002Fready",[231,9435,9436],{},"ready",[231,9438,9439],{},"curl localhost:3000\u002Fapi\u002Fhealth"," retorna JSON con ",[231,9442,9443],{},"\"database\": \"ok\"",[19,9445,9447],{"id":9446},"paso-3-como-configurar-los-scrapes-de-prometheus","Paso 3 — ¿Cómo configurar los scrapes de Prometheus?",[12,9449,8933,9450,101],{},[27,9451,9452],{},"30 minutos",[12,9454,9008,9455,9458],{},[231,9456,9457],{},"prometheus\u002Fprometheus.yml"," es donde dices a Prometheus qué endpoints raspar. Para un cluster de 4 servidores, queda así:",[224,9460,9462],{"className":9015,"code":9461,"language":9017,"meta":229,"style":229},"global:\n  scrape_interval: 15s\n  evaluation_interval: 15s\n\nalerting:\n  alertmanagers:\n    - static_configs:\n        - targets: ['alertmanager:9093']\n\nrule_files:\n  - 'alerts.yml'\n\nscrape_configs:\n  - job_name: 'prometheus'\n    static_configs:\n      - targets: ['localhost:9090']\n\n  - job_name: 'node'\n    static_configs:\n      - targets:\n          - 'server-1.tudominio.internal:9100'\n          - 'server-2.tudominio.internal:9100'\n          - 'server-3.tudominio.internal:9100'\n          - 'worker-1.tudominio.internal:9100'\n        labels:\n          environment: 'production'\n\n  - job_name: 'apps'\n    static_configs:\n      - targets:\n          - 'api.tudominio.internal:8080'\n          - 'worker.tudominio.internal:8080'\n        labels:\n          environment: 'production'\n    metrics_path: '\u002Fmetrics'\n",[231,9463,9464,9471,9481,9490,9494,9501,9508,9518,9535,9539,9546,9554,9558,9565,9577,9584,9597,9601,9612,9618,9626,9634,9641,9648,9655,9662,9672,9676,9687,9693,9701,9708,9715,9721,9729],{"__ignoreMap":229},[234,9465,9466,9469],{"class":236,"line":237},[234,9467,9468],{"class":9024},"global",[234,9470,9028],{"class":387},[234,9472,9473,9476,9478],{"class":236,"line":244},[234,9474,9475],{"class":9024},"  scrape_interval",[234,9477,6562],{"class":387},[234,9479,9480],{"class":255},"15s\n",[234,9482,9483,9486,9488],{"class":236,"line":271},[234,9484,9485],{"class":9024},"  evaluation_interval",[234,9487,6562],{"class":387},[234,9489,9480],{"class":255},[234,9491,9492],{"class":236,"line":415},[234,9493,412],{"emptyLinePlaceholder":411},[234,9495,9496,9499],{"class":236,"line":434},[234,9497,9498],{"class":9024},"alerting",[234,9500,9028],{"class":387},[234,9502,9503,9506],{"class":236,"line":459},[234,9504,9505],{"class":9024},"  alertmanagers",[234,9507,9028],{"class":387},[234,9509,9510,9513,9516],{"class":236,"line":464},[234,9511,9512],{"class":387},"    - ",[234,9514,9515],{"class":9024},"static_configs",[234,9517,9028],{"class":387},[234,9519,9520,9523,9526,9529,9532],{"class":236,"line":479},[234,9521,9522],{"class":387},"        - ",[234,9524,9525],{"class":9024},"targets",[234,9527,9528],{"class":387},": [",[234,9530,9531],{"class":255},"'alertmanager:9093'",[234,9533,9534],{"class":387},"]\n",[234,9536,9537],{"class":236,"line":484},[234,9538,412],{"emptyLinePlaceholder":411},[234,9540,9541,9544],{"class":236,"line":490},[234,9542,9543],{"class":9024},"rule_files",[234,9545,9028],{"class":387},[234,9547,9548,9551],{"class":236,"line":508},[234,9549,9550],{"class":387},"  - ",[234,9552,9553],{"class":255},"'alerts.yml'\n",[234,9555,9556],{"class":236,"line":529},[234,9557,412],{"emptyLinePlaceholder":411},[234,9559,9560,9563],{"class":236,"line":535},[234,9561,9562],{"class":9024},"scrape_configs",[234,9564,9028],{"class":387},[234,9566,9567,9569,9572,9574],{"class":236,"line":546},[234,9568,9550],{"class":387},[234,9570,9571],{"class":9024},"job_name",[234,9573,6562],{"class":387},[234,9575,9576],{"class":255},"'prometheus'\n",[234,9578,9579,9582],{"class":236,"line":552},[234,9580,9581],{"class":9024},"    static_configs",[234,9583,9028],{"class":387},[234,9585,9586,9588,9590,9592,9595],{"class":236,"line":557},[234,9587,9057],{"class":387},[234,9589,9525],{"class":9024},[234,9591,9528],{"class":387},[234,9593,9594],{"class":255},"'localhost:9090'",[234,9596,9534],{"class":387},[234,9598,9599],{"class":236,"line":594},[234,9600,412],{"emptyLinePlaceholder":411},[234,9602,9603,9605,9607,9609],{"class":236,"line":635},[234,9604,9550],{"class":387},[234,9606,9571],{"class":9024},[234,9608,6562],{"class":387},[234,9610,9611],{"class":255},"'node'\n",[234,9613,9614,9616],{"class":236,"line":643},[234,9615,9581],{"class":9024},[234,9617,9028],{"class":387},[234,9619,9620,9622,9624],{"class":236,"line":659},[234,9621,9057],{"class":387},[234,9623,9525],{"class":9024},[234,9625,9028],{"class":387},[234,9627,9628,9631],{"class":236,"line":683},[234,9629,9630],{"class":387},"          - ",[234,9632,9633],{"class":255},"'server-1.tudominio.internal:9100'\n",[234,9635,9636,9638],{"class":236,"line":695},[234,9637,9630],{"class":387},[234,9639,9640],{"class":255},"'server-2.tudominio.internal:9100'\n",[234,9642,9643,9645],{"class":236,"line":717},[234,9644,9630],{"class":387},[234,9646,9647],{"class":255},"'server-3.tudominio.internal:9100'\n",[234,9649,9650,9652],{"class":236,"line":723},[234,9651,9630],{"class":387},[234,9653,9654],{"class":255},"'worker-1.tudominio.internal:9100'\n",[234,9656,9657,9660],{"class":236,"line":729},[234,9658,9659],{"class":9024},"        labels",[234,9661,9028],{"class":387},[234,9663,9664,9667,9669],{"class":236,"line":734},[234,9665,9666],{"class":9024},"          environment",[234,9668,6562],{"class":387},[234,9670,9671],{"class":255},"'production'\n",[234,9673,9674],{"class":236,"line":771},[234,9675,412],{"emptyLinePlaceholder":411},[234,9677,9678,9680,9682,9684],{"class":236,"line":776},[234,9679,9550],{"class":387},[234,9681,9571],{"class":9024},[234,9683,6562],{"class":387},[234,9685,9686],{"class":255},"'apps'\n",[234,9688,9689,9691],{"class":236,"line":815},[234,9690,9581],{"class":9024},[234,9692,9028],{"class":387},[234,9694,9695,9697,9699],{"class":236,"line":820},[234,9696,9057],{"class":387},[234,9698,9525],{"class":9024},[234,9700,9028],{"class":387},[234,9702,9703,9705],{"class":236,"line":826},[234,9704,9630],{"class":387},[234,9706,9707],{"class":255},"'api.tudominio.internal:8080'\n",[234,9709,9710,9712],{"class":236,"line":846},[234,9711,9630],{"class":387},[234,9713,9714],{"class":255},"'worker.tudominio.internal:8080'\n",[234,9716,9717,9719],{"class":236,"line":859},[234,9718,9659],{"class":9024},[234,9720,9028],{"class":387},[234,9722,9723,9725,9727],{"class":236,"line":872},[234,9724,9666],{"class":9024},[234,9726,6562],{"class":387},[234,9728,9671],{"class":255},[234,9730,9731,9734,9736],{"class":236,"line":898},[234,9732,9733],{"class":9024},"    metrics_path",[234,9735,6562],{"class":387},[234,9737,9738],{"class":255},"'\u002Fmetrics'\n",[12,9740,9741,9742,9744,9745,9748],{},"Para clusters mayores o que cambian de composición con frecuencia, cambia ",[231,9743,9515],{}," por ",[231,9746,9747],{},"file_sd_configs"," apuntando a un JSON que generas automáticamente. Para 4 servidores estáticos, el archivo de arriba resuelve.",[12,9750,9751,9752,9755,9756,9759,9760,9763,9764,9767],{},"Reload: ",[231,9753,9754],{},"curl -X POST localhost:9090\u002F-\u002Freload",". Verifica en ",[231,9757,9758],{},"localhost:9090\u002Ftargets"," si todos los jobs están ",[231,9761,9762],{},"UP",". Los que estén ",[231,9765,9766],{},"DOWN"," todavía no fueron instrumentados — ese es el paso 4.",[19,9769,9771],{"id":9770},"paso-4-como-instalar-el-node_exporter-en-cada-servidor","Paso 4 — ¿Cómo instalar el node_exporter en cada servidor?",[12,9773,8933,9774,9777],{},[27,9775,9776],{},"15 minutos"," para 4 servidores.",[12,9779,9780],{},"En cada servidor monitoreado, corre el node_exporter. Hay dos formas: binario directo vía systemd, o contenedor Docker. En 2026 el consenso es container — facilita actualización y aislamiento. En cada nodo:",[224,9782,9784],{"className":226,"code":9783,"language":228,"meta":229,"style":229},"docker run -d \\\n  --name node-exporter \\\n  --restart unless-stopped \\\n  --net=\"host\" \\\n  --pid=\"host\" \\\n  -v \"\u002F:\u002Fhost:ro,rslave\" \\\n  prom\u002Fnode-exporter:v1.8.2 \\\n  --path.rootfs=\u002Fhost\n",[231,9785,9786,9799,9809,9819,9829,9838,9848,9855],{"__ignoreMap":229},[234,9787,9788,9790,9793,9796],{"class":236,"line":237},[234,9789,1118],{"class":247},[234,9791,9792],{"class":255}," run",[234,9794,9795],{"class":251}," -d",[234,9797,9798],{"class":383}," \\\n",[234,9800,9801,9804,9807],{"class":236,"line":244},[234,9802,9803],{"class":251},"  --name",[234,9805,9806],{"class":255}," node-exporter",[234,9808,9798],{"class":383},[234,9810,9811,9814,9817],{"class":236,"line":271},[234,9812,9813],{"class":251},"  --restart",[234,9815,9816],{"class":255}," unless-stopped",[234,9818,9798],{"class":383},[234,9820,9821,9824,9827],{"class":236,"line":415},[234,9822,9823],{"class":251},"  --net=",[234,9825,9826],{"class":255},"\"host\"",[234,9828,9798],{"class":383},[234,9830,9831,9834,9836],{"class":236,"line":434},[234,9832,9833],{"class":251},"  --pid=",[234,9835,9826],{"class":255},[234,9837,9798],{"class":383},[234,9839,9840,9843,9846],{"class":236,"line":459},[234,9841,9842],{"class":251},"  -v",[234,9844,9845],{"class":255}," \"\u002F:\u002Fhost:ro,rslave\"",[234,9847,9798],{"class":383},[234,9849,9850,9853],{"class":236,"line":464},[234,9851,9852],{"class":255},"  prom\u002Fnode-exporter:v1.8.2",[234,9854,9798],{"class":383},[234,9856,9857],{"class":236,"line":479},[234,9858,9859],{"class":251},"  --path.rootfs=\u002Fhost\n",[12,9861,9008,9862,9865,9866,9869,9870,571,9873,2403,9876,9879],{},[231,9863,9864],{},"--net=host"," es necesario para que vea las interfaces de red reales. El bind mount en ",[231,9867,9868],{},"\u002Fhost"," permite leer ",[231,9871,9872],{},"\u002Fproc",[231,9874,9875],{},"\u002Fsys",[231,9877,9878],{},"\u002Fetc\u002Fpasswd"," del host (read-only) sin correr el contenedor con privilegios de root.",[12,9881,9882,9883,1272],{},"Firewall: abre el puerto 9100 solo para la IP del servidor de observabilidad. En Ubuntu con ",[231,9884,9885],{},"ufw",[224,9887,9889],{"className":226,"code":9888,"language":228,"meta":229,"style":229},"ufw allow from \u003CIP_DEL_OBSERVABILITY> to any port 9100\n",[231,9890,9891],{"__ignoreMap":229},[234,9892,9893,9895,9898,9901,9904,9907,9910,9912,9915,9918,9921],{"class":236,"line":237},[234,9894,9885],{"class":247},[234,9896,9897],{"class":255}," allow",[234,9899,9900],{"class":255}," from",[234,9902,9903],{"class":383}," \u003C",[234,9905,9906],{"class":255},"IP_DEL_OBSERVABILIT",[234,9908,9909],{"class":387},"Y",[234,9911,1935],{"class":383},[234,9913,9914],{"class":255}," to",[234,9916,9917],{"class":255}," any",[234,9919,9920],{"class":255}," port",[234,9922,9923],{"class":251}," 9100\n",[12,9925,9926,9927,9930,9931,101],{},"Validación: del servidor de observability, ",[231,9928,9929],{},"curl http:\u002F\u002Fserver-1.tudominio.internal:9100\u002Fmetrics"," debe retornar cientos de líneas empezando con ",[231,9932,9933],{},"# HELP node_cpu_seconds_total...",[19,9935,9937],{"id":9936},"paso-5-como-configurar-loki-promtail","Paso 5 — ¿Cómo configurar Loki + Promtail?",[12,9939,8933,9940,101],{},[27,9941,9452],{},[12,9943,9944,9945,1272],{},"Loki ya está corriendo en el compose del paso 2. Falta el ",[231,9946,9947],{},"loki-config.yml",[224,9949,9951],{"className":9015,"code":9950,"language":9017,"meta":229,"style":229},"auth_enabled: false\n\nserver:\n  http_listen_port: 3100\n\ncommon:\n  path_prefix: \u002Floki\n  storage:\n    filesystem:\n      chunks_directory: \u002Floki\u002Fchunks\n      rules_directory: \u002Floki\u002Frules\n  replication_factor: 1\n  ring:\n    kvstore:\n      store: inmemory\n\nschema_config:\n  configs:\n    - from: 2024-01-01\n      store: tsdb\n      object_store: filesystem\n      schema: v13\n      index:\n        prefix: index_\n        period: 24h\n\nlimits_config:\n  retention_period: 720h  # 30 días\n  reject_old_samples: true\n  reject_old_samples_max_age: 168h\n",[231,9952,9953,9963,9967,9974,9984,9988,9995,10005,10012,10019,10029,10039,10049,10056,10063,10073,10077,10084,10091,10102,10111,10121,10131,10138,10148,10158,10162,10169,10182,10192],{"__ignoreMap":229},[234,9954,9955,9958,9960],{"class":236,"line":237},[234,9956,9957],{"class":9024},"auth_enabled",[234,9959,6562],{"class":387},[234,9961,9962],{"class":251},"false\n",[234,9964,9965],{"class":236,"line":244},[234,9966,412],{"emptyLinePlaceholder":411},[234,9968,9969,9972],{"class":236,"line":271},[234,9970,9971],{"class":9024},"server",[234,9973,9028],{"class":387},[234,9975,9976,9979,9981],{"class":236,"line":415},[234,9977,9978],{"class":9024},"  http_listen_port",[234,9980,6562],{"class":387},[234,9982,9983],{"class":251},"3100\n",[234,9985,9986],{"class":236,"line":434},[234,9987,412],{"emptyLinePlaceholder":411},[234,9989,9990,9993],{"class":236,"line":459},[234,9991,9992],{"class":9024},"common",[234,9994,9028],{"class":387},[234,9996,9997,10000,10002],{"class":236,"line":464},[234,9998,9999],{"class":9024},"  path_prefix",[234,10001,6562],{"class":387},[234,10003,10004],{"class":255},"\u002Floki\n",[234,10006,10007,10010],{"class":236,"line":479},[234,10008,10009],{"class":9024},"  storage",[234,10011,9028],{"class":387},[234,10013,10014,10017],{"class":236,"line":484},[234,10015,10016],{"class":9024},"    filesystem",[234,10018,9028],{"class":387},[234,10020,10021,10024,10026],{"class":236,"line":490},[234,10022,10023],{"class":9024},"      chunks_directory",[234,10025,6562],{"class":387},[234,10027,10028],{"class":255},"\u002Floki\u002Fchunks\n",[234,10030,10031,10034,10036],{"class":236,"line":508},[234,10032,10033],{"class":9024},"      rules_directory",[234,10035,6562],{"class":387},[234,10037,10038],{"class":255},"\u002Floki\u002Frules\n",[234,10040,10041,10044,10046],{"class":236,"line":529},[234,10042,10043],{"class":9024},"  replication_factor",[234,10045,6562],{"class":387},[234,10047,10048],{"class":251},"1\n",[234,10050,10051,10054],{"class":236,"line":535},[234,10052,10053],{"class":9024},"  ring",[234,10055,9028],{"class":387},[234,10057,10058,10061],{"class":236,"line":546},[234,10059,10060],{"class":9024},"    kvstore",[234,10062,9028],{"class":387},[234,10064,10065,10068,10070],{"class":236,"line":552},[234,10066,10067],{"class":9024},"      store",[234,10069,6562],{"class":387},[234,10071,10072],{"class":255},"inmemory\n",[234,10074,10075],{"class":236,"line":557},[234,10076,412],{"emptyLinePlaceholder":411},[234,10078,10079,10082],{"class":236,"line":594},[234,10080,10081],{"class":9024},"schema_config",[234,10083,9028],{"class":387},[234,10085,10086,10089],{"class":236,"line":635},[234,10087,10088],{"class":9024},"  configs",[234,10090,9028],{"class":387},[234,10092,10093,10095,10097,10099],{"class":236,"line":643},[234,10094,9512],{"class":387},[234,10096,391],{"class":9024},[234,10098,6562],{"class":387},[234,10100,10101],{"class":251},"2024-01-01\n",[234,10103,10104,10106,10108],{"class":236,"line":659},[234,10105,10067],{"class":9024},[234,10107,6562],{"class":387},[234,10109,10110],{"class":255},"tsdb\n",[234,10112,10113,10116,10118],{"class":236,"line":683},[234,10114,10115],{"class":9024},"      object_store",[234,10117,6562],{"class":387},[234,10119,10120],{"class":255},"filesystem\n",[234,10122,10123,10126,10128],{"class":236,"line":695},[234,10124,10125],{"class":9024},"      schema",[234,10127,6562],{"class":387},[234,10129,10130],{"class":255},"v13\n",[234,10132,10133,10136],{"class":236,"line":717},[234,10134,10135],{"class":9024},"      index",[234,10137,9028],{"class":387},[234,10139,10140,10143,10145],{"class":236,"line":723},[234,10141,10142],{"class":9024},"        prefix",[234,10144,6562],{"class":387},[234,10146,10147],{"class":255},"index_\n",[234,10149,10150,10153,10155],{"class":236,"line":729},[234,10151,10152],{"class":9024},"        period",[234,10154,6562],{"class":387},[234,10156,10157],{"class":255},"24h\n",[234,10159,10160],{"class":236,"line":734},[234,10161,412],{"emptyLinePlaceholder":411},[234,10163,10164,10167],{"class":236,"line":771},[234,10165,10166],{"class":9024},"limits_config",[234,10168,9028],{"class":387},[234,10170,10171,10174,10176,10179],{"class":236,"line":776},[234,10172,10173],{"class":9024},"  retention_period",[234,10175,6562],{"class":387},[234,10177,10178],{"class":255},"720h",[234,10180,10181],{"class":240},"  # 30 días\n",[234,10183,10184,10187,10189],{"class":236,"line":815},[234,10185,10186],{"class":9024},"  reject_old_samples",[234,10188,6562],{"class":387},[234,10190,10191],{"class":251},"true\n",[234,10193,10194,10197,10199],{"class":236,"line":820},[234,10195,10196],{"class":9024},"  reject_old_samples_max_age",[234,10198,6562],{"class":387},[234,10200,10201],{"class":255},"168h\n",[12,10203,10204],{},"Storage en filesystem es suficiente para empezar. Cuando pases de 50 GB de logs por día o quieras retención de 90+ días, migra a S3 (o compatible). No migres antes — complica la operación sin ganancia real.",[12,10206,10207],{},"En cada servidor monitoreado, instala Promtail (o Grafana Agent) también vía container:",[224,10209,10211],{"className":9015,"code":10210,"language":9017,"meta":229,"style":229},"# \u002Fopt\u002Fpromtail\u002Fpromtail-config.yml en cada servidor\nserver:\n  http_listen_port: 9080\n\nclients:\n  - url: http:\u002F\u002Fmonitor.tudominio.com:3100\u002Floki\u002Fapi\u002Fv1\u002Fpush\n\nscrape_configs:\n  - job_name: system\n    static_configs:\n      - targets: [localhost]\n        labels:\n          job: varlogs\n          host: ${HOSTNAME}\n          __path__: \u002Fvar\u002Flog\u002F*.log\n\n  - job_name: docker\n    docker_sd_configs:\n      - host: unix:\u002F\u002F\u002Fvar\u002Frun\u002Fdocker.sock\n    relabel_configs:\n      - source_labels: ['__meta_docker_container_name']\n        target_label: 'container'\n",[231,10212,10213,10218,10224,10233,10237,10244,10256,10260,10266,10277,10283,10295,10301,10311,10321,10331,10335,10346,10353,10364,10371,10385],{"__ignoreMap":229},[234,10214,10215],{"class":236,"line":237},[234,10216,10217],{"class":240},"# \u002Fopt\u002Fpromtail\u002Fpromtail-config.yml en cada servidor\n",[234,10219,10220,10222],{"class":236,"line":244},[234,10221,9971],{"class":9024},[234,10223,9028],{"class":387},[234,10225,10226,10228,10230],{"class":236,"line":271},[234,10227,9978],{"class":9024},[234,10229,6562],{"class":387},[234,10231,10232],{"class":251},"9080\n",[234,10234,10235],{"class":236,"line":415},[234,10236,412],{"emptyLinePlaceholder":411},[234,10238,10239,10242],{"class":236,"line":434},[234,10240,10241],{"class":9024},"clients",[234,10243,9028],{"class":387},[234,10245,10246,10248,10251,10253],{"class":236,"line":459},[234,10247,9550],{"class":387},[234,10249,10250],{"class":9024},"url",[234,10252,6562],{"class":387},[234,10254,10255],{"class":255},"http:\u002F\u002Fmonitor.tudominio.com:3100\u002Floki\u002Fapi\u002Fv1\u002Fpush\n",[234,10257,10258],{"class":236,"line":464},[234,10259,412],{"emptyLinePlaceholder":411},[234,10261,10262,10264],{"class":236,"line":479},[234,10263,9562],{"class":9024},[234,10265,9028],{"class":387},[234,10267,10268,10270,10272,10274],{"class":236,"line":484},[234,10269,9550],{"class":387},[234,10271,9571],{"class":9024},[234,10273,6562],{"class":387},[234,10275,10276],{"class":255},"system\n",[234,10278,10279,10281],{"class":236,"line":490},[234,10280,9581],{"class":9024},[234,10282,9028],{"class":387},[234,10284,10285,10287,10289,10291,10293],{"class":236,"line":508},[234,10286,9057],{"class":387},[234,10288,9525],{"class":9024},[234,10290,9528],{"class":387},[234,10292,8962],{"class":255},[234,10294,9534],{"class":387},[234,10296,10297,10299],{"class":236,"line":529},[234,10298,9659],{"class":9024},[234,10300,9028],{"class":387},[234,10302,10303,10306,10308],{"class":236,"line":535},[234,10304,10305],{"class":9024},"          job",[234,10307,6562],{"class":387},[234,10309,10310],{"class":255},"varlogs\n",[234,10312,10313,10316,10318],{"class":236,"line":546},[234,10314,10315],{"class":9024},"          host",[234,10317,6562],{"class":387},[234,10319,10320],{"class":255},"${HOSTNAME}\n",[234,10322,10323,10326,10328],{"class":236,"line":552},[234,10324,10325],{"class":9024},"          __path__",[234,10327,6562],{"class":387},[234,10329,10330],{"class":255},"\u002Fvar\u002Flog\u002F*.log\n",[234,10332,10333],{"class":236,"line":557},[234,10334,412],{"emptyLinePlaceholder":411},[234,10336,10337,10339,10341,10343],{"class":236,"line":594},[234,10338,9550],{"class":387},[234,10340,9571],{"class":9024},[234,10342,6562],{"class":387},[234,10344,10345],{"class":255},"docker\n",[234,10347,10348,10351],{"class":236,"line":635},[234,10349,10350],{"class":9024},"    docker_sd_configs",[234,10352,9028],{"class":387},[234,10354,10355,10357,10359,10361],{"class":236,"line":643},[234,10356,9057],{"class":387},[234,10358,1650],{"class":9024},[234,10360,6562],{"class":387},[234,10362,10363],{"class":255},"unix:\u002F\u002F\u002Fvar\u002Frun\u002Fdocker.sock\n",[234,10365,10366,10369],{"class":236,"line":659},[234,10367,10368],{"class":9024},"    relabel_configs",[234,10370,9028],{"class":387},[234,10372,10373,10375,10378,10380,10383],{"class":236,"line":683},[234,10374,9057],{"class":387},[234,10376,10377],{"class":9024},"source_labels",[234,10379,9528],{"class":387},[234,10381,10382],{"class":255},"'__meta_docker_container_name'",[234,10384,9534],{"class":387},[234,10386,10387,10390,10392],{"class":236,"line":695},[234,10388,10389],{"class":9024},"        target_label",[234,10391,6562],{"class":387},[234,10393,10394],{"class":255},"'container'\n",[12,10396,10397,10398,10401,10402,10404],{},"Importante: el endpoint ",[231,10399,10400],{},"http:\u002F\u002Fmonitor.tudominio.com:3100\u002Floki\u002Fapi\u002Fv1\u002Fpush"," necesita estar accesible desde los servidores. Si seguiste el paso 2 y ataste Loki en ",[231,10403,9366],{},", tienes dos opciones: exponer el 3100 vía reverse proxy con autenticación básica, o abrir un túnel SSH\u002FWireGuard entre los servidores. La segunda opción es más segura y la que recomendamos.",[12,10406,10407,10408,10411],{},"Validación: en Grafana, ve a Explore, selecciona la fuente de datos Loki, corre ",[231,10409,10410],{},"{job=\"varlogs\"}"," y ve los logs apareciendo en tiempo real.",[19,10413,10415],{"id":10414},"paso-6-como-importar-los-dashboards-de-grafana","Paso 6 — ¿Cómo importar los dashboards de Grafana?",[12,10417,8933,10418,101],{},[27,10419,10420],{},"20 minutos",[12,10422,10423,10424,10427,10428,101],{},"Accede a ",[231,10425,10426],{},"https:\u002F\u002Fmonitor.tudominio.com"," (después de configurar el reverse proxy del paso 8 — puedes saltar para allá ahora si quieres). Login admin con la contraseña del ",[231,10429,9374],{},[12,10431,10432,10433,1272],{},"Agrega las dos fuentes de datos vía provisioning automático. En ",[231,10434,10435],{},"grafana\u002Fprovisioning\u002Fdatasources\u002Fdatasources.yml",[224,10437,10439],{"className":9015,"code":10438,"language":9017,"meta":229,"style":229},"apiVersion: 1\ndatasources:\n  - name: Prometheus\n    type: prometheus\n    access: proxy\n    url: http:\u002F\u002Fprometheus:9090\n    isDefault: true\n  - name: Loki\n    type: loki\n    access: proxy\n    url: http:\u002F\u002Floki:3100\n",[231,10440,10441,10450,10457,10469,10479,10489,10499,10508,10519,10528,10536],{"__ignoreMap":229},[234,10442,10443,10446,10448],{"class":236,"line":237},[234,10444,10445],{"class":9024},"apiVersion",[234,10447,6562],{"class":387},[234,10449,10048],{"class":251},[234,10451,10452,10455],{"class":236,"line":244},[234,10453,10454],{"class":9024},"datasources",[234,10456,9028],{"class":387},[234,10458,10459,10461,10464,10466],{"class":236,"line":271},[234,10460,9550],{"class":387},[234,10462,10463],{"class":9024},"name",[234,10465,6562],{"class":387},[234,10467,10468],{"class":255},"Prometheus\n",[234,10470,10471,10474,10476],{"class":236,"line":415},[234,10472,10473],{"class":9024},"    type",[234,10475,6562],{"class":387},[234,10477,10478],{"class":255},"prometheus\n",[234,10480,10481,10484,10486],{"class":236,"line":434},[234,10482,10483],{"class":9024},"    access",[234,10485,6562],{"class":387},[234,10487,10488],{"class":255},"proxy\n",[234,10490,10491,10494,10496],{"class":236,"line":459},[234,10492,10493],{"class":9024},"    url",[234,10495,6562],{"class":387},[234,10497,10498],{"class":255},"http:\u002F\u002Fprometheus:9090\n",[234,10500,10501,10504,10506],{"class":236,"line":464},[234,10502,10503],{"class":9024},"    isDefault",[234,10505,6562],{"class":387},[234,10507,10191],{"class":251},[234,10509,10510,10512,10514,10516],{"class":236,"line":479},[234,10511,9550],{"class":387},[234,10513,10463],{"class":9024},[234,10515,6562],{"class":387},[234,10517,10518],{"class":255},"Loki\n",[234,10520,10521,10523,10525],{"class":236,"line":484},[234,10522,10473],{"class":9024},[234,10524,6562],{"class":387},[234,10526,10527],{"class":255},"loki\n",[234,10529,10530,10532,10534],{"class":236,"line":490},[234,10531,10483],{"class":9024},[234,10533,6562],{"class":387},[234,10535,10488],{"class":255},[234,10537,10538,10540,10542],{"class":236,"line":508},[234,10539,10493],{"class":9024},[234,10541,6562],{"class":387},[234,10543,10544],{"class":255},"http:\u002F\u002Floki:3100\n",[12,10546,10547,10548,10551],{},"Reinicia Grafana con ",[231,10549,10550],{},"docker compose restart grafana"," y las fuentes aparecen automáticamente.",[12,10553,10554,10555,10558],{},"Importa los dashboards listos. En ",[27,10556,10557],{},"Dashboards → New → Import",", pega el ID del dashboard:",[2735,10560,10561,10567,10573],{},[70,10562,10563,10566],{},[27,10564,10565],{},"1860"," — Node Exporter Full. CPU, RAM, disco, red, sistema de archivos. Es el dashboard más usado de la comunidad Prometheus, con razón.",[70,10568,10569,10572],{},[27,10570,10571],{},"13639"," — Logs \u002F App. Visualización básica de logs de Loki con filtros por job, container, host.",[70,10574,10575,10578],{},[27,10576,10577],{},"15172"," — Cluster overview. Visión consolidada por servidor, útil para cluster pequeño.",[12,10580,10581,10582,10585],{},"Customiza cada uno para usar ",[231,10583,10584],{},"environment=\"production\""," en el filtro default. Después de dos semanas usándolos, vas a querer crear dashboards propios para workloads específicos — no hay atajo ahí, es tiempo de silla.",[19,10587,10589],{"id":10588},"paso-7-como-configurar-alertas-basicas","Paso 7 — ¿Cómo configurar alertas básicas?",[12,10591,8933,10592,101],{},[27,10593,8992],{},[12,10595,10596],{},"Alertas son donde 80% de los equipos tropiezan: o ponen poquísimos y descubren incidentes por los clientes, o ponen decenas y desensibilizan al equipo.",[12,10598,10599,10600,10603,10604,1272],{},"Empieza con ",[27,10601,10602],{},"seis alertas esenciales",". En ",[231,10605,10606],{},"prometheus\u002Falerts.yml",[224,10608,10610],{"className":9015,"code":10609,"language":9017,"meta":229,"style":229},"groups:\n  - name: essentials\n    interval: 30s\n    rules:\n      - alert: ServerDown\n        expr: up{job=\"node\"} == 0\n        for: 2m\n        labels:\n          severity: critical\n        annotations:\n          summary: \"Servidor {{ $labels.instance }} está fuera del aire\"\n\n      - alert: HighCPU\n        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100) > 80\n        for: 10m\n        labels:\n          severity: warning\n\n      - alert: DiskAlmostFull\n        expr: (node_filesystem_avail_bytes{mountpoint=\"\u002F\"} \u002F node_filesystem_size_bytes{mountpoint=\"\u002F\"}) * 100 \u003C 15\n        for: 5m\n        labels:\n          severity: critical\n\n      - alert: HighMemory\n        expr: (1 - (node_memory_MemAvailable_bytes \u002F node_memory_MemTotal_bytes)) * 100 > 90\n        for: 10m\n        labels:\n          severity: warning\n\n      - alert: HighHTTPErrorRate\n        expr: sum(rate(http_requests_total{status=~\"5..\"}[5m])) \u002F sum(rate(http_requests_total[5m])) > 0.05\n        for: 5m\n        labels:\n          severity: critical\n\n      - alert: HighLatency\n        expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 2\n        for: 10m\n        labels:\n          severity: warning\n",[231,10611,10612,10619,10630,10640,10647,10659,10669,10679,10685,10695,10702,10712,10716,10727,10736,10745,10751,10760,10764,10775,10784,10793,10799,10807,10811,10822,10831,10839,10845,10853,10857,10868,10877,10885,10891,10899,10903,10914,10923,10931,10937],{"__ignoreMap":229},[234,10613,10614,10617],{"class":236,"line":237},[234,10615,10616],{"class":9024},"groups",[234,10618,9028],{"class":387},[234,10620,10621,10623,10625,10627],{"class":236,"line":244},[234,10622,9550],{"class":387},[234,10624,10463],{"class":9024},[234,10626,6562],{"class":387},[234,10628,10629],{"class":255},"essentials\n",[234,10631,10632,10635,10637],{"class":236,"line":271},[234,10633,10634],{"class":9024},"    interval",[234,10636,6562],{"class":387},[234,10638,10639],{"class":255},"30s\n",[234,10641,10642,10645],{"class":236,"line":415},[234,10643,10644],{"class":9024},"    rules",[234,10646,9028],{"class":387},[234,10648,10649,10651,10654,10656],{"class":236,"line":434},[234,10650,9057],{"class":387},[234,10652,10653],{"class":9024},"alert",[234,10655,6562],{"class":387},[234,10657,10658],{"class":255},"ServerDown\n",[234,10660,10661,10664,10666],{"class":236,"line":459},[234,10662,10663],{"class":9024},"        expr",[234,10665,6562],{"class":387},[234,10667,10668],{"class":255},"up{job=\"node\"} == 0\n",[234,10670,10671,10674,10676],{"class":236,"line":464},[234,10672,10673],{"class":9024},"        for",[234,10675,6562],{"class":387},[234,10677,10678],{"class":255},"2m\n",[234,10680,10681,10683],{"class":236,"line":479},[234,10682,9659],{"class":9024},[234,10684,9028],{"class":387},[234,10686,10687,10690,10692],{"class":236,"line":484},[234,10688,10689],{"class":9024},"          severity",[234,10691,6562],{"class":387},[234,10693,10694],{"class":255},"critical\n",[234,10696,10697,10700],{"class":236,"line":490},[234,10698,10699],{"class":9024},"        annotations",[234,10701,9028],{"class":387},[234,10703,10704,10707,10709],{"class":236,"line":508},[234,10705,10706],{"class":9024},"          summary",[234,10708,6562],{"class":387},[234,10710,10711],{"class":255},"\"Servidor {{ $labels.instance }} está fuera del aire\"\n",[234,10713,10714],{"class":236,"line":529},[234,10715,412],{"emptyLinePlaceholder":411},[234,10717,10718,10720,10722,10724],{"class":236,"line":535},[234,10719,9057],{"class":387},[234,10721,10653],{"class":9024},[234,10723,6562],{"class":387},[234,10725,10726],{"class":255},"HighCPU\n",[234,10728,10729,10731,10733],{"class":236,"line":546},[234,10730,10663],{"class":9024},[234,10732,6562],{"class":387},[234,10734,10735],{"class":255},"100 - (avg by(instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100) > 80\n",[234,10737,10738,10740,10742],{"class":236,"line":552},[234,10739,10673],{"class":9024},[234,10741,6562],{"class":387},[234,10743,10744],{"class":255},"10m\n",[234,10746,10747,10749],{"class":236,"line":557},[234,10748,9659],{"class":9024},[234,10750,9028],{"class":387},[234,10752,10753,10755,10757],{"class":236,"line":594},[234,10754,10689],{"class":9024},[234,10756,6562],{"class":387},[234,10758,10759],{"class":255},"warning\n",[234,10761,10762],{"class":236,"line":635},[234,10763,412],{"emptyLinePlaceholder":411},[234,10765,10766,10768,10770,10772],{"class":236,"line":643},[234,10767,9057],{"class":387},[234,10769,10653],{"class":9024},[234,10771,6562],{"class":387},[234,10773,10774],{"class":255},"DiskAlmostFull\n",[234,10776,10777,10779,10781],{"class":236,"line":659},[234,10778,10663],{"class":9024},[234,10780,6562],{"class":387},[234,10782,10783],{"class":255},"(node_filesystem_avail_bytes{mountpoint=\"\u002F\"} \u002F node_filesystem_size_bytes{mountpoint=\"\u002F\"}) * 100 \u003C 15\n",[234,10785,10786,10788,10790],{"class":236,"line":683},[234,10787,10673],{"class":9024},[234,10789,6562],{"class":387},[234,10791,10792],{"class":255},"5m\n",[234,10794,10795,10797],{"class":236,"line":695},[234,10796,9659],{"class":9024},[234,10798,9028],{"class":387},[234,10800,10801,10803,10805],{"class":236,"line":717},[234,10802,10689],{"class":9024},[234,10804,6562],{"class":387},[234,10806,10694],{"class":255},[234,10808,10809],{"class":236,"line":723},[234,10810,412],{"emptyLinePlaceholder":411},[234,10812,10813,10815,10817,10819],{"class":236,"line":729},[234,10814,9057],{"class":387},[234,10816,10653],{"class":9024},[234,10818,6562],{"class":387},[234,10820,10821],{"class":255},"HighMemory\n",[234,10823,10824,10826,10828],{"class":236,"line":734},[234,10825,10663],{"class":9024},[234,10827,6562],{"class":387},[234,10829,10830],{"class":255},"(1 - (node_memory_MemAvailable_bytes \u002F node_memory_MemTotal_bytes)) * 100 > 90\n",[234,10832,10833,10835,10837],{"class":236,"line":771},[234,10834,10673],{"class":9024},[234,10836,6562],{"class":387},[234,10838,10744],{"class":255},[234,10840,10841,10843],{"class":236,"line":776},[234,10842,9659],{"class":9024},[234,10844,9028],{"class":387},[234,10846,10847,10849,10851],{"class":236,"line":815},[234,10848,10689],{"class":9024},[234,10850,6562],{"class":387},[234,10852,10759],{"class":255},[234,10854,10855],{"class":236,"line":820},[234,10856,412],{"emptyLinePlaceholder":411},[234,10858,10859,10861,10863,10865],{"class":236,"line":826},[234,10860,9057],{"class":387},[234,10862,10653],{"class":9024},[234,10864,6562],{"class":387},[234,10866,10867],{"class":255},"HighHTTPErrorRate\n",[234,10869,10870,10872,10874],{"class":236,"line":846},[234,10871,10663],{"class":9024},[234,10873,6562],{"class":387},[234,10875,10876],{"class":255},"sum(rate(http_requests_total{status=~\"5..\"}[5m])) \u002F sum(rate(http_requests_total[5m])) > 0.05\n",[234,10878,10879,10881,10883],{"class":236,"line":859},[234,10880,10673],{"class":9024},[234,10882,6562],{"class":387},[234,10884,10792],{"class":255},[234,10886,10887,10889],{"class":236,"line":872},[234,10888,9659],{"class":9024},[234,10890,9028],{"class":387},[234,10892,10893,10895,10897],{"class":236,"line":898},[234,10894,10689],{"class":9024},[234,10896,6562],{"class":387},[234,10898,10694],{"class":255},[234,10900,10901],{"class":236,"line":913},[234,10902,412],{"emptyLinePlaceholder":411},[234,10904,10905,10907,10909,10911],{"class":236,"line":1886},[234,10906,9057],{"class":387},[234,10908,10653],{"class":9024},[234,10910,6562],{"class":387},[234,10912,10913],{"class":255},"HighLatency\n",[234,10915,10916,10918,10920],{"class":236,"line":1901},[234,10917,10663],{"class":9024},[234,10919,6562],{"class":387},[234,10921,10922],{"class":255},"histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 2\n",[234,10924,10925,10927,10929],{"class":236,"line":1920},[234,10926,10673],{"class":9024},[234,10928,6562],{"class":387},[234,10930,10744],{"class":255},[234,10932,10933,10935],{"class":236,"line":1944},[234,10934,9659],{"class":9024},[234,10936,9028],{"class":387},[234,10938,10939,10941,10943],{"class":236,"line":1962},[234,10940,10689],{"class":9024},[234,10942,6562],{"class":387},[234,10944,10759],{"class":255},[12,10946,10947,10948,10951],{},"Y el ",[231,10949,10950],{},"alertmanager\u002Falertmanager.yml"," apuntando a un webhook de Slack o Discord:",[224,10953,10955],{"className":9015,"code":10954,"language":9017,"meta":229,"style":229},"route:\n  group_by: ['alertname', 'severity']\n  group_wait: 30s\n  group_interval: 5m\n  repeat_interval: 4h\n  receiver: 'slack-default'\n  routes:\n    - match:\n        severity: critical\n      receiver: 'slack-critical'\n      repeat_interval: 1h\n\nreceivers:\n  - name: 'slack-default'\n    slack_configs:\n      - api_url: 'https:\u002F\u002Fhooks.slack.com\u002Fservices\u002FTU\u002FWEBHOOK\u002FAQUI'\n        channel: '#alerts'\n        send_resolved: true\n\n  - name: 'slack-critical'\n    slack_configs:\n      - api_url: 'https:\u002F\u002Fhooks.slack.com\u002Fservices\u002FTU\u002FWEBHOOK\u002FAQUI'\n        channel: '#alerts-critical'\n        send_resolved: true\n",[231,10956,10957,10964,10981,10990,10999,11009,11019,11026,11035,11044,11054,11064,11068,11075,11085,11092,11104,11114,11123,11127,11137,11143,11153,11162],{"__ignoreMap":229},[234,10958,10959,10962],{"class":236,"line":237},[234,10960,10961],{"class":9024},"route",[234,10963,9028],{"class":387},[234,10965,10966,10969,10971,10974,10976,10979],{"class":236,"line":244},[234,10967,10968],{"class":9024},"  group_by",[234,10970,9528],{"class":387},[234,10972,10973],{"class":255},"'alertname'",[234,10975,571],{"class":387},[234,10977,10978],{"class":255},"'severity'",[234,10980,9534],{"class":387},[234,10982,10983,10986,10988],{"class":236,"line":271},[234,10984,10985],{"class":9024},"  group_wait",[234,10987,6562],{"class":387},[234,10989,10639],{"class":255},[234,10991,10992,10995,10997],{"class":236,"line":415},[234,10993,10994],{"class":9024},"  group_interval",[234,10996,6562],{"class":387},[234,10998,10792],{"class":255},[234,11000,11001,11004,11006],{"class":236,"line":434},[234,11002,11003],{"class":9024},"  repeat_interval",[234,11005,6562],{"class":387},[234,11007,11008],{"class":255},"4h\n",[234,11010,11011,11014,11016],{"class":236,"line":459},[234,11012,11013],{"class":9024},"  receiver",[234,11015,6562],{"class":387},[234,11017,11018],{"class":255},"'slack-default'\n",[234,11020,11021,11024],{"class":236,"line":464},[234,11022,11023],{"class":9024},"  routes",[234,11025,9028],{"class":387},[234,11027,11028,11030,11033],{"class":236,"line":479},[234,11029,9512],{"class":387},[234,11031,11032],{"class":9024},"match",[234,11034,9028],{"class":387},[234,11036,11037,11040,11042],{"class":236,"line":484},[234,11038,11039],{"class":9024},"        severity",[234,11041,6562],{"class":387},[234,11043,10694],{"class":255},[234,11045,11046,11049,11051],{"class":236,"line":490},[234,11047,11048],{"class":9024},"      receiver",[234,11050,6562],{"class":387},[234,11052,11053],{"class":255},"'slack-critical'\n",[234,11055,11056,11059,11061],{"class":236,"line":508},[234,11057,11058],{"class":9024},"      repeat_interval",[234,11060,6562],{"class":387},[234,11062,11063],{"class":255},"1h\n",[234,11065,11066],{"class":236,"line":529},[234,11067,412],{"emptyLinePlaceholder":411},[234,11069,11070,11073],{"class":236,"line":535},[234,11071,11072],{"class":9024},"receivers",[234,11074,9028],{"class":387},[234,11076,11077,11079,11081,11083],{"class":236,"line":546},[234,11078,9550],{"class":387},[234,11080,10463],{"class":9024},[234,11082,6562],{"class":387},[234,11084,11018],{"class":255},[234,11086,11087,11090],{"class":236,"line":552},[234,11088,11089],{"class":9024},"    slack_configs",[234,11091,9028],{"class":387},[234,11093,11094,11096,11099,11101],{"class":236,"line":557},[234,11095,9057],{"class":387},[234,11097,11098],{"class":9024},"api_url",[234,11100,6562],{"class":387},[234,11102,11103],{"class":255},"'https:\u002F\u002Fhooks.slack.com\u002Fservices\u002FTU\u002FWEBHOOK\u002FAQUI'\n",[234,11105,11106,11109,11111],{"class":236,"line":594},[234,11107,11108],{"class":9024},"        channel",[234,11110,6562],{"class":387},[234,11112,11113],{"class":255},"'#alerts'\n",[234,11115,11116,11119,11121],{"class":236,"line":635},[234,11117,11118],{"class":9024},"        send_resolved",[234,11120,6562],{"class":387},[234,11122,10191],{"class":251},[234,11124,11125],{"class":236,"line":643},[234,11126,412],{"emptyLinePlaceholder":411},[234,11128,11129,11131,11133,11135],{"class":236,"line":659},[234,11130,9550],{"class":387},[234,11132,10463],{"class":9024},[234,11134,6562],{"class":387},[234,11136,11053],{"class":255},[234,11138,11139,11141],{"class":236,"line":683},[234,11140,11089],{"class":9024},[234,11142,9028],{"class":387},[234,11144,11145,11147,11149,11151],{"class":236,"line":695},[234,11146,9057],{"class":387},[234,11148,11098],{"class":9024},[234,11150,6562],{"class":387},[234,11152,11103],{"class":255},[234,11154,11155,11157,11159],{"class":236,"line":717},[234,11156,11108],{"class":9024},[234,11158,6562],{"class":387},[234,11160,11161],{"class":255},"'#alerts-critical'\n",[234,11163,11164,11166,11168],{"class":236,"line":723},[234,11165,11118],{"class":9024},[234,11167,6562],{"class":387},[234,11169,10191],{"class":251},[12,11171,11172,11173,11176,11177,11180],{},"Dos detalles que ahorran noche de sueño. El ",[231,11174,11175],{},"for: 10m"," en CPU evita que picos cortos se vuelvan alertas — el servidor puede llegar a 95% por 30 segundos y eso ser normal. El ",[231,11178,11179],{},"repeat_interval: 4h"," para warnings garantiza que un warning resuelto en una hora no se vuelva 60 mensajes — el Alertmanager agrupa.",[12,11182,11183,11184,11186,11187,11190,11191,11194],{},"Recarga Prometheus (",[231,11185,9754],{},") y prueba forzando una alerta: ",[231,11188,11189],{},"stress --cpu 4 --timeout 700s"," en algún servidor debe disparar ",[231,11192,11193],{},"HighCPU"," en 10 minutos.",[19,11196,11198],{"id":11197},"paso-8-como-poner-reverse-proxy-y-tls-al-frente","Paso 8 — ¿Cómo poner reverse proxy y TLS al frente?",[12,11200,8933,11201,101],{},[27,11202,10420],{},[12,11204,11205,11206,11208],{},"Para acceder a Grafana vía ",[231,11207,10426],{}," con certificado válido, necesitas algo al frente del puerto 3000. Dos opciones:",[67,11210,11211,11220],{},[70,11212,11213,11215,11216,11219],{},[27,11214,5604],{}," — si ya tienes el cluster HeroCtl corriendo, basta declarar Grafana como job con ",[231,11217,11218],{},"ingress: { host: monitor.tudominio.com, tls: true }",". Certificado Let's Encrypt automático, sin herramienta adicional.",[70,11221,11222,11225,11226],{},[27,11223,11224],{},"Caddy standalone"," en la propia VPS de observabilidad — también emite Let's Encrypt automáticamente. Caddyfile mínimo:",[224,11227,11230],{"className":11228,"code":11229,"language":2530},[2528],"monitor.tudominio.com {\n  reverse_proxy localhost:3000\n  basicauth \u002Flogin {\n    admin \u003Chash_bcrypt>\n  }\n}\n",[231,11231,11229],{"__ignoreMap":229},[12,11233,11234,11235,11238],{},"Para defensa en profundidad, mantén autenticación básica del Caddy\u002Frouter al frente del login de Grafana — dos barreras, no una. La segunda es especialmente importante porque el login default de Grafana es ",[231,11236,11237],{},"admin\u002Fadmin"," y la primera cosa que los bots hacen en un Grafana expuesto es probar esa combinación.",[19,11240,11242],{"id":11241},"paso-9-como-instrumentar-metricas-de-aplicacion","Paso 9 — ¿Cómo instrumentar métricas de aplicación?",[12,11244,8933,11245,101],{},[27,11246,11247],{},"varía según número de aplicaciones",[12,11249,11250],{},"Métricas de sistema son la mitad de la historia. La otra mitad es lo que tu aplicación está haciendo — cuántas requests por segundo, cuál la latencia p99, cuántos errores, cuál el tamaño de la cola de jobs en background.",[12,11252,11253],{},"Cada lenguaje popular tiene cliente Prometheus oficial:",[2735,11255,11256,11264,11272,11279],{},[70,11257,11258,6562,11261],{},[27,11259,11260],{},"Node.js",[231,11262,11263],{},"prom-client",[70,11265,11266,6562,11269],{},[27,11267,11268],{},"Python",[231,11270,11271],{},"prometheus-client",[70,11273,11274,6562,11277],{},[27,11275,11276],{},"Ruby",[231,11278,11271],{},[70,11280,11281,6562,11284],{},[27,11282,11283],{},"Go",[231,11285,11286],{},"github.com\u002Fprometheus\u002Fclient_golang",[12,11288,11289],{},"El estándar mínimo son tres métricas por endpoint HTTP:",[2735,11291,11292,11306,11312],{},[70,11293,11294,11297,11298,571,11301,571,11304,101],{},[231,11295,11296],{},"http_requests_total"," — counter, con labels ",[231,11299,11300],{},"method",[231,11302,11303],{},"path",[231,11305,614],{},[70,11307,11308,11311],{},[231,11309,11310],{},"http_request_duration_seconds"," — histogram, mismo set de labels.",[70,11313,11314,11317,11318,11321],{},[231,11315,11316],{},"app_errors_total"," — counter, con label ",[231,11319,11320],{},"kind"," (\"validation\", \"db\", \"external_api\", etc).",[12,11323,11324,11325,11327,11328,11330],{},"Expone todo eso en ",[231,11326,8913],{},". Agrega el endpoint en el ",[231,11329,9562],{}," de Prometheus. En horas tienes dashboards por endpoint, alertas por tasa de error, y la capacidad de responder \"qué estaba ocurriendo a las 3:14 de ayer\" con un gráfico en lugar de un tiro al aire.",[12,11332,11333,11334,11337,11338,11341],{},"Cuidado con ",[27,11335,11336],{},"cardinalidad",". Cada combinación única de labels se vuelve una serie temporal separada. Si pones ",[231,11339,11340],{},"user_id"," como label, con 100k usuarios creas 100k series — y Prometheus va a consumir 8+ GB de RAM solo para indexar eso. Regla práctica: labels tienen valores en conjuntos pequeños (status code: 5 valores; método: 5 valores; path: decenas). Identificadores únicos van en logs, no en métricas.",[19,11343,11345],{"id":11344},"como-correr-esto-dentro-de-heroctl-en-lugar-de-vps-dedicada","¿Cómo correr esto dentro de HeroCtl en lugar de VPS dedicada?",[12,11347,11348],{},"Para clusters que ya corren el orquestador, tiene sentido considerar la stack como un job más. Trade-off: ahorras una VPS, pero pierdes aislamiento (si el cluster muere, el monitoring muere junto).",[12,11350,11351],{},"La topología queda así:",[2735,11353,11354,11360,11366,11372],{},[70,11355,11356,11359],{},[27,11357,11358],{},"1 job spec único"," con 4 tasks: prometheus, grafana, loki, alertmanager.",[70,11361,11362,11365],{},[27,11363,11364],{},"Volúmenes replicados"," en el cluster — los datos sobreviven a falla de un nodo.",[70,11367,11368,11371],{},[27,11369,11370],{},"Router integrado"," hace el TLS automático vía subdominio. No necesita Caddy adicional.",[70,11373,11374,11377],{},[27,11375,11376],{},"Métricas del propio cluster"," ya son expuestas en formato Prometheus en la API administrativa, entonces el scrape es directo.",[12,11379,11380],{},"Para producción crítica, recomendamos la separación física (VPS dedicada fuera del cluster). Para proyecto personal, MVP, o equipo pequeño donde \"todo cae junto\" es aceptable, correr dentro es más barato y operacionalmente más simple. El job spec entero queda en torno de 80 líneas de manifiesto.",[19,11382,11384],{"id":11383},"cuanto-cuesta-esa-stack-al-mes-en-brasil","¿Cuánto cuesta esa stack al mes en Brasil?",[119,11386,11387,11397],{},[122,11388,11389],{},[125,11390,11391,11394],{},[128,11392,11393],{},"Ítem",[128,11395,11396],{},"Costo mensual (BRL)",[141,11398,11399,11407,11415,11423],{},[125,11400,11401,11404],{},[146,11402,11403],{},"VPS observability dedicada (4 GB RAM)",[146,11405,11406],{},"R$40 a R$80",[125,11408,11409,11412],{},[146,11410,11411],{},"Object storage para retención larga de logs (opcional)",[146,11413,11414],{},"R$30",[125,11416,11417,11420],{},[146,11418,11419],{},"Tiempo de mantenimiento (2 a 4h × valor de la hora)",[146,11421,11422],{},"R$200 a R$400",[125,11424,11425,11430],{},[146,11426,11427],{},[27,11428,11429],{},"Total operacional",[146,11431,11432],{},[27,11433,11434],{},"R$300 a R$500",[12,11436,11437],{},"Para comparación, una suscripción de Datadog o New Relic con cobertura equivalente (5 hosts, retención de logs de 30 días, alertas, dashboards) sale en torno de R$1.500 a R$2.000 al mes — sin contar el overage automático que aparece al final del mes cuando alguien olvida un log verboso encendido.",[12,11439,11440],{},"La diferencia no es pequeña: en un año, la stack open-source self-hosted ahorra entre R$12.000 y R$18.000. Para startup en etapa inicial, eso es medio ingeniero júnior.",[19,11442,11444],{"id":11443},"tabla-de-puertos-recursos-y-caracteristicas-por-componente","Tabla de puertos, recursos y características por componente",[119,11446,11447,11467],{},[122,11448,11449],{},[125,11450,11451,11453,11456,11458,11461,11464],{},[128,11452,130],{},[128,11454,11455],{},"Puerto",[128,11457,3874],{},[128,11459,11460],{},"Disco",[128,11462,11463],{},"Retención default",[128,11465,11466],{},"Formato de los datos",[141,11468,11469,11488,11506,11524,11543,11559],{},[125,11470,11471,11473,11476,11479,11482,11485],{},[146,11472,8837],{},[146,11474,11475],{},"9090",[146,11477,11478],{},"512 MB",[146,11480,11481],{},"10 GB",[146,11483,11484],{},"15 días",[146,11486,11487],{},"TSDB binario",[125,11489,11490,11492,11495,11498,11501,11503],{},[146,11491,8843],{},[146,11493,11494],{},"3000",[146,11496,11497],{},"256 MB",[146,11499,11500],{},"1 GB",[146,11502,3056],{},[146,11504,11505],{},"SQLite o Postgres",[125,11507,11508,11510,11513,11515,11518,11521],{},[146,11509,8849],{},[146,11511,11512],{},"3100",[146,11514,11478],{},[146,11516,11517],{},"30 GB",[146,11519,11520],{},"30 días (configurable)",[146,11522,11523],{},"chunks comprimidos",[125,11525,11526,11529,11532,11535,11538,11540],{},[146,11527,11528],{},"Promtail \u002F Agent",[146,11530,11531],{},"9080",[146,11533,11534],{},"128 MB",[146,11536,11537],{},"mínimo",[146,11539,3056],{},[146,11541,11542],{},"pasa por valor",[125,11544,11545,11547,11550,11552,11554,11556],{},[146,11546,8867],{},[146,11548,11549],{},"9093",[146,11551,11534],{},[146,11553,11500],{},[146,11555,3056],{},[146,11557,11558],{},"log de notificaciones",[125,11560,11561,11563,11566,11569,11571,11573],{},[146,11562,8861],{},[146,11564,11565],{},"9100",[146,11567,11568],{},"64 MB",[146,11570,11537],{},[146,11572,3056],{},[146,11574,11575],{},"endpoint de scrape",[12,11577,11578],{},"Esas son las mínimas viables para cluster pequeño. En producción con 30 servidores y tráfico real, multiplica RAM por 3 y disco por 5.",[19,11580,11582],{"id":11581},"los-cuatro-errores-que-matan-stack-de-monitoring-nueva","Los cuatro errores que matan stack de monitoring nueva",[12,11584,11585],{},"Equipos montando observabilidad por primera vez tropiezan casi siempre en los mismos cuatro errores. Saber sobre ellos antes ahorra meses.",[12,11587,11588,11591,11592,11595],{},[27,11589,11590],{},"No monitorear el monitoring."," Prometheus paró de scrape el jueves; nadie vio. El miércoles de la semana siguiente un servidor cayó de verdad y descubrieron que no había alerta porque Prometheus estaba muerto hace 6 días. Solución: configura un cron externo simple (hasta un Pingdom gratuito sirve) que pegue en ",[231,11593,11594],{},"https:\u002F\u002Fmonitor.tudominio.com\u002Fapi\u002Fhealth"," cada 5 minutos y te avise cuando el propio Grafana caiga.",[12,11597,11598,11601,11602,11605],{},[27,11599,11600],{},"Sin estrategia de retención."," Disco se llena en tres meses, Prometheus para de grabar, alguien borra todo en la desesperación, pierde 90 días de historial. Configura ",[231,11603,11604],{},"--storage.tsdb.retention.time=30d"," desde el día uno y establece un job de housekeeping.",[12,11607,11608,11611,11612,571,11614,11617],{},[27,11609,11610],{},"Cardinalidad alta en labels."," Ya cubrimos en el paso 9, pero vale la pena repetir: cada ",[231,11613,11340],{},[231,11615,11616],{},"request_id"," o UUID que se vuelve label es un número que multiplica explosivamente el consumo de RAM de Prometheus. Identificadores únicos van a Loki, no a Prometheus.",[12,11619,11620,11623],{},[27,11621,11622],{},"Alertas ruidosas."," El equipo recibe 200 alertas por día. En dos semanas, nadie mira más. Cuando el sitio caiga de verdad, la alerta va a estar en medio de otras 199. Solución: empieza con seis alertas (las del paso 7), audita cada dos semanas, y excluye todo lo que disparó pero no exigió acción humana. Alerta sin acción es ruido.",[19,11625,3226],{"id":3225},[12,11627,11628,11631],{},[27,11629,11630],{},"¿Puedo correr todo en una VPS de 2 GB?","\nTécnicamente sí, para cluster de hasta 3 servidores y pocas aplicaciones. En la práctica vas a chocar contra el techo de RAM en 2 a 3 meses, especialmente si importas dashboards densos en Grafana. Paga los 50 reales más y ve directo a la VPS de 4 GB — el tiempo que ahorras no peleando con OOM kills se paga solo.",[12,11633,11634,11637],{},[27,11635,11636],{},"¿Cuánto disco para 30 días de logs?","\nDepende totalmente del volumen de logs de tu aplicación. Regla gruesa para startup pequeña: cluster de 4 servidores con aplicaciones web normales genera 1 a 5 GB de logs por día después de compresión de Loki. Treinta días da entre 30 y 150 GB. Empieza con 50 GB de SSD, monitorea el crecimiento por dos semanas, expande si es necesario. Si vas mucho más allá de eso, es hora de ir a object storage.",[12,11639,11640,11643],{},[27,11641,11642],{},"Grafana Cloud vs self-hosted, ¿cuál elegir?","\nGrafana Cloud free tier es generoso (10k series, 50 GB de logs, retención de 14 días) y elimina el trabajo de mantener el servidor. Para proyecto solo o equipo muy pequeño, tiene sentido. A partir del momento en que pasas del free tier, los precios escalan rápido — a partir de US$50\u002Fmes — y pierdes el control sobre los datos. Self-hosted cuesta hardware + tiempo, Cloud cuesta dinero + lock-in. Para empresa que pretende crecer y tiene un dev DevOps en el equipo, self-hosted gana.",[12,11645,11646,11649],{},[27,11647,11648],{},"¿Promtail o Grafana Agent?","\nEn 2026, Grafana Agent (rebautizado a Grafana Alloy) está sustituyendo al Promtail oficialmente. Para setup nuevo, ve directo a Alloy. Para setup que ya corre Promtail hace tiempo, no hay urgencia en migrar — Promtail va a seguir funcionando por años.",[12,11651,11652,11655,11656,11658],{},[27,11653,11654],{},"¿OpenTelemetry encaja dónde en esa stack?","\nOTel es el estándar de instrumentación de aplicación que está consolidando. En lugar de usar ",[231,11657,11263],{}," directo, usas el SDK de OTel y él exporta a Prometheus, Loki y Tempo simultáneamente. La ventaja grande es portabilidad — si quieres cambiar Prometheus por otra cosa de aquí a 3 años, tu aplicación no cambia una línea. Para startup empezando hoy, recomendamos OTel desde el día uno.",[12,11660,11661,11664,11665,11668,11669,11672],{},[27,11662,11663],{},"¿Cómo hago backup de Prometheus?","\nPrometheus tiene snapshot vía API: ",[231,11666,11667],{},"curl -X POST localhost:9090\u002Fapi\u002Fv1\u002Fadmin\u002Ftsdb\u002Fsnapshot"," crea un snapshot en el directorio de datos. Hazlo una vez al día vía cron, haz ",[231,11670,11671],{},"tar.gz"," y envíalo a object storage. En caso de desastre, lo que pierdes son métricas — y métricas, diferente de logs, son típicamente recuperables en horas (vuelves a recolectar y los dashboards vuelven). Logs perdidos están perdidos para siempre, entonces invierte más en backup de Loki.",[12,11674,11675,11678],{},[27,11676,11677],{},"¿Tempo (traces distribuidos) vale la pena instalar ahora?","\nNo. Traces se vuelven útiles a partir del momento en que tienes 5+ servicios conversando entre sí y depurar latencia involucra seguir una request por varios hops. Para arquitectura monolítica o pocos servicios, traces dan trabajo desproporcional al valor. Agrégalos cuando la complejidad lo pida.",[12,11680,11681,11684],{},[27,11682,11683],{},"¿Loki indexa full-text como ELK?","\nNo, y esa es la feature, no bug. Loki indexa solo labels (job, host, container, severity) y el contenido del log queda comprimido sin índice. Para buscar texto, filtras por labels primero y después haces grep en los chunks resultantes. Eso es lo que vuelve a Loki diez veces más barato que ELK en RAM y CPU. A cambio, queries de texto libre en todo el historial son más lentas. Para 90% de los casos de debugging, filtrar por job + host + ventana de tiempo ya reduce a decenas de MB donde el grep vuela.",[19,11686,11688],{"id":11687},"proximos-pasos","Próximos pasos",[12,11690,11691],{},"¿Subiste la stack, tienes dashboard, tienes alerta, tienes log buscable? Bien. Las próximas tres cosas que valen la inversión son, en orden:",[67,11693,11694,11700,11714],{},[70,11695,11696,11699],{},[27,11697,11698],{},"Custom dashboards por aplicación"," — métricas de negocio (suscripciones creadas\u002Fhora, jobs procesados, cola de e-mails) en lugar de solo infraestructura.",[70,11701,11702,11705,11706,11709,11710,11713],{},[27,11703,11704],{},"Runbooks linkados en las alertas"," — toda regla en ",[231,11707,11708],{},"alerts.yml"," debe tener ",[231,11711,11712],{},"annotations.runbook_url"," apuntando a una página explicando qué hacer. Cuando la alerta dispare a las 3 de la mañana, el sueño no piensa.",[70,11715,11716,11719],{},[27,11717,11718],{},"Revisión mensual de alertas"," — 30 minutos una vez al mes auditando lo que disparó en el mes anterior, eliminando lo que se volvió ruido, ajustando thresholds.",[12,11721,11722,11723,11727,11728,101],{},"Para quien quiere ir más allá y entender por qué elegimos esta stack en lugar de SaaS gestionado, lee ",[3337,11724,11726],{"href":11725},"\u002Fes\u002Fblog\u002Fobservabilidad-sin-datadog-stack-startup","Observabilidad sin Datadog: la stack de la startup",". Y para cerrar el ciclo de operación — porque no sirve saber que la base cayó si no logras restaurar — vale la pena leer ",[3337,11729,11731],{"href":11730},"\u002Fes\u002Fblog\u002Fbackup-de-base-de-datos-en-cluster","Backup de base en cluster: estrategias para las 3 de la mañana",[12,11733,11734],{},"Si quieres saltarte ese montaje todo y correr la stack como job dentro de un orquestador que ya cuida de TLS, rolling update y replicación de volumen:",[224,11736,11737],{"className":226,"code":5318,"language":228,"meta":229,"style":229},[231,11738,11739],{"__ignoreMap":229},[234,11740,11741,11743,11745,11747,11749],{"class":236,"line":237},[234,11742,1220],{"class":247},[234,11744,2958],{"class":251},[234,11746,5329],{"class":255},[234,11748,2964],{"class":383},[234,11750,2967],{"class":247},[12,11752,11753],{},"Cuatro horas se vuelven cuarenta minutos. El resto es el mismo trabajo de pensar qué alertas importan — y en esa parte nadie te libra.",[3351,11755,11756],{},"html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}",{"title":229,"searchDepth":244,"depth":244,"links":11758},[11759,11760,11761,11762,11763,11764,11765,11766,11767,11768,11769,11770,11771,11772,11773,11774,11775,11776],{"id":21,"depth":244,"text":22},{"id":8826,"depth":244,"text":8827},{"id":8886,"depth":244,"text":8887},{"id":8929,"depth":244,"text":8930},{"id":8986,"depth":244,"text":8987},{"id":9446,"depth":244,"text":9447},{"id":9770,"depth":244,"text":9771},{"id":9936,"depth":244,"text":9937},{"id":10414,"depth":244,"text":10415},{"id":10588,"depth":244,"text":10589},{"id":11197,"depth":244,"text":11198},{"id":11241,"depth":244,"text":11242},{"id":11344,"depth":244,"text":11345},{"id":11383,"depth":244,"text":11384},{"id":11443,"depth":244,"text":11444},{"id":11581,"depth":244,"text":11582},{"id":3225,"depth":244,"text":3226},{"id":11687,"depth":244,"text":11688},"2026-05-12","Tutorial honesto para subir métricas, logs y dashboards de tu cluster — en 4 horas, sin Datadog. Stack open-source que cabe en 1 VPS de R$80\u002Fmes.",{},"\u002Fes\u002Fblog\u002Fstack-monitoring-prometheus-grafana-loki",{"title":8779,"description":11778},{"loc":11780},"es\u002Fblog\u002Fstack-monitoring-prometheus-grafana-loki",[11785,11786,11787,11788,3393,3379],"prometheus","grafana","loki","monitoring","324n4tX2U_bO1GLljCpReEqIs6qn0hm9GJVO3FvHiGM",{"id":11791,"title":11792,"author":7,"body":11793,"category":3379,"cover":3380,"date":12743,"description":12744,"draft":3383,"extension":3384,"lastReviewed":3380,"meta":12745,"navigation":411,"path":12746,"readingTime":4402,"seo":12747,"sitemap":12748,"stem":12749,"tags":12750,"__hash__":12755},"blog_es\u002Fes\u002Fblog\u002Fcloudflare-frente-a-cluster-auto-hospedado.md","CloudFlare frente al cluster auto-hospedado: ¿vale la pena en 2026?",{"type":9,"value":11794,"toc":12721},[11795,11798,11802,11805,11808,11811,11815,11818,11874,11877,11881,11884,12024,12027,12031,12034,12040,12046,12053,12056,12060,12063,12089,12096,12099,12119,12123,12126,12164,12167,12171,12174,12247,12266,12270,12273,12279,12285,12291,12297,12301,12304,12324,12327,12331,12534,12537,12541,12544,12564,12568,12571,12575,12581,12585,12588,12592,12595,12631,12634,12638,12641,12645,12648,12652,12659,12663,12666,12670,12673,12676,12692,12695,12698,12716,12719],[12,11796,11797],{},"La pregunta vuelve cada semana en un grupo de DevOps: \"subí mi cluster con tres servidores en DigitalOcean, ¿vale poner CloudFlare al frente?\". La respuesta corta es \"casi siempre sí\" — pero el \"casi\" carga trade-offs que nadie menciona hasta la primera vez que algo se rompe en producción y pasas dos horas depurando una regla de caché que enmascaró un 500 del app. Este post es la versión larga, con criterios mensurables, de la decisión que necesitas tomar antes de mover el nameserver.",[19,11799,11801],{"id":11800},"tldr-el-resumen-de-200-palabras","TL;DR — el resumen de 200 palabras",[12,11803,11804],{},"CloudFlare gratis se volvió estándar de hecho para cualquier site con tráfico: protege contra DDoS sin límite contractual, emite certificado SSL automático, cachea assets en más de 300 ciudades, esconde la IP de origen del servidor y aún entrega DNS con sub-10ms. Para cluster auto-hospedado — sea HeroCtl, Coolify, k3s o Docker Swarm — poner CloudFlare al frente es decisión fácil en cerca de 90% de los casos.",[12,11806,11807],{},"El 10% restante tiene trade-offs concretos: latencia adicional de 10 a 30ms en rutas dinámicas, TLS termina en CloudFlare por defecto (ya no es punta-a-punta hasta tu servidor), reglas de caché pueden enmascarar bugs sutiles en el app, y el lock-in crece conforme adoptas Workers, R2 y Pages.",[12,11809,11810],{},"Vale la pena cuando: quieres DDoS protection sin pagar; caché global para reducir coste de banda; esconder la IP del servidor de scanners. No vale cuando: cumplimiento financiero\u002Fsalud exige TLS verdaderamente punta-a-punta; necesitas p99 por debajo de 50ms en rutas dinámicas; el cluster ya tiene CDN edge interna en múltiples data centers. Cluster con router integrado ya cubre cerca de 60% de lo que CloudFlare ofrece — combinar los dos es el camino más común.",[19,11812,11814],{"id":11813},"que-ofrece-cloudflare-gratis-en-2026","¿Qué ofrece CloudFlare gratis en 2026?",[12,11816,11817],{},"La oferta gratuita aumentó de tamaño año a año. Hoy, el plan gratis cubre lo que era plan pagado de 2019:",[2735,11819,11820,11826,11832,11838,11844,11850,11856,11862,11868],{},[70,11821,11822,11825],{},[27,11823,11824],{},"DDoS protection sin límite contractual"," — Layer 3, 4 y 7. CloudFlare absorbe ataques de cientos de Gbps sin cobrar excedente.",[70,11827,11828,11831],{},[27,11829,11830],{},"Certificado SSL\u002FTLS automático"," — emitido en minutos por el propio CloudFlare, renovado automáticamente. Wildcard requiere plan Advanced Certificate Manager (US$ 10\u002Fmes).",[70,11833,11834,11837],{},[27,11835,11836],{},"CDN global"," — más de 300 ciudades en más de 120 países. Incluye São Paulo, Río, Fortaleza, Curitiba y Porto Alegre.",[70,11839,11840,11843],{},[27,11841,11842],{},"DNS authoritative"," — sub-10ms global medio, anycast, con APIs para automatización.",[70,11845,11846,11849],{},[27,11847,11848],{},"Bot protection básica"," — bloqueo de bots conocidos y desafíos JavaScript en tráfico sospechoso.",[70,11851,11852,11855],{},[27,11853,11854],{},"Caché de assets estáticos"," — extensiones reconocidas (CSS, JS, imágenes, fuentes) cacheadas por defecto.",[70,11857,11858,11861],{},[27,11859,11860],{},"Page Rules"," — tres reglas gratis para forzar HTTPS, caché extra, redirects.",[70,11863,11864,11867],{},[27,11865,11866],{},"Always Online"," — cuando el origen cae, CloudFlare sirve la última versión cacheada.",[70,11869,11870,11873],{},[27,11871,11872],{},"Web Analytics"," — métricas de RUM (visitas, países, navegadores), sin cookies.",[12,11875,11876],{},"La línea de corte es generosa lo suficiente para que un site de 10 mil visitantes\u002Fdía corra 100% en el gratis sin ningún problema operacional.",[19,11878,11880],{"id":11879},"y-que-cobra-cloudflare-extra","¿Y qué cobra CloudFlare extra?",[12,11882,11883],{},"Cuatro planes: Free, Pro (US$ 25\u002Fmes por dominio), Business (US$ 250\u002Fmes por dominio) y Enterprise (bajo consulta, en general por encima de US$ 5 mil\u002Fmes).",[119,11885,11886,11903],{},[122,11887,11888],{},[125,11889,11890,11893,11895,11898,11901],{},[128,11891,11892],{},"Recurso",[128,11894,7829],{},[128,11896,11897],{},"Pro US$ 25",[128,11899,11900],{},"Business US$ 250",[128,11902,4360],{},[141,11904,11905,11921,11935,11948,11962,11977,11990,12007],{},[125,11906,11907,11910,11912,11915,11918],{},[146,11908,11909],{},"WAF managed rulesets",[146,11911,3059],{},[146,11913,11914],{},"Sí (OWASP básico)",[146,11916,11917],{},"Sí (avanzado)",[146,11919,11920],{},"Custom",[125,11922,11923,11926,11928,11931,11933],{},[146,11924,11925],{},"Image Resizing",[146,11927,3059],{},[146,11929,11930],{},"Sí (US$ 5\u002FM)",[146,11932,3065],{},[146,11934,3065],{},[125,11936,11937,11940,11942,11944,11946],{},[146,11938,11939],{},"Polish (optimización de imagen)",[146,11941,3059],{},[146,11943,3065],{},[146,11945,3065],{},[146,11947,3065],{},[125,11949,11950,11953,11955,11958,11960],{},[146,11951,11952],{},"Argo Smart Routing",[146,11954,3059],{},[146,11956,11957],{},"US$ 5\u002Fmes add-on",[146,11959,3065],{},[146,11961,3065],{},[125,11963,11964,11967,11969,11972,11974],{},[146,11965,11966],{},"Page Rules incluidas",[146,11968,2699],{},[146,11970,11971],{},"20",[146,11973,5693],{},[146,11975,11976],{},"125+",[125,11978,11979,11982,11984,11986,11988],{},[146,11980,11981],{},"Cache Reserve",[146,11983,3059],{},[146,11985,3059],{},[146,11987,3065],{},[146,11989,3065],{},[125,11991,11992,11995,11998,12001,12004],{},[146,11993,11994],{},"Customer Support SLA",[146,11996,11997],{},"Best-effort",[146,11999,12000],{},"24h",[146,12002,12003],{},"Chat 24\u002F7",[146,12005,12006],{},"Ingeniero dedicado",[125,12008,12009,12012,12015,12018,12021],{},[146,12010,12011],{},"Análisis de logs",[146,12013,12014],{},"Última hora",[146,12016,12017],{},"Últimas 24h",[146,12019,12020],{},"Últimos 7 días",[146,12022,12023],{},"30 días",[12,12025,12026],{},"Workers y R2 tienen tier gratuito independiente del plan: 100 mil peticiones\u002Fdía para Workers, 10 GB de almacenamiento y 1 millón de operaciones Class A\u002Fmes para R2. Para un site marketing modesto, da para correr storage de imágenes en el R2 sin nunca llegar a la factura.",[19,12028,12030],{"id":12029},"cloudflare-anade-latencia","¿CloudFlare añade latencia?",[12,12032,12033],{},"La pregunta honesta. Respuesta también honesta: depende de la ruta.",[12,12035,12036,12039],{},[27,12037,12038],{},"Para rutas con caché"," (HTML estático, assets, imágenes optimizadas), CloudFlare reduce latencia. El usuario en Recife toma el contenido del POP de Fortaleza o São Paulo en 15 a 40ms, en vez de hacer round-trip hasta tu servidor en New Jersey o Frankfurt. Ahorro típico: 150 a 250ms por request.",[12,12041,12042,12045],{},[27,12043,12044],{},"Para rutas dinámicas"," (API, dashboard logado, checkout), el tráfico pasa por el proxy de CloudFlare antes de llegar a tu servidor. Eso añade entre 10 y 30ms en condiciones normales. El número exacto depende de cuál POP el usuario está conectado y dónde está el servidor de origen.",[12,12047,12048,12049,12052],{},"Medimos en el cluster público en producción: la media de tiempo de respuesta de ",[231,12050,12051],{},"manage.heroctl.com\u002Fv1\u002Fnodes"," es de 38ms sin proxy CloudFlare y 51ms con proxy activado, requiriendo del mismo notebook en São Paulo. Un delta de 13ms — perceptible en benchmark, invisible para humano.",[12,12054,12055],{},"La latencia solo es dealbreaker en tres escenarios reales: juegos online, subasta financiera de alta frecuencia, y cargas WebSocket de baja latencia (trading, colaboración en vivo). Para el resto, los 13ms desaparecen en el tiempo de render del navegador.",[19,12057,12059],{"id":12058},"cloudflare-rompe-tls-punta-a-punta","¿CloudFlare rompe TLS punta-a-punta?",[12,12061,12062],{},"Por defecto, sí. Mira los modos:",[2735,12064,12065,12071,12077,12083],{},[70,12066,12067,12070],{},[27,12068,12069],{},"Flexible"," (NUNCA uses esto) — TLS solo entre cliente y CloudFlare. Conexión CloudFlare → servidor es HTTP puro. Vulnerable a interceptación en la pierna interna.",[70,12072,12073,12076],{},[27,12074,12075],{},"Full"," — TLS entre cliente y CloudFlare, y separadamente entre CloudFlare y servidor. Pero CloudFlare acepta certificado inválido\u002Fauto-firmado en el servidor. Riesgo de man-in-the-middle entre CloudFlare y el origen.",[70,12078,12079,12082],{},[27,12080,12081],{},"Full (strict)"," — TLS en ambas piernas, y CloudFlare exige certificado válido en el origen. Esta es la configuración mínima razonable.",[70,12084,12085,12088],{},[27,12086,12087],{},"Strict (SSL-Only Origin Pull)"," — CloudFlare verifica que el certificado del origen fue emitido por una CA pública y válida para el hostname. Más seguro que Full strict.",[12,12090,12091,12092,12095],{},"En todos esos modos, ",[27,12093,12094],{},"CloudFlare descifra el tráfico en el medio del camino",". Ven cuerpo de request, headers, cookies — todo. Para la mayoría de los casos eso es aceptable (el contrato con CloudFlare es claro), pero en cumplimiento estricto (salud, financiero, gobierno) puede romper requisito de auditoría.",[12,12097,12098],{},"La salida real para punta-a-punta:",[2735,12100,12101,12107,12113],{},[70,12102,12103,12106],{},[27,12104,12105],{},"Authenticated Origin Pulls"," — CloudFlare presenta un certificado cliente cuando conecta a tu origen; el servidor solo acepta conexiones de esa cadena. Sigue descifrando en el medio, pero al menos solo CloudFlare consigue llegar a tu origen.",[70,12108,12109,12112],{},[27,12110,12111],{},"CloudFlare Tunnel + cliente mTLS en la punta"," — para apps internas, Tunnel sustituye IP público y exige certificado de cliente.",[70,12114,12115,12118],{},[27,12116,12117],{},"Gray cloud (DNS only)"," — desactiva el proxy. Pierdes DDoS protection, caché, WAF — pero ganas conexión directa cliente-servidor con TLS verdaderamente punta-a-punta. Es una opción válida cuando cumplimiento manda.",[19,12120,12122],{"id":12121},"voy-a-quedar-lock-in-con-cloudflare","¿Voy a quedar lock-in con CloudFlare?",[12,12124,12125],{},"Depende exclusivamente de qué features adoptas. Vamos por capa:",[2735,12127,12128,12134,12140,12146,12152,12158],{},[70,12129,12130,12133],{},[27,12131,12132],{},"DNS"," — trivialmente reversible. Mover nameserver toma 24 a 48h de propagación y nada se rompe. Lock-in cero.",[70,12135,12136,12139],{},[27,12137,12138],{},"Proxy + caché + WAF"," — reversible en horas. Desactivas el orange cloud, ajustas DNS para apuntar directo al servidor, reconfiguras WAF en tu origen (si la hay). Lock-in bajo.",[70,12141,12142,12145],{},[27,12143,12144],{},"Workers"," — lock-in real. La API de Workers es propietaria; reescribir para Lambda@Edge o Fastly Compute@Edge cuesta de días a semanas dependiendo del código. No es el peor caso, pero cuenta con re-trabajo.",[70,12147,12148,12151],{},[27,12149,12150],{},"R2 Object Storage"," — API compatible con S3, así que código sigue funcionando. Pero R2 no cobra egress (S3 cobra US$ 0,09\u002FGB), entonces mover a otro proveedor encarece la cuenta. Lock-in económico, no técnico.",[70,12153,12154,12157],{},[27,12155,12156],{},"Pages"," — lock-in moderado. Build process es custom; rewrite para Vercel\u002FNetlify\u002Fstatic host genérico toma una tarde, pero exige.",[70,12159,12160,12163],{},[27,12161,12162],{},"Zero Trust"," — lock-in alto. Políticas, identidad, túneles: rewrite completo para Tailscale\u002FTwingate\u002Fequivalente.",[12,12165,12166],{},"La recomendación operacional es: usa el core CloudFlare (DNS + proxy + WAF + Page Rules) sin titubear — puedes revertir en un día. Adopta Workers\u002FR2\u002FPages solo con consciencia clara de que estás aceptando lock-in proporcional al valor que aquella feature entrega.",[19,12168,12170],{"id":12169},"configuracion-minima-recomendada-para-cluster-auto-hospedado","Configuración mínima recomendada para cluster auto-hospedado",[12,12172,12173],{},"Secuencia práctica, sin secreto:",[67,12175,12176,12182,12191,12201,12207,12213,12219,12229,12235,12241],{},[70,12177,12178,12181],{},[27,12179,12180],{},"Crea cuenta en CloudFlare"," y añade el dominio. El site va a escanear tus DNS records actuales y copiarlos a la nueva zona.",[70,12183,12184,12187,12188,101],{},[27,12185,12186],{},"Cambia los nameservers"," en el registrar (Hostinger, Registro.br, GoDaddy, donde quiera que esté). Espera de 4 a 48 horas por la propagación. Verifica con ",[231,12189,12190],{},"dig NS heroctl.com +short",[70,12192,12193,12196,12197,12200],{},[27,12194,12195],{},"DNS records del cluster",": crea un registro A para el dominio raíz apuntando al IP del servidor que recibe tráfico, y un registro A wildcard ",[231,12198,12199],{},"*"," apuntando al mismo IP. Marca ambos con proxy activado (orange cloud).",[70,12202,12203,12206],{},[27,12204,12205],{},"SSL\u002FTLS mode",": configura Full (strict). Eso exige que el cluster tenga un certificado válido. El router integrado de HeroCtl emite Let's Encrypt automáticamente — funciona out-of-the-box.",[70,12208,12209,12212],{},[27,12210,12211],{},"Always Use HTTPS",": ON. Redirige cualquier HTTP a HTTPS en el borde.",[70,12214,12215,12218],{},[27,12216,12217],{},"HSTS",": 6 meses, include subdomains, sin preload por ahora. Preload es decisión definitiva — no se puede deshacer rápido si algo se rompe.",[70,12220,12221,12224,12225,12228],{},[27,12222,12223],{},"Page Rule de caché"," para assets estáticos: ",[231,12226,12227],{},"*heroctl.com\u002Fstatic\u002F*"," → Cache Level: Cache Everything, Edge Cache TTL: 1 mes.",[70,12230,12231,12234],{},[27,12232,12233],{},"WAF managed ruleset"," (Pro+): activa el CloudFlare Managed Ruleset y el OWASP Core Rule Set en modo Block para reglas de score alto.",[70,12236,12237,12240],{},[27,12238,12239],{},"Security Level",": Medium. Low deja pasar bot demasiado; High desafía gente legítima.",[70,12242,12243,12246],{},[27,12244,12245],{},"Bot Fight Mode",": ON en el plan gratis. Controla scrapers básicos sin pedir CAPTCHA al humano.",[12,12248,12249,12250,12253,12254,12257,12258,12261,12262,12265],{},"Después de aplicar todo eso, corre ",[231,12251,12252],{},"curl -I https:\u002F\u002Ftudominio.com"," y confirma: header ",[231,12255,12256],{},"cf-ray"," presente, header ",[231,12259,12260],{},"server: cloudflare",", header ",[231,12263,12264],{},"strict-transport-security"," con max-age largo.",[19,12267,12269],{"id":12268},"cuando-no-vale-cloudflare","¿Cuándo NO vale CloudFlare?",[12,12271,12272],{},"Cuatro escenarios donde la recomendación cambia. Importan más de lo que parecen.",[12,12274,12275,12278],{},[27,12276,12277],{},"Cluster con CDN\u002Fedge interna robusta."," Si ya corres en cuatro o cinco regiones geográficamente esparcidas, con balanceo DNS por proximidad y caché local en cada región, la CDN de CloudFlare añade latencia sin ganancia. Vale correr gray cloud (solo DNS) y mantener el resto directo.",[12,12280,12281,12284],{},[27,12282,12283],{},"Cumplimiento financiero o de salud con mTLS punta-a-punta obligatorio."," La LGPD por sí sola no exige eso; pero auditorías específicas (PCI-DSS Level 1 con requisitos custom, certificaciones HIPAA estrictas, frameworks bancarios) pueden exigir que tráfico cifrado nunca sea descifrado en tercero. Como CloudFlare descifra en el medio del camino, no pasa.",[12,12286,12287,12290],{},[27,12288,12289],{},"Apps puramente internas (intranet\u002FSaaS B2B cerrado)."," CloudFlare Free no cubre Zero Trust avanzado. Para app que sirve exclusivamente a empleados, Tailscale o WireGuard nativo entregan más con menos.",[12,12292,12293,12296],{},[27,12294,12295],{},"Sites pequeños sin tráfico y sin enemigo público."," Blog personal de 200 visitas\u002Fmes, sin formulario de pago, sin datos sensibles. DNS directo en Hostinger\u002FRegistro.br + Let's Encrypt del router integrado sirve perfectamente. Añadir CloudFlare es ceremonia innecesaria.",[19,12298,12300],{"id":12299},"como-cloudflare-interactua-con-cluster-de-alta-disponibilidad","¿Cómo CloudFlare interactúa con cluster de alta disponibilidad?",[12,12302,12303],{},"Aquí el diseño importa. Cluster con tres o más nodos sirve tráfico en todos ellos — no tiene nodo \"principal\" único. La configuración pragmática es:",[2735,12305,12306,12312,12318],{},[70,12307,12308,12311],{},[27,12309,12310],{},"DNS round-robin con salud",": registra A records para el IP de todos los nodos que corren el router. CloudFlare hace health check (Pro+) y remueve nodo roto de la rotación automáticamente.",[70,12313,12314,12317],{},[27,12315,12316],{},"Failover de CloudFlare",": ~30 segundos para detectar nodo muerto y sacarlo de la rotación (configurable hasta 5 segundos en el Enterprise).",[70,12319,12320,12323],{},[27,12321,12322],{},"Failover interno del cluster",": el router integrado de HeroCtl re-enruta tráfico entre nodos saludables en cerca de 5 segundos. Elección de nuevo coordinador ocurre en ~7 segundos cuando el nodo-líder cae.",[12,12325,12326],{},"Combinado, downtime real percibido por el usuario queda por debajo de 40 segundos en el peor caso (CloudFlare detecta + cluster reacciona). Sin CloudFlare, queda en ~7 segundos (cluster solo). Con CloudFlare y configuración de monitorización agresiva (Pro+), vuelve a ~10 segundos. La elección es clara: si no necesitas DDoS protection, el cluster solo ya es más rápido. Si necesitas, CloudFlare añade 30s de detección a cambio de protección contra ataque.",[19,12328,12330],{"id":12329},"tabla-comparativa-12-criterios-de-decision","Tabla comparativa: 12 criterios de decisión",[119,12332,12333,12351],{},[122,12334,12335],{},[125,12336,12337,12339,12342,12345,12348],{},[128,12338,2983],{},[128,12340,12341],{},"Sin CloudFlare",[128,12343,12344],{},"CF Free",[128,12346,12347],{},"CF Pro US$ 25",[128,12349,12350],{},"CF Business US$ 250",[141,12352,12353,12369,12386,12403,12419,12432,12447,12462,12477,12489,12501,12517],{},[125,12354,12355,12358,12361,12364,12366],{},[146,12356,12357],{},"DDoS Layer 3\u002F4",[146,12359,12360],{},"Te las arreglas",[146,12362,12363],{},"Ilimitado",[146,12365,12363],{},[146,12367,12368],{},"Ilimitado + SLA",[125,12370,12371,12374,12377,12380,12383],{},[146,12372,12373],{},"DDoS Layer 7",[146,12375,12376],{},"No tiene",[146,12378,12379],{},"Básico",[146,12381,12382],{},"Avanzado",[146,12384,12385],{},"Avanzado + Custom Rules",[125,12387,12388,12391,12394,12397,12400],{},[146,12389,12390],{},"Latencia adicional en rutas dinámicas",[146,12392,12393],{},"0ms",[146,12395,12396],{},"+13 a 30ms",[146,12398,12399],{},"+10 a 25ms (Argo opcional)",[146,12401,12402],{},"+5 a 15ms (Argo incluido)",[125,12404,12405,12408,12411,12414,12416],{},[146,12406,12407],{},"Caché global de estáticos",[146,12409,12410],{},"Lo montas",[146,12412,12413],{},"300+ ciudades",[146,12415,12413],{},[146,12417,12418],{},"300+ ciudades + Reserve",[125,12420,12421,12424,12426,12428,12430],{},[146,12422,12423],{},"Esconde IP del servidor",[146,12425,3059],{},[146,12427,3065],{},[146,12429,3065],{},[146,12431,3065],{},[125,12433,12434,12437,12439,12442,12444],{},[146,12435,12436],{},"TLS punta-a-punta verdadero",[146,12438,3065],{},[146,12440,12441],{},"No (descifra)",[146,12443,3059],{},[146,12445,12446],{},"No (pero Origin Pulls)",[125,12448,12449,12452,12454,12456,12459],{},[146,12450,12451],{},"WAF managed",[146,12453,12376],{},[146,12455,3059],{},[146,12457,12458],{},"OWASP básico",[146,12460,12461],{},"OWASP avanzado",[125,12463,12464,12467,12469,12471,12474],{},[146,12465,12466],{},"Bot protection",[146,12468,12410],{},[146,12470,12245],{},[146,12472,12473],{},"Super Bot Fight",[146,12475,12476],{},"Bot Management ML",[125,12478,12479,12481,12483,12485,12487],{},[146,12480,11860],{},[146,12482,3056],{},[146,12484,2699],{},[146,12486,11971],{},[146,12488,5693],{},[125,12490,12491,12493,12495,12497,12499],{},[146,12492,11866],{},[146,12494,3059],{},[146,12496,3065],{},[146,12498,3065],{},[146,12500,3065],{},[125,12502,12503,12506,12509,12511,12514],{},[146,12504,12505],{},"Coste mensual por dominio",[146,12507,12508],{},"US$ 0",[146,12510,12508],{},[146,12512,12513],{},"US$ 25",[146,12515,12516],{},"US$ 250",[125,12518,12519,12522,12525,12528,12531],{},[146,12520,12521],{},"Lock-in proporcional",[146,12523,12524],{},"Cero",[146,12526,12527],{},"Bajo (DNS+proxy)",[146,12529,12530],{},"Bajo a medio",[146,12532,12533],{},"Medio (Workers\u002FR2 empiezan a entrar)",[12,12535,12536],{},"La línea que decide para la mayoría es \"DDoS Layer 7 + esconde IP\". Esos dos solos justifican el plan gratis. Las líneas pagadas solo tienen sentido con tráfico voluminoso o requisito formal de WAF.",[19,12538,12540],{"id":12539},"cloudflare-gratis-tiene-limite-de-trafico","¿CloudFlare gratis tiene límite de tráfico?",[12,12542,12543],{},"No hay límite contractual de banda en el plan gratis para tráfico web normal a través del proxy. Pero existen tres límites prácticos que vale mencionar:",[2735,12545,12546,12552,12558],{},[70,12547,12548,12551],{},[27,12549,12550],{},"Section 2.8 de los Terms of Service",": el plan gratis es para sites cuyo contenido principal es HTML, y CloudFlare se reserva el derecho de pedir upgrade si usas el servicio primariamente para servir vídeo o archivos grandes. En la práctica, raramente accionan — pero si te vuelves un host de vídeos pirateados de 50TB\u002Fmes, espera recibir email.",[70,12553,12554,12557],{},[27,12555,12556],{},"Workers gratis",": 100 mil peticiones\u002Fdía. Por encima de eso, Workers Paid (US$ 5\u002Fmes) con 10M peticiones incluidas.",[70,12559,12560,12563],{},[27,12561,12562],{},"R2 gratis",": 10GB de almacenamiento, 1M Class A operations\u002Fmes, 10M Class B operations\u002Fmes. Por encima, US$ 0,015\u002FGB-mes.",[19,12565,12567],{"id":12566},"puedo-usar-cloudflare-dns-sin-el-proxy","¿Puedo usar CloudFlare DNS sin el proxy?",[12,12569,12570],{},"Sí — modo \"DNS only\" (gray cloud). Usas el DNS de CloudFlare (rápido, free, anycast global) pero tráfico va directo a tu servidor sin pasar por el proxy. Pierdes DDoS, caché, WAF, IP hiding — mantienes solo la infraestructura DNS. Útil cuando: cumplimiento prohíbe descifrado en tercero; solo quieres DNS rápido sin tocar el path del tráfico; estás probando antes de activar el proxy.",[19,12572,12574],{"id":12573},"waf-gratis-bloquea-sql-injection","¿WAF gratis bloquea SQL injection?",[12,12576,12577,12578,12580],{},"CloudFlare Free tiene ",[27,12579,12245],{}," y reglas automáticas de mitigación para patrones obvios, pero no tiene el Managed Ruleset OWASP completo. Para bloqueo confiable de SQL injection, XSS, RCE patterns conocidos, necesitas el plan Pro o superior. Alternativa: correr ModSecurity o WAF propio en tu origen — funciona, pero añade CPU y configuración.",[19,12582,12584],{"id":12583},"cloudflare-tiene-datacenter-en-brasil","¿CloudFlare tiene datacenter en Brasil?",[12,12586,12587],{},"Sí. En 2026 son cinco POPs brasileños: São Paulo (dos POPs), Río de Janeiro, Fortaleza, Curitiba y Porto Alegre. Latencia típica de cualquier ciudad del Sudeste a un POP queda por debajo de 20ms. El POP de Fortaleza atiende muy bien el Nordeste por causa de los cables submarinos que aterrizan ahí (EllaLink, Monet, GlobeNet). Para el Norte, aún es camino más largo — Manaus llega a Fortaleza en 80 a 120ms.",[19,12589,12591],{"id":12590},"como-migrar-nameservers-de-hostinger-a-cloudflare","¿Cómo migrar nameservers de Hostinger a CloudFlare?",[12,12593,12594],{},"Cuatro pasos. Tarda menos de una hora activa, más hasta 48h de propagación:",[67,12596,12597,12603,12615,12621],{},[70,12598,12599,12602],{},[27,12600,12601],{},"CloudFlare",": añade el dominio. El wizard escanea tus DNS actuales y crea los records correspondientes en la nueva zona. Confirma que copió todo — MX, TXT (SPF\u002FDKIM\u002FDMARC), CNAME, A. Errores de copia aquí causan email derribado por una semana.",[70,12604,12605,12607,12608,2403,12611,12614],{},[27,12606,12601],{},": te da dos nameservers (algo como ",[231,12609,12610],{},"kim.ns.cloudflare.com",[231,12612,12613],{},"walt.ns.cloudflare.com","). Anota.",[70,12616,12617,12620],{},[27,12618,12619],{},"Hostinger",": panel → Dominios → tu dominio → Nameservers → \"Use custom nameservers\" → pega los dos de CloudFlare. Guarda.",[70,12622,12623,12626,12627,12630],{},[27,12624,12625],{},"Aguarda propagación",". Verifica con ",[231,12628,12629],{},"dig NS tudominio.com +short",". Cuando aparezcan los nameservers de CloudFlare, el dominio está bajo gestión de ellos. Records DNS siguen siendo editados en el panel de CloudFlare de ahí en adelante.",[12,12632,12633],{},"Importante: mientras la propagación ocurre, parte de los usuarios aún resuelve vía Hostinger. No apagues la zona vieja hasta confirmar que 100% de los resolvers ya cambiaron (24 a 48 horas es seguro).",[19,12635,12637],{"id":12636},"tls-termina-donde-e2e-se-rompe","¿TLS termina dónde? ¿E2E se rompe?",[12,12639,12640],{},"En modo proxy (orange cloud), TLS termina en CloudFlare. Re-establecen otra conexión TLS a tu servidor (en modo Full strict). Técnicamente: descifra, procesa, re-cifra. Para punta-a-punta verdadero: gray cloud (DNS only) o CloudFlare Tunnel con configuración custom. Para la mayoría de las aplicaciones, \"TLS verdaderamente punta-a-punta\" es menos importante de lo que parece — el ataque que eso protege (interceptación en el medio de la red) requiere atacante ya dentro de la red de CloudFlare, escenario poco realista.",[19,12642,12644],{"id":12643},"cloudflare-workers-vs-serverless-de-mi-cloud-cuando-vale","¿CloudFlare Workers vs serverless de mi cloud — cuándo vale?",[12,12646,12647],{},"Workers son buenos para: edge computing donde latencia \u003C50ms importa (geo-routing, A\u002FB testing, rewrite de header); transformación liviana de request\u002Fresponse; auth en el borde (validar JWT antes de llegar al origen). No son buenos para: workload con más de 30 segundos de runtime; integración heavy con bases relacionales (latencia de cold start de DB driver mata); código que necesita bibliotecas que dependen de filesystem o proceso. Lambda de AWS sigue mejor para workload de runtime largo; Workers ganan en el borde. Usa ambos, no sustituyas uno por el otro.",[19,12649,12651],{"id":12650},"puedo-usar-cloudflare-r2-con-cluster-auto-hospedado","¿Puedo usar CloudFlare R2 con cluster auto-hospedado?",[12,12653,12654,12655,12658],{},"Sí — R2 es S3-compatible en la API. Tu app usa ",[231,12656,12657],{},"aws-sdk"," configurado con endpoint de R2 y credenciales R2; código sigue igual. Ventaja económica: cero egress fee. Puedes servir descargas pesadas (instaladores, vídeos de producto, backups) directo del R2 sin pagar por banda saliendo. Desventaja: durabilidad documentada es 99.999999999% (11 nueves), misma del S3, pero histórico operacional del R2 es más corto. Para hot path crítico, algunos equipos prefieren mantener S3 y usar R2 solo para cold storage y static delivery.",[19,12660,12662],{"id":12661},"origen-cayo-always-online-resuelve","¿Origen cayó — Always Online resuelve?",[12,12664,12665],{},"En parte. Always Online sirve la última versión cacheada de páginas HTML cuando el servidor está fuera. Pero: solo funciona para rutas que estaban siendo cacheadas; solo sirve la versión estática (sin datos dinámicos actualizados); solo dura mientras CloudFlare mantiene el snapshot (generalmente algunos días). Es una red de seguridad buena para blog estático y marketing. No sustituye alta disponibilidad real del cluster — para app dinámico, lo que resuelve es el cluster tener tres nodos y elección automática cuando uno cae.",[19,12667,12669],{"id":12668},"cerrando-combinando-cloudflare-con-cluster-auto-hospedado","Cerrando — combinando CloudFlare con cluster auto-hospedado",[12,12671,12672],{},"La combinación que recomendamos para 90% de los casos es: cluster auto-hospedado con tres o más nodos (alta disponibilidad real) + CloudFlare Free en el borde (DDoS, caché, IP hiding). El cluster cuida del enrutamiento interno, certificados automáticos, failover entre nodos en segundos. CloudFlare cuida de protección pública, caché global y ofuscación de IP. Las dos capas se complementan — no compiten.",[12,12674,12675],{},"Para empezar desde cero con esa combinación:",[224,12677,12678],{"className":226,"code":5318,"language":228,"meta":229,"style":229},[231,12679,12680],{"__ignoreMap":229},[234,12681,12682,12684,12686,12688,12690],{"class":236,"line":237},[234,12683,1220],{"class":247},[234,12685,2958],{"class":251},[234,12687,5329],{"class":255},[234,12689,2964],{"class":383},[234,12691,2967],{"class":247},[12,12693,12694],{},"Te quedas con cluster funcional en tres nodos, certificado Let's Encrypt automático en el dominio que elijas, panel web para enviar jobs, alta disponibilidad real. Después, añades CloudFlare Free al frente del dominio y configuras conforme la sección \"Configuración mínima\" de este post. Total de tiempo: una tarde.",[12,12696,12697],{},"Más lectura en esta línea:",[2735,12699,12700,12710],{},[70,12701,12702,12705,12706,12709],{},[3337,12703,12704],{"href":3344},"Deploy Docker en producción: del compose al cluster"," — cómo salir del ",[231,12707,12708],{},"docker compose up"," y llegar a alta disponibilidad real, con los pasos intermedios.",[70,12711,12712,12715],{},[3337,12713,12714],{"href":11725},"Observabilidad sin DataDog: stack para startup"," — métricas, logs y tracing sin pagar US$ 2.000\u002Fmes de SaaS de observabilidad.",[12,12717,12718],{},"CloudFlare es una de las pocas herramientas donde el tier gratis es tan bueno que rechazarlo es terquedad. Pero como toda elección de infra, la parte difícil es entender exactamente dónde está la frontera — y, principalmente, dónde pasa por dentro del tráfico cifrado de tu aplicación.",[3351,12720,4377],{},{"title":229,"searchDepth":244,"depth":244,"links":12722},[12723,12724,12725,12726,12727,12728,12729,12730,12731,12732,12733,12734,12735,12736,12737,12738,12739,12740,12741,12742],{"id":11800,"depth":244,"text":11801},{"id":11813,"depth":244,"text":11814},{"id":11879,"depth":244,"text":11880},{"id":12029,"depth":244,"text":12030},{"id":12058,"depth":244,"text":12059},{"id":12121,"depth":244,"text":12122},{"id":12169,"depth":244,"text":12170},{"id":12268,"depth":244,"text":12269},{"id":12299,"depth":244,"text":12300},{"id":12329,"depth":244,"text":12330},{"id":12539,"depth":244,"text":12540},{"id":12566,"depth":244,"text":12567},{"id":12573,"depth":244,"text":12574},{"id":12583,"depth":244,"text":12584},{"id":12590,"depth":244,"text":12591},{"id":12636,"depth":244,"text":12637},{"id":12643,"depth":244,"text":12644},{"id":12650,"depth":244,"text":12651},{"id":12661,"depth":244,"text":12662},{"id":12668,"depth":244,"text":12669},"2026-05-08","CloudFlare gratis bloquea DDoS, cachea estático y esconde la IP del servidor. Pero añade latencia, lock-in y features que tal vez no uses. Cuándo vale y cuándo es overkill.",{},"\u002Fes\u002Fblog\u002Fcloudflare-frente-a-cluster-auto-hospedado",{"title":11792,"description":12744},{"loc":12746},"es\u002Fblog\u002Fcloudflare-frente-a-cluster-auto-hospedado",[12751,12752,12753,12754,3379],"cloudflare","cdn","ddos","performance","anNMR0LCPmSxGr2HA6KuK6H9TYKnYL7Z73S9z0DPL44",1777362182052]