S

super-dev — full-stack

@super-dev.app · Full-stack · ★ ouvert aux missions
12,8k abonnés·47 vidéos·Actif depuis 2017

Développeur full-stack .NET / Angular avec une casquette DevOps sur Azure Cloud. Aussi à l'aise sur Flutter + Firebase. Je construis des produits, je les déploie, je les surveille.

Retour aux articles
DEVOPS
DEVOPS

Images Docker multi-stage pour .NET + Angular

Article 5 sur 6 — Azure DevOps de zéro
Prerender, Container Apps, secrets, CI/CD, Docker, observabilité : livrer sur Azure sans serveur ni douleur.

Embarquer le SDK .NET et node_modules dans l'image qu'on déploie en prod, c'est expédier 800 Mo d'outillage qui ne servira jamais à l'exécution. Le build multi-stage sépare ce qui compile de ce qui tourne : on obtient une image finale minuscule, ne contenant que le strict nécessaire au runtime.

Le principe : compiler puis jeter

Un Dockerfile multi-stage déclare plusieurs FROM . Chaque FROM ouvre un stage isolé ; seul le dernier stage devient l'image livrée. On copie sélectivement les artefacts d'un stage de build vers un stage de runtime, et tout le reste — SDK, sources, caches — est abandonné.

Bash
1# Stage 1 : build de l'API .NET
2FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
3WORKDIR /src
4COPY *.csproj ./
5RUN dotnet restore
6COPY . ./
7RUN dotnet publish -c Release -o /app/publish
8
9# Stage 2 : runtime seul (pas de SDK)
10FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble-chiseled AS final
11WORKDIR /app
12COPY --from=build /app/publish ./
13ENV ASPNETCORE_URLS=http://+:8080
14EXPOSE 8080
15ENTRYPOINT ["dotnet", "Api.dll"]

Cache de layers : ordonner pour ne pas tout reconstruire

Docker met chaque instruction en cache et l'invalide dès qu'une couche en amont change. D'où la règle d'or : copier les fichiers de dépendances avant le code source . En faisant COPY *.csproj puis dotnet restore avant de copier le reste, le restore n'est rejoué que si le .csproj change — pas à chaque modification d'un fichier C#. Même logique côté Angular avec package.json et npm ci avant le COPY des sources : un changement de code ne réinvalide jamais l'install des dépendances, ce qui divise les temps de build par dix.

Une image finale minuscule

Le choix de l'image de base de runtime fait toute la différence. Les images chiseled de Microsoft ( aspnet:9.0-noble-chiseled ) suppriment shell, gestionnaire de paquets et binaires superflus : surface d'attaque réduite, image souvent sous les 110 Mo, exécution en utilisateur non-root par défaut. Pour servir le front Angular, nginx alpine joue le rôle de stage final.

Bash
1# Build Angular puis service par nginx
2FROM node:22-alpine AS web
3WORKDIR /app
4COPY package*.json ./
5RUN npm ci
6COPY . ./
7RUN npm run build
8
9FROM nginx:1.27-alpine AS final
10COPY --from=web /app/dist/browser /usr/share/nginx/html
11COPY nginx.conf /etc/nginx/conf.d/default.conf

Le build Angular ne produit que des fichiers statiques : aucun runtime Node n'est nécessaire en prod. On copie le dossier dist/browser dans la racine nginx et on ajoute un try_files $uri /index.html dans la conf pour le fallback SPA .

Orchestrer en local avec Compose

Pour faire tourner API et front ensemble pendant le dev, un docker-compose.yml câble les deux services et leur réseau :

YAML
1services:
2 api:
3 build: ./api
4 ports:
5 - "8080:8080"
6 web:
7 build: ./web
8 ports:
9 - "4200:80"
10 depends_on:
11 - api

La documentation des builds multi-stage détaille les builds ciblés ( --target build ) et le partage de stages, utiles pour isoler une étape de test dans le pipeline CI.

Une image de prod ne devrait contenir que ce qui s'exécute. Le multi-stage rend cette discipline gratuite : le SDK reste dans le stage de build, jamais dans ce que vous déployez .
super-dev — portfolio.app
// Continuer dans DEVOPS
>_
Un pipeline CI/CD GitHub Actions → Azure, de zéro
8 min • 1,9k lectures
Observabilité .NET avec OpenTelemetry
8 min • 1,6k lectures