Neuigkeiten von trion.
Immer gut informiert.

Docker für Angular Multi-Stage Builds

Docker

Docker hat mit Version 17.05 sogenannte Multi-Stage Builds eingeführt. Dieser Beitrag zeigt, wie Docker-Multi-Stage-Builds effektiv für Angular-Anwendungen eingesetzt werden können und um was es sich bei Docker-Multi-Stage-Builds überhaupt handelt.

Typischerweise werden Docker-Container eingesetzt, um damit eine gekapselte Umgebung zur Ausführung von Programmen anzubieten. Oft sind das Dienstanwendungen, die dank Docker umgebungsagnostisch betrieben werden können - eine wichtige Eigenschaft im Zeitalter von Clouddeployment. Beispiele hierfür sind langlaufende Anwendungen, die Dienste bereitstellen, die durch anderen Anwendungen oder direkt durch einen Nutzer in Anspruch genommen werden. Solche Anwendungen finden sich häufig im Backend.

Tip
Wie Docker sich für Spring Boot und Angular Anwendungen einsetzen lässt, wird ausführlich in Docker für lokale Tests mit Angular und Spring Boot erklärt.

Docker Build-Container

Docker eignet sich jedoch auch für die Verwendung mit sehr kurzlebigen Anwendungen, die lediglich einen Zweck erfüllen und nach getaner Arbeit nicht mehr benötigt werden. Zum Beispiel können sich damit Builds sehr einfach umsetzen lassen, ohne dass das System, auf dem der Build durchgeführt wird, sämtliche Werkzeuge und eine möglicherweise komplexe Umgebung bereitstellen muss.

In dem Artikel NetBeans Docker und ROOT wurde gezeigt, wie Docker für die Entwicklung von C++ Programmen mit komplexen Library-Abhängigkeiten nutzen lässt, ohne dabei auf eine IDE Integration verzichten zu müssen.

Typische Build-Container stellen so z.B. Maven, Gradle oder andere Buildchains und ggf. benötigte Bibliotheken zur Verfügung. Das funktioniert dann auch für TypeScript- und Angular-Anwendungen.

AngularCLI Docker Build-Container

Zur Veranschaulichung soll eine einfache Angular-Anwendung verwendet werden. Am einfachsten lässt sich ein neues Projekt mit Angular CLI erzeugen. Ist Angular CLI bereits installiert, so erzeugt der Aufruf von ng new MyProject ein neues Projekt.

Ist noch kein Angular CLI installiert, so kann dazu ein Docker-Build-Container verwendet werden, wie für den eigentlich Build auch.

Als fertige Docker-Images für Angular CLI stehen zur Auswahl:

Image

Beschreibung

trion/ng-cli

Minimales Angular CLI und NPM Setup

trion/ng-cli-karma

Angular CLI und Abhängigkeiten mit headless Browser für Karma

trion/ng-cli-e2e

Angular CLI und Abhängigkeiten für e2e Tests mit Protractor in Docker

Für einen einfachen Build ohne Tests reicht daher das trion/ng-cli Image. Befindet man sich bereits im Verzeichnis mit der Angular-Anwendung, könnte ein Build unter Verwendung des Docker-Build-Containers so aussehen:

docker run -u $(id -u) --rm -v "$PWD":/app trion/ng-cli \
    npm install && ng build --prod --aot --sourcemaps
Tip
Die trion/ng-cli* Images werden durch Docker Tags für die jeweilige Version von Angular-CLI breitgestellt. Damit lässt sich die Version spezifizieren und Umstellungen besser planen und kontrollieren.

Nun liegen die Buildergebnisse im aktuellen Verzeichnis. Zur weiteren Distribution und ggf. für lokale Tests nahe an einer echten Umgebung gilt es nun die Anwendung in ein Docker-Image zu verpacken, auf Basis dessen dann Docker-Container gestartet werden können.

Angular Docker Image

Angular-Anwendungen werden in einem Webbrowser ausgeführt, aber die Auslieferung geschieht in der Regel durch einen Webserver. Da die Angular-Anwendung lediglich aus statischen HTML, CSS und JavaScript Dateien besteht, bietet sich ein schlanker Webserver wie nginx an. Lediglich für das Angular-Routing müssen ein paar Rewrite-Regeln erstellt werden, damit auch der direkte Einsprung in eine Route und das Neuladen korrekt funktionieren.

Die nginx Konfiguration für eine Angular-Anwendung könnte zum Beispiel so aussehen:

server {
  listen 8080;

  location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri$args $uri$args/ $uri $uri/ /index.html =404;
  }
}

Für das Beispiel wird angenommen, dass mit Angular-CLI der Angular-Frontendbuild umgesetzt ist, und sich die Ergebnisse im dist Ordner befinden. Damit stellt sich ein mögliches Dockerfile wie folgt dar:

FROM nginx:alpine

EXPOSE 8080
COPY dist /usr/share/nginx/html/
COPY nginx/default.conf /etc/nginx/conf.d/default.conf.template

RUN chown -R nginx /etc/nginx

CMD ["/bin/sh","-c",\
"cp /etc/nginx/conf.d/default.conf.template\
 /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]

Die nginx Konfigurationsdatei kann dann z.B. in einem nginx Unterordner des Angularprojekts abgelegt werden.

Nachdem die Angular Anwendung mittels ng build gebaut wurde, kann das Resultat gemeinsam mit nginx zu einem Image vereinigt werden.

Um das Image zu bauen und unter dem Namen trion/angular-app verfügbar zu machen, wird aus dem Verzeichnis mit der Angular-Anwendung folgendes Kommando verwendet:

docker build -t trion/angular-app .

Multi-Stage Docker Images

Für den Build der Angular-Anwendung wurde nun ein Docker-Image verwendet, genauso wie zur Ausführung mit dem nginx-Webserver. Seit Docker 17.05 gibt es sogenannte Multi-Stage Builds. Dies Feature trägt dem relativ häufigen Anwendungsfall Rechnung, dass für einen Build unterschiedliche Docker Images in den verschiedenen Buildphasen zum Einsatz kommen. Statt den Ablauf dann manuell - und damit fehleranfällig - oder per eigenem Script abzubilden, erlaubt Docker nun mehrere Stufen in einem Dockerfile zu bechreiben. Dabei können Daten selektiv aus einem Container in den nächsten propagiert werden.

Für das Beispiel mit dem Build auf Basis von Angular-CLI und dem Laufzeitimage auf Basis des nginx-Webservers können beide Phasen nun in einem Dockerfile abgebildet werden.

Die wesentlichen Unterschiede im Vergleich zu den einzelnen Dockerfiles sind dabei die Nutzung mehrerer Basisimages (FROM) Kopien von Daten statt der Verwendung von Volume-Mounts.

Beispiel für einen Multistage-Build:

FROM trion/ng-cli:latest AS ngcli  #(1)
USER root
WORKDIR /app
COPY . .                           #(2)
RUN npm install
RUN ng build --prod --aot --progress=false

FROM nginx:alpine AS app           #(3)
ENV PORT=8080
EXPOSE 8080
COPY --from=ngcli /app/dist /usr/share/nginx/html/  #(4)
COPY nginx/default.conf /etc/nginx/conf.d/default.conf.template
RUN chown -R nginx /etc/nginx

CMD ["/bin/sh","-c",\
"cp /etc/nginx/conf.d/default.conf.template \
 /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"]
  1. Angular-CLI-Basisimage mit Alias Namen 'ngcli'

  2. Statt eines Volume-Mounts werden die Quellen in den Buildcontainer kopiert

  3. Wechsel des Docker Base-Image für den Ausführungscontainer

  4. Buildergebnisse aus dem Buildcontainer werden in den Ausführungscontainer kopiert

Der Verzicht auf Volumemounts deutet schon darauf hin, dass Multistage-Builds sich auch sehr gut für CI Umgebungen und Continous Deployment eignen. Ein Einsatz auf Entwicklerarbeitsplätzen ist ebenfalls denkbar, insbesondere wenn es um den Test im Zusammenhang mit dem Container geht.




Zu den Themen Angular als auch Docker bieten wir sowohl Unterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.