[{"data":1,"prerenderedAt":781},["ShallowReactive",2],{"doc-en-\u002Fen\u002Fdocs\u002Foperations\u002Finstallation":3,"docs-en-all":712},{"id":4,"title":5,"body":6,"category":695,"description":696,"draft":697,"extension":698,"icon":699,"lastReviewed":700,"meta":701,"navigation":242,"order":145,"path":702,"prerequisites":703,"readingTime":704,"seo":705,"stem":706,"tags":707,"__hash__":711},"docs_en\u002Fen\u002Fdocs\u002Foperations\u002Finstallation.md","Installation",{"type":7,"value":8,"toc":681},"minimark",[9,13,18,21,110,125,129,132,165,168,208,211,215,218,276,290,324,328,331,387,390,394,397,421,432,435,469,477,481,486,492,530,536,540,557,561,564,568,582,586,589,660,671,677],[10,11,12],"p",{},"HeroCtl runs on any Linux server with Docker. The binary is single and contains the three execution modes: server, agent, and CLI. You decide the machine's role via flag, not via package.",[14,15,17],"h2",{"id":16},"prerequisites","Prerequisites",[10,19,20],{},"Before installing, confirm the environment. The list is short and firm.",[22,23,24,40],"table",{},[25,26,27],"thead",{},[28,29,30,34,37],"tr",{},[31,32,33],"th",{},"Requirement",[31,35,36],{},"Minimum",[31,38,39],{},"Recommended",[41,42,43,55,66,77,88,99],"tbody",{},[28,44,45,49,52],{},[46,47,48],"td",{},"System",[46,50,51],{},"Linux x86_64 (kernel 5.4+)",[46,53,54],{},"Ubuntu 22.04 or Debian 12",[28,56,57,60,63],{},[46,58,59],{},"CPU",[46,61,62],{},"2 vCPU",[46,64,65],{},"4 vCPU",[28,67,68,71,74],{},[46,69,70],{},"RAM",[46,72,73],{},"2 GB",[46,75,76],{},"4 GB",[28,78,79,82,85],{},[46,80,81],{},"Disk",[46,83,84],{},"20 GB SSD",[46,86,87],{},"40 GB SSD",[28,89,90,93,96],{},[46,91,92],{},"Docker",[46,94,95],{},"20.10+",[46,97,98],{},"24+",[28,100,101,104,107],{},[46,102,103],{},"Ports",[46,105,106],{},"8080, 8081, 8082 open between nodes",[46,108,109],{},"same",[111,112,113],"blockquote",{},[10,114,115,119,120,124],{},[116,117,118],"strong",{},"Note:"," Docker must be running before install. Check with ",[121,122,123],"code",{},"docker info",". If the output shows a socket error, fix permissions before proceeding.",[14,126,128],{"id":127},"install-command","Install command",[10,130,131],{},"One command delivers the binary, configures the service, and generates the default configuration file.",[133,134,139],"pre",{"className":135,"code":136,"language":137,"meta":138,"style":138},"language-bash shiki shiki-themes github-dark-default","curl -sSL https:\u002F\u002Fget.heroctl.com\u002Finstall.sh | sh\n","bash","",[121,140,141],{"__ignoreMap":138},[142,143,146,150,154,158,162],"span",{"class":144,"line":145},"line",1,[142,147,149],{"class":148},"sQhOw","curl",[142,151,153],{"class":152},"sFSAA"," -sSL",[142,155,157],{"class":156},"s9uIt"," https:\u002F\u002Fget.heroctl.com\u002Finstall.sh",[142,159,161],{"class":160},"suJrU"," |",[142,163,164],{"class":148}," sh\n",[10,166,167],{},"The script runs four steps, in order:",[169,170,171,179,194,201],"ol",{},[172,173,174,175,178],"li",{},"Downloads the latest binary to ",[121,176,177],{},"\u002Fusr\u002Flocal\u002Fbin\u002Fheroctl",".",[172,180,181,182,185,186,189,190,193],{},"Creates the ",[121,183,184],{},"heroctl"," system user and the ",[121,187,188],{},"\u002Fetc\u002Fheroctl\u002F"," and ",[121,191,192],{},"\u002Fvar\u002Flib\u002Fheroctl\u002F"," directories.",[172,195,196,197,200],{},"Registers a systemd service (",[121,198,199],{},"heroctl.service",") without enabling auto-start.",[172,202,203,204,207],{},"Generates ",[121,205,206],{},"\u002Fetc\u002Fheroctl\u002Fserver.yaml"," with safe defaults (bind on 127.0.0.1, no TLS).",[10,209,210],{},"No surprises. The script does not touch anything outside those paths and does not start processes.",[14,212,214],{"id":213},"post-install-verification","Post-install verification",[10,216,217],{},"After the script finishes, two commands confirm the binary responds.",[133,219,221],{"className":135,"code":220,"language":137,"meta":138,"style":138},"heroctl version\n# heroctl 1.0.0 (commit abc123, build 2026-04-20)\n\nheroctl status\n# binary: ok\n# config: \u002Fetc\u002Fheroctl\u002Fserver.yaml\n# docker: connected (24.0.7)\n# service: inactive (não iniciado)\n",[121,222,223,230,237,244,252,258,264,270],{"__ignoreMap":138},[142,224,225,227],{"class":144,"line":145},[142,226,184],{"class":148},[142,228,229],{"class":156}," version\n",[142,231,233],{"class":144,"line":232},2,[142,234,236],{"class":235},"sH3jZ","# heroctl 1.0.0 (commit abc123, build 2026-04-20)\n",[142,238,240],{"class":144,"line":239},3,[142,241,243],{"emptyLinePlaceholder":242},true,"\n",[142,245,247,249],{"class":144,"line":246},4,[142,248,184],{"class":148},[142,250,251],{"class":156}," status\n",[142,253,255],{"class":144,"line":254},5,[142,256,257],{"class":235},"# binary: ok\n",[142,259,261],{"class":144,"line":260},6,[142,262,263],{"class":235},"# config: \u002Fetc\u002Fheroctl\u002Fserver.yaml\n",[142,265,267],{"class":144,"line":266},7,[142,268,269],{"class":235},"# docker: connected (24.0.7)\n",[142,271,273],{"class":144,"line":272},8,[142,274,275],{"class":235},"# service: inactive (não iniciado)\n",[10,277,278,279,282,283,285,286,289],{},"If ",[121,280,281],{},"docker: connected"," shows an error, the binary cannot talk to the socket. Add the ",[121,284,184],{}," user to the ",[121,287,288],{},"docker"," group and restart the service.",[133,291,293],{"className":135,"code":292,"language":137,"meta":138,"style":138},"sudo usermod -aG docker heroctl\nsudo systemctl restart heroctl\n",[121,294,295,312],{"__ignoreMap":138},[142,296,297,300,303,306,309],{"class":144,"line":145},[142,298,299],{"class":148},"sudo",[142,301,302],{"class":156}," usermod",[142,304,305],{"class":152}," -aG",[142,307,308],{"class":156}," docker",[142,310,311],{"class":156}," heroctl\n",[142,313,314,316,319,322],{"class":144,"line":232},[142,315,299],{"class":148},[142,317,318],{"class":156}," systemctl",[142,320,321],{"class":156}," restart",[142,323,311],{"class":156},[14,325,327],{"id":326},"the-three-modes-of-the-binary","The three modes of the binary",[10,329,330],{},"The same executable changes role via the initial flag. There is no separate package.",[22,332,333,346],{},[25,334,335],{},[28,336,337,340,343],{},[31,338,339],{},"Mode",[31,341,342],{},"Command",[31,344,345],{},"What for",[41,347,348,361,374],{},[28,349,350,353,358],{},[46,351,352],{},"Server",[46,354,355],{},[121,356,357],{},"heroctl server",[46,359,360],{},"Control plane. Decides where everything runs.",[28,362,363,366,371],{},[46,364,365],{},"Agent",[46,367,368],{},[121,369,370],{},"heroctl agent",[46,372,373],{},"Worker. Runs containers and reports health.",[28,375,376,379,384],{},[46,377,378],{},"CLI",[46,380,381],{},[121,382,383],{},"heroctl \u003Ccmd>",[46,385,386],{},"Local client. Talks to any server via API.",[10,388,389],{},"In production, 3 nodes run server + agent. Extra workers run agent only. CLI mode is stateless and can run from the operator's laptop.",[14,391,393],{"id":392},"initial-bootstrap","Initial bootstrap",[10,395,396],{},"The first node needs to initialize the cluster state. This step is unique and only happens once in the install's life.",[133,398,400],{"className":135,"code":399,"language":137,"meta":138,"style":138},"sudo heroctl server --bootstrap --advertise 10.0.0.1\n",[121,401,402],{"__ignoreMap":138},[142,403,404,406,409,412,415,418],{"class":144,"line":145},[142,405,299],{"class":148},[142,407,408],{"class":156}," heroctl",[142,410,411],{"class":156}," server",[142,413,414],{"class":152}," --bootstrap",[142,416,417],{"class":152}," --advertise",[142,419,420],{"class":152}," 10.0.0.1\n",[10,422,423,424,427,428,431],{},"The ",[121,425,426],{},"--advertise"," flag is the IP that other nodes will use to connect. In a test environment, it can be ",[121,429,430],{},"127.0.0.1",". In production, it is the machine's private IP.",[10,433,434],{},"After bootstrap, the service stays running in the background. Confirm with:",[133,436,438],{"className":135,"code":437,"language":137,"meta":138,"style":138},"heroctl cluster status\n# nodes:    1\n# leader:   self\n# applied:  42\n# health:   ok\n",[121,439,440,449,454,459,464],{"__ignoreMap":138},[142,441,442,444,447],{"class":144,"line":145},[142,443,184],{"class":148},[142,445,446],{"class":156}," cluster",[142,448,251],{"class":156},[142,450,451],{"class":144,"line":232},[142,452,453],{"class":235},"# nodes:    1\n",[142,455,456],{"class":144,"line":239},[142,457,458],{"class":235},"# leader:   self\n",[142,460,461],{"class":144,"line":246},[142,462,463],{"class":235},"# applied:  42\n",[142,465,466],{"class":144,"line":254},[142,467,468],{"class":235},"# health:   ok\n",[10,470,471,472,178],{},"To bring up additional nodes and form a real cluster, head over to ",[473,474,476],"a",{"href":475},"\u002Fen\u002Fdocs\u002Foperations\u002Ffirst-cluster","Bring up a 3-node cluster",[14,478,480],{"id":479},"common-problems","Common problems",[482,483,485],"h3",{"id":484},"port-8080-in-use","Port 8080 in use",[10,487,488,489,491],{},"HeroCtl reserves 8080 for the HTTP API. If another process already occupies it, edit ",[121,490,206],{}," before bootstrap.",[133,493,497],{"className":494,"code":495,"language":496,"meta":138,"style":138},"language-yaml shiki shiki-themes github-dark-default","api:\n  bind: 0.0.0.0\n  port: 9080\n","yaml",[121,498,499,509,520],{"__ignoreMap":138},[142,500,501,505],{"class":144,"line":145},[142,502,504],{"class":503},"sPWt5","api",[142,506,508],{"class":507},"sZEs4",":\n",[142,510,511,514,517],{"class":144,"line":232},[142,512,513],{"class":503},"  bind",[142,515,516],{"class":507},": ",[142,518,519],{"class":152},"0.0.0.0\n",[142,521,522,525,527],{"class":144,"line":239},[142,523,524],{"class":503},"  port",[142,526,516],{"class":507},[142,528,529],{"class":152},"9080\n",[10,531,532,533,178],{},"Restart the service with ",[121,534,535],{},"sudo systemctl restart heroctl",[482,537,539],{"id":538},"docker-permission-denied","Docker permission denied",[10,541,542,543,546,547,549,550,552,553,556],{},"The message ",[121,544,545],{},"permission denied while trying to connect to the Docker daemon"," appears when the ",[121,548,184],{}," user is not in the ",[121,551,288],{}," group. Fix with the ",[121,554,555],{},"usermod"," shown above and restart the service.",[482,558,560],{"id":559},"firewall-blocking-the-cluster","Firewall blocking the cluster",[10,562,563],{},"On providers like DigitalOcean or Hetzner, the external firewall blocks 8080–8082 by default. Open the three ports between node IPs before trying to form a cluster. For external API access, keep 8080 open only for administrative IPs.",[482,565,567],{"id":566},"binary-not-found-after-install","Binary not found after install",[10,569,570,571,574,575,578,579,178],{},"The script installs to ",[121,572,573],{},"\u002Fusr\u002Flocal\u002Fbin\u002F",". If the current session's ",[121,576,577],{},"PATH"," does not include that directory, open a new shell or run ",[121,580,581],{},"hash -r",[14,583,585],{"id":584},"uninstall","Uninstall",[10,587,588],{},"Reversal is symmetric. Removes service, binary, and data, in that order.",[133,590,592],{"className":135,"code":591,"language":137,"meta":138,"style":138},"sudo systemctl stop heroctl && sudo systemctl disable heroctl\nsudo rm \u002Fetc\u002Fsystemd\u002Fsystem\u002Fheroctl.service\nsudo rm \u002Fusr\u002Flocal\u002Fbin\u002Fheroctl\nsudo rm -rf \u002Fetc\u002Fheroctl \u002Fvar\u002Flib\u002Fheroctl\nsudo userdel heroctl\n",[121,593,594,617,627,636,651],{"__ignoreMap":138},[142,595,596,598,600,603,605,608,610,612,615],{"class":144,"line":145},[142,597,299],{"class":148},[142,599,318],{"class":156},[142,601,602],{"class":156}," stop",[142,604,408],{"class":156},[142,606,607],{"class":507}," && ",[142,609,299],{"class":148},[142,611,318],{"class":156},[142,613,614],{"class":156}," disable",[142,616,311],{"class":156},[142,618,619,621,624],{"class":144,"line":232},[142,620,299],{"class":148},[142,622,623],{"class":156}," rm",[142,625,626],{"class":156}," \u002Fetc\u002Fsystemd\u002Fsystem\u002Fheroctl.service\n",[142,628,629,631,633],{"class":144,"line":239},[142,630,299],{"class":148},[142,632,623],{"class":156},[142,634,635],{"class":156}," \u002Fusr\u002Flocal\u002Fbin\u002Fheroctl\n",[142,637,638,640,642,645,648],{"class":144,"line":246},[142,639,299],{"class":148},[142,641,623],{"class":156},[142,643,644],{"class":152}," -rf",[142,646,647],{"class":156}," \u002Fetc\u002Fheroctl",[142,649,650],{"class":156}," \u002Fvar\u002Flib\u002Fheroctl\n",[142,652,653,655,658],{"class":144,"line":254},[142,654,299],{"class":148},[142,656,657],{"class":156}," userdel",[142,659,311],{"class":156},[111,661,662],{},[10,663,664,667,668,670],{},[116,665,666],{},"Warning:"," the ",[121,669,192],{}," directory contains the cluster state. Deleting it means losing deploy history and job definitions. Take a snapshot first if there is any chance you will reinstall.",[10,672,673,674,178],{},"Next step: ",[473,675,676],{"href":475},"form a 3-node cluster",[678,679,680],"style",{},"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);}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}",{"title":138,"searchDepth":232,"depth":232,"links":682},[683,684,685,686,687,688,694],{"id":16,"depth":232,"text":17},{"id":127,"depth":232,"text":128},{"id":213,"depth":232,"text":214},{"id":326,"depth":232,"text":327},{"id":392,"depth":232,"text":393},{"id":479,"depth":232,"text":480,"children":689},[690,691,692,693],{"id":484,"depth":239,"text":485},{"id":538,"depth":239,"text":539},{"id":559,"depth":239,"text":560},{"id":566,"depth":239,"text":567},{"id":584,"depth":232,"text":585},"operacoes","Install HeroCtl on any Linux server with Docker in a single command. Covers prerequisites, bootstrap, and verification.",false,"md","i-lucide-download","2026-04-26",{},"\u002Fen\u002Fdocs\u002Foperations\u002Finstallation",[],"6 min read",{"title":5,"description":696},"en\u002Fdocs\u002Foperations\u002Finstallation",[708,709,710],"installation","getting-started","linux","VzZs2pTpTA6beWDDjZxigDBtA2SW6qz1HNF3U46KkDI",[713,718,724,729,735,740,746,751,756,759,760,764,770,775],{"path":714,"title":715,"description":716,"category":504,"order":145,"icon":717},"\u002Fen\u002Fdocs\u002Fapi\u002Fapi-reference","REST API reference","Endpoints, JWT authentication, curl examples, and error patterns of the HeroCtl API.","i-lucide-code",{"path":719,"title":720,"description":721,"category":722,"order":145,"icon":723},"\u002Fen\u002Fdocs\u002Fdeploy\u002Ffirst-deploy","Deploy your first app","Bring up a Node.js application with a Postgres database in 50 lines of YAML. Includes health check, rolling deploy, and rollback.","deploy","i-lucide-rocket",{"path":725,"title":726,"description":727,"category":722,"order":232,"icon":728},"\u002Fen\u002Fdocs\u002Fdeploy\u002Frolling-canary-blue-green","Rolling, canary, blue-green, and rainbow","Four deploy strategies. When to use each, with complete examples and honest trade-offs.","i-lucide-git-branch",{"path":730,"title":731,"description":732,"category":733,"order":232,"icon":734},"\u002Fen\u002Fdocs\u002Fnetworking\u002Ffirewall","Firewall configuration","Which ports HeroCtl uses, which need to stay open, and which should never be exposed to the internet.","rede","i-lucide-shield",{"path":736,"title":737,"description":738,"category":733,"order":145,"icon":739},"\u002Fen\u002Fdocs\u002Fnetworking\u002Fingress-tls","Ingress and automatic TLS","How to expose applications on port 443 with certificates issued and renewed automatically, without operating an external router.","i-lucide-globe",{"path":741,"title":742,"description":743,"category":744,"order":232,"icon":745},"\u002Fen\u002Fdocs\u002Fobservability\u002Fbackup-restore","Backup and restore of cluster state","How to save, schedule, and restore HeroCtl control plane snapshots. Disaster recovery strategy.","observabilidade","i-lucide-archive",{"path":747,"title":748,"description":749,"category":744,"order":145,"icon":750},"\u002Fen\u002Fdocs\u002Fobservability\u002Fmetrics-logs","Metrics and logs","Collect metrics, logs, and traces without standing up an external observability stack. When it's worth it, and when to integrate with an outside tool.","i-lucide-activity",{"path":752,"title":753,"description":754,"category":695,"order":239,"icon":755},"\u002Fen\u002Fdocs\u002Foperations\u002Fcli-reference","Complete CLI reference","All heroctl commands with synopsis, flags, and example. Use as a desk reference.","i-lucide-terminal",{"path":475,"title":476,"description":757,"category":695,"order":232,"icon":758},"Form a cluster with 3 servers in under 10 minutes. Tolerates 1-node failure with no downtime.","i-lucide-network",{"path":702,"title":5,"description":696,"category":695,"order":145,"icon":699},{"path":761,"title":762,"description":763,"category":695,"order":246,"icon":739},"\u002Fen\u002Fdocs\u002Foperations\u002Fmulti-region","Multi-region (planned for Q4 2026)","What to expect from multi-region in HeroCtl, how to run across regions today, and the roadmap through 2027.",{"path":765,"title":766,"description":767,"category":768,"order":232,"icon":769},"\u002Fen\u002Fdocs\u002Fsecurity\u002Frbac","RBAC and access control (Business+)","Role, policy, and token model to limit who can submit, read, and operate the cluster.","seguranca","i-lucide-users",{"path":771,"title":772,"description":773,"category":768,"order":145,"icon":774},"\u002Fen\u002Fdocs\u002Fsecurity\u002Fsecrets","Secret management","How to keep passwords, tokens, and keys outside the job spec, with encryption at rest and versioned rotation.","i-lucide-key",{"path":776,"title":777,"description":778,"category":779,"order":145,"icon":780},"\u002Fen\u002Fdocs\u002Ftroubleshooting\u002Fcommon-problems","Troubleshooting common problems","The 12 most frequent problems in HeroCtl clusters, with symptom, diagnosis, and step-by-step fix.","troubleshooting","i-lucide-alert-triangle",1777362181064]