RE:NODE

Hosting aplikací13 min čtení

Nasazení ASP.NET Core aplikace z GitHubu na server

Jak dostat projekt v ASP.NET Core z repozitáře na GitHubu na běžící server: dotnet publish, startovací příkaz, Kestrel, port, HTTPS a první nasazení.

7 čtenářů

Aplikace v ASP.NET Core se z GitHubu nasazuje ve čtyřech krocích: server stáhne repozitář, dotnet publish promění projekt ve složku zkompilovaného výstupu, dotnet YourApp.dll spustí Kestrel a Kestrel naslouchá na portu, který ti přidělil hosting, na všech rozhraních. Téměř každé nezdařené první nasazení je jeden z těchto čtyř kroků udělaný trochu špatně - publish, který vybere špatný projekt, startovací příkaz, který skončí místo toho, aby zůstal na popředí, nebo Kestrel naslouchající na localhost:5000, protože mu nikdo neřekl nic jiného. Tento článek prochází jednotlivé kroky v pořadí, v jakém se spouštějí, včetně nastavení, na kterých záleží, teprve když aplikace neběží na tvém notebooku.

Nic z toho není vázané na konkrétního poskytovatele. Příklady používají panel s nasazením z Gitu a reverzní proxy před aplikací, protože na tom běží většina malých .NET aplikací, ale stejné příkazy fungují i na VDS se systemd.

Jak nasazení z Gitu spustí .NET aplikaci#

Nasazení z Gitu není kouzlo ani build pipeline. Je to clone (u dalších nasazení pull) do souborového systému serveru a po něm startovací příkaz. U .NET projektu má tento příkaz dva úkoly: sestavit kód a spustit ho.

předané HTTPGitHub repovětev mainProxy slotTLS pro tvou doménuPull při startunebo deploy při pushidotnet publishRelease do ./outdotnet out/App.dllKestrel na portu
Od pushe k běžícímu procesu Kestrel

Existují dva poctivé způsoby, jak to zařídit, a volba rozhoduje o tom, jak dlouho trvá restart.

  1. Build na serveru. Startovací příkaz spustí dotnet publish a pak spustí výsledek. Do Gitu se nedostává nic zkompilovaného. Cena je, že každý start - ne jen každé nasazení - zaplatí za restore a kompilaci, což je 20 sekund na rychlém stroji a minuta i víc na půlce jádra.
  2. Build v CI, běh na serveru. Workflow v GitHub Actions aplikaci publikuje a výstup commitne do samostatné větve (řekněme deploy), kterou server sleduje. Startovací příkaz je pak jen dotnet App.dll. Starty jsou rychlé a server nikdy nepotřebuje paměť pro SDK, za cenu workflow souboru, který je třeba udržovat.

Většina lidí by měla začít prvním způsobem a přejít na druhý, až je začne rušit doba restartu. Oba jsou popsané níže.

Co repozitář potřebuje, aby šel nasadit#

.NET repozitář, který se u tebe čistě sestaví, může na serveru stejně selhat, obvykle z jednoho z těchto důvodů.

Projektů je víc. dotnet publish bez argumentu hledá soubor projektu nebo solution v aktuálním adresáři. Pokud kořen obsahuje .sln s webovým projektem, knihovnou tříd a testovacím projektem, publikování solution zapíše všechny do stejné výstupní složky a dostaneš nepořádek nebo chybu, že víc projektů zapisuje stejný soubor. Pojmenuj projekt, který myslíš: dotnet publish src/Shop.Web/Shop.Web.csproj.

Jsou commitnuté `bin` a `obj`. Být nesmí. Commitnutá složka obj nese stav restoru z tvého stroje (včetně absolutních cest do tvé NuGet cache) a build na serveru pak selhává způsoby, které vypadají jako poškození balíčků. Standardní výstup dotnet new gitignore vylučuje obojí.

Verze SDK není připnutá. Soubor global.json v kořeni říká příkazu dotnet, které SDK použít:

global.json
{  "sdk": {    "version": "10.0.100",    "rollForward": "latestFeature"  }}

rollForward: latestFeature přijme jakékoli pozdější feature pásmo téže hlavní verze, takže 10.0.100 odpovídá i 10.0.2xx. Bez global.json kód sestaví nejnovější nainstalované SDK, což je obvykle v pořádku. S global.json, který jmenuje SDK, jež server nemá, se build zastaví s hláškou „A compatible .NET SDK was not found“ - proto připni hlavní verzi, kterou cílíš, ne přesný patch z notebooku. Kterou hlavní verzi cílit, najdeš v článku Verze .NET a podpora LTS.

Verze balíčků se pohybují. Pokud chceš, aby server obnovil přesně to, co jsi testoval, zapni lock soubor. Přidej do projektu <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>, commitni vygenerovaný packages.lock.json a na serveru obnovuj s --locked-mode, který selže, místo aby tiše vyřešil něco nového.

Startovací příkaz#

Startovací příkaz se spouští při každém startu kontejneru, ne jen po pushi. Pro variantu s buildem na serveru vypadá takto:

bash
dotnet publish src/Shop.Web/Shop.Web.csproj -c Release -o out \  --disable-build-servers && exec dotnet out/Shop.Web.dll

Co která část dělá:

  • -c Release sestaví s optimalizacemi. Od .NET 8 je výchozí konfigurace u dotnet publish Release pro projekty cílící na .NET 8 a novější, ale explicitní zápis nic nestojí a chrání starší projekty.
  • -o out uloží publikované soubory do známé složky. Bez něj skončí v bin/Release/net10.0/publish/, což je cesta, která se mění s cílovým frameworkem.
  • --disable-build-servers (SDK 7 a novější) zabrání tomu, aby MSBuild a kompilátorový server Roslyn zůstaly po buildu rezidentní. Na desktopu ty procesy na pozadí zrychlují další build. Na serveru s 1 GB sedí vedle tvé aplikace a drží několik set megabajtů paměti kvůli buildu, který se znovu uskuteční až při dalším restartu.
  • exec nahradí shell procesem .NET, takže stop signál z panelu dorazí přímo do aplikace a ta se může čistě ukončit.

Příkaz musí zůstat na popředí. Skript, který proces pošle na pozadí, nebo startovací příkaz, který jen sestaví, vypadá jako aplikace, která skončila, a bude restartována ve smyčce.

U varianty s buildem v CI workflow publikuje na runneru GitHubu a výstupní složku pošle do větve deploy, a startovací příkaz se zkrátí na exec dotnet Shop.Web.dll. Restart pak trvá sekundu nebo dvě. Výměnou je, že publikovaný výstup ve větvi musí odpovídat runtime na serveru: build závislý na frameworku pro .NET 10 tam potřebuje runtime .NET 10. Rozdíl mezi výstupem závislým na frameworku a self-contained, který tuto závislost odstraňuje, vysvětluje článek dotnet publish a možnosti runtime.

Kestrel, port a ASPNETCORE_URLS#

Kestrel je webový server zabudovaný v ASP.NET Core. Je rychlý, je určený pro produkci a bez konfigurace naslouchá na http://localhost:5000 - což uvnitř kontejneru znamená, že se k němu zvenku nikdo nedostane. Soubor launchSettings.json, který nastavuje tvé lokální porty, čte jen dotnet run a Visual Studio; publikovaná aplikace spuštěná přes dotnet App.dll ho úplně ignoruje.

Tvůj plán má přidělený port, který ukazuje panel, a aplikace musí naslouchat na tomto portu na všech rozhraních. Kestrel čte své adresy z několika míst, v tomto pořadí priority (pozdější přebíjí dřívější):

ZdrojPříkladPoznámky
ASPNETCORE_HTTP_PORTS8080.NET 8 a novější. Jen porty, všechna rozhraní
ASPNETCORE_URLShttp://0.0.0.0:8080Úplné URL; přebíjí proměnnou s porty
argument --urls--urls http://0.0.0.0:8080Přebíjí prostředí
Kestrel:Endpoints v konfiguraciappsettings.jsonNahrazuje výše uvedená nastavení URL
volání Listen v kóduListenAnyIP(port)Také nahrazuje nastavení URL

Pro aplikaci hostovanou v panelu je nejjednodušší správné nastavení proměnná prostředí na záložce Startup:

env
ASPNETCORE_URLS=http://0.0.0.0:25571

Použij číslo portu, které tvůj plán skutečně ukazuje. Pokud nechceš kopírovat port na dvě místa, přečti si ho v kódu. Panely předávají přidělený port procesu jako proměnnou prostředí (na panelech založených na Pterodactylu je to SERVER_PORT) a můžeš se na něj navázat explicitně:

Program.cs
var builder = WebApplication.CreateBuilder(args);var port = Environment.GetEnvironmentVariable("PORT")        ?? Environment.GetEnvironmentVariable("SERVER_PORT");if (port is not null){    builder.WebHost.ConfigureKestrel(k => k.ListenAnyIP(int.Parse(port)));}var app = builder.Build();app.MapGet("/healthz", () => Results.Ok(new { ok = true }));app.Run();

ListenAnyIP naváže IPv4 i IPv6 na tomto portu. http://+:8080 a http://*:8080 v ASPNETCORE_URLS znamenají totéž co 0.0.0.0; fungují všechny tři.

Naslouchej na prostém HTTP. TLS ukončuje proxy před aplikací, takže Kestrel certifikát nepotřebuje a konfigurace HTTPS endpointu uvnitř kontejneru ti přinese problém s certifikátem bez jakéhokoli užitku. Vývojový certifikát ze šablony ASP.NET Core na serveru neexistuje; pokud je v konfiguraci stále URL s https://, start selže s hláškou „Unable to configure HTTPS endpoint. No server certificate was specified“.

Prostředí, appsettings a connection stringy#

ASP.NET Core si vybírá prostředí z ASPNETCORE_ENVIRONMENT (nebo DOTNET_ENVIRONMENT) a pokud není nastaveno ani jedno, prostředí je Production. Tato výchozí hodnota je pro server správná a mění chování, které si všimneš: vývojářská stránka s výjimkami je vypnutá, appsettings.Development.json se nenačítá a user secrets se nečtou.

To poslední je klasická chyba prvního nasazení. Lokálně všechno fungovalo, protože heslo k databázi bylo v user secrets; na serveru tam prostě není a aplikace při prvním dotazu selže na prázdném connection stringu. Konfigurace na serveru pochází z appsettings.json, potom z appsettings.Production.json, potom z proměnných prostředí, a vyhrává prostředí.

Tajemství dej do proměnných prostředí na záložce Startup. Vnořené klíče používají dvojité podtržítko:

env
ConnectionStrings__Default=Host=203.0.113.10;Port=5432;Database=shop;Username=shop;Password=...Stripe__SecretKey=sk_live_...

builder.Configuration.GetConnectionString("Default") pak přečte první z nich a builder.Configuration["Stripe:SecretKey"] druhou. Celé vrstvení, vzor options a ověřování nastavení při startu najdeš v článku Konfigurace a tajemství v ASP.NET Core. Aplikační plán na RE:NODE obsahuje dva databázové sloty vytvářené v panelu s vygenerovaným hostem, uživatelem a heslem, takže connection string zkopíruješ na záložku Startup, místo abys ho vymýšlel. Pokud potřebuješ SQL Server, MySQL nebo PostgreSQL jako samostatný server, je to databázový hosting; balíčky providerů pro každou z nich pokrývá článek .NET s PostgreSQL, MySQL nebo SQL Serverem.

Ještě jeden soubor musí přežít nasazení: úložiště klíčů Data Protection. ASP.NET Core jím šifruje autentizační cookies a antiforgery tokeny a ve výchozím stavu leží v domovském adresáři uživatele. Pokud toto umístění není trvalé, nebo se klíče vygenerují znovu, každý uživatel je při restartu odhlášen. Ulož je někam, o čem víš, že přežije nové nasazení a leží mimo sledovaný repozitář:

csharp
builder.Services.AddDataProtection()    .PersistKeysToFileSystem(new DirectoryInfo("/home/container/keys"));

Cestu uprav podle toho, kde má tvůj server trvalé soubory. Co se rozbije, když se klíče nečekaně otočí, vysvětluje Základy autentizace v ASP.NET Core.

Doména, HTTPS a předané hlavičky#

S Kestrelem na prostém HTTP patří doména a certifikát proxy. Na RE:NODE obsahuje každý aplikační plán proxy slot: nasměruj A záznam na zobrazenou adresu a certifikát se vystaví a obnovuje automaticky, v rámci 21denního okna. Skutečná adresa klienta přichází v X-Forwarded-For.

Aplikaci je třeba říct, aby té hlavičce věřila, jinak uvidí každý požadavek jako přicházející z proxy po prostém HTTP. Když se to nestane, pokazí se dvě věci:

  • HttpContext.Connection.RemoteIpAddress je adresa proxy, takže logy a rate limity berou každého návštěvníka jako jednu osobu.
  • Request.Scheme je http, takže UseHttpsRedirection přesměruje požadavek, který už přišel přes HTTPS, a prohlížeč se točí ve smyčce, dokud to nevzdá s hláškou „too many redirects“.

Řešením je middleware pro předané hlavičky, registrovaný jako první:

csharp
builder.Services.Configure<ForwardedHeadersOptions>(o =>{    o.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;});var app = builder.Build();app.UseForwardedHeaders();

Ve výchozím stavu middleware důvěřuje jen proxy na loopbacku, takže proxy na jiné adrese je ignorována, dokud ji nepřidáš do KnownProxies. Jak to udělat správně, aniž by si kdokoli mohl padělat IP, je celé téma článku ASP.NET Core za reverzní proxy, spolu s HSTS a WebSockety. Pokud je pro tebe celá myšlenka nová, základ najdeš v článku Co reverzní proxy ve skutečnosti dělá.

První nasazení krok za krokem#

  1. Pushni repozitář s global.json, bez bin a obj a s health endpointem, například /healthz, který vrací 200 bez sahání na databázi.
  2. Připoj repozitář v panelu. Na RE:NODE je to jen GitHub, přes GitHub App s krátkodobými tokeny, takže fungují i privátní repozitáře.
  3. Nastav startovací příkaz: publish do out, pak exec dotnet out/YourApp.dll.
  4. Na záložce Startup nastav ASPNETCORE_URLS na http://0.0.0.0: plus tvůj přidělený port a přidej connection stringy a tajemství.
  5. Spusť server a sleduj konzoli. Zdravý start končí řádky Now listening on: http://0.0.0.0:25571 a Application started. Pokud místo toho vidíš http://localhost:5000, krok 4 se neujal.
  6. Zavolej health endpoint přes IP a port. Pak nastav proxy slot a zavolej ho přes doménu.
  7. Zapni oba přepínače nasazení, pokud je chceš: pull při každém startu a deploy při pushi, který restartuje jen server, který už běžel - takže server, který jsi záměrně zastavil, zůstane zastavený. Každé nasazení se zaznamenává, takže víš, který commit je živý.

Stranu panelu provází se snímky obrazovky průvodce nasazeními. Postup je stejný jako u Node; více o tom, jak spolu pull při startu a deploy při pushi souvisejí, najdeš v článku Nasazení Node.js aplikace z GitHubu.

Řešení potíží s prvním nasazením#

„Now listening on: http://localhost:5000“ a nic se nepřipojí. Kestrel tvou adresu nikdy nedostal. Zkontroluj pravopis ASPNETCORE_URLS a že hodnota začíná http://. Překlep v názvu proměnné prostředí nevyvolá vůbec žádnou chybu.

„A compatible .NET SDK was not found“. global.json jmenuje SDK, které není nainstalované. Uvolni ho na hlavní verzi pomocí rollForward, nebo ho odstraň.

„You must install or update .NET to run this application“. Výstup závislý na frameworku cílí na runtime, který server nemá - například aplikace net10.0 na stroji jen s runtime .NET 8. Cílit na nainstalovanou verzi, nebo publikuj self-contained.

„Failed to bind to address ... address already in use“. Port drží jiný proces (často tvá vlastní předchozí instance nebo build server, na který jsi zapomněl), nebo dva endpointy v konfiguraci uvádějí stejný port.

Build umře v půlce bez chyby. Paměť. Kompilátor byl zastaven na limitu; viz varování výše.

Aplikace se restartuje každých pár minut. Přečti poslední řádky před každým restartem. Neošetřená výjimka při startu, například selhané připojení k databázi v Program.cs, ukončí proces, platforma ho spustí znovu a smyčka se opakuje. Selži s jasnou zprávou, ne se stack trace, a o tom, jak se restartovací smyčky detekují, se dočteš v článku Proč se tvůj server pořád restartuje.

Po nasazení jsou všichni uživatelé odhlášeni. Klíče Data Protection nebyly trvale uložené. Viz výše.

Statické soubory v produkci vrací 404. dotnet publish zkopíruje wwwroot jen tehdy, jsou-li soubory součástí projektu. Soubory přidané front-end buildem, který běží až po publishi, nebo vyloučené v .csproj, se do out nikdy nedostanou. Nejdřív sestav front-end, pak publikuj.

FAQ#

Potřebuju před Kestrelem IIS nebo nginx?

Ne. Kestrel je už léta podporovaný edge server. Potřebuješ ukončení TLS a doménu, což poskytne proxy slot; přidání nginx dovnitř kontejneru to zdvojí a dá ti druhou sadu timeoutů k ladění.

Mám publikovaný výstup commitovat do hlavní větve?

Ne do větve, ve které vyvíjíš. Zkompilovaný výstup ve stejné větvi jako zdroje vytváří obrovské diffy a konflikty při merge. Pokud chceš rychlé restarty, nech CI publikovat do samostatné větve a server na ni nasměruj.

Proč každý restart trvá tak dlouho?

Protože startovací příkaz pokaždé dělá restore a kompilaci. To je cena za build na serveru. Předem sestavení v CI nebo udržování malé solution to zkrátí na sekundy.

Můžu databázové migrace spouštět jako součást nasazení?

Můžeš, ale zamysli se, co se stane, když migrace selže v půlce. Článek Migrace EF Core v produkci srovnává jejich aplikování při startu s migration bundles a idempotentními skripty.

Na kterou verzi .NET má cílit nová aplikace?

Na aktuální vydání s dlouhodobou podporou, kterým je od října 2026 .NET 10. Je podporované do listopadu 2028, takže tě nic nenutí k upgradu v průběhu roku.

Kolik paměti potřebuje malé API v ASP.NET Core?

Minimal API v klidu zabírá výrazně pod 100 MB; typická aplikace s EF Core a pár desítkami endpointů se při lehké zátěži pohybuje mezi 100 a 300 MB. Build na serveru potřebuje víc než aplikace. Proč číslo, které vidíš, závisí na režimu GC, vysvětluje Paměť a garbage collection v .NET.


Komentáře

Zcela anonymně: žádný účet, žádný e-mail, žádné cookie. Ukládáme jen jméno, které napíšeš, text a čas - nic dalšího. Počet odkazů je omezený a značkování se nevykresluje.

0/2000