Alle berichten

Docker-containers bouwen, deployen en gebruiken

Schrijf een multi-stage Dockerfile voor een .NET-app, bouw hem voor een Raspberry Pi, push hem naar Docker Hub en draai hem op elke server.

Bijgewerkt op 1 november 20265 min lezen

Heb je ooit iets willen deployen dat je gebouwd hebt, om dan te ontdekken dat de server niet de juiste runtime, libraries of configuratie heeft? Je app in een Docker-container draaien lost dat op. In deze tutorial schrijven we een Dockerfile voor een .NET-web-API, bouwen we een image voor een Raspberry Pi, pushen we het naar Docker Hub en draaien we het op de server.

Nieuw met Docker? Lees dan eerst de introductie tot Docker: die behandelt images, containers en de basiscommando's.

Een korte opfrissing

Docker bouwt je omgeving na op elke machine met images en containers. Een image bevat alles wat nodig is om je app te draaien: de basislaag van het OS, de runtime, libraries en je gecompileerde code. Een container is een draaiende instantie van een image. Je kunt veel containers starten van één image, en verschillende images naast elkaar draaien op dezelfde machine.

Docker is wel geen magie: de machine heeft Docker zelf nog nodig. Installeer Docker op je ontwikkelmachine en op de server, en je kunt beginnen.

Een Dockerfile schrijven

Je beschrijft een image in een Dockerfile in je project. Hier is een moderne multi-stage Dockerfile voor een ASP.NET Core-API met de naam MyApp.Api:

Dockerfile
# Build-stage: de .NET SDK compileert en publiceert de app
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG TARGETARCH
WORKDIR /src

# Eerst restoren, zodat Docker de NuGet-packages kan cachen
COPY MyApp.Api/MyApp.Api.csproj MyApp.Api/
RUN dotnet restore MyApp.Api/MyApp.Api.csproj -a $TARGETARCH

# Kopieer de rest van de code en publiceer een release-build
COPY . .
RUN dotnet publish MyApp.Api/MyApp.Api.csproj -c Release -a $TARGETARCH --no-restore -o /app/publish

# Runtime-stage: alleen de ASP.NET Core-runtime en de gepubliceerde app
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
USER $APP_UID
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyApp.Api.dll"]

We lopen hem stap voor stap door:

  1. Twee stages. De eerste stage (build) gebruikt het grote .NET SDK-image om de app te compileren. De tweede stage (final) start van het veel kleinere ASP.NET Core-runtime-image en kopieert alleen de gepubliceerde output. De SDK komt dus nooit in het image dat je uitrolt.
  2. Builds voor andere platformen. --platform=$BUILDPLATFORM draait de SDK native op je eigen machine, en -a $TARGETARCH laat .NET compileren voor de doelarchitectuur (bijvoorbeeld arm64 voor een Raspberry Pi). Dat is veel sneller dan de hele build te emuleren.
  3. Layer-caching. We kopiëren eerst de .csproj en voeren dotnet restore uit voordat we de rest van de code kopiëren. Zolang je dependencies niet veranderen, hergebruikt Docker die laag en slaat hij de restore over bij de volgende build.
  4. Veiligheid. USER $APP_UID draait de app als de non-root-gebruiker die de officiële .NET-images meeleveren.
  5. Poort. Sinds .NET 8 luisteren ASP.NET Core-images standaard op poort 8080, dus dat documenteren we met EXPOSE 8080.
  6. Entry point. ENTRYPOINT is het commando dat uitgevoerd wordt wanneer een container start.

Voeg ook een .dockerignore toe naast de Dockerfile met minstens **/bin en **/obj, zodat lokale build-output nooit in het image terechtkomt.

Het image bouwen en pushen

Open een terminal in de root van je solution en bouw het image voor een Raspberry Pi (Linux op ARM64):

Shell
docker buildx build --platform linux/arm64 -t ekozf/myapp-api:latest -f MyApp.Api/Dockerfile .
  • --platform bepaalt voor welk platform het image gebouwd wordt. Gebruik linux/amd64 voor een gewone server, of allebei (linux/amd64,linux/arm64) voor een multi-platform-image.
  • -t geeft het image een tag. Het formaat is <docker-hub-gebruiker>/<naam>:<tag>, zodat het klaar is om te pushen.
  • -f wijst naar de Dockerfile, en de . op het einde is de build-context: de map waaruit Docker bestanden mag kopiëren.

In de buildx-referentie vind je alle flags. Log nu in en push het image naar Docker Hub:

Shell
docker login
docker push ekozf/myapp-api:latest

Tip: voeg --push toe aan het build-commando om in één keer te bouwen en te pushen.

Het image ophalen en draaien op de server

Het image staat op Docker Hub, dus elke machine met Docker kan het draaien. Voer op de server uit:

Shell
docker pull ekozf/myapp-api:latest

Op Linux heb je misschien sudo nodig, tenzij je gebruiker in de groep docker zit. Zegt Docker dat het image niet bestaat, dan is het waarschijnlijk privé: voer docker login ook op de server uit, of maak de repository publiek in de instellingen op Docker Hub.

Start nu een container:

Shell
docker run -d --rm -p 80:8080 --name myapp-api ekozf/myapp-api:latest

Dit doet elke flag:

  1. -d draait de container op de achtergrond (detached).
  2. --rm verwijdert de container zodra hij stopt.
  3. -p 80:8080 publiceert poort 8080 van de container op poort 80 van de host. Zet er een IP-adres voor (-p 192.168.1.10:80:8080) om maar op één netwerkinterface te luisteren.
  4. --name myapp-api geeft de container een naam, zodat we zijn ID niet nodig hebben.
  5. ekozf/myapp-api:latest is het image dat we draaien.

Elke docker run maakt een nieuwe container van het image. Je kunt hem stoppen, herstarten en verwijderen zonder dat het image verandert, en je kunt meerdere containers van hetzelfde image draaien.

De tag :latest betekent gewoon "de meest recente push". Push en draai voor alles wat belangrijk is expliciete versies zoals :1.4.0, zodat je altijd weet wat er draait en kunt terugrollen. Alle opties staan in de docker run-referentie.

Voor een service die een herstart moet overleven, vervang je --rm door --restart unless-stopped.

Werken met de draaiende container

Bekijk de draaiende containers:

Shell
docker ps

Dit is mijn Raspberry Pi met drie draaiende containers. De kolommen zijn:

  • CONTAINER ID: het unieke ID. De eerste paar tekens volstaan om ernaar te verwijzen.
  • IMAGE: het image waarvan de container gemaakt is.
  • COMMAND: het commando waarmee de container gestart is, hier het entry point dotnet ... .dll.
  • CREATED en STATUS: wanneer hij gemaakt is en of hij draait.
  • PORTS: de gepubliceerde poorten.
  • NAMES: de naam van de container.

Om in de container rond te kijken, open je er een shell in:

Shell
docker exec -it myapp-api bash

De officiële .NET-images zijn gebaseerd op Debian, dus bash is beschikbaar. Bij images op basis van Alpine gebruik je sh. Typ exit om eruit te gaan. Wat je app naar de console schrijft, zie je met docker logs -f myapp-api.

Stop de container als je klaar bent:

Shell
docker stop myapp-api

Omdat we hem met --rm gestart hebben, verwijdert Docker de container ook zodra hij stopt.

Bonus: Docker Compose

Lange docker run-commando's zijn makkelijk verkeerd te typen. Met Docker Compose beschrijf je de container in plaats daarvan in een bestand compose.yaml:

YAML
services:
  api:
    image: ekozf/myapp-api:latest
    ports:
      - "80:8080"
    restart: unless-stopped

Start hem met docker compose up -d, volg de logs met docker compose logs -f en stop hem met docker compose down. Om te updaten voer je docker compose pull uit en daarna opnieuw docker compose up -d.

Conclusie

Je hebt een multi-stage Dockerfile geschreven, een image gebouwd voor een andere architectuur, het via Docker Hub verspreid en op een server gedraaid. Dat is de kern van het deployen van bijna elke app met Docker. Wil je dieper gaan, bekijk dan de officiële documentatie. Veel plezier met deployen!

Veelgestelde vragen

Waarom luistert mijn ASP.NET Core-container op poort 8080?

Sinds .NET 8 gebruiken de officiële ASP.NET Core-images poort 8080 als standaard HTTP-poort. Publiceer hem met -p 80:8080 om de app op poort 80 van de host te bereiken.

Hoe bouw ik een Docker-image voor een Raspberry Pi?

Gebruik docker buildx build --platform linux/arm64. Combineer in een .NET-Dockerfile --platform=$BUILDPLATFORM op de SDK-stage met dotnet publish -a $TARGETARCH, zodat de build native draait en alleen de output ARM64 is.

  • Docker
  • .NET
  • DevOps
  • Raspberry Pi

Knijp, scroll of tik twee keer om te zoomen. Sleep om te verplaatsen.