Eigenes Image

Eigene App in Container verpacken

Wenn man eine Zeit lang die Container für sich arbeiten lässt und diese nutzt, kommt irgendwann der Gedanke, auch eigene Applikation in einen Container zu verpacken und auszuliefern. Das wollen wir am Beispiel unser Blazor Beispielprojektes ein Mal durchspielen.

Um ein Image zu erstellen, benötigen wir eine Bauanleitung. Diese wird in Form eines sogenannten Docker-Files definiert. Die Datei beschreibt Schritt für Schritt, was wir am Basissystem machen müssen, um unsere Applikation zu veröffentlichen. Dazu kommen noch einige Konfigurationsangaben, wie Port oder verfügbare Umgebungsvariablen.

Anpassbarkeit des Images

Als erstes müssen wir überlegen, welche Möglichkeiten wir dem “Nutzer” des Images zur Verfügung stellen müssen (und wollen). Da es sich um eine Web-Anwendung handelt, sollten wir mindestens den Port für den Web-Server freigeben, so dass die Anwendung auch von Außen ansprechbar ist.

Da unsere Anwendung eine Datenbankverbindung benötigt, sollten wir erlauben diese zu konfigurieren. Hier könnten wir entweder direkt den kompletten “Connection String” empfangen, oder diesen in Teilen konfigurierbar machen.

Auswahl des Basis-Images

Unsere Anwendung basiert auf der .Net Laufzeitumgebung und benötigt das ASP .Net Core Framework. Im ersten Schritt wollen wir auch, dass die App in dem Container (einfachheitshalber) direkt gebaut wird. Die Applikation nutzt .Net 10 als Version der Laufzeitumgebung. Eine Liste mit verfügbaren (offiziellen) Repositories für .Net finden wir direkt bei Microsoft.

Für unsere Zwecke werden wir das “sdk” Basis-Image nutzen.

Dockerfile

Die Datei Dockerfile ist eine einfache Textdatei, die einem Schema folgt. Jede neue Zeile (Anweisung) erzeugt dabei eine neue Schicht im Dateisystem des fertigen Images. Die Datei fängt immer mit einer FROM Anweisung, gefolgt von dem Basis-Image, auf dem wir unsere Applikation aufbauen.

# Basis-Image .Net 10 SDK
FROM mcr.microsoft.com/dotnet/sdk:10.0

Kopieren des Projektes

Im nächsten Schritt (nächste Zeile) legen wir das Verzeichnis (WORKDIR) an, in dem wir “arbeiten” wollen. Und kopieren (COPY) den aktuellen Inhalt des Projektes in dieses Verzeichnis.

FROM mcr.microsoft.com/dotnet/sdk:10.0
# Verzeichnis für die App
WORKDIR /build
# Kopiere das Projekt in das Arbeitsverzeichnis
COPY . ./

Bauen des Projektes

Nun folgen die Standardschritte von dotnet um eine Applikation für die Veröffentlichung zu bauen. Dieser Schritt ist spezifisch für die jeweilige Sprache / Framework (und unabhängig von Docker / Containern). Die Befehle werden durch die RUN Anweisung ausgeführt. Jede Zeile erzeugt, wie erwähnt, eine neue Schicht im Dateisystem.

FROM mcr.microsoft.com/dotnet/sdk:10.0
...
# Wiederherstellen der Abhängigkeiten
RUN dotnet resore
# Veröffentlichen der App in das Verzeichnis /app
RUN dotnet publish BlazorWebAppMovies.csproj -c Release -o /app

Konfiguration des Ports und Datenbankverbindung

Da wir eine Web-Anwendung haben, die eine Datenbank benötigt, sollen wir diese parametrisieren. Für das Arbeiten einer Web-Anwendung im Container ist es wichtig, dass wir ein Port zur Verfügung stellen, über den die Applikation erreichbar ist. In dem SDK Package ist das Port für Webanwendungen auf 8080 gesetzt (kann über die Umgebungsvariable ASPNETCORE_HTTP_PORTS angepasst werden).

Standard port in SDK image

Der Port wird über die Anweisung EXPOSE angegeben und kann neben dem Port selbst auch dar Protokoll enthalten.

Unsere Applikation ist auch von einer Datenbank abhängig. Wir müssen den “Connection String” mit übergeben, damit der Container sich mit der Datenbank verbinden kann. In .Net werden die Einstellungen in appsettings.json Datei (JSON Format) gespeichert. Will man diese Einstellungen mit Umgebungsvariablen überschreiben, werden die Verschachtlungen über ein __ (doppeltes Unterstrich) abgebildet. Aus aktuell { "ConnectionStrings": { "BlazorWebAppMoviesContext": "" } } wird so ConnectionStrings__BlazorWebAppMoviesContext. Da Umgebungsvariablen oft in Großbuchstaben geschrieben werden (und für .Net Konfigurationen es irrelevant ist), werden wir es als CONNECTIONSTRINGS__BLAZORWEBAPPMOVIESCONTEXT schreiben.

Die Umgebungsvariablen werden in Dockerfile mit der Anweisung ENV definiert. Schlüssel und Wert werden über = getrennt.

FROM mcr.microsoft.com/dotnet/sdk:10.0
...
# Neues Arbeitsverzeichnis
WORKDIR /app
# Datenbank Connection string
ENV CONNECTIONSTRINGS__BLAZORWEBAPPMOVIESCONTEXT=""
# Port für Webserver
EXPOSE 8080

Starten der Anwendung

Der letzte Schritt ist nun die Anweisung, um die Anwendung in dem Container zu startet. Da wir die Applikation nicht manuell innerhalb des Containers starten wollen, sollte dies direkt beim “Instanziieren” des Containers geschehen.

Die Anwendung selbst wird über die ENTRYPOINT Anweisung ausgeführt. Die Anweisung nimmt eine Liste an Parametern entgegen. Die Applikationen in .Net startet man mit dem dotnet Befehl und dem Applikationsnamen (entspricht dem Projektnamen).

FROM mcr.microsoft.com/dotnet/sdk:10.0
...
# Start der .Net Anwendung
ENTRYPOINT ["dotnet", "BlazorWebAppMovies.dll"]

Image bauen

Im letzten Schritt können wir nun die “Bauanleitung” dazu nutzen unser erstes Image mit unserer Anwendung zu bauen. Dazu dient der Befehl docker build.

docker build -t blazor-movies:from-sdk .
  • -t: Tag des Images, also der Image-Name + Version. In unserem Fall vergeben wir den Namen blazor-movies. Da wir einige Images bauen werden und diese miteinander vergleichen möchten, vergeben wir eine sprechenden Versionsnamen. Aktuelle Buildanleitung basiert auf dem SDK-Image. Der Versionsname ist bei uns deshalb from-sdk.
  • .: Das . hinter dem Tab bestimmt den Build-Context (Pfad). Der Build-Context steht beim Bauen zur Verfügung (wie bei uns der Quellcode der Anwendung). . bedeutet dabei, der aktuelle Ordner.
Hinweis

Sie können die Datei der Bauanleitung auch anders benennen als Dockerfile, müssen dann aber beim Bauen den Dateinamen als Parameter (-f) mit übergeben. Wenn Sie zum Beispiel die Datei Dockerfile.dev nennen würden, würde der Baubefehl wie folgt aussehen:

docker build -t blazor-movies:dev -f Dockerfile.dev .

Finale Version

FROM mcr.microsoft.com/dotnet/sdk:10.0
# Verzeichnis für die App
WORKDIR /build
# Kopiere das Projekt in das Arbeitsverzeichnis
COPY . ./
# Wiederherstellen der Abhängigkeiten
RUN dotnet restore
# Veröffentlichen der App in das Verzeichnis /app
RUN dotnet publish BlazorWebAppMovies.csproj -c Release -o /app
# Arbeitsverzeichnis für Ausführung
WORKDIR /app
# Datenbank Connection string
ENV CONNECTIONSTRINGS__BLAZORWEBAPPMOVIESCONTEXT=""
# Port für Webserver
EXPOSE 8080
# Start der .Net Anwendung
ENTRYPOINT ["dotnet", "BlazorWebAppMovies.dll"]

Unser Image in Docker Desktop

docs