Parte 1 - Dockerfile pentru API .NET
Un Dockerfile e o rețetă care descrie cum se construiește o imagine pas cu pas. Fiecare linie e o instrucțiune (FROM, RUN, COPY, etc.) care produce un layer - un strat al filesystem-ului final. Layer-ele sunt cached: dacă o linie nu se schimbă, build-ul reutilizează layer-ul existent.
Structura
Instrucțiunile esențiale:
| Instrucțiune | Ce face |
|---|---|
FROM imagine:tag |
imaginea de bază pe care construim (linie obligatorie, prima) |
WORKDIR /path |
setează directorul curent în container |
COPY src dest |
copiază fișiere din build context în imagine |
RUN comanda |
execută o comandă la build time (generează un layer nou) |
ENV VAR=value |
setează variabile de mediu |
EXPOSE 8080 |
declarativ - documentare port (nu publică efectiv) |
USER nume |
schimbă user-ul pentru comenzile următoare și runtime |
ENTRYPOINT [...] |
comanda principală la docker run |
CMD [...] |
argumentele default (sau comanda standalone) la docker run |
ENTRYPOINT vs CMD: ENTRYPOINT e binarul principal (ex dotnet), CMD e argumentele default (ex ["app.dll"]). La docker run, argumentele suplimentare se adaugă la ENTRYPOINT.
Multi-stage build pentru .NET
Pentru .NET aveți nevoie de două stagii:
- Build stage - folosește imaginea SDK (~700MB), face
dotnet restore+dotnet publish. - Runtime stage - folosește imaginea aspnet (~110MB pe alpine), copiază output-ul de publish din stage 1.
Imaginea finală conține doar runtime-ul + DLL-urile aplicației. SDK-ul, sursele, build-ul intermediar - nu sunt acolo. Atac surface mai mic, transferuri mai rapide, deploy mai eficient.
# Stage 1: Build cu .NET 8 SDK
FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
WORKDIR /src
# Layer cache: COPY *.csproj inainte de COPY restul sursei.
# Daca dependintele NuGet nu se schimba, layer-ul de restore e cached.
COPY *.csproj ./
RUN dotnet restore Lab12.csproj
# Acum copiem restul sursei si compilam
COPY . .
RUN dotnet publish Lab12.csproj -c Release -o /out --no-restore /p:UseAppHost=false
# Stage 2: Runtime ASP.NET Core 8
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
# Copiem output-ul publish-ului din stage 1
COPY --from=build /out .
# User non-root pentru security (least privilege)
RUN mkdir -p /app/logs \
&& addgroup -S app \
&& adduser -S -G app app \
&& chown -R app:app /app
USER app
# Kestrel asculta default pe localhost:5000 - in container nu accepta conexiuni externe.
# Forteaza ascultarea pe 0.0.0.0:8080 prin env var.
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "Lab12.dll"]
De ce ordinea instrucțiunilor contează
Layer caching merge secvențial: dacă o instrucțiune se schimbă, toate cele de după se re-execută. Asta înseamnă că puneți ce se schimbă rar la început și ce se schimbă des la final.
Exemplul concret: dotnet restore rulează pe baza lui *.csproj. Dacă .csproj nu s-a schimbat (NuGet packages nu s-au schimbat), restore-ul e cached - economisiți 30-60s de download.
Dacă scrieți:
COPY . .
RUN dotnet restore
RUN dotnet publish ...
Atunci la fiecare modificare în cod, layer-ul COPY . . se invalidează, deci dotnet restore rulează din nou (download NuGet de la zero). Lent.
Cu pattern-ul corect:
COPY *.csproj ./ # se schimba doar la add/remove package
RUN dotnet restore
COPY . . # se schimba la fiecare salvare cod
RUN dotnet publish
Restore-ul rămâne cached până când modificați .csproj. Magnific.
.dockerignore
Ca .gitignore, dar pentru Docker. Definește ce NU intră în build context. Util pentru:
- Performanță (context-ul mai mic = upload mai rapid către Docker daemon)
- Security (nu trimiteți
.env, chei, etc. către daemon) - Corectness (nu copiați
bin/cu artefacte de Windows într-un container Linux)
Pentru .NET API tipic:
**/bin/
**/obj/
**/.vs/
**/*.user
.git/
.github/
**/.DS_Store
README.md
*.md
# Frontend separat - alt build context
news-portal-app/
# Tests nu se livreaza in productie
Lab12.Tests/
# Sensitive
*.env
!.env.example
certs/
**/logs/
Fără .dockerignore, primul COPY . . ar copia 200+MB de bin/Debug/net8.0/. Cu el, doar codul real (~5MB).
Build + run (când ajungem la deploy)
Comenzile uzuale pentru un Dockerfile, rulate pe server-ul cursului (după ce am instalat Docker în secțiunea 04):
# Build din folder-ul cu Dockerfile (build context = .)
docker build -t newsportal-api .
# Verificati imaginea construita
docker images | grep newsportal-api
# REPOSITORY TAG IMAGE ID CREATED SIZE
# newsportal-api latest abc123def456 12 seconds ago 118MB
# Pornire standalone (mapeaza port 8080 host -> 8080 container)
docker run -d --name newsportal-api-test -p 8080:8080 newsportal-api
# Logs
docker logs -f newsportal-api-test
# Cleanup
docker stop newsportal-api-test && docker rm newsportal-api-test
Containerul pornește și crash-ează imediat: aplicația încearcă să se conecteze la Postgres (Host=db;...), dar nu există nicio bază de date încă. Ăsta-i pretextul perfect pentru secțiunea 02 - docker compose, care orchestrează și DB-ul, și API-ul, și proxy-ul, într-un singur ansamblu.
Ce ați învățat
- Multi-stage build pentru .NET (SDK -> aspnet, imagine finală mică)
- Layer caching prin ordinea COPY/RUN
.dockerignorepentru context curat- USER non-root (least privilege)
ASPNETCORE_URLS=http://+:8080(Kestrel ascultă pe 0.0.0.0)EXPOSE(declarativ) vs-p(efectiv la run)
În secțiunea următoare legăm toate piesele cu docker compose.