RE:NODE

Pre-flight check: zachyť rozbitý deploy z GitHubu dřív, než proběhne

Text: Tým RE:NODE
16 min čtení


Většina deployů, které selžou, neselže při samotném deployi. Kód se stáhne, závislosti se nainstalují, kontejner nastartuje a aplikace se pak ukončí, protože chybí start skript, nebo poslouchá na portu 3000, kam zvenku nic nedosáhne, nebo hledá složku .next, kterou nikdo nesestavil. Každou z těchto chyb je vidět v repozitáři ještě předtím, než se nasadí jediný bajt. Pre-flight check na záložce GitHub v panelu přečte repozitář, porovná ho s tím, jak ho server skutečně spustí, a vypíše, co se rozbije, od nejhoršího, s opravou, kterou si můžeš zkopírovat.

Tento článek popisuje, co check čte, jak číst kartu, každou třídu problémů, které najde, se skutečným příkladem konfigurace, která ji vyvolá, a jak každý z nich opravit. Popisuje také, co check nevidí, protože zelená karta není příslib, že aplikace funguje. Většina oprav platí na jakémkoli hostingu: napevno zadaný port nebo bind na localhost rozbije deploy všude, nejen na RE:NODE.

Co check čte a čeho se nikdy nedotkne#

Check pracuje se dvěma věcmi: s jedním rekurzivním výpisem stromu větve a s krátkým seznamem malých souborů načtených podle jména. Výpis stromu obsahuje jen cesty a velikosti, žádný obsah. Soubory, které smí přečíst, jsou ty, které rozhodují o startu aplikace: package.json, deno.json nebo deno.jsonc, go.mod a Go soubory hlavního balíčku, requirements.txt nebo pyproject.toml, .nvmrc, .env.example a vstupní soubor, který by server spustil. Repozitář nikdy neklonuje a přeskočí každý soubor větší než 256 KB, protože nic, co stojí za kontrolu při startu, není tak velké.

Běží na dvou místech. Když na záložce GitHub vybereš repozitář a větev, spustí se dřív, než se propojení uloží, takže problémy uvidíš dřív, než se cokoli nasadí. Potom běží znovu při každé návštěvě záložky. Výsledky se cachují po dobu deseti minut podle commitu, ale hlava větve se zjišťuje pokaždé, takže push, který jsi právě udělal, se pozná okamžitě a cache šetří jen opakované čtení souborů, které se nezměnily.

Tři věci, které záměrně nedělá:

  • Nikdy neotevře `.env`. Commitnutý .env se hlásí jen podle názvu souboru. Jeho obsah se nikdy nestahuje, takže se nic z něj nemůže dostat do nálezu.
  • Nikdy ti necituje soubor zpět. Výstupem je seznam nálezů - vět napsaných panelem, které citují nejvýš skript nebo název proměnné. Každý řetězec prochází stejným redaktorem jako funkce s AI, takže start skript s natvrdo zapsaným heslem se cituje bez hesla.
  • Nikdy neselže hlasitě. GitHub nedostupný, rate limit, odinstalovaná GitHub App, větev, která ještě neexistuje: každé z toho se změní v jedinou poznámku, že check nemohl proběhnout. Check, který by stáhl celou záložku s sebou, by byl horší než žádný check.
jen čteníopravíš, pak propojíšRepozitář na GitHubuhlava větvePre-flight checkstrom + pár souborůPre-flight kartaod nejhoršíhoPropojení uloženoStart serverupull, install, boot
Kde stojí pre-flight check při deployi z GitHubu

Check se řídí stejnou logikou startu, jakou používá server. Nemá vlastní představu o tom, jak má vypadat aplikace v Node nebo Go; čte seznam vstupních souborů, na které se startovací skript vrací, a proměnnou, kterou záložka Startup používá pro každý runtime. Když se změní start, změní se s ním i check.

Jak číst pre-flight kartu#

Karta je na záložce GitHub pod nadpisem „Pre-flight check“ a obsahuje řádek, který říká, jaký commit a jakou větev přečetla. Každý nález má jednu ze čtyř úrovní a seznam je seřazený od nejhoršího nahoře:

LevelLabel on the cardWhat it means
failWill breakAplikace se nespustí, nebo se spustí a nebude dostupná
warnWorth fixingPoběží, ale špatně nebo jinak, než je zamýšleno
infoGood to knowSouvislosti, nebo něco, o čem check nemohl rozhodnout
passFineTato část odpovídá tomu, jak server startuje

Každý nález má název, větu s podrobnostmi a - pokud existuje - opravu v malém bloku kódu s tlačítkem Copy. Oprava je napsaná pro tento server: když v ní stojí $SERVER_PORT, tato proměnná je při startu opravdu nastavená. Tlačítko Re-run spustí check znovu na požádání.

Co check testuje, závisí na runtime serveru. Servery Node.js, Bun, Deno, Go a Python dostanou celou sadu: start příkaz, port a host, build, verzi. Web hosting dostane kontroly index souboru a buildu. Jiné typy serverů, například Java, .NET nebo Rust, spouštějí vlastní build a start příkaz, takže dostanou jen kontroly tajemství a karta to řekne, místo aby mlčela.

Není co spustit: start skripty a vstupní soubory#

Nejčastější selhání je aplikace, kterou server nemá co spustit. Kontejner nastartuje, nenajde žádný příkaz a do sekundy skončí. Konzole ukáže krátkou dávku výstupu a pak nic, což je matoucí, pokud to nečekáš.

U serveru Node.js start spustí npm install a potom npm run start. Pokud package.json nemá skript start, vrátí se k prvnímu vstupnímu souboru z pevného seznamu: index.js, server.js, app.js, main.js, bot.js, index.mjs, index.cjs, stejná jména pod src/ a nakonec verze v TypeScriptu jako index.ts a src/server.ts (spouštěné přes ts-node). Pokud neexistuje nic z toho, karta ukáže „No start script and no entry file“ na úrovni fail.

package.json, který to vyvolá, obvykle vypadá takhle - projekt, který se kdy spouštěl jen přes npm run dev na notebooku:

package.json (before)
{  "name": "my-api",  "main": "src/api.js",  "scripts": {    "dev": "nodemon src/api.js"  },  "dependencies": { "express": "^4.19.2" }}

src/api.js není jedno z náhradních jmen a start tu není. Oprava je jeden řádek:

package.json (after)
{  "scripts": {    "dev": "nodemon src/api.js",    "start": "node src/api.js"  }}

package.json, který nejde naparsovat, se hlásí samostatně, před čímkoli jiným, protože npm ho odmítne a nic dalšího už nehraje roli. Oprava, kterou karta nabízí, je spustit lokálně npm install a přečíst si chybu. Nejčastějším viníkem je čárka navíc na konci.

Ostatní runtime mají vlastní verze téže kontroly:

  • Bun spustí bun install a potom bun run start, pokud má package.json start skript, jinak vstupní soubor uvedený na záložce Startup, jinak první soubor ze stejného seznamu náhrad. Pokud záložka Startup uvádí soubor, který větev nemá, ale existuje náhrada, karta ti řekne, který soubor se opravdu spustí, a navrhne nastavit záložku Startup tak, aby odpovídala.
  • Deno spustí deno task start z deno.json nebo deno.jsonc. Bez start tasku spustí vstupní soubor ze záložky Startup příkazem deno run -A - ten uděluje všechna oprávnění a karta to říká. Náhradní jména jsou main.ts, mod.ts, server.ts, index.ts, main.js, src/main.ts a src/index.ts. package.json bez deno.json dostane poznámku: na serveru s Deno nic nespouští npm install, takže buď nech Deno přečíst závislosti samo, nebo použij specifikátory npm:.
  • Python spustí vstupní soubor uvedený na záložce Startup. Pokud žádný není uveden, panel hledá main.py, bot.py, app.py, run.py a __main__.py v tomto pořadí a při propojení nastaví záložku Startup na první nalezený. Pokud záložka Startup uvádí server.py a větev takový soubor nemá, je to fail a oprava jmenuje kandidáta, kterého našla.
  • Go sestaví balíček uvedený na záložce Startup (GO_MAIN, ve výchozím stavu .) příkazem go build a neúspěšný build zastaví start. Check chce go.mod, Go soubory v dané složce, package main a func main(). Složka s knihovním balíčkem se hlásí jako taková, protože go build na knihovně nevytvoří žádný program.

Případ Go zachytí rozložení, na kterém se zasekne spousta lidí: kořen modulu obsahuje knihovní balíček a program leží v cmd/server. Nastav GO_MAIN na záložce Startup na ./cmd/server a nález zezelená. Pokud go.mod není vůbec, spusť go mod init se jménem svého modulu, pak go mod tidy a commitni go.mod i go.sum.

Špatný port, špatný host#

Druhé nejčastější selhání je aplikace, která v pořádku nastartuje a nejde se na ni dostat. Je to to nejfrustrující, protože konzole říká „listening“ a prohlížeč neříká nic.

Zvenku je dostupný jen vlastní port serveru. Panel při startu nastaví dvě proměnné prostředí, PORT a SERVER_PORT, na tento port. Aplikace, která má natvrdo 3000, 5000, 8000 nebo 8080, poslouchá na portu, kam se nic nesměruje, pokud tvoje alokace náhodou není to číslo. A aplikace navázaná na localhost nebo 127.0.0.1 je dostupná jen zevnitř svého kontejneru, ať používá jakýkoli port. Podrobnější vysvětlení, proč to tak je, najdeš v článku porty herních serverů vysvětleně - stejná pravidla platí i pro aplikace.

Check čte nejdřív start skript a potom vstupní soubor a hlásí:

  • start skript, který pevně určuje port, například next start -p 3000 nebo --port 8080 (warn);
  • vstupní soubor, který volá .listen(3000) nebo nastavuje port = 5000 (warn);
  • vstupní soubor, který se váže na localhost nebo 127.0.0.1 (warn);
  • vstupní soubor, který čte process.env.PORT, SERVER_PORT, Bun.env.PORT nebo Deno.env.get("PORT") (pass);
  • nenalezený žádný webový server (info) - v pořádku u bota pro Discord nebo Telegram, ale stojí za druhý pohled, pokud jsi čekal HTTP.

Tento vstupní soubor s Expressem vyvolá hned dva nálezy: napevno zadaný port a bind na localhost:

server.js (before)
const express = require('express');const app = express();app.get('/', (req, res) => res.send('ok'));app.listen(3000, 'localhost', () => console.log('listening on 3000'));

Oprava, kterou karta navrhuje:

server.js (after)
const port = Number(process.env.PORT);app.listen(port, '0.0.0.0', () => console.log(`listening on ${port}`));

Stejný princip v ostatních runtime. Python: přečti port přes int(os.environ.get("SERVER_PORT", os.environ.get("PORT", 8080))) a předej host="0.0.0.0". Go: http.ListenAndServe(":"+os.Getenv("PORT"), mux) - samotná dvojtečka poslouchá na všech rozhraních, zatímco řetězec "localhost:8080" se hlásí. Bun: Bun.serve({ port: Number(process.env.PORT), hostname: '0.0.0.0', fetch }).

Deno má zvláštní případ, který stojí za to znát. Deno.serve(handler) bez portu poslouchá na 8000, což není tvoje alokace. Obecný check by řekl jen to, že nemůže poznat, který port se používá; tento pojmenuje problém:

main.ts
Deno.serve(  { port: Number(Deno.env.get("PORT")), hostname: "0.0.0.0" },  (_req) => new Response("ok"),);

Někdy karta na úrovni info řekne „Could not tell which port“. To se stane, když se server vytváří někde, kam check nečte, například v souboru importovaném ze vstupního souboru. Není to chyba; otevři soubor, který volá listen, a zkontroluj to očima.

Buildy, které nikdy neběžely#

Frameworky s build krokem selhávají zvláštním způsobem: start příkaz hledá výstup buildu, který neexistuje. Check zná čtyři z nich a ví, co každý vytváří - Next.js sestaví .next, Nuxt sestaví .output, Angular a Vite sestaví dist. Pokud projekt na některém z nich závisí, položí tři otázky: spouští start skript, prestart nebo postinstall build; pokud ne, je výstup buildu commitnutý; a pokud ani to, je to fail.

Typický package.json pro Next.js, který selže:

package.json (before)
{  "scripts": {    "dev": "next dev",    "build": "next build",    "start": "next start"  }}

build existuje, ale nic ho na serveru nevolá. Oprava, kterou karta nabízí, sestaví a pak servíruje na správném hostu a portu:

package.json (after)
{  "scripts": {    "build": "next build",    "start": "next build && next start -H 0.0.0.0 -p $SERVER_PORT"  }}

Alternativy jsou postinstall, který spustí build, nebo commitnutí výstupu buildu. Commitnutí .next funguje a karta to propustí, s připomínkou, že pak musíš před každým pushem znovu sestavit. Většina lidí na to do týdne zapomene. My raději stavíme na serveru.

Start skript, který spouští vývojový server, je samostatné varování. next dev, nuxt dev, ng serve a samotné vite jsou pomalé, neminifikované a často navázané na localhost. Pro Vite je navrženou opravou vite build && vite preview --host 0.0.0.0 --port $SERVER_PORT; pro Nuxt nuxt build && node .output/server/index.mjs. Průvodce deployem Next.js se věnuje paměťové stránce buildování na malém serveru.

Čisté TypeScript dostane stejné zacházení i bez frameworku. Pokud je typescript závislost a start skript spouští node dist/index.js, ale dist/index.js není commitnutý a nic před startem nespouští tsc, karta ohlásí „TypeScript is never compiled“ a navrhne tsc && node dist/index.js.

Nesoulad verzí runtime#

Projekt napsaný pro jednu hlavní verzi runtime se může na jiné nenainstalovat nebo nespustit, obvykle kvůli nativnímu modulu nebo změně syntaxe. Check porovnává to, co repozitář žádá, s tím, co server provozuje.

Pro Node.js čte nejdřív .nvmrc a potom engines.node v package.json. .nvmrc obsahující 20 na serveru s Node 22 je varování. U engines projde rozsah >=18 na čemkoli od 18 výš; ^20 nebo holé 20 projde jen na Node 20. Nabízená oprava je buď vybrat odpovídající verzi pod Runtime version na stejné záložce, nebo upravit soubor. Rozhodni podle toho, co je pravda: pokud projekt opravdu potřebuje Node 20, změň server; pokud je .nvmrc pozůstatek před dvěma lety, změň soubor.

.nvmrc
22

Pro Go čte řádek go v go.mod. Pokud go.mod žádá novější Go, než má server, build buď před každým startem stáhne novější toolchain, což pokaždé vyžaduje síť a disk, nebo odmítne sestavit. Vyber tuto verzi Go nebo novější pod Runtime version, nebo sniž řádek go, pokud ho kód ve skutečnosti nepotřebuje.

Kontrola verze běží jen tehdy, když image serveru uvádí verzi, kterou check dokáže přečíst. Pokud to nepozná, mlčí, místo aby hádal.

Python, web hosting a další specifika runtime#

Python má dva nálezy, které zachytí lidi přicházející z jiných platforem.

Server před startem instaluje requirements.txt přes pip. Projekt, který deklaruje závislosti jen v pyproject.toml, dostane varování, protože se z něj nic neinstaluje. Vyexportuj soubor requirements a commitni ho:

bash
$ pip freeze > requirements.txt$ uv pip compile pyproject.toml -o requirements.txt

Funguje kterýkoli příkaz; druhý drží soubor u toho, co deklaruje pyproject.toml, místo všeho, co máš v lokálním prostředí. Průvodce requirements v Pythonu jde do pinování víc do hloubky.

Procfile se hlásí jako nepřečtený. Server spouští vstupní soubor ze záložky Startup, ne příkaz z Procfile. Pokud tvůj Procfile říká web: gunicorn app:app, ten příkaz se ignoruje, takže se ujisti, že vstupní soubor spouští server sám.

Web hosting je jednodušší a přísnější. Hosting servíruje složku tak, jak je, a nikdy nespouští npm run build. Check chce v kořeni index.html, index.php nebo index.htm. Pokud žádný nenajde, ale vidí dist/index.html, build/index.html, out/index.html, public/index.html nebo _site/index.html, řekne to a navrhne commitnout sestavené soubory nebo nasměrovat cestu deploye na tu složku. Projekt se skriptem build a bez commitnutého výstupu buildu dostane ze stejného důvodu samostatné varování. K části o buildu viz průvodce hostingem statických webů.

Tajemství: commitnutý .env a .env.example#

Toto je jediný nález, který se týká bezpečnosti, ne toho, jestli aplikace nastartuje, a běží pro každý typ serveru.

Každý soubor s názvem .env, .env.local, .env.production, .env.prod, .env.development nebo .env.dev, kdekoli ve stromu, je fail: „A .env is committed to the repository“. Každé heslo a klíč v něm si může přečíst každý, kdo má přístup do repozitáře, v každém commitu od chvíle, kdy byl přidán. Na web hostingu je to horší: soubor se servíruje spolu se zbytkem složky, takže ho jde stáhnout přímo z webu.

Oprava, popořadě:

bash
$ git rm --cached .env$ echo ".env" >> .gitignore$ git commit -m "Stop tracking .env"$ git push

Potom vyměň přihlašovací údaje - hesla k databázím, API tokeny, tokeny botů - a vytvoř nový .env na serveru přes záložku Files. .env se z GitHubu nikdy nestahuje, takže server má vždy jen ten, který tam dáš ty. Proměnné prostředí a tajemství pokrývají širší praxi.

.env.example dostane nález na úrovni info, který vypíše klíče, jež deklaruje, nejvýš třicet, takže na první pohled vidíš, co .env serveru potřebuje. Hodnoty v příkladu se nezobrazují. Pokud projekt používá dotenv nebo python-dotenv a nemá .env.example, karta poznamená, že aplikace čte .env, a připomene ti, abys ho nahrál.

Co pre-flight check nezachytí#

Check čte soubory. Nic nespouští, a to určuje jeho meze.

  • Chyby za běhu. Chybějící proměnná prostředí, která shodí aplikaci při prvním požadavku, databáze, která odmítne spojení, překlep v route - nic z toho není vidět ve výpisu souborů.
  • Paměť. Build Next.js, který potřebuje víc RAM, než má tarif, projde checkem a selže při buildu. Limity paměti v Node vysvětlují, jak to poznat.
  • Nepřímé porty. Port čtený z konfiguračního souboru nebo nastavený v importovaném modulu se může zobrazit jako „Could not tell“. Zkontroluj ho ručně.
  • Soukromé závislosti. Balíček ze soukromého registru potřebuje při instalaci přihlašovací údaje; check instalace netestuje.
  • Cokoli mimo větev. Nahrané soubory, vlastní .env serveru a obsah databáze nejsou součástí repozitáře.

Zelená karta znamená, že start najde, co spustit, na správném portu, sestavené a na odpovídající verzi. Tím odpadnou nudná selhání, takže to, co zbyde v konzoli, je skutečná chyba.

Když konzole přece jen ukáže pád, lišta „Work on this server with AI“ na záložce Console má volbu „Server won't start“, která zkopíruje hotový prompt s koncem výpisu konzole. Nálezy z pre-flight se dostanou i do kontextu AI, takže asistent, který má opravit repozitář, začíná od toho, o čem se už ví, že je špatně. Průvodce deployem z GitHubu popisuje propojení a deploy při pushi a sekce o deployích v našich průvodcích popisuje jednotlivá nastavení.

FAQ#

Blokuje pre-flight check deploy?

Ne. Hlásí, ale nezastavuje tě. Repozitář s nálezy na úrovni fail můžeš propojit a deploy proběhne a selže tak, jak karta předpověděla. Smysl je vidět to předtím, ne potom.

Proč karta říká, že soubor chybí, když v repozitáři je?

Tři obvyklé důvody: soubor je na jiné větvi, než je ta propojená, cesta deploye míří do podsložky, která ho neobsahuje, nebo je repozitář tak velký, že GitHub vrátil jen část seznamu souborů, což karta označí nahoře. Nejdřív zkontroluj název větve v hlavičce karty.

Opravil jsem problém a pushnul. Proč ho karta pořád ukazuje?

Neměla by nadlouho. Hlava větve se kontroluje při každé návštěvě, takže nový commit vyvolá nové čtení. Pokud ho pořád ukazuje, stiskni Re-run a ověř, že jsi pushnul na větev, se kterou je server propojený, a ne na jinou.

Vidí check hodnoty v mém .env?

Ne. Commitnutý .env se hlásí podle názvu a jeho obsah se nikdy nevyžaduje. .env.example se čte, ale na kartě se objeví jen názvy klíčů. Každý řetězec na kartě také projde před zobrazením redaktorem.

Které runtime check pokrývá?

Node.js, Bun, Deno, Go a Python dostanou kontroly startu, portu, buildu a verze; web hosting dostane kontroly index souboru a buildu. Jiné typy serverů dostanou kontroly commitnutých tajemství a poznámku, že kontroly runtime se nepoužijí.

Funguje to s GitLabem nebo Bitbucketem?

Ne. Git deploy na RE:NODE je jen pro GitHub, přes GitHub App, a check čte repozitář přes přístup této App - takže soukromé repozitáře fungují, pokud je App vidí.

Pomohlo vám to?