1. Projektüberblick
Es ist eine moderne E-Commerce-Anwendung, die nach dem Microservices-Prinzip aufgebaut ist. Die Anwendung besteht aus mehreren voneinander unabhängigen Services, die gemeinsam eine vollständige Business-Funktionalität bereitstellen.
Ziel dieses Projekts ist es, alle EMart-Services zu containerisieren, lokal mit Docker Compose zu betreiben.
2. Vergleich zum vorherigen Projekt (Profile App)
| "Profile" Anwendung | Microservices Anwendung |
|---|---|
| Monolithische Architektur | Microservices-Architektur |
| Ein Backend | Mehrere Backends |
| Ein WAR-Artifact | Mehrere Services & Images |
| Gemeinsame Datenbank | Datenbank pro Service |
3. Microservices-Architektur
Microservices-Architektur Erklärung:
Der User öffnet die EMart-Webseite im Browser.
Nginx ist der Einstiegspunkt und leitet Requests je nach Pfad weiter.
/→ Angular Client (Frontend)/api→ Emart API (NodeJS)/webapi→ Books API (Java)
Jeder Service verarbeitet nur seinen Bereich (entkoppelt).
REST-Calls für EMart-Funktionen.
Eigene Datenbank nur für diesen Service.
REST-Calls für Books-Funktionen.
Eigene Datenbank nur für diesen Service.
Ergebnis: Nginx routet den Traffic, Services sind getrennt und jede API hat ihre eigene Datenbank.
4. Container-Strategie
Jeder Service wird in einem eigenen Container betrieben:
- Nginx Container als zentraler Einstiegspunkt
- Angular Container für das Frontend
- NodeJS Container für die EMart API
- MongoDB Container für die EMart API
- Java Container für die Books API
- MySQL Container für die Books API
Docker Compose orchestriert alle Container und stellt sicher, dass sie gemeinsam gestartet und vernetzt werden.
Flow of Execution (High-Level)
Client ↓ Nginx API Gateway ↓ Routing nach URL ├─ Angular Frontend ├─ EMart API (NodeJS → MongoDB) └─ Books API (Java → MySQL)
5. Praktischer Teil – Containerisierung der Microservices App
1) Allgemeine Setup
- Source Code klonen & in VS Code öffnen
- Source Code in VS Code öffnen
Um ein sauberes Dockerfile zu schreiben, sind zwei Dinge entscheidend:
- den Build-Prozess des Services zu verstehen
- den Hosting-/Runtime-Prozess zu kennen (wo Artefakt dieses Services später entsteht).
2) Dockerfile für Client (Angular) herstellen
1) Wo wird der Dockerfile erstellt?
Öffne den Ordner client/ und lege dort den Dockerfile an (z.B. client/Dockerfile)
sowie deine nginx.conf im selben Ordner.
-
Stage 1 – Frontend Build (Node.js): Ziel: Angular/Frontend wird gebaut und am Ende liegt ein statischer "dist/" Ordner vor
Nutzt Node 14 Image von Hub, um npm installieren.
FROM node:14 AS web-build
Setzt das Arbeitsverzeichnis im Container für alle folgenden Befehle.
WORKDIR /usr/src/app
Kopiert den kompletten Inhalt (Build-Kontext) in /usr/src/app/client.
COPY ./ ./client
Installiert Abhängigkeiten und baut die Production-Version in dist/.
RUN cd client && npm install && npm run build --prod -
Stage 2 – Runtime (Nginx): Ziel: Nur die gebauten statischen Dateien werden in ein Nginx-Image kopiert.
Nutzt Nginx Images von Hub, damit Nginx die gebauten statischen Dateien ausliefert.
FROM nginx:latest
Kopiert das Build-Resultat aus Stage 1 ("dist/client/") nach Nginx Webroot
COPY --from=web-build /usr/src/app/client/dist/client/ /usr/share/nginx/html
Ersetzt die Default-Nginx-Config durch deine eigene (Routing/Proxy/SPA-Fallback).
COPY nginx.conf /etc/nginx/conf.d/default.conf
Dokumentiert den Port,.
EXPOSE 4200
-
Server Block – Grundkonfiguration des Nginx Webservers
Definiert einen virtuellen Nginx-Server, der die Angular-Frontend-Anwendung ausliefert.
server {
Nginx hört auf Port 4200 für eingehende HTTP-Anfragen (IPv4).
listen 4200;
Aktiviert den gleichen Port für IPv6-Verbindungen.
listen [::]:4200;
Definiert den Servernamen – hier lokal, da der Zugriff container-intern oder per Port-Mapping erfolgt.
server_name localhost;
Angular Haupt-Location für alle Requests an die Root-URL (/).
location / {
Gibt das Verzeichnis an, aus dem Nginx statische Dateien ausliefert (Angular Build Output).
root /usr/share/nginx/html;
Legt die Standard-Dateien fest, die bei einem Verzeichnisaufruf geladen werden.
index index.html index.htm;
}
Definiert eine Fehlerseite für Serverfehler (HTTP 500–504).
error_page 500 502 503 504 /50x.html;
Liefert die Fehlerseite aus dem gleichen statischen Webroot-Verzeichnis aus.
location = /50x.html {
root /usr/share/nginx/html;
}
Schließt den Server-Block ab.
}
Wichtige Hinweise :
• Wenn Nginx in nginx.conf auf 80 lauscht (Standard), dann ist EXPOSE 4200 nicht nötig
– oder passt listen 4200; in der Config an.
• Der Pfad dist/client/ hängt vom Angular-Projektnamen ab. Falls Projekt anders heißt,
muss der Ordnername im COPY --from=... angepasst werden.
4.) Dockerfile für NodeAPI (NodeJS) herstellen
Ziel: Den NodeJS Backend-Service als Docker-Image bauen und als Container auf Port 5000 starten.
Das Dockerfile nutzt ein Multi-Stage Build, damit Abhängigkeiten sauber installiert und anschließend in ein Runtime-Image übernommen werden.
Öffne den Ordner nodeapi/ und lege dort den Dockerfile an (z.B. nodeapi/Dockerfile)
Multi-Stage Dockerfile (NodeJS Backend)
-
Stage 1 – Build (Dependencies installieren)
Nutzt für Build-Stage mit Node 14 Image von Hub. Hier werden Abhängigkeiten installiert, damit die Runtime später sofort starten kann.
FROM node:14 AS nodeapi-build
Setzt das Arbeitsverzeichnis im Container für die folgenden Befehle.
WORKDIR /usr/src/app
Kopiert den Projektinhalt in den Ordner/usr/src/app/nodeapi.
COPY ./ ./nodeapi/
Wechselt in das NodeAPI-Verzeichnis und installiert alle npm Abhängigkeiten (node_moduleswird erstellt).
RUN cd nodeapi && npm install -
Stage 2 – Runtime (Service starten)
Nutzt erneut Node 14 als Runtime-Image (hier läuft später der Service). Diese Stage enthält nur das, was wirklich zum Starten benötigt wird.
FROM node:14
Setzt das Arbeitsverzeichnis für den Runtime-Container.
WORKDIR /usr/src/app/
Kopiert den kompletten Output aus der Build-Stage (inkl. installierter Dependencies) in die Runtime-Stage.
COPY --from=nodeapi-build /usr/src/app/nodeapi/ ./
Debug/Check: Listet Dateien im Container (hilft beim Troubleshooting während der Entwicklung).
RUN ls
Dokumentiert den Port, auf dem die NodeAPI im Container läuft (hier: 5000).
EXPOSE 5000
Startet den Service mitnpm start. Der Befehl wechselt vorher ins App-Verzeichnis, damit das Start-Script korrekt ausgeführt wird.
CMD ["/bin/sh", "-c", "cd /usr/src/app/ && npm start"]
5) Dockerfile für BooksAPI (Java) herstellen
Ziel: Den Java Backend-Service (BooksAPI) als Docker-Image bauen und als Container auf Port 9000 starten.
Das Dockerfile nutzt ein Multi-Stage Build: Stage 1 baut das JAR mit Maven, Stage 2 startet nur das fertige Artefakt.
Öffne den Ordner javaapi/ und lege dort den Dockerfile an (z.B. javaapi/Dockerfile)
Multi-Stage Dockerfile (Java Backend)
-
Stage 1 – Build (Maven Build im Container)
Build-Stage basiert auf OpenJDK 8. Diese Stage wird nur genutzt, um das Java-Projekt zu kompilieren.
FROM openjdk:8 AS BUILD_IMAGE
Setzt das Arbeitsverzeichnis für alle folgenden Befehle.
WORKDIR /usr/src/app/
Aktualisiert Paketlisten und installiert Maven direkt im Build-Container, damit das Projekt gebaut werden kann.
RUN apt update && apt install maven -y
Kopiert den kompletten Quellcode in den Container (Build-Kontext).
COPY ./ /usr/src/app/
Baut das Projekt und erzeugt ein JAR imtarget/-Ordner.-DskipTestsüberspringt Tests, um den Build schneller zu machen (für CI/Prod kann man Tests separat laufen lassen).
RUN mvn install -DskipTests -
Stage 2 – Runtime (nur JAR ausführen)
Runtime-Stage: erneut OpenJDK 8, aber ohne Maven/Build-Tools. Dadurch ist das Image schlanker und enthält nur das Nötigste zum Start.
FROM openjdk:8
Arbeitsverzeichnis im Runtime-Container.
WORKDIR /usr/src/app/
Kopiert nur das fertige Build-Artefakt (JAR) aus der Build-Stage in die Runtime-Stage. Das reduziert Image-Größe und macht den Container sicherer.
COPY --from=BUILD_IMAGE /usr/src/app/target/book-work-0.0.1-SNAPSHOT.jar ./book-work-0.0.1.jar
Dokumentiert den Port, auf dem die BooksAPI im Container erreichbar ist (9000).
EXPOSE 9000
Startet die Anwendung direkt per Java.-jarführt das JAR als ausführbare Spring/Java-App aus.
ENTRYPOINT ["java","-jar","book-work-0.0.1.jar"]
Warum Multi-Stage?
Maven und Build-Abhängigkeiten bleiben in Stage 1 und landen nicht in der Runtime.
Ergebnis: kleineres Image, schnellerer Start und weniger Angriffsfläche.
6) Dockerfile für API Gateway (Nginx)
Für das API Gateway nutzen wir bewusst das offizielle Nginx Image von Docker Hub. Wir bauen hier kein eigenes Nginx „from scratch“, sondern überschreiben nur die Standard-Konfiguration.
Vorgehen: Wir legen im Projekt einen Ordner nginx/ an und speichern darin eine Datei
default.conf. Diese Datei wird später in den Container nach
/etc/nginx/conf.d/default.conf kopiert und ersetzt damit die Default-Nginx-Konfiguration.
Warum so?
Nginx ist bereits fertig optimiert. Wir müssen nur Routing/Reverse-Proxy Regeln definieren.
Das spart Zeit, ist stabil und entspricht Best Practices.
default.conf (Gateway Routing)
Diese Konfiguration macht Nginx zum zentralen Einstiegspunkt:
/ → Frontend (client:4200),
/api → NodeJS API (api:5000),
/webapi → BooksAPI Java (webapi:9000).
upstream client {
server client:4200;
}
upstream api {
server api:5000;
}
upstream webapi {
server webapi:9000;
}
server {
listen 80;
location / {
proxy_set_header Host $host; # Original Host Header weitergeben
proxy_set_header X-Real-IP $remote_addr; # echte Client-IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Proxy-Kette
proxy_set_header X-Forwarded-Proto $scheme; # http/https Info
proxy_http_version 1.1; # wichtig für moderne Web-Apps
proxy_set_header Upgrade $http_upgrade; # WebSocket Upgrade
proxy_set_header Connection "upgrade"; # WebSocket Connection Header
proxy_pass http://client/; # / → Angular Client
}
location /api {
proxy_pass http://api:5000; # /api → NodeJS API
}
location /webapi {
proxy_pass http://webapi:9000; # /webapi → Java BooksAPI
}
}
3) Kurze Erklärung: Was passiert im Traffic?
- Browser → Nginx Gateway (Port
80) - / wird an client weitergeleitet (Angular UI)
- /api wird an api weitergeleitet (NodeJS Backend)
- /webapi wird an webapi weitergeleitet (Java BooksAPI)
Wichtig:
Die Namen client, api und webapi sind Service-Namen aus Docker Compose.
Docker DNS löst diese automatisch im internen Netzwerk auf – dadurch brauchen wir keine IPs.
7) Docker Compose (Multi-Container Setup)
Mit Docker Compose starte ich die komplette EMart-Microservices-Anwendung mit einem Befehl. Compose übernimmt dabei:
- Orchestrierung mehrerer Container (Start/Stop/Restart)
- Netzwerk zwischen Services (DNS über Service-Namen wie
api,client) - Abhängigkeiten über
depends_on(Start-Reihenfolge) - Ports für Zugriff vom Host (z.B.
80,4200,5000)
Services im Überblick
Client (Angular) → http://localhost:4200
API (NodeJS + Mongo) → http://localhost:5000
BooksAPI (Java + MySQL) → http://localhost:9000
API Gateway (Nginx) → http://localhost:80 (Entry Point)
MongoDB → localhost:27017
MySQL → localhost:3306
Architektur (Compose-Flow)
compose.yaml (dein aktuelles Setup)
Hinweis: depends_on steuert nur die Start-Reihenfolge, nicht ob die DB schon „ready“ ist.
Für produktionsnahe Setups nutzt man oft zusätzlich Healthchecks/Wait-Skripte.
Docker Compose – Microservices Setup (EMart)
version: "3.8" # Docker Compose Version services: client: build: context: ./client # Dockerfile für Angular Client (Nginx) ports: - "4200:4200" # Frontend Zugriff im Browser container_name: client depends_on: - api # Client benötigt Node API - webapi # Client benötigt Java API api: build: context: ./nodeapi # Dockerfile für NodeJS Backend ports: - "5000:5000" # Node API Port restart: always # Container startet automatisch neu container_name: api depends_on: - nginx # API wird über API Gateway erreicht - emongo # Node API nutzt MongoDB webapi: build: context: ./javaapi # Dockerfile für Java Backend ports: - "9000:9000" # Java API Port restart: always container_name: webapi depends_on: - emartdb # Java API nutzt MySQL nginx: image: nginx:latest # Offizielles Nginx Image (API Gateway) container_name: nginx restart: always volumes: - "./nginx/default.conf:/etc/nginx/conf.d/default.conf" # Routing & Reverse Proxy Config ports: - "80:80" # Zentrale Entry-URL für Benutzer emongo: image: mongo:4 # Offizielles MongoDB Image container_name: emongo environment: - MONGO_INITDB_DATABASE=epoc # Initiale MongoDB Datenbank ports: - "27017:27017" # MongoDB Port emartdb: image: mysql:8.0.33 # MySQL Image für Books API container_name: emartdb ports: - "3306:3306" # MySQL Standard Port environment: - MYSQL_ROOT_PASSWORD=emartdbpass # Root Passwort - MYSQL_DATABASE=books # Datenbank für Java API
Nur der API Gateway (Nginx) nutzt ein Volume, da die Routing-Logik
über eine externe default.conf gesteuert wird.
So kann das Routing angepasst werden, ohne das Docker Image neu zu bauen.
Ergebnis:
Der Benutzer nutzt http://localhost als einzigen Einstiegspunkt.
Nginx routet intern per Docker DNS zu client, api und webapi,
während MongoDB und MySQL als Daten-Layer dienen.
8) Run Microservices App on AWS-EC2 nicht Vagrant VM
- Ziel:
In diesem Schritt wird die EMart Microservices-Anwendung auf AWS EC2 Instanzen betrieben. Die Anwendung ist bereits vollständig containerisiert (Dockerfiles + Docker Compose) und wird nun unverändert auf Cloud-Infrastruktur ausgeführt.
- Architektur:
User ↓ EC2 Instance (Docker Host) ↓ Docker Compose ├─ Nginx API Gateway ├─ Angular Client ├─ NodeJS API + MongoDB └─ Java Books API + MySQL
- Eine EC2 fungiert als Docker Host
- Alle Services laufen isoliert als Container
- Kommunikation erfolgt über Docker-Netzwerk (Service-Namen)
- Deployment der Microservices-Anwendung auf EC2 (Docker)
-
EC2 Instance erstellen
Ubuntu EC2 mit Key Pair, Security Group und Storage konfigurieren.- Inbound Rules: SSH (22) → My IP
- Inbound Rules: HTTP (80) → My IP
- Storage: 20 GiB
-
Docker Engine & Docker Compose installieren
Installation von Docker und Docker Compose auf der EC2 (Docker Host). -
Projekt-Repository klonen
Repository klonen und in das Projektverzeichnis wechseln, das diedocker-compose.ymlenthält. -
Docker Images bauen
Alle Images der Microservices-Anwendung werden lokal gebaut.docker compose build
Troubleshooting – Docker Build Fehler
Während des Builds der Java-Books-API trat ein Fehler beim Erstellen des Docker-Images auf.
Problem:
failed to resolve source metadata for docker.io/library/openjdk:8
docker.io/library/openjdk:8: not found
Ursache: Das Image openjdk:8 ist auf Docker Hub nicht mehr verfügbar
und wird nicht mehr gepflegt.
Lösung:
openjdk:8→eclipse-temurin:8-jdk(Build Stage)openjdk:8→eclipse-temurin:8-jre(Runtime Stage)
Ergebnis:
Der Build läuft wieder stabil mit einem gepflegten,
sicheren und modernen Base-Image.
-
Docker Images prüfen
Kontrolle, ob alle Images erfolgreich erstellt wurden.docker images
-
Container starten
Start aller Container im Hintergrund.docker compose up -d
-
Zugriff auf die Anwendung
Zugriff über den Browser auf den Nginx API Gateway.http://<EC2_PUBLIC_IP>:80
6. Fazit & Nächster Schritt (CI/CD)
Dieses Projekt zeigt die vollständige Evolution einer Anwendung: vom manuellen Setup über die Containerisierung mit Docker bis hin zum Betrieb einer Multi-Container-Microservices-Anwendung auf einer EC2-Instanz.
Durch Dockerfiles und Docker Compose ist die Anwendung nun:
- reproduzierbar
- portabel (lokal, VM, Cloud)
- klar strukturiert nach Services
Aktuell werden Build, Test und Deployment noch manuell ausgeführt (Docker Build, Docker Compose, Push nach Docker Hub). In realen Produktivumgebungen ist dieser Ansatz:
- fehleranfällig
- nicht skalierbar
- nicht standardisiert
Eine CI/CD-Pipeline automatisiert genau diese Schritte:
- Automatischer Build der Docker Images bei jedem Code-Commit
- Automatische Tests der Services
- Automatischer Push der Images nach Docker Hub
- Automatisches Deployment auf EC2 oder Kubernetes
CI/CD ist somit der natürliche nächste Schritt, um:
- manuelle Arbeit zu eliminieren
- Release-Zyklen zu beschleunigen
- Fehler frühzeitig zu erkennen