[{"data":1,"prerenderedAt":1391},["ShallowReactive",2],{"doc-es-\u002Fes\u002Fdocs\u002Fdeploy\u002Fprimer-deploy":3,"docs-es-all":1323},{"id":4,"title":5,"body":6,"category":1306,"description":1307,"draft":1308,"extension":1309,"icon":1310,"lastReviewed":1311,"meta":1312,"navigation":149,"order":119,"path":1313,"prerequisites":1314,"readingTime":1316,"seo":1317,"stem":1318,"tags":1319,"__hash__":1322},"docs_es\u002Fes\u002Fdocs\u002Fdeploy\u002Fprimer-deploy.md","Deploy de la primera app",{"type":7,"value":8,"toc":1294},"minimark",[9,13,18,21,92,95,99,106,578,581,609,613,616,638,641,649,652,656,659,680,686,689,743,747,750,776,783,802,810,814,817,820,837,840,985,997,1001,1004,1007,1075,1082,1131,1134,1138,1141,1159,1173,1180,1183,1227,1231,1234,1256,1259,1270,1274,1290],[10,11,12],"p",{},"Un deploy en HeroCtl es un archivo YAML enviado al cluster. El cluster decide dónde correr, cuándo actualizar y cómo reaccionar si algo se rompe. Tú solo describes el deseo.",[14,15,17],"h2",{"id":16},"anatomia-del-job-spec","Anatomía del job spec",[10,19,20],{},"Un job define un servicio completo: imagen, réplicas, recursos, ingress, secretos. Todo en un archivo solo. Cinco bloques importan:",[22,23,24,37],"table",{},[25,26,27],"thead",{},[28,29,30,34],"tr",{},[31,32,33],"th",{},"Bloque",[31,35,36],{},"Función",[38,39,40,52,62,72,82],"tbody",{},[28,41,42,49],{},[43,44,45],"td",{},[46,47,48],"code",{},"meta",[43,50,51],{},"nombre, versión, tags",[28,53,54,59],{},[43,55,56],{},[46,57,58],{},"task",[43,60,61],{},"imagen, comando, env, recursos",[28,63,64,69],{},[43,65,66],{},[46,67,68],{},"count",[43,70,71],{},"cuántas réplicas",[28,73,74,79],{},[43,75,76],{},[46,77,78],{},"health",[43,80,81],{},"cómo saber si está vivo",[28,83,84,89],{},[43,85,86],{},[46,87,88],{},"ingress",[43,90,91],{},"dominio público y TLS",[10,93,94],{},"50 líneas cubren el 90% de los casos.",[14,96,98],{"id":97},"ejemplo-completo-web-nodejs-postgres","Ejemplo completo: web Node.js + Postgres",[10,100,101,102,105],{},"Vamos a levantar una API Node.js con 2 réplicas y un Postgres adyacente. Crea el archivo ",[46,103,104],{},"app.yaml",":",[107,108,113],"pre",{"className":109,"code":110,"language":111,"meta":112,"style":112},"language-yaml shiki shiki-themes github-dark-default","job: api-vendas\nversion: 1\n\ntasks:\n  - name: postgres\n    image: postgres:16-alpine\n    count: 1\n    resources:\n      cpu: 500\n      memory: 512\n    env:\n      POSTGRES_DB: vendas\n      POSTGRES_USER: app\n    secrets:\n      POSTGRES_PASSWORD: db-password\n    volumes:\n      - name: pgdata\n        path: \u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n        size: 10Gi\n    health:\n      tcp: 5432\n      interval: 10s\n      timeout: 3s\n\n  - name: web\n    image: minhaempresa\u002Fapi-vendas:1.4.2\n    count: 2\n    resources:\n      cpu: 250\n      memory: 256\n    env:\n      DATABASE_URL: postgres:\u002F\u002Fapp@postgres.local:5432\u002Fvendas\n      NODE_ENV: production\n    secrets:\n      DATABASE_PASSWORD: db-password\n      JWT_SECRET: jwt-secret\n    health:\n      http: \u002Fhealthz\n      port: 3000\n      interval: 5s\n      healthy_after: 2\n      unhealthy_after: 3\n    ingress:\n      host: api.minhaempresa.com\n      port: 3000\n      tls: true\n","yaml","",[46,114,115,132,144,151,160,174,185,195,203,214,225,233,244,255,263,274,282,295,306,317,325,336,347,358,363,375,385,395,402,412,422,429,440,451,458,468,479,486,497,508,518,528,539,547,558,567],{"__ignoreMap":112},[116,117,120,124,128],"span",{"class":118,"line":119},"line",1,[116,121,123],{"class":122},"sPWt5","job",[116,125,127],{"class":126},"sZEs4",": ",[116,129,131],{"class":130},"s9uIt","api-vendas\n",[116,133,135,138,140],{"class":118,"line":134},2,[116,136,137],{"class":122},"version",[116,139,127],{"class":126},[116,141,143],{"class":142},"sFSAA","1\n",[116,145,147],{"class":118,"line":146},3,[116,148,150],{"emptyLinePlaceholder":149},true,"\n",[116,152,154,157],{"class":118,"line":153},4,[116,155,156],{"class":122},"tasks",[116,158,159],{"class":126},":\n",[116,161,163,166,169,171],{"class":118,"line":162},5,[116,164,165],{"class":126},"  - ",[116,167,168],{"class":122},"name",[116,170,127],{"class":126},[116,172,173],{"class":130},"postgres\n",[116,175,177,180,182],{"class":118,"line":176},6,[116,178,179],{"class":122},"    image",[116,181,127],{"class":126},[116,183,184],{"class":130},"postgres:16-alpine\n",[116,186,188,191,193],{"class":118,"line":187},7,[116,189,190],{"class":122},"    count",[116,192,127],{"class":126},[116,194,143],{"class":142},[116,196,198,201],{"class":118,"line":197},8,[116,199,200],{"class":122},"    resources",[116,202,159],{"class":126},[116,204,206,209,211],{"class":118,"line":205},9,[116,207,208],{"class":122},"      cpu",[116,210,127],{"class":126},[116,212,213],{"class":142},"500\n",[116,215,217,220,222],{"class":118,"line":216},10,[116,218,219],{"class":122},"      memory",[116,221,127],{"class":126},[116,223,224],{"class":142},"512\n",[116,226,228,231],{"class":118,"line":227},11,[116,229,230],{"class":122},"    env",[116,232,159],{"class":126},[116,234,236,239,241],{"class":118,"line":235},12,[116,237,238],{"class":122},"      POSTGRES_DB",[116,240,127],{"class":126},[116,242,243],{"class":130},"vendas\n",[116,245,247,250,252],{"class":118,"line":246},13,[116,248,249],{"class":122},"      POSTGRES_USER",[116,251,127],{"class":126},[116,253,254],{"class":130},"app\n",[116,256,258,261],{"class":118,"line":257},14,[116,259,260],{"class":122},"    secrets",[116,262,159],{"class":126},[116,264,266,269,271],{"class":118,"line":265},15,[116,267,268],{"class":122},"      POSTGRES_PASSWORD",[116,270,127],{"class":126},[116,272,273],{"class":130},"db-password\n",[116,275,277,280],{"class":118,"line":276},16,[116,278,279],{"class":122},"    volumes",[116,281,159],{"class":126},[116,283,285,288,290,292],{"class":118,"line":284},17,[116,286,287],{"class":126},"      - ",[116,289,168],{"class":122},[116,291,127],{"class":126},[116,293,294],{"class":130},"pgdata\n",[116,296,298,301,303],{"class":118,"line":297},18,[116,299,300],{"class":122},"        path",[116,302,127],{"class":126},[116,304,305],{"class":130},"\u002Fvar\u002Flib\u002Fpostgresql\u002Fdata\n",[116,307,309,312,314],{"class":118,"line":308},19,[116,310,311],{"class":122},"        size",[116,313,127],{"class":126},[116,315,316],{"class":130},"10Gi\n",[116,318,320,323],{"class":118,"line":319},20,[116,321,322],{"class":122},"    health",[116,324,159],{"class":126},[116,326,328,331,333],{"class":118,"line":327},21,[116,329,330],{"class":122},"      tcp",[116,332,127],{"class":126},[116,334,335],{"class":142},"5432\n",[116,337,339,342,344],{"class":118,"line":338},22,[116,340,341],{"class":122},"      interval",[116,343,127],{"class":126},[116,345,346],{"class":130},"10s\n",[116,348,350,353,355],{"class":118,"line":349},23,[116,351,352],{"class":122},"      timeout",[116,354,127],{"class":126},[116,356,357],{"class":130},"3s\n",[116,359,361],{"class":118,"line":360},24,[116,362,150],{"emptyLinePlaceholder":149},[116,364,366,368,370,372],{"class":118,"line":365},25,[116,367,165],{"class":126},[116,369,168],{"class":122},[116,371,127],{"class":126},[116,373,374],{"class":130},"web\n",[116,376,378,380,382],{"class":118,"line":377},26,[116,379,179],{"class":122},[116,381,127],{"class":126},[116,383,384],{"class":130},"minhaempresa\u002Fapi-vendas:1.4.2\n",[116,386,388,390,392],{"class":118,"line":387},27,[116,389,190],{"class":122},[116,391,127],{"class":126},[116,393,394],{"class":142},"2\n",[116,396,398,400],{"class":118,"line":397},28,[116,399,200],{"class":122},[116,401,159],{"class":126},[116,403,405,407,409],{"class":118,"line":404},29,[116,406,208],{"class":122},[116,408,127],{"class":126},[116,410,411],{"class":142},"250\n",[116,413,415,417,419],{"class":118,"line":414},30,[116,416,219],{"class":122},[116,418,127],{"class":126},[116,420,421],{"class":142},"256\n",[116,423,425,427],{"class":118,"line":424},31,[116,426,230],{"class":122},[116,428,159],{"class":126},[116,430,432,435,437],{"class":118,"line":431},32,[116,433,434],{"class":122},"      DATABASE_URL",[116,436,127],{"class":126},[116,438,439],{"class":130},"postgres:\u002F\u002Fapp@postgres.local:5432\u002Fvendas\n",[116,441,443,446,448],{"class":118,"line":442},33,[116,444,445],{"class":122},"      NODE_ENV",[116,447,127],{"class":126},[116,449,450],{"class":130},"production\n",[116,452,454,456],{"class":118,"line":453},34,[116,455,260],{"class":122},[116,457,159],{"class":126},[116,459,461,464,466],{"class":118,"line":460},35,[116,462,463],{"class":122},"      DATABASE_PASSWORD",[116,465,127],{"class":126},[116,467,273],{"class":130},[116,469,471,474,476],{"class":118,"line":470},36,[116,472,473],{"class":122},"      JWT_SECRET",[116,475,127],{"class":126},[116,477,478],{"class":130},"jwt-secret\n",[116,480,482,484],{"class":118,"line":481},37,[116,483,322],{"class":122},[116,485,159],{"class":126},[116,487,489,492,494],{"class":118,"line":488},38,[116,490,491],{"class":122},"      http",[116,493,127],{"class":126},[116,495,496],{"class":130},"\u002Fhealthz\n",[116,498,500,503,505],{"class":118,"line":499},39,[116,501,502],{"class":122},"      port",[116,504,127],{"class":126},[116,506,507],{"class":142},"3000\n",[116,509,511,513,515],{"class":118,"line":510},40,[116,512,341],{"class":122},[116,514,127],{"class":126},[116,516,517],{"class":130},"5s\n",[116,519,521,524,526],{"class":118,"line":520},41,[116,522,523],{"class":122},"      healthy_after",[116,525,127],{"class":126},[116,527,394],{"class":142},[116,529,531,534,536],{"class":118,"line":530},42,[116,532,533],{"class":122},"      unhealthy_after",[116,535,127],{"class":126},[116,537,538],{"class":142},"3\n",[116,540,542,545],{"class":118,"line":541},43,[116,543,544],{"class":122},"    ingress",[116,546,159],{"class":126},[116,548,550,553,555],{"class":118,"line":549},44,[116,551,552],{"class":122},"      host",[116,554,127],{"class":126},[116,556,557],{"class":130},"api.minhaempresa.com\n",[116,559,561,563,565],{"class":118,"line":560},45,[116,562,502],{"class":122},[116,564,127],{"class":126},[116,566,507],{"class":142},[116,568,570,573,575],{"class":118,"line":569},46,[116,571,572],{"class":122},"      tls",[116,574,127],{"class":126},[116,576,577],{"class":142},"true\n",[10,579,580],{},"Cincuenta líneas, una app entera con base persistente, secretos inyectados, health check y dominio con certificado.",[582,583,584],"blockquote",{},[10,585,586,590,591,594,595,598,599,602,603,608],{},[587,588,589],"strong",{},"Nota:"," los secretos referenciados (",[46,592,593],{},"db-password",", ",[46,596,597],{},"jwt-secret",") deben existir antes. Créalos con ",[46,600,601],{},"heroctl secret create db-password --value '...'",". Mira ",[604,605,607],"a",{"href":606},"\u002Fes\u002Fdocs\u002Foperaciones\u002Freferencia-cli","Referencia del CLI"," para todos los comandos.",[14,610,612],{"id":611},"submitir-el-job","Submitir el job",[10,614,615],{},"Con el archivo listo, un comando envía el deseo al cluster:",[107,617,621],{"className":618,"code":619,"language":620,"meta":112,"style":112},"language-bash shiki shiki-themes github-dark-default","heroctl job submit app.yaml\n","bash",[46,622,623],{"__ignoreMap":112},[116,624,625,629,632,635],{"class":118,"line":119},[116,626,628],{"class":627},"sQhOw","heroctl",[116,630,631],{"class":130}," job",[116,633,634],{"class":130}," submit",[116,636,637],{"class":130}," app.yaml\n",[10,639,640],{},"Salida:",[107,642,647],{"className":643,"code":645,"language":646},[644],"language-text","job:       api-vendas\nversion:   1 (new)\ntasks:     2 (postgres, web)\nplan:\n  + create alloc postgres-a1b2  on node-2\n  + create alloc web-c3d4       on node-1\n  + create alloc web-e5f6       on node-3\ndeploy:    rolling\nstatus:    accepted (id: dep-2026-04-26-001)\n","text",[46,648,645],{"__ignoreMap":112},[10,650,651],{},"El cluster planificó dónde correrá cada contenedor e inició la ejecución en segundo plano. El comando vuelve en 1–2 segundos. No espera a que la app suba.",[14,653,655],{"id":654},"acompanar-el-progreso","Acompañar el progreso",[10,657,658],{},"Para ver lo que está pasando de hecho:",[107,660,662],{"className":618,"code":661,"language":620,"meta":112,"style":112},"heroctl alloc list --job api-vendas\n",[46,663,664],{"__ignoreMap":112},[116,665,666,668,671,674,677],{"class":118,"line":119},[116,667,628],{"class":627},[116,669,670],{"class":130}," alloc",[116,672,673],{"class":130}," list",[116,675,676],{"class":142}," --job",[116,678,679],{"class":130}," api-vendas\n",[107,681,684],{"className":682,"code":683,"language":646},[644],"ALLOC          TASK      NODE     STATUS    HEALTH    AGE\npostgres-a1b2  postgres  node-2   running   healthy   12s\nweb-c3d4       web       node-1   running   starting  8s\nweb-e5f6       web       node-3   running   healthy   8s\n",[46,685,683],{"__ignoreMap":112},[10,687,688],{},"Los estados que importan:",[22,690,691,701],{},[25,692,693],{},[28,694,695,698],{},[31,696,697],{},"Status",[31,699,700],{},"Significa",[38,702,703,713,723,733],{},[28,704,705,710],{},[43,706,707],{},[46,708,709],{},"pending",[43,711,712],{},"esperando recursos en el nodo",[28,714,715,720],{},[43,716,717],{},[46,718,719],{},"running + starting",[43,721,722],{},"el contenedor subió, el health check aún no pasó",[28,724,725,730],{},[43,726,727],{},[46,728,729],{},"running + healthy",[43,731,732],{},"recibiendo tráfico",[28,734,735,740],{},[43,736,737],{},[46,738,739],{},"failed",[43,741,742],{},"crasheó. Mira logs.",[14,744,746],{"id":745},"logs-en-tiempo-real","Logs en tiempo real",[10,748,749],{},"Para ver la salida de la app mientras sube:",[107,751,753],{"className":618,"code":752,"language":620,"meta":112,"style":112},"heroctl logs -f --job api-vendas --task web\n",[46,754,755],{"__ignoreMap":112},[116,756,757,759,762,765,767,770,773],{"class":118,"line":119},[116,758,628],{"class":627},[116,760,761],{"class":130}," logs",[116,763,764],{"class":142}," -f",[116,766,676],{"class":142},[116,768,769],{"class":130}," api-vendas",[116,771,772],{"class":142}," --task",[116,774,775],{"class":130}," web\n",[10,777,778,779,782],{},"La flag ",[46,780,781],{},"-f"," sigue el stream. Sal con Ctrl+C. Para una alloc específica:",[107,784,786],{"className":618,"code":785,"language":620,"meta":112,"style":112},"heroctl logs -f --alloc web-c3d4\n",[46,787,788],{"__ignoreMap":112},[116,789,790,792,794,796,799],{"class":118,"line":119},[116,791,628],{"class":627},[116,793,761],{"class":130},[116,795,764],{"class":142},[116,797,798],{"class":142}," --alloc",[116,800,801],{"class":130}," web-c3d4\n",[10,803,804,805,809],{},"Los logs se almacenan por 7 días por defecto. Para historial largo, integra con un destino externo (mira ",[604,806,808],{"href":807},"\u002Fes\u002Fdocs\u002Fobservabilidad\u002Fmetricas-logs","Observabilidad",").",[14,811,813],{"id":812},"el-health-check-es-obligatorio","El health check es obligatorio",[10,815,816],{},"HeroCtl no hace deploy sin health check. No es restricción cosmética: sin él, no hay cómo distinguir contenedor subiendo de contenedor roto.",[10,818,819],{},"Tu aplicación debe exponer un endpoint que:",[821,822,823,831,834],"ol",{},[824,825,826,827,830],"li",{},"Retorne ",[46,828,829],{},"200 OK"," solo cuando la app esté lista para recibir tráfico.",[824,832,833],{},"Valide dependencias reales (base, cache, cola).",[824,835,836],{},"Responda en menos de 1 segundo.",[10,838,839],{},"Ejemplo mínimo en Node.js:",[107,841,845],{"className":842,"code":843,"language":844,"meta":112,"style":112},"language-javascript shiki shiki-themes github-dark-default","app.get('\u002Fhealthz', async (req, res) => {\n  try {\n    await db.query('SELECT 1')\n    res.status(200).json({ ok: true })\n  } catch (err) {\n    res.status(503).json({ ok: false, error: err.message })\n  }\n})\n","javascript",[46,846,847,888,895,914,941,952,975,980],{"__ignoreMap":112},[116,848,849,852,856,859,862,864,868,871,874,876,879,882,885],{"class":118,"line":119},[116,850,851],{"class":126},"app.",[116,853,855],{"class":854},"sc3cj","get",[116,857,858],{"class":126},"(",[116,860,861],{"class":130},"'\u002Fhealthz'",[116,863,594],{"class":126},[116,865,867],{"class":866},"suJrU","async",[116,869,870],{"class":126}," (",[116,872,873],{"class":627},"req",[116,875,594],{"class":126},[116,877,878],{"class":627},"res",[116,880,881],{"class":126},") ",[116,883,884],{"class":866},"=>",[116,886,887],{"class":126}," {\n",[116,889,890,893],{"class":118,"line":134},[116,891,892],{"class":866},"  try",[116,894,887],{"class":126},[116,896,897,900,903,906,908,911],{"class":118,"line":146},[116,898,899],{"class":866},"    await",[116,901,902],{"class":126}," db.",[116,904,905],{"class":854},"query",[116,907,858],{"class":126},[116,909,910],{"class":130},"'SELECT 1'",[116,912,913],{"class":126},")\n",[116,915,916,919,922,924,927,929,932,935,938],{"class":118,"line":153},[116,917,918],{"class":126},"    res.",[116,920,921],{"class":854},"status",[116,923,858],{"class":126},[116,925,926],{"class":142},"200",[116,928,809],{"class":126},[116,930,931],{"class":854},"json",[116,933,934],{"class":126},"({ ok: ",[116,936,937],{"class":142},"true",[116,939,940],{"class":126}," })\n",[116,942,943,946,949],{"class":118,"line":162},[116,944,945],{"class":126},"  } ",[116,947,948],{"class":866},"catch",[116,950,951],{"class":126}," (err) {\n",[116,953,954,956,958,960,963,965,967,969,972],{"class":118,"line":176},[116,955,918],{"class":126},[116,957,921],{"class":854},[116,959,858],{"class":126},[116,961,962],{"class":142},"503",[116,964,809],{"class":126},[116,966,931],{"class":854},[116,968,934],{"class":126},[116,970,971],{"class":142},"false",[116,973,974],{"class":126},", error: err.message })\n",[116,976,977],{"class":118,"line":187},[116,978,979],{"class":126},"  }\n",[116,981,982],{"class":118,"line":197},[116,983,984],{"class":126},"})\n",[582,986,987],{},[10,988,989,992,993,996],{},[587,990,991],{},"Atención:"," un ",[46,994,995],{},"\u002Fhealthz"," que siempre retorna 200 es peor que no tenerlo. Esconde fallas y el deploy pasa creyendo que todo está bien.",[14,998,1000],{"id":999},"rolling-update-por-defecto","Rolling update por defecto",[10,1002,1003],{},"Sin configuración extra, las actualizaciones son rolling update. El cluster cambia una réplica a la vez, espera a que quede saludable, y solo entonces toca la próxima.",[10,1005,1006],{},"Defaults del rolling update:",[22,1008,1009,1022],{},[25,1010,1011],{},[28,1012,1013,1016,1019],{},[31,1014,1015],{},"Parámetro",[31,1017,1018],{},"Valor",[31,1020,1021],{},"Qué hace",[38,1023,1024,1037,1050,1063],{},[28,1025,1026,1031,1034],{},[43,1027,1028],{},[46,1029,1030],{},"max_parallel",[43,1032,1033],{},"1",[43,1035,1036],{},"cuántas réplicas actualizar al mismo tiempo",[28,1038,1039,1044,1047],{},[43,1040,1041],{},[46,1042,1043],{},"min_healthy_time",[43,1045,1046],{},"10s",[43,1048,1049],{},"cuánto tiempo la nueva debe quedar saludable",[28,1051,1052,1057,1060],{},[43,1053,1054],{},[46,1055,1056],{},"healthy_deadline",[43,1058,1059],{},"300s",[43,1061,1062],{},"cuánto esperar antes de considerar falla",[28,1064,1065,1070,1072],{},[43,1066,1067],{},[46,1068,1069],{},"auto_revert",[43,1071,937],{},[43,1073,1074],{},"vuelve solo si falla",[10,1076,1077,1078,1081],{},"Para customizar, agrega un bloque ",[46,1079,1080],{},"update"," en el task:",[107,1083,1085],{"className":109,"code":1084,"language":111,"meta":112,"style":112},"update:\n  max_parallel: 2\n  min_healthy_time: 30s\n  healthy_deadline: 600s\n  auto_revert: true\n",[46,1086,1087,1093,1102,1112,1122],{"__ignoreMap":112},[116,1088,1089,1091],{"class":118,"line":119},[116,1090,1080],{"class":122},[116,1092,159],{"class":126},[116,1094,1095,1098,1100],{"class":118,"line":134},[116,1096,1097],{"class":122},"  max_parallel",[116,1099,127],{"class":126},[116,1101,394],{"class":142},[116,1103,1104,1107,1109],{"class":118,"line":146},[116,1105,1106],{"class":122},"  min_healthy_time",[116,1108,127],{"class":126},[116,1110,1111],{"class":130},"30s\n",[116,1113,1114,1117,1119],{"class":118,"line":153},[116,1115,1116],{"class":122},"  healthy_deadline",[116,1118,127],{"class":126},[116,1120,1121],{"class":130},"600s\n",[116,1123,1124,1127,1129],{"class":118,"line":162},[116,1125,1126],{"class":122},"  auto_revert",[116,1128,127],{"class":126},[116,1130,577],{"class":142},[10,1132,1133],{},"Más paralelismo = deploy rápido + ventana mayor de riesgo. Más tiempo saludable = deploy lento + confianza mayor.",[14,1135,1137],{"id":1136},"actualizar-la-app","Actualizar la app",[10,1139,1140],{},"Cambiaste código, hiciste build, push de la imagen con tag nueva. Para promover:",[821,1142,1143,1156],{},[824,1144,1145,1146,1148,1149,1152,1153,809],{},"Edita ",[46,1147,104],{}," cambiando el tag de la imagen (",[46,1150,1151],{},"1.4.2"," → ",[46,1154,1155],{},"1.4.3",[824,1157,1158],{},"Submitea de nuevo:",[107,1160,1161],{"className":618,"code":619,"language":620,"meta":112,"style":112},[46,1162,1163],{"__ignoreMap":112},[116,1164,1165,1167,1169,1171],{"class":118,"line":119},[116,1166,628],{"class":627},[116,1168,631],{"class":130},[116,1170,634],{"class":130},[116,1172,637],{"class":130},[10,1174,1175,1176,1179],{},"El cluster compara las dos versiones, percibe que solo la imagen del ",[46,1177,1178],{},"web"," cambió y corre rolling update solo en ese task. Postgres sigue intocado.",[10,1181,1182],{},"Acompaña con:",[107,1184,1186],{"className":618,"code":1185,"language":620,"meta":112,"style":112},"heroctl deploy status dep-2026-04-26-002\n# task: web\n# strategy: rolling\n# progress: 1\u002F2 (50%)\n# state: rolling\n# next: web-e5f6 (em 8s)\n",[46,1187,1188,1201,1207,1212,1217,1222],{"__ignoreMap":112},[116,1189,1190,1192,1195,1198],{"class":118,"line":119},[116,1191,628],{"class":627},[116,1193,1194],{"class":130}," deploy",[116,1196,1197],{"class":130}," status",[116,1199,1200],{"class":130}," dep-2026-04-26-002\n",[116,1202,1203],{"class":118,"line":134},[116,1204,1206],{"class":1205},"sH3jZ","# task: web\n",[116,1208,1209],{"class":118,"line":146},[116,1210,1211],{"class":1205},"# strategy: rolling\n",[116,1213,1214],{"class":118,"line":153},[116,1215,1216],{"class":1205},"# progress: 1\u002F2 (50%)\n",[116,1218,1219],{"class":118,"line":162},[116,1220,1221],{"class":1205},"# state: rolling\n",[116,1223,1224],{"class":118,"line":176},[116,1225,1226],{"class":1205},"# next: web-e5f6 (em 8s)\n",[14,1228,1230],{"id":1229},"rollback","Rollback",[10,1232,1233],{},"Si algo sale mal y necesitas volver:",[107,1235,1237],{"className":618,"code":1236,"language":620,"meta":112,"style":112},"heroctl job revert api-vendas --version 1\n",[46,1238,1239],{"__ignoreMap":112},[116,1240,1241,1243,1245,1248,1250,1253],{"class":118,"line":119},[116,1242,628],{"class":627},[116,1244,631],{"class":130},[116,1246,1247],{"class":130}," revert",[116,1249,769],{"class":130},[116,1251,1252],{"class":142}," --version",[116,1254,1255],{"class":142}," 1\n",[10,1257,1258],{},"El comando reaplica el spec de la versión anterior, con el mismo rolling update. En 30–60 segundos estás de vuelta al estado bueno conocido. No hay \"rollback de base\" — las schema migrations son responsabilidad de la aplicación.",[582,1260,1261],{},[10,1262,1263,1265,1266,1269],{},[587,1264,589],{}," las versiones antiguas quedan guardadas indefinidamente. Lístalas con ",[46,1267,1268],{},"heroctl job history api-vendas"," para ver todas las versiones y quién submiteó cada una.",[14,1271,1273],{"id":1272},"proximos-pasos","Próximos pasos",[1275,1276,1277,1285],"ul",{},[824,1278,1279,1280,1284],{},"¿Quieres entender cuándo rolling no alcanza? Mira ",[604,1281,1283],{"href":1282},"\u002Fes\u002Fdocs\u002Fdeploy\u002Frolling-canary-blue-green","Rolling, canary, blue-green y rainbow",".",[824,1286,1287,1288,1284],{},"Para todos los comandos disponibles: ",[604,1289,607],{"href":606},[1291,1292,1293],"style",{},"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 .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}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 .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}",{"title":112,"searchDepth":134,"depth":134,"links":1295},[1296,1297,1298,1299,1300,1301,1302,1303,1304,1305],{"id":16,"depth":134,"text":17},{"id":97,"depth":134,"text":98},{"id":611,"depth":134,"text":612},{"id":654,"depth":134,"text":655},{"id":745,"depth":134,"text":746},{"id":812,"depth":134,"text":813},{"id":999,"depth":134,"text":1000},{"id":1136,"depth":134,"text":1137},{"id":1229,"depth":134,"text":1230},{"id":1272,"depth":134,"text":1273},"deploy","Levanta una aplicación Node.js con base Postgres en 50 líneas de YAML. Incluye health check, rolling update y rollback.",false,"md","i-lucide-rocket","2026-04-26",{},"\u002Fes\u002Fdocs\u002Fdeploy\u002Fprimer-deploy",[1315],"primeiro-cluster","10 min",{"title":5,"description":1307},"es\u002Fdocs\u002Fdeploy\u002Fprimer-deploy",[1306,1320,111,1321],"primeros-pasos","rolling","V3oyeM9Z1G-WLyZ-I6F0J4gstSmzizA5JycR8LexMV8",[1324,1330,1331,1334,1340,1344,1350,1355,1360,1364,1370,1374,1380,1385],{"path":1325,"title":1326,"description":1327,"category":1328,"order":119,"icon":1329},"\u002Fes\u002Fdocs\u002Fapi\u002Freferencia-api","Referencia de la API REST","Endpoints, autenticación JWT, ejemplos con curl y patrones de error de la API de HeroCtl.","api","i-lucide-code",{"path":1313,"title":5,"description":1307,"category":1306,"order":119,"icon":1310},{"path":1282,"title":1283,"description":1332,"category":1306,"order":134,"icon":1333},"Cuatro estrategias de deploy. Cuándo usar cada una, con ejemplos completos y trade-offs honestos.","i-lucide-git-branch",{"path":1335,"title":1336,"description":1337,"category":1338,"order":134,"icon":1339},"\u002Fes\u002Fdocs\u002Fobservabilidad\u002Fbackup-restauracion","Backup y restauración del estado del cluster","Cómo guardar, programar y restaurar snapshots del plano de control de HeroCtl. Estrategia de disaster recovery.","observabilidade","i-lucide-archive",{"path":807,"title":1341,"description":1342,"category":1338,"order":119,"icon":1343},"Métricas y logs","Recolección de métricas, logs y traces sin montar una pila de observabilidad externa. Cuándo vale, y cuándo integrar con herramienta de fuera.","i-lucide-activity",{"path":1345,"title":1346,"description":1347,"category":1348,"order":119,"icon":1349},"\u002Fes\u002Fdocs\u002Foperaciones\u002Finstalacion","Instalación","Instala HeroCtl en cualquier servidor Linux con Docker en un solo comando. Cubre prerrequisitos, bootstrap y verificación.","operacoes","i-lucide-download",{"path":1351,"title":1352,"description":1353,"category":1348,"order":153,"icon":1354},"\u002Fes\u002Fdocs\u002Foperaciones\u002Fmulti-region","Multi-region (en planificación Q4 2026)","Qué esperar de multi-region en HeroCtl, cómo correr en varias regiones hoy y la hoja de ruta hasta 2027.","i-lucide-globe",{"path":1356,"title":1357,"description":1358,"category":1348,"order":134,"icon":1359},"\u002Fes\u002Fdocs\u002Foperaciones\u002Fprimer-cluster","Levantar cluster de 3 nodos","Forma un cluster con 3 servidores en menos de 10 minutos. Tolera falla de 1 nodo sin indisponibilidad.","i-lucide-network",{"path":606,"title":1361,"description":1362,"category":1348,"order":146,"icon":1363},"Referencia completa del CLI","Todos los comandos heroctl con sinopsis, flags y ejemplo. Úsalo como chuleta de mesa.","i-lucide-terminal",{"path":1365,"title":1366,"description":1367,"category":1368,"order":134,"icon":1369},"\u002Fes\u002Fdocs\u002Fred\u002Ffirewall","Configuración de firewall","Qué puertos usa HeroCtl, cuáles necesitan estar abiertos y cuáles nunca deberían exponerse a internet.","rede","i-lucide-shield",{"path":1371,"title":1372,"description":1373,"category":1368,"order":119,"icon":1354},"\u002Fes\u002Fdocs\u002Fred\u002Fingress-tls","Ingress y TLS automático","Cómo exponer aplicaciones por el puerto 443 con certificados emitidos y renovados automáticamente, sin operar un router externo.",{"path":1375,"title":1376,"description":1377,"category":1378,"order":134,"icon":1379},"\u002Fes\u002Fdocs\u002Fseguridad\u002Frbac","RBAC y control de acceso (Business+)","Modelo de roles, políticas y tokens para limitar quién puede enviar, leer y operar el cluster.","seguranca","i-lucide-users",{"path":1381,"title":1382,"description":1383,"category":1378,"order":119,"icon":1384},"\u002Fes\u002Fdocs\u002Fseguridad\u002Fsecretos","Gestión de secretos","Cómo guardar contraseñas, tokens y claves fuera del spec del job, con cifrado en reposo y rotación versionada.","i-lucide-key",{"path":1386,"title":1387,"description":1388,"category":1389,"order":119,"icon":1390},"\u002Fes\u002Fdocs\u002Ftroubleshooting\u002Fproblemas-comunes","Troubleshooting de problemas comunes","Los 12 problemas más frecuentes en clusters HeroCtl, con síntoma, diagnóstico y corrección paso a paso.","troubleshooting","i-lucide-alert-triangle",1777362182771]