Niniejsza analiza prezentuje szczegółowe porównanie dwóch kluczowych poleceń .NET CLI: dotnet build oraz dotnet publish, które są fundamentalne dla procesu tworzenia i wdrażania aplikacji .NET. Dotnet build odpowiada za kompilację kodu źródłowego i weryfikację poprawności na etapie rozwoju, natomiast dotnet publish umożliwia przygotowanie aplikacji do wdrożenia, tworząc kompletny, samodzielny pakiet deploymentowy. Kluczowe różnice obejmują cel poleceń, strukturę wyjściową, zależności, optymalizacje oraz scenariusze użycia. Dotnet build generuje kod pośredni (IL) i podstawowe pliki konfiguracyjne, podczas gdy dotnet publish tworzy gotowy do wdrożenia pakiet zawierający wszystkie zależności i runtime. Zrozumienie tych różnic jest niezbędne do skutecznego zarządzania cyklem życia aplikacji .NET, począwszy od rozwoju po produkcyjne wdrożenie.
Podstawowe różnice między poleceniami
Dotnet build i dotnet publish różnią się swoim przeznaczeniem oraz rezultatami działania na poszczególnych etapach cyklu rozwoju oprogramowania:
- dotnet build – kompiluje kod źródłowy do postaci kodu .dll (IL), generuje binaria oraz assemblies, skupiając się na weryfikacji poprawności syntaktycznej kodu i rozwiązywaniu zależności podczas fazy rozwoju aplikacji,
- dotnet publish – przygotowuje aplikację do wdrożenia poprzez utworzenie pakietu deploymentowego, kompilując kod, pakując wszystkie zależności, pliki konfiguracyjne oraz komponenty runtime niezbędne do uruchomienia w środowisku docelowym,
- publish tworzy komplet plików gotowych do przeniesienia na serwer lub dystrybucji do użytkowników.
W zakresie wyjścia, dotnet build tworzy pliki .dll oraz podstawowe konfiguracje, lecz nie zawiera wszystkich wymaganych elementów do samodzielnego uruchomienia aplikacji – wymaga obecności odpowiedniej wersji runtime .NET na maszynie docelowej. Dotnet publish może wygenerować pakiet z dołączonym środowiskiem .NET (self-contained deployment), umożliwiając uruchomienie na systemach bez .NET.
Dotnet build działa szybciej i tworzy mniejsze paczki, natomiast dotnet publish gwarantuje samodzielność aplikacji kosztem dłuższego procesu i większego rozmiaru wynikowych plików, co jest szczególnie istotne w środowiskach kontenerowych.
Analiza wyjść i gotowość do wdrożenia
Różnice w strukturze i zawartości plików wyjściowych mają decydujący wpływ na gotowość aplikacji do wdrożenia:
- dotnet build – generuje skompilowany kod w postaci plików .dll (IL), pliki konfiguracyjne (.deps.json i .runtimeconfig.json), umieszczone w folderze bin odpowiedniej konfiguracji,
- dotnet build – wynik nie jest gotowy do wdrożenia na innych maszynach, szczególnie w starszych wersjach .NET; zależności NuGet nie są kopiowane do katalogu wyjściowego, aplikacja wymaga zainstalowanego runtime oraz dostępu do globalnego cache NuGet,
- dotnet publish – tworzy kompletny pakiet deploymentowy z wszystkimi zależnościami, kopiuje wszystkie pakiety z cache NuGet, generuje pliki specyficzne dla platformy i umożliwia uruchomienie aplikacji bez dodatkowej instalacji .NET.
W nowszych wersjach .NET (3.0 i wyższych), dotnet build automatycznie kopiuje zależności, ale tylko dotnet publish oferuje rozbudowane opcje optymalizacji i konfiguracji, takie jak self-contained czy framework-dependent deployment.
Najważniejsze typy deploymentu:
- Framework-dependent deployment (FDD) – wymaga runtime .NET na maszynie, generuje pliki platformowe i .dll;
- Self-contained deployment (SCD) – zawiera runtime .NET, aplikacja uruchamiana na dowolnym systemie bez konieczności instalacji .NET.
Ta elastyczność jest osiągalna tylko poprzez dotnet publish.
Opcje konfiguracyjne i parametry
Zarówno dotnet build, jak i dotnet publish oferują rozbudowane opcje konfiguracyjne. Najważniejsze parametry dotnet build:
- -c|–configuration – wybór konfiguracji (Debug/Release);
- -f|–framework – określenie frameworka docelowego;
- -o|–output – katalog wyjściowy dla artefaktów;
- –no-restore – pomija przywracanie pakietów NuGet,
- –no-dependencies – buduje tylko wskazany projekt,
- -r|–runtime – wyznacza identyfikator runtime,
- -v|–verbosity – poziom szczegółowości komunikatów.
Dotnet publish oprócz ww. opcji, posiada także specjalistyczne parametry związane z deploymentem:
- –self-contained – deployment z runtime .NET,
- –runtime – wybór platformy docelowej,
- PublishReadyToRun – kompilacja assemblies w trybie R2R,
- PublishSingleFile – pakowanie całości w jeden plik,
- PublishTrimmed – usuwanie nieużywanych bibliotek dla mniejszych paczek.
W .NET 8 domyślnie dotnet publish używa konfiguracji Release dla projektów targetujących najnowsze wersje – wpływa to na skrypty CI/CD.
Charakterystyki wydajnościowe i optymalizacja
Wydajność poleceń zależy od zakresu ich działania:
- dotnet build – szybka kompilacja; zoptymalizowany pod iteracyjny cykl programisty dzięki ponownemu użyciu procesów MSBuild i kompilatora,
- dotnet publish – realizuje dodatkowe operacje, rozwiązuje zależności, kopiuje pliki, generuje różne typy deploymentu oraz przeprowadza optymalizacje, przez co czas wykonania jest dłuższy,
- opcja PublishTrimmed (w .NET 7+) może nawet o 26% obniżyć czas publish, zwłaszcza przy ustawieniu TrimMode=partial; całkowite wyłączenie trimmingu (PublishTrimmed=false) skraca czas publikacji, ale zwiększa rozmiar artefaktów,
- w środowiskach Docker publish znacząco wydłuża budowę obrazu; wykorzystanie opcji takich jak –no-restore i –no-build pozwala zaoszczędzić czas.
Scenariusze wdrożeniowe i przypadki użycia
Wybór polecenia zależy od etapu i charakteru wdrożenia:
- dotnet build – idealny do lokalnej kompilacji, testowania, debugowania podczas pracy deweloperskiej,
- Framework-dependent deployment (FDD) – najlepszy dla serwerów z zainstalowanym runtime .NET, pozwala na mniejsze i łatwe do wdrożenia pakiety,
- Self-contained deployment (SCD) – używany, gdy nie mamy kontroli nad środowiskiem lub potrzebujemy pełnej niezależności, szczególnie w desktopach, chmurze i kontenerach,
- dotnet publish – kluczowe narzędzie w pipelines CI/CD, umożliwia automatyczne deploymenty do różnych środowisk,
- w konteneryzacji self-contained deployment wymaga użycia publish,
- Native AOT compilation (PublishAot=true) diametralnie poprawia cold start i efektywność pamięciową, idealna dla serverless.
Najczęstsze pułapki i rozwiązywanie problemów
W praktyce pojawiają się typowe błędy i wyzwania, na które warto zwrócić uwagę:
- błędne przekonanie, że output dotnet build jest gotowy do wdrożenia – niesie ryzyko błędów brakujących plików lub wersji .NET,
- problemy z konfiguracją pipelines po zmianach w .NET 8 – publish domyślnie używa Release,
- niską wydajność publish – można ograniczyć przez właściwą konfigurację trimmingu (TrimMode=partial lub PublishTrimmed=false),
- zarządzanie artefaktami dla wielu projektów wymaga przemyślanego ustawienia katalogów wyjściowych,
- self-contained deployment uruchamiany na niewspieranej platformie kończy się błędem,
- w kontenerach ograniczenia pamięci/pojemności są znaczące przy dużych aplikacjach z wieloma zależnościami,
- konflikty wersji SDK, runtime i packages mogą prowadzić do trudnych do wykrycia błędów produkcyjnych.
Najlepsze praktyki i rekomendacje
Aby zapewnić efektywność i niezawodność procesu build/publish, stosuj następujące dobre praktyki:
- w środowisku developerskim stosuj dotnet build do szybkiej iteracji kodu,
- produkcyjne wdrożenia zawsze realizuj poprzez dotnet publish z precyzyjnie zdefiniowanymi parametrami (np.
dotnet publish -c Release -r linux-x64 -o ./publish --self-contained), - w pipelines CI/CD rozdzielaj etapy build i publish; przechowuj i wersjonuj artefakty,
- po zmianach w .NET 8 jawnie określaj konfiguracje w pipeline’ach,
- optymalizuj wydajność używając –no-build, –no-restore i testuj różne warianty trimmingu,
- do celów bezpieczeństwa preferuj self-contained deployment dla lepszej izolacji i kontroli wersji,
- testy jednostkowe prowadź na wyjściu build, integracyjne i wdrożeniowe na artefaktach publish,
- dokumentuj zespółowe standardy i wytyczne korzystania z build/publish.
Zaawansowane scenariusze i optymalizacje
W rozbudowanych środowiskach enterprise i przy skomplikowanej architekturze, dogłębne zrozumienie różnic między build i publish jest kluczowe:
- Native AOT compilation (PublishAot=true) – radykalnie skraca czas startu i zmniejsza zapotrzebowanie na pamięć, wymaga szczegółowych testów kompatybilności;
- architektury mikrousług mogą łączyć self-contained i FDD w ramach jednej solution;
- trimming pozwala na bardzo małe paczki dla IoT/mobile;
- wielostopniowe buildy Docker oddzielają szybki build/test od publish;
- deployment na wiele platform z jednego pipeline’u;
- monitoring produkcyjny pozwala analizować realny wpływ różnych opcji publish na system.
Wpływ na architekturę aplikacji
Strategie deploymentu mają realny wpływ na decyzje architektoniczne projektu:
- architektura modularna – połączenie build do core modules oraz publish do rozszerzeń/pluginów wymagających izolacji wersji,
- architektura pluginowa – wymusza zróżnicowane podejście do ładowania zależności w zależności od wyboru build/publish,
- inne ścieżki ładowania plików konfiguracyjnych i zależności przy różnych strategiach deploymentu,
- self-contained i ReadyToRun zapewniają szybszy cold start, FDD korzysta z optymalizacji współdzielonego runtime.
Zarządzanie zależnościami i wersjami
Zaawansowane zarządzanie zależnościami polega na właściwym korzystaniu z mechanizmów build i publish:
- centralizacja wersji w
Directory.Build.props, - spójność wersji SDK, runtime i packages na każdym etapie pipeline,
- FDD korzysta z automatycznych aktualizacji bezpieczeństwa, SCD wymaga ręcznego utrzymania aktualności.
Monitorowanie i diagnostyka
Wybór strategii deploymentu wpływa także na monitorowanie, logowanie i proces diagnostyki:
- self-contained deployment daje lepszą izolację i przewidywalność diagnostyczną,
- FDD może prowadzić do konfliktów wersji przy wielu zainstalowanych środowiskach runtime,
- Native AOT zmienia sposób prowadzenia debugowania i diagnostyki,
- różne ścieżki logów i zależności mogą wymagać innych ustawień dla build vs publish.