Po co programiście .NET Docker w 2024 roku
Typowe problemy bez kontenerów
Przy klasycznym podejściu do wdrożeń aplikacji .NET każdy serwer wygląda trochę inaczej. Inna wersja .NET SDK, inne paczki systemowe, różne ustawienia firewalli, inne connection stringi. Efekt to znane hasło: „u mnie działa”. Kod przechodzi testy lokalnie, a po wdrożeniu na serwer produkcyjny pojawiają się błędy, których nikt nie widział wcześniej.
Dochodzi do tego ręczna konfiguracja: instalacja .NET Runtime, konfiguracja IIS lub Kestrel + reverse proxy, biblioteki natywne (np. do raportów PDF czy integracji z systemami legacy). Każdy administrator robi to trochę po swojemu, często bez dokumentacji. Przy większym zespole, kilku środowiskach (dev, test, staging, prod) i mikrousługach robi się z tego chaos trudny do ogarnięcia.
Bez kontenerów onboarding nowych osób jest powolny. Nowy programista .NET dostaje listę kroków do skonfigurowania środowiska, która ma kilka stron. Instalacja odpowiednich wersji SDK, narzędzi, baz danych lokalnie i konfiguracja wszystkiego tak, aby zachowywało się jak produkcja, często zajmuje cały dzień, a czasem kilka dni.
Jak Docker upraszcza życie .NET-developera
Docker wprowadza prosty model: aplikacja + wszystkie zależności siedzą w obrazie kontenera. Obraz jest budowany skryptowo (Dockerfile), więc konfiguracja środowiska jest powtarzalna i wersjonowana w Git. Nie ma już znaczenia, na jakim serwerze działa aplikacja, jeśli tylko jest tam Docker (lub inny kompatybilny runtime kontenerów).
Programista może przygotować obraz aplikacji ASP.NET Core tak, aby zawierał:
- odpowiednią wersję .NET Runtime lub SDK,
- wymagane biblioteki systemowe,
- konfigurację serwera (np. Kestrel, ustawienia portu),
- prekompilowany kod aplikacji.
Taki obraz da się uruchomić na laptopie, w środowisku testowym, a potem w Kubernetes bez modyfikacji. Onboarding nowego dewelopera skraca się do polecenia typu: docker compose up. Całe lokalne środowisko (API, baza, message broker) startuje w kilku kontenerach.
Kontener vs maszyna wirtualna – tylko to, co potrzebne
Częste pytanie: czym Docker różni się od maszyny wirtualnej? Z punktu widzenia programisty .NET ważne są trzy rzeczy:
- VM ma pełny system operacyjny (kernel + user space). Kontener współdzieli kernel hosta, zawiera tylko to, co w user space (biblioteki, runtime, aplikacja).
- Kontener startuje w sekundach, VM w dziesiątkach sekund lub minutach.
- Obraz kontenera jest z reguły dużo mniejszy niż obraz VM.
Kontener to więc lekki, izolowany proces. Idealny do trzymania pojedynczej aplikacji (np. jednej mikrousługi ASP.NET Core). Skaluje się szybko i tanio, w porównaniu z całymi maszynami wirtualnymi.
Gdzie Docker styka się z Kubernetesem
Dla klastra Kubernetes obraz kontenera jest jednostką wdrożenia. Nieważne, czy runtime pod spodem to Docker, containerd czy coś innego – ważne, że jest obraz w zgodnym formacie (OCI). Kubernetes pobiera obraz z rejestru, uruchamia pod, restartuje kontenery przy awarii, skaluje repliki.
Dla programisty .NET praktyka jest prosta:
- lokalnie przygotowujesz Dockerfile,
- budujesz obraz i testujesz kontener,
- w pipeline CI/CD publikujesz obraz do rejestru,
- manifesty Kubernetesa (Deployment, Service, Ingress) wskazują konkretny tag obrazu.
Dobrze przygotowany obraz Dockera to zatem najważniejszy krok, aby wejść w świat Kubernetesa bez bólu. Kiedy obraz działa powtarzalnie lokalnie, łatwiej diagnozować problemy już w klastrze.
Kluczowe pojęcia Dockera w praktyce
Obraz, kontener, warstwy i rejestr na przykładzie .NET
W kontekście aplikacji ASP.NET Core pojęcia są proste:
- obraz to „szablon” zawierający system podstawowy (np. Ubuntu), .NET Runtime oraz pliki aplikacji,
- kontener to uruchomiony egzemplarz obrazu (proces),
- warstwy (layers) to kolejne kroki budowania obrazu cache’owane przez Dockera,
- rejestr (registry) to serwer, z którego pobierasz i do którego wysyłasz obrazy (Docker Hub, Azure Container Registry, GHCR itp.).
Przykład: budujesz obraz dla prostego Web API z .NET 8.0. Bazujesz na oficjalnym obrazie mcr.microsoft.com/dotnet/aspnet:8.0, kopiujesz pliki aplikacji i konfigurujesz ENTRYPOINT. Docker zapisuje to jako kilka warstw: system, .NET, biblioteki, twoje DLL-e. Przy kolejnym buildzie zmieni się tylko warstwa z kodem – reszta zostanie zcache’owana.
Rejestr jest kluczowy, gdy wchodzisz w CI/CD. Pipeline buduje obraz, taguje go np. jako my-api:1.2.3 i wypycha do rejestru. Kubernetes, serwer stagingowy czy inna maszyna produkcyjna pobiera dokładnie ten sam obraz i uruchamia go w kontenerze.
Docker Engine, CLI i Docker Desktop – co faktycznie potrzebne
Na codzienne użycie Dockera składają się trzy elementy:
- Docker Engine – daemon odpowiedzialny za uruchamianie kontenerów i zarządzanie obrazami,
- Docker CLI – narzędzie wiersza poleceń (
docker), którym komunikujesz się z Enginem, - Docker Desktop – aplikacja dla Windows i macOS: GUI + wbudowany Engine.
Na Windowsie i macOS najprościej zainstalować Docker Desktop. Na Linuksie wystarczy sam Engine i CLI z repozytoriów dystrybucji. Do nauki i typowej pracy programisty .NET to w zupełności wystarcza.
Dobre nawyki tagowania obrazów
Obrazy Dockera mają postać repozytorium:nazwa_taga, np. my-api:latest, my-api:1.2.3. Tag latest bywa wygodny lokalnie, ale w środowiskach testowych i produkcyjnych wprowadza chaos. Dla programisty .NET kluczowe są trzy rodzaje tagów:
- tag wersji aplikacji, np.
1.0.0,1.1.0, - tag środowiska, np.
1.0.0-staging(jeśli rozdzielasz obrazy dla różnych środowisk), - tag builda, często numer commita Git (
sha-abc123) lub numer z CI.
Dobra praktyka: dla danego wdrożenia do Kubernetesa używaj konkretnego taga, nigdy samego latest. Ułatwia to rollback, diagnozowanie błędów i odtwarzanie sytuacji sprzed czasu.
Publiczne i prywatne rejestry kontenerów
Najpopularniejsze rejestry w praktyce .NET-owca:
- Docker Hub – publiczne i prywatne repozytoria, często używane do obrazów bazowych.
- GitHub Container Registry (GHCR) – wygodny, gdy kod trzymasz na GitHubie.
- Azure Container Registry (ACR) – naturalny wybór, gdy infrastrukturę masz w Azure.
Modele użycia są podobne: logujesz się, oznaczasz obraz pełną nazwą (np. myregistry.azurecr.io/my-api:1.0.0) i wypychasz go poleceniem docker push. W Kubernetes konfigurujesz imagePullSecrets, aby klaster mógł pobrać obraz z prywatnego rejestru.

Przygotowanie środowiska deweloperskiego .NET + Docker
Windows z WSL2 czy natywny Linux
Dla .NET 6+ wygodnie pracuje się zarówno na Windowsie, jak i na Linuksie. W 2024 roku praktyczny wybór często wygląda tak:
- Windows + WSL2 + Docker Desktop – dobre, gdy korzystasz z Visual Studio, potrzebujesz IIS lub innych narzędzi Windowsowych, ale chcesz uruchamiać kontenery linuksowe.
- Natywny Linux (Ubuntu, Fedora itp.) – mniejsza warstwa pośrednia, prostsze zarządzanie Dockerem i ewentualnie Kubernetesem, świetne do pracy z mikrousługami.
Przy WSL2 Docker Desktop działa pod spodem na lekkiej maszynie linuksowej i udostępnia DAE Mon zarówno w Windows, jak i w WSL. Aplikacje .NET i Docker mogą być uruchamiane z poziomu WSL, co eliminuje wiele problemów z różnicami w ścieżkach i systemie plików.
Co zainstalować, aby ruszyć z Dockerem
Minimalny zestaw narzędzi dla programisty .NET zaczynającego z Dockerem:
- .NET SDK 8.0 (lub 7.0, jeśli projekt jeszcze nie jest na najnowszej wersji),
- Docker Desktop (Windows, macOS) lub Docker Engine + CLI (Linux),
- edytor/IDE: Visual Studio, Rider lub VS Code z rozszerzeniem C# i Docker.
Po instalacji uruchom podstawowy test:
docker version
docker info
docker run hello-world
Jeśli hello-world wypisze komunikat o poprawnym uruchomieniu kontenera, środowisko jest gotowe do pracy.
Przykładowy projekt ASP.NET Core jako baza
Dobrym punktem startu jest proste Web API. W katalogu roboczym uruchom:
dotnet new webapi -n Demo.Api
cd Demo.Api
dotnet run
Domyślny szablon ASP.NET Core Web API tworzy minimalne API z jednym kontrolerem WeatherForecast. Aplikacja domyślnie nasłuchuje na porcie 5000/5001 (HTTPS), co później zostanie wystawione w kontenerze.
Jeśli interesuje cię szerszy kontekst narzędzi, projekt więcej o informatyka dobrze pokazuje, jak narzędzia developerskie układają się w większy obraz ekosystemu nowych technologii.
Tak przygotowany projekt posłuży dalej jako przykładowa aplikacja do konteneryzacji, optymalizacji obrazów i uruchomienia w Docker Compose oraz Kubernetesie.
Pierwszy Dockerfile dla aplikacji .NET – krok po kroku
Struktura bazowego projektu .NET
W typowym projekcie Web API utworzonym szablonem zobaczysz pliki:
Program.cs– konfiguracja hosta i pipeline’u HTTP (minimal hosting model),appsettings.json– podstawowa konfiguracja,Properties/launchSettings.json– profile uruchomieniowe lokalnie,Controllers/WeatherForecastController.cs– przykładowy kontroler.
Dla budowy obrazu Dockera kluczowe jest, aby Dockerfile leżał w katalogu rozwiązania lub projektu, a kontekst builda zawierał pliki *.csproj, pliki źródłowe i konfigurację.
Multi-stage build: build stage i runtime stage
Multi-stage build to wzorzec, w którym budowanie aplikacji odbywa się w jednym etapie, a wynik (opublikowane pliki) kopiujesz do drugiego, lżejszego obrazu runtime. Daje to kilka korzyści:
- obraz końcowy jest mniejszy (brak SDK, narzędzi buildowych),
- zmniejsza się powierzchnia ataku (mniej narzędzi w kontenerze produkcyjnym),
- build jest czystszy – oddzielasz kompilację od uruchomienia.
Dla .NET to praktycznie standard. Używasz obrazu mcr.microsoft.com/dotnet/sdk:8.0 jako etapu build i mcr.microsoft.com/dotnet/aspnet:8.0 jako etapu runtime.
Przykładowy Dockerfile dla ASP.NET Core
Poniżej pełen, prosty Dockerfile dla minimalnego Web API w .NET 8:
# Etap build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Kopiowanie pliku csproj i przyspieszenie restore
COPY Demo.Api.csproj ./
RUN dotnet restore Demo.Api.csproj
# Kopiowanie reszty kodu i build
COPY . .
RUN dotnet publish Demo.Api.csproj -c Release -o /app/publish
# Etap runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Demo.Api.dll"]
Kluczowe elementy:
WORKDIR /srciWORKDIR /appustawiają katalog roboczy,- osobne
COPY Demo.Api.csproj ./przeddotnet restorepomaga w cache’u zależności, ASPNETCORE_URLS=http://+:8080ustawia port wewnątrz kontenera,EXPOSE 8080to informacja „dokumentacyjna”, która pomaga narzędziom (np. Docker Desktop) sugerować mapowanie portów.
Budowanie i uruchamianie obrazu Dockera
Z katalogu, w którym leży Dockerfile, wykonaj:
Budowanie obrazu krok po kroku
Podstawowy build obrazu dla projektu Demo.Api wygląda tak:
docker build -t demo-api:1.0.0 .
Elementy komendy:
-t demo-api:1.0.0– nadanie nazwy i taga,.– kontekst builda, czyli bieżący katalog (Docker szuka tam Dockerfile i plików źródłowych).
Po zbudowaniu możesz sprawdzić, czy obraz istnieje:
docker images | grep demo-api
Następny krok to uruchomienie kontenera z mapowaniem portu:
docker run --rm -p 8080:8080 --name demo-api demo-api:1.0.0
-p 8080:8080– mapuje port hosta 8080 na port kontenera 8080,--rm– po zatrzymaniu kontenera usuwa go automatycznie,--name demo-api– łatwiejsze odwoływanie się do kontenera.
Po uruchomieniu sprawdzasz endpoint:
curl http://localhost:8080/weatherforecast
Typowe pułapki przy pierwszym Dockerfile .NET
Pierwsze podejście do Dockerfile’a potrafi skończyć się na kilku klasycznych błędach:
- Kontekst builda za szeroki – budowa z poziomu katalogu wyżej niż trzeba (np. całe repo z
.git) wydłuża build i powiększa warstwy. - Brak cache’owania restore – wrzucenie
COPY . .przeddotnet restorepowoduje ponowny restore przy każdej zmianie pliku. - Twarde ścieżki – używanie ścieżek zależnych od systemu (np.
C:temp) otwiera drogę do niespodzianek w kontenerze linuksowym.
Dobry nawyk: trzymaj Dockerfile w katalogu projektu (tam, gdzie .csproj), buduj z docker build ., a pliki ignorowane skonfiguruj przez .dockerignore.
.dockerignore – mniej śmieci w obrazie
Bez .dockerignore do kontekstu builda trafia wszystko z katalogu. To droga na skróty do długich buildów i niepotrzebnych danych w warstwach.
Przykładowy .dockerignore dla projektu .NET:
bin/
obj/
**/bin/
**/obj/
.git/
.gitignore
.vscode/
.idea/
*.user
*.suo
*.swp
Dockerfile*
docker-compose*.yml
Plik .dockerignore połóż obok Dockerfile. Docker przed zbudowaniem obrazu zastosuje filtr i nie wyśle tych plików do demona.

Obrazy .NET, rozmiar i wydajność – jak nie płacić za powietrze
Wybór właściwego obrazu bazowego .NET
Microsoft publikuje kilka wariantów obrazów .NET. Dla ASP.NET Core w 2024 roku najczęściej wchodzą w grę:
mcr.microsoft.com/dotnet/aspnet:8.0– standardowy runtime,mcr.microsoft.com/dotnet/aspnet:8.0-alpine– lżejszy, oparty na Alpine Linux,mcr.microsoft.com/dotnet/runtime:8.0– dla aplikacji konsolowych, workerów itp.
Jeśli zależy ci głównie na kompatybilności i szybszym rozwiązywaniu problemów, zacznij od standardowego obrazu. Alpine ma mniejszy rozmiar, ale czasem potrafi „wystrzelić” z problemami w natywnych zależnościach i diagnostyce.
Minimalizacja rozmiaru – szybkie wygrane
Kilka prostych zmian, które zwykle zmniejszają obraz o dziesiątki procent:
- użycie multi-stage build (już jest w Dockerfile),
- przerzucenie na Alpine, gdy aplikacja nie używa ciężkich natywnych bibliotek,
- czyste
dotnet publishz parametrami ograniczającymi output.
Przykład Dockerfile z Alpine:
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
COPY Demo.Api.csproj ./
RUN dotnet restore Demo.Api.csproj
COPY . .
RUN dotnet publish Demo.Api.csproj -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS runtime
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Demo.Api.dll"]
Trimming i single-file – kiedy mają sens
Dla prostych usług i workerów można użyć publish trimmingu i single-file, żeby zejść niżej z rozmiarem i skrócić cold-start. Przykład:
dotnet publish Demo.Api.csproj -c Release -o /app/publish
-p:PublishTrimmed=true
-p:PublishSingleFile=true
-p:SelfContained=false
Przy trimmingu trzeba dobrze przetestować aplikację. Reflection, dynamiczne ładowanie typów, pluginy – to miejsca, gdzie trimmer potrafi usunąć coś, co jednak jest potrzebne.
Porządek w warstwach – łączenie RUN i czyszczenie cache’y
Przy dodawaniu własnych zależności systemowych warto łączyć komendy RUN i czyścić cache managera pakietów:
RUN apt-get update &&
apt-get install -y --no-install-recommends libpq5 &&
rm -rf /var/lib/apt/lists/*
Jedna warstwa zamiast trzech, mniej śmieci w obrazie. W projektach, gdzie budujesz obraz dziesiątki razy dziennie, tę różnicę widać bardzo szybko.
Praca lokalna z wieloma usługami – Docker Compose dla .NET-owca
Po co Compose przy zwykłym Web API
Pojedynczy kontener dla API to początek. W typowej aplikacji dochodzą:
- baza danych (SQL Server, PostgreSQL),
- baza cache (Redis),
- broker wiadomości (RabbitMQ, Kafka),
- inne usługi .NET, które komunikują się po HTTP/GRPC.
Odpalenie tego ręcznie docker run ... dla każdej usługi szybko przestaje mieć sens. Docker Compose pozwala opisać cały zestaw w jednym pliku YAML i ruszyć go jedną komendą.
Minimalny docker-compose.yml dla API + bazy
Przykład zestawu: Demo.Api + PostgreSQL:
version: "3.9"
services:
api:
image: demo-api:1.0.0
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ConnectionStrings__Default=Host=db;Port=5432;Database=demo;Username=demo;Password=demo
depends_on:
- db
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=demo
- POSTGRES_PASSWORD=demo
- POSTGRES_DB=demo
volumes:
- demo-db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
demo-db-data:
Tutaj api odwołuje się do bazy po hostname db (nazwa usługi = nazwa hosta w sieci Compose). Nie trzeba znać IP, Compose sam tworzy sieć i DNS.
Uruchamianie i praca z Docker Compose
Typowy cykl:
docker compose up --build
--buildwymusza zbudowanie obrazów według sekcjibuild,- bez parametrów Compose loguje wszystko w jednym strumieniu.
W drugim terminalu możesz podsłuchiwać tylko logi API:
docker compose logs -f api
Zatrzymanie zestawu:
docker compose down
Jeżeli chcesz skasować też volumy (np. czysty stan bazy):
docker compose down -v
Profile Compose – różne zestawy dla dev/test
Przy rozbudowanych projektach dobrze jest podzielić środowiska na profile Compose. Przykład:
services:
api:
...
db:
...
redis:
...
profiles: ["dev"]
seq:
...
profiles: ["dev", "debug"]
Uruchomienie tylko podstawowych usług:
docker compose --profile "" up
Uruchomienie z dev-toolami:
Jeśli interesują Cię konkrety i przykłady, rzuć okiem na: Kubernetes na start: k3s, MicroK8s czy Minikube — co ma sens na laptopie?.
docker compose --profile dev --profile debug up

Debugowanie i workflow developerski z Dockerem
Tryb „dev” vs tryb „prod” w kontenerze
Kontener możesz używać na dwa sposoby:
- dev – kod na hoście, kontener tylko jako „runtime” (np. baza danych, Nginx),
- prod-like – aplikacja zbudowana w obrazie, kontener jak w staging/produkcji.
Dla debugowania logiki biznesowej wygodny bywa pierwszy tryb: odpalasz dotnet run z hosta, a baza i inne zależności jadą w kontenerach. Dla testów integracyjnych i przygotowania do Kubernetesa lepiej zbliżyć się do scenariusza produkcyjnego – aplikacja też w kontenerze.
Visual Studio + Docker – co faktycznie używać
Visual Studio ma wbudowane wsparcie Dockera:
- dodanie wsparcia przez „Add > Docker Support”,
- profil uruchomieniowy „Docker”,
- integrację z docker-compose, jeśli wybierzesz multi-container.
Automatycznie generowany Dockerfile zwykle działa, ale warto go potem uprościć i dostosować do swoich zasad (tagowanie, zmienne środowiskowe, healthcheck). Dobrze jest też rozumieć, co dzieje się pod spodem: VS przy każdym F5 buduje obraz, uruchamia kontener z odpowiednimi portami i attachuje debugger.
VS Code, devcontainers i debug przez CLI
W VS Code scenariusze są dwa:
- Debug na hoście, kontenery tylko jako zależności.
- Praca w devcontainerze, gdzie całe środowisko dev siedzi w kontenerze.
Devcontainer (folder .devcontainer) opisuje obraz, rozszerzenia VS Code i ustawienia. Zespół może wtedy dostać identyczne środowisko niezależnie od hosta. Komenda Remote-Containers: Reopen in Container przerzuca edycję i build do tego kontenera.
Debugowanie aplikacji .NET działającej w kontenerze można też ogarnąć z poziomu CLI:
docker exec -it demo-api bash
dotnet --info
albo przez attachowanie z VS Code do procesu dotnet wewnątrz kontenera (launch configuration typu attach + podanie PID/portu).
Podgląd logów, shell w kontenerze i inspekcja stanu
Kilka komend, które wchodzą w nawyk:
docker ps
docker logs -f demo-api
docker exec -it demo-api sh # lub bash, jeśli jest
docker inspect demo-api
Przy problemach z siecią między kontenerami:
docker exec -it demo-api sh
apk add curl # na Alpine, jednorazowo w kontenerze dev
curl http://db:5432
Taki szybki test często pokazuje, czy problem leży w DNS/porcie, czy w samej aplikacji.
Konfiguracja, sekrety i zmienne środowiskowe w kontenerach
Konfiguracja ASP.NET Core a zmienne środowiskowe
ASP.NET Core 8 składa konfigurację z kilku źródeł:
appsettings.jsoni jego warianty środowiskowe,- zmienne środowiskowe,
- sekrety użytkownika (Secret Manager),
- zewnętrzne dostawce (Key Vault, Consul itp.).
W kontenerach naturalnym źródłem są zmienne środowiskowe. Dla sekcji zagnieżdżonych używaj __ jako separatora. Przykład:
environment:
- Logging__LogLevel__Default=Information
- ConnectionStrings__Default=Host=db;Database=demo;Username=demo;Password=demo
Aplikacja wewnątrz kontenera odczyta to tak, jakby wartości były w appsettings.json.
Parametryzacja przez docker run i Compose
Dla szybkich testów można nadawać zmienne bezpośrednio:
docker run --rm -p 8080:8080
-e ASPNETCORE_ENVIRONMENT=Staging
-e ConnectionStrings__Default="Host=db;Database=demo;Username=demo;Password=demo"
demo-api:1.0.0
W Compose wygodniej trzymać konfigurację w jednym miejscu:
services:
api:
environment:
ASPNETCORE_ENVIRONMENT: Development
ConnectionStrings__Default: Host=db;Database=demo;Username=demo;Password=demo
Pliki .env i podział konfiguracji na środowiska
Jeżeli konfiguracja zaczyna puchnąć, użyj plików .env. Compose domyślnie wczyta plik .env z katalogu, w którym leży docker-compose.yml:
Łączenie .env z docker-compose.yml
Plik .env przechowuje wartości, a docker-compose.yml opisuje strukturę usług. Przykład:
# .env
ASPNETCORE_ENVIRONMENT=Development
DB_HOST=db
DB_NAME=demo
DB_USER=demo
DB_PASSWORD=demo
# docker-compose.yml
services:
api:
image: demo-api:1.0.0
environment:
ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT}
ConnectionStrings__Default: Host=${DB_HOST};Database=${DB_NAME};Username=${DB_USER};Password=${DB_PASSWORD}
Dla wariantu staging można użyć .env.staging i przełączać plik:
cp .env.staging .env
docker compose up -d
Bez zmian w compose, zmienia się tylko zawartość .env.
Oddzielenie sekretów od konfiguracji „nie-wrażliwej”
W konfiguracji mieszają się dwa typy danych:
- parametry środowiska (host bazy, nazwy kolejek, flagi feature’ów),
- wrażliwe dane (hasła, connection stringi, klucze API).
Pierwsze bez problemu mogą żyć w repo (np. defaultowe wartości w appsettings.Development.json), drugie nie powinny trafiać do gita ani do plików współdzielonych szeroko w zespole.
Prosty podział:
.envw repo: tylko niewrażliwe ustawienia,.env.local(ignorowany przez git): nadpisuje wrażliwe rzeczy lokalnie,- sekrety w systemie docelowym (Kubernetes secrets, Key Vault),
- w dev – Secret Manager (
dotnet user-secrets) albo lokalne pliki ignorowane przez git.
Przekazywanie sekretnych danych w Compose
Docker Compose ma sekcję secrets, ale w praktyce w środowisku developerskim szybciej używa się zwykłych zmiennych środowiskowych z plików .env ignorowanych przez git:
# .env.local (dodaj do .gitignore)
DB_PASSWORD=supersekret
API_KEY_SOME_SERVICE=xyz
# docker-compose.override.yml
services:
api:
environment:
DB_PASSWORD: ${DB_PASSWORD}
ApiKeys__SomeService: ${API_KEY_SOME_SERVICE}
Plik docker-compose.override.yml jest automatycznie wczytywany przez docker compose, możesz mieć inny dla każdego developera (np. sufiks z nazwą użytkownika) i trzymać go poza repozytorium.
Kaskada konfiguracji w ASP.NET Core i kontenery
Standardowa kolejność źródeł konfiguracji ASP.NET Core daje taki efekt:
appsettings.json– konfiguracja bazowa,appsettings.<Environment>.json– np.appsettings.Development.json,- zmienne środowiskowe – nadpisują to, co w JSON-ach,
- opcjonalnie: dostawcy zewnętrzni (Key Vault, Consul, ConfigMap + Secret w K8s).
W praktyce:
- do repo idą
appsettings.jsoniappsettings.Development.jsonz bezpiecznymi wartościami domyślnymi, - w kontenerze w staging/produkcji większość wartości jest nadpisywana zmiennymi środowiskowymi.
Kod konfiguracji w Program.cs nie musi się specjalnie zmieniać. Cała „magia” dzieje się na poziomie środowiska i orkiestracji.
Ładowanie sekretnych ustawień z zewnętrznego źródła
Jeżeli środowisko oferuje menedżera sekretów (np. Azure Key Vault), można spiąć go bezpośrednio z kontenerem. Przykładowo, dla Azure:
builder.Configuration
.AddAzureKeyVault(
new Uri(builder.Configuration["KeyVault__Url"]),
new DefaultAzureCredential());
Kluczowa rzecz: adres Key Vault i sposób uwierzytelnienia są w zmiennych środowiskowych, natomiast konkretne hasła i connection stringi nigdy nie lądują w obrazie Dockera ani w repo.
Debugowanie konfiguracji w kontenerze
Przy problemach z konfiguracją dobrze jest od środka sprawdzić, co aplikacja faktycznie widzi. Kilka szybkich trików:
- uruchom kontener z dodatkową zmienną debugującą, np.
DEBUG_CONFIG_DUMP=true, - na starcie aplikacji, jeżeli ta flaga jest ustawiona, loguj wybrane sekcje konfiguracji (bez sekretów!),
- wejdź do kontenera i podejrzyj zmienne środowiskowe.
docker exec -it demo-api sh
env | sort | grep ConnectionStrings
Dobrym nawykiem jest stworzenie małego endpointu diagnostycznego, który zwróci tylko „bezpieczną” konfigurację (np. nazwy usług, adresy hostów, tryb środowiska), bez haseł i kluczy. Ułatwia to śledzenie różnic między dev/staging/prod.
Docker a Kubernetes w projekcie .NET
Od obrazu Dockera do manifestu Kubernetesa
Jeżeli obraz Dockera jest sensownie zrobiony (mały, z healthcheckiem, bez zbędnych narzędzi), przejście do Kubernetesa sprowadza się do opisania:
- jak uruchomić kontener (Deployment/StatefulSet),
- jak wystawić go w klasrze (Service, Ingress),
- skąd pobrać konfigurację i sekrety (ConfigMap, Secret),
- jak skalować (replicas, autoscaling).
Minimalny Deployment dla API .NET może wyglądać tak:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-api
spec:
replicas: 2
selector:
matchLabels:
app: demo-api
template:
metadata:
labels:
app: demo-api
spec:
containers:
- name: demo-api
image: demo-api:1.0.0
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
Przy takim podejściu wszystkie praktyki, które zostały wypracowane przy Dockerze lokalnie (ports, healthcheck, zmienne środowiskowe), mają bezpośrednie przełożenie na Kubernetesa.
ConfigMap i Secret jako źródła konfiguracji
ConfigMap służy do „zwykłej” konfiguracji, Secret do wrażliwych danych. Aplikacja .NET i tak widzi je jako zmienne środowiskowe, więc kod nie musi być świadomy, skąd pochodzą.
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-api-config
data:
ASPNETCORE_ENVIRONMENT: "Production"
Logging__LogLevel__Default: "Information"
apiVersion: v1
kind: Secret
metadata:
name: demo-api-secrets
type: Opaque
data:
ConnectionStrings__Default: <base64-encoded-connection-string>
Podpięcie ich w Deployment:
envFrom:
- configMapRef:
name: demo-api-config
- secretRef:
name: demo-api-secrets
Z perspektywy ASP.NET Core wszystko ląduje w standardowym systemie konfiguracji. Różnicę czuć tylko po stronie operacyjnej.
Health checki .NET i sondy Kubernetesa
ASP.NET Core posiada wbudowaną infrastrukturę health-checków:
builder.Services.AddHealthChecks()
.AddNpgSql(builder.Configuration.GetConnectionString("Default"));
var app = builder.Build();
app.MapHealthChecks("/health");
app.Run();
W Kubernetesa przekłada się to na liveness i readiness probe:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Jeżeli aplikacja przestaje odpowiadać lub baza pada, Kubernetes automatycznie restartuje lub wyłącza pod z ruchu. To zachowanie warto najpierw przećwiczyć lokalnie, uruchamiając API w kontenerze Dockera i ręcznie sprawdzając endpoint healthchecka przy różnych awariach zależności.
Praca lokalna z Kubernetesem a Docker
Kilka opcji na środowisko K8s przy biurku:
- kind (Kubernetes in Docker) – klaster uruchomiony w kontenerach,
- minikube – pojedynczy węzeł K8s w VM/na hoście,
- Docker Desktop Kubernetes – dostępny na Windows/Mac,
- klastry zarządzane (np. AKS) z dostępem zdalnym – dobre dla zespołów.
Typowy flow dla kind:
Jeśli chcesz pójść krok dalej, pomocny może być też wpis: C# w chmurze Azure: jak pisać aplikacje, które łatwo skalować i utrzymać.
kind create cluster --name demo
# budowa obrazu lokalnie
docker build -t demo-api:1.0.0 .
# załadowanie obrazu do klastra kind
kind load docker-image demo-api:1.0.0 --name demo
# deploy manifestów
kubectl apply -f k8s/
Bez pushowania obrazu do zewnętrznego rejestru. Szybki sposób na weryfikację, jak manifesty Kubernetesowe współpracują z tym samym obrazem, którego używasz w Compose.
Mapowanie portów i obserwacja ruchu
W K8s nie ma już prostego -p 8080:8080, ale narzędzia typu kubectl port-forward dają podobny efekt:
kubectl port-forward deployment/demo-api 8080:8080
Równolegle użyj kubectl logs -f, aby obserwować, jak ruch z lokalnego portu trafia do podów w klastrze:
kubectl logs -f deploy/demo-api
Ten zestaw komend w praktyce zastępuje docker run -p i docker logs, które były używane na początku przy samym Dockerze.
Strategia rolloutów i współgranie z pipeline’ami
Gotowy obraz Dockera z tagowaniem opartym o wersje/commit staje się centralnym artefaktem CI/CD. Pipeline:
- buduje i testuje aplikację,
- buduje obraz Dockera i wysyła do rejestru (ACR/ECR/GHCR),
- aktualizuje manifesty Kubernetesa (np. zmiana tagu),
- wywołuje rollout w klastrze.
Żeby ułatwić ten proces:
- utrzymuj Dockerfile w repo obok kodu,
- używaj powtarzalnego schematu tagów (np.
demo-api:1.2.3,demo-api:1.2.3-commitsha), - traktuj manifesty K8s jak kod – wersjonuj, review, testuj.
Dzięki temu lokalne docker compose up i produkcyjny rollout na Kubernetesie używają tej samej bazy: obrazu Dockera zbudowanego według tych samych zasad.
Najczęściej zadawane pytania (FAQ)
Dlaczego jako programista .NET w 2024 roku powinienem używać Dockera?
Docker rozwiązuje typowe problemy „u mnie działa”. Aplikacja .NET, jej runtime, paczki systemowe i konfiguracja lądują w jednym obrazie. Ten sam obraz uruchamiasz na laptopie, testach, stagingu i w produkcji – bez ręcznego ustawiania serwerów.
Onboarding nowych osób w zespole też przyspiesza. Zamiast kilkustronicowej instrukcji instalacji środowiska, nowy developer odpala docker compose up i ma lokalnie cały zestaw usług (API, baza, message broker) w kilku kontenerach.
Jak zacząć z Dockerem dla aplikacji ASP.NET Core krok po kroku?
Minimalny start wygląda tak:
- Zainstaluj .NET SDK (najlepiej 8.0) oraz Docker Desktop (Windows/macOS) albo Docker Engine + CLI (Linux).
- W projekcie ASP.NET Core dodaj prosty
Dockerfileoparty namcr.microsoft.com/dotnet/aspnet:8.0i skopiuj do niego zbudowaną aplikację. - Zbuduj obraz
docker build -t my-api:dev ., a potem uruchomdocker run -p 8080:80 my-api:devi sprawdź API w przeglądarce lub przez curl/Postmana.
Czym Docker różni się od maszyny wirtualnej w kontekście .NET?
Maszyna wirtualna ma pełny system operacyjny z własnym kernelem. Kontener współdzieli kernel hosta i zawiera tylko część user space: biblioteki, runtime .NET i samą aplikację. Dzięki temu obrazy są lżejsze, a kontenery startują w sekundach, a nie minutach.
W praktyce kontener traktujesz jak lekki, izolowany proces dla jednej aplikacji lub mikrousługi ASP.NET Core. VM sprawdza się jako „grubsza” jednostka infrastruktury, kontener – jako najmniejsza sensowna jednostka wdrożenia.
Jak połączyć Docker i Kubernetes dla aplikacji .NET?
Docker służy do zbudowania i przetestowania obrazu lokalnie. W Dockerfile przygotowujesz środowisko dla swojej aplikacji .NET, budujesz obraz i uruchamiasz kontener, żeby sprawdzić, czy wszystko działa tak, jak trzeba.
Potem pipeline CI/CD wypycha ten sam obraz do rejestru (np. ACR, Docker Hub, GHCR). W manifestach Kubernetesa (Deployment, Service, Ingress) wskazujesz konkretny tag obrazu. Kubernetes pobiera ten obraz, uruchamia pody, skaluje repliki i restartuje kontenery przy awarii.
Jaki system wybrać pod Docker + .NET: Windows z WSL2 czy Linux?
Dla większości nowych projektów .NET 6+ wygodne są dwa scenariusze:
- Windows + WSL2 + Docker Desktop – dobry wybór, gdy używasz Visual Studio i innych narzędzi windowsowych, ale chcesz pracować na obrazach linuksowych. Docker Desktop działa w tle na lekkiej maszynie linuksowej.
- Natywny Linux (np. Ubuntu) – prostsza konfiguracja Dockera i Kubernetesa, mniej warstw pośrednich, często lepszy wybór przy mikroserwisach.
Jeśli dopiero wchodzisz w kontenery i siedzisz na Windowsie, zacznij od Docker Desktop + WSL2. Na serwerach produkcyjnych i w klastrach K8s zazwyczaj i tak używany jest Linux.
Jak poprawnie tagować obrazy Dockera dla aplikacji .NET?
Unikaj polegania na samym latest poza lokalnym developmentem. W testach i produkcji stosuj tagi, które jednoznacznie wskazują wersję:
- tag wersji aplikacji, np.
1.0.0,1.1.0, - tag powiązany z buildem/commitem, np.
1.0.0-sha-abc123, - opcjonalnie tag środowiska, np.
1.0.0-staging.
Dzięki temu możesz łatwo zrobić rollback do konkretnej wersji obrazu w Kubernetesie albo odtworzyć dokładnie te same warunki, w których wystąpił błąd.
Jakie rejestry kontenerów wybrać do projektów .NET i czym się różnią?
W praktyce dla .NET–owca liczą się głównie trzy opcje:
- Docker Hub – najpopularniejszy, dobre miejsce na obrazy bazowe i proste projekty, obsługuje repozytoria publiczne i prywatne.
- GitHub Container Registry (GHCR) – naturalny wybór, jeśli kod trzymasz na GitHubie i chcesz, by CI/CD od razu publikował obraz tam, gdzie jest repozytorium.
- Azure Container Registry (ACR) – sensowny, gdy infrastruktura stoi w Azure; integruje się z AKS i Azure DevOps.
Proces jest podobny wszędzie: logujesz się, tagujesz obraz pełną ścieżką (np. myregistry.azurecr.io/my-api:1.0.0) i wykonujesz docker push. W Kubernetesie konfigurujesz imagePullSecrets, żeby klaster miał dostęp do prywatnego rejestru.
Źródła
- Docker Overview. Docker, Inc. – Oficjalne wprowadzenie do Dockera, obrazy, kontenery, rejestry
- Docker Engine Overview. Docker, Inc. – Architektura Docker Engine, daemon, CLI, podstawy działania
- Open Container Initiative Image Format Specification. Open Container Initiative – Specyfikacja formatu obrazów kontenerów OCI
- Kubernetes Concepts. Cloud Native Computing Foundation – Podstawowe pojęcia: Pod, Deployment, Service, Ingress
- .NET and Docker overview. Microsoft – Oficjalne wskazówki użycia Dockera z aplikacjami .NET
- Azure Container Registry Documentation. Microsoft Azure – Koncepcje ACR, push/pull obrazów, integracja z Kubernetes
- GitHub Container Registry Documentation. GitHub – Zasady użycia GHCR, logowanie, tagowanie i publikacja obrazów






