DevOps Projekt 4: Containerisierung der Java Web App „Profile“

Ziel dieses Projekts ist es, die bestehende Java Web App Profile vollständig zu containerisieren und die Images anschließend in DockerHub zu veröffentlichen. Dabei wird ein klassischer Multi-Service Stack aufgebaut: Nginx (Web) → Tomcat (App) → MySQL sowie Memcached und RabbitMQ.

Docker Docker Compose DockerHub Tomcat 10 JDK 21 Maven MySQL 8.0.33 Memcached RabbitMQ Nginx

1. Projektziel

Die Anwendung soll in standardisierte Container verpackt werden, damit sie überall gleich läuft (lokal, CI/CD, Cloud). Zusätzlich werden die Images auf DockerHub veröffentlicht, um sie später in anderen Umgebungen einfach wiederzuverwenden.

2. Architektur

Der Container-Stack orientiert sich an einer klassischen Multi-Tier Architektur: Nginx als Einstiegspunkt, Tomcat als Application Layer und Backend Services (DB/Cache/Queue).

Hinweis:
Alle Container laufen im selben Docker-Netzwerk (profile-net), damit die Services per Service-Name miteinander sprechen können (z.B. mysql, memcached, rabbitmq).

3. Verwendete Services & Docker Images

Die folgende Tabelle zeigt alle verwendeten Services, die exakten Docker Images, deren Versionen sowie die Herkunft (Docker Hub).

Service Rolle im Projekt Docker Image Version
MySQL Persistente relationale Datenbank mysql 8.0.33
Memcached Caching Service für Performance memcached 1.6
RabbitMQ Message Broker / Queue rabbitmq latest
JDK Java Runtime für Build & App eclipse-temurin 21
Maven Build Tool für WAR Artefakt maven 3.9.9
Tomcat Java Application Server tomcat 10-jdk21
Nginx Web Server / Reverse Proxy nginx 1.27
📦 Quelle der Images:
Alle Images stammen aus dem offiziellen Docker Hub und werden entweder direkt verwendet oder über eigene Dockerfiles angepasst.
⚠️ Best Practice:
In produktiven Umgebungen sollten feste Versions-Tags (z. B. rabbitmq:3.13) anstelle von latest verwendet werden.

3. Praktischer Teil – Containerisierung der Java Web App Profile

Dieser Abschnitt zeigt die reale Umsetzung (Hands-on) – Schritt für Schritt.

1) Docker Engine installieren (setup on VM using Vagrant)

- Ziel:

Vorbereitung einer stabilen und reproduzierbaren Docker-Umgebung, um die Profile Java Web Application lokal zu containerisieren und Docker Images für Docker Hub zu bauen.

- Warum Docker Engine?

  • Docker Engine ist die Laufzeitumgebung für Container
  • Ermöglicht den Build, Run und Test von Images
  • Grundlage für Docker Compose

- Vorgehen

  1. Source Code klonen & neue Branch erstellen
    • Projekt aus GitHub in VS Code klonen
    • Im Projektverzeichnis eine neue Branch für Containerisierung erstellen
    git checkout -b containers
  2. Lokale VM mit Vagrant vorbereiten
    • Im Projekt einen vagrant/-Ordner erstellen
    • Ubuntu VM für saubere Docker-Installation verwenden
    • Isolation vom Host-System (Best Practice)
    vagrant up
    vagrant ssh
    sudo -i
  3. Docker Engine installieren (Dockerdocs)
    • Installation gemäß offizieller Docker-Dokumentation
    
    apt update -y
    apt install ca-certificates curl gnupg -y
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
    gpg --dearmor -o /etc/apt/keyrings/docker.gpg
    chmod a+r /etc/apt/keyrings/docker.gpg
    
    echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
    https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) stable" | \
    tee /etc/apt/sources.list.d/docker.list > /dev/null
    
    apt update -y
    apt install docker-ce docker-ce-cli containerd.io -y
    systemctl status docker
    
    Elastic Beanstalk Application – HTTP Not Secure
  4. Vagrant User zur Docker-Gruppe hinzufügen
    • Docker läuft als Root-Service. Ohne diese Einstellung müsste jeder Docker-Befehl mit sudo ausgeführt werden.
    
    usermod -aG docker vagrant
    newgrp docker
    
Ergebnis: Docker Engine läuft stabil auf der Ubuntu VM und der vagrant-User kann Docker-Befehle direkt ausführen.

4.) Dockerfile für Tomcat (App Image) herstellen

- Ziel:

Java Web App profile als Tomcat-Container bereitstellen: Build (WAR)Deploy in TomcatContainer starten.

- Multi-Stage Dockerfile, Warum?

  • Stage 1 (Maven): nur zum Bauen des WAR-Artefakts
  • Stage 2 (Tomcat): enthält nur Tomcat + WAR → Vorteile: kleineres Image
  • Build-Tools (Maven, Git, Cache) landen nicht im finalen Runtime-Image

- Vorgehen (Schritt für Schritt)

  1. Docker-Ordnerstruktur im Projekt anlegen
    profile/
    └── src/
    └── vagrant/
    └── Docker-files/
        └── app/
            └── Dockerfile
  2. Basis-Images auf Docker Hub auswählen
    • maven:3.9.9-eclipse-temurin-21-jammy → Build-Stage
    • tomcat:10-jdk21 → Runtime-Stage

    Wir bauen diese Images nicht „from scratch“, weil sie bereits gepflegt und standardisiert sind.

  3. Dockerfile erstellen (Multi-Stage->to reduce the size of image)
    • Stage 1 – BUILD (Maven)
      Verwendet ein Maven-Image mit JDK 21, ausschließlich zum Bauen des Java-Artefakts.
      FROM maven:3.9.9-eclipse-temurin-21-jammy AS BUILD_IMAGE
      Klont den Source Code der Profile-Anwendung aus GitHub in den Build-Container(BUILD_IMAGE).
      RUN git clone https://github.com/benutzername/profile.git
      Wechselt in das Projekt, nutzt den Branch containers und erzeugt das WAR-File im target/-Ordner.
      RUN cd profile && git checkout containers && mvn install
    • Stage 2 – RUNTIME (Tomcat)
      Verwendet Tomcat-Image mit JDK 21 für den produktiven Betrieb der Anwendung.
      FROM tomcat:10-jdk21
      Entfernt alle Standard-Tomcat-Applikationen (ROOT, docs, examples).
      RUN rm -rf /usr/local/tomcat/webapps/*
      Kopiert das gebaute WAR aus dem Build-Stage und deployed es als Hauptanwendung (ROOT).
      COPY --from=BUILD_IMAGE profile/target/profile-v2.war /usr/local/tomcat/webapps/ROOT.war
    • Stage 3 – Container starten
      Dokumentiert, dass die Anwendung innerhalb des Containers auf Port 8080 läuft.
      EXPOSE 8080
      Startet Tomcat im Vordergrund, damit der Container aktiv bleibt.
      CMD ["catalina.sh", "run"]
    Ergebnis: Das finale Image enthält nur Tomcat + ROOT.war. Maven/Git sind nur im Build-Stage vorhanden → sauber, klein, produktionsnah.

5) Dockerfile für MySQL (DB Image) herstellen

- Ziel:

Eine reproduzierbare MySQL-Datenbank als Container bereitstellen – inkl. automatischem Import des Schemas über db_backup.sql

- Warum:

  • Vereinfachte Initialisierung (Schema & Daten)
  • Vorbereitung für Cloud Native
  • Daten unabhängig vom App-Container

- Vorgehen (Schritt für Schritt)

    1) Projektstruktur vorbereiten

    • Erstelle im Projektordner Docker-files/ einen Unterordner db/.
      Warum? So liegen DB-Dateien (SQL, Dockerfile) sauber getrennt von der App.
    • Kopiere die Datei db_backup.sql in Docker-files/db/.
      Warum? Diese Datei initialisiert später automatisch das Datenbankschema + Daten.
    Docker-files/
    ├── app/
    │   └── Dockerfile
    └── db/
        ├── Dockerfile
        └── db_backup.sql

    2) Basis-Image auswählen

    • Verwende das offizielle Image mysql:8.0.33 von Docker Hub.

    3) Dockerfile schreiben (DB Image)

    Dieses Dockerfile baut ein MySQL-Image, das beim ersten Start die Datenbank automatisch initialisiert.

    • Nutzt das offizielle MySQL-Image in einer festen Version für reproduzierbare Builds
      FROM mysql:8.0.33

      Metadaten für Nachvollziehbarkeit im Portfolio/Team (wer, welches Projekt).
      LABEL "Project"="Profile"
      LABEL "Author"="Steve"

      Setzt das root-Passwort für MySQL (wird beim ersten Container-Start verwendet)
      Hinweis: Werte müssen zur Anwendungskonfiguration passen (application.properties)

      ENV MYSQL_ROOT_PASSWORD="vprodbpass"

      Legt beim ersten Start automatisch eine Datenbank an ("accounts")
      Warum? Die App erwartet diese DB und verbindet sich später darauf.

      ENV MYSQL_DATABASE="accounts"

      Kopiert das DB-Schema + Initialdaten in den Auto-Init-Ordner von MySQL
      Alles in /docker-entrypoint-initdb.d/ wird beim ersten Start ausgeführt/importiert

      ADD db_backup.sql /docker-entrypoint-initdb.d/db_backup.sql

    Warum genau diese ENV-Werte?

    • MYSQL_ROOT_PASSWORD: Damit der DB-Container ein Root-Passwort bekommt (MySQL startet sonst nicht korrekt).
    • MYSQL_DATABASE: Erzeugt direkt die benötigte DB (z.B. accounts), damit die App sofort starten kann.
    • db_backup.sql: Importiert Tabellen + Daten automatisch (kein manuelles SQL-Setup nötig).
    • Werte in: src/main/resources/application.properties (dort stehen DB-Name / User / Passwort, die die App erwartet).

6) Dockerfile für Nginx (Web Image)

- Ziel:

Ein eigenes Nginx-Image wird verwendet, um die Rolle des Reverse Proxys klar zu kapseln und zu standardisieren.

- Wie (Vorgehen)

  1. Docker-Ordnerstruktur im Projekt anlegen
    profile/
    └── src/
    └── vagrant/
    └── Docker-files/
        └── app/
        └── db/
        └── web/
            └── Dockerfile
            └── nginvproapp.conf
  2. Nginx-Config erstellen: nginvproapp.conf

    Nginx bekommt eine eigene Konfiguration als Datei, damit der Container beim Start sofort als Reverse Proxy funktioniert. Diese Datei definiert Nginx als Reverse Proxy:

    • server Nginx beschreibt den Entry Point und hört auf Port 80
    • Nginx leitet Requests weiter an upstream vproapp:8080 (Tomcat-App Container)
    • Tomcat bleibt intern, Nginx ist der Entry Point
    •         upstream vproapp {
                  server vproapp:8080;
              }
              server {
                  listen 80;
                  location / {
                      proxy_pass http://vproapp;
                  }
              }
              
  3. Basis-Images auf Docker Hub auswählen
    • nginx

  4. Dockerfile erstellen (Multi-Stage->to reduce the size of image)
    • Basis: offizielles Nginx Image (enthält Nginx + Standardstruktur)
      FROM nginx
      Metadaten: für Image-Identifikation
      LABEL Project="Profile"
      LABEL Author="Steve"
      Entfernt die Standard-Konfiguration von Nginx (default.conf),
      damit Nginx nicht die Default-Welcome-Page ausliefert.

      RUN rm -rf /etc/nginx/conf.d/default.conf
      Kopiert deine eigene Reverse-Proxy-Konfiguration in den Container,
      Nginx leitet damit Traffic automatisch an den Tomcat-Service weiter.

      COPY nginvproapp.conf /etc/nginx/conf.d/vproapp.conf

7) Dockerfile für MEMCACHE und RabbitMQ

Für Memcached und RabbitMQ werden offizielle Docker Images von DockerHub verwendet, da es sich um standardisierte Infrastruktur-Services handelt, die keine Anpassung am Code oder an der Konfiguration benötigen.

  • Image für Memcache: memcached
  • Image für RabbitMQ: rabbitmq

8) DockerHub Setup

Ziel

Ein zentrales Repository für Docker Images.

Vorgehen

  1. Account auf https://hub.docker.com erstellen
  2. Neues Repository anlegen:
    • profileapp(Für tomcat)
    • profiledb(Für mysql)
    • profileweb(Für nginx)

9) Docker Compose (Multi-Container Setup)

- Ziel:

Alle Container unserer Anwendung (Java Web App (Tomcat), MySQL (Datenbank), Memcached (Cache), RabbitMQ (Messaging), Nginx (Reverse Proxy)) gemeinsam zu definieren , in der richtigen Reihenfolge zu starten und zu vernetzen – mit einem einzigen Befehl.

Statt viele einzelne docker run-Befehle zu nutzen, beschreibt Docker Compose das gesamte System als Code.

- Docker Compose – Orchestrierung der 5 Container (Profile)

Docker Compose beschreibt die komplette Anwendung als System (docker-compose.yml) und startet alle Container inklusive Netzwerk, DNS-Namen und Abhängigkeiten mit docker compose up -d.

Was Docker Compose hier konkret “macht”:
Es erstellt ein gemeinsames internes Netzwerk, startet die 5 Container als zusammenhängendes System und sorgt dafür, dass die App die Backends über Service-Namen (z.B. db, memcached, rabbitmq) erreichen kann – ohne feste IPs.

- Containers Information für Docker-Compose

  • db – MySQL - Dockerfile (container_name: vprodb)
    Persistente Datenbank für die Anwendung.
    Port: 3306
    Volume: /var/lib/mysql (Daten bleiben erhalten)
  • mc – Memcached - DockerhubImage (container_name: vprocache01)
    In-Memory Cache für schnellere Zugriffe.
    Port: 11211
  • rmq – RabbitMQ - DockerhubImage (container_name: vprormq01)
    Message Broker für asynchrone Kommunikation.
    Port: 5672
    User: guest / guest
  • app – Tomcat - Dockerfile(container_name: vproapp)
    Java Web Application (profile-v2.war).
    Verbindet sich zu DB, Cache und MQ.
    Port: 8080
  • web – Nginx - Dockerfile (container_name: vproweb)
    Reverse Proxy für den Benutzerzugriff.
    Leitet HTTP-Traffic an den Tomcat weiter.
    Port: 80

    Ergebnis:

    User (Browser)
       ↓
    Nginx (vproweb :80)
       ↓
    Tomcat App (vproapp :8080)
       ↓
    ──────────────────────────
       ↳ MySQL (db :3306)
       ↳ Memcached (mc :11211)
       ↳ RabbitMQ (rmq :5672)
      

- Docker Compose – Wie (Vorgehen)

Prerequisite:
  • Wir starten 5 Container gemeinsam: db(vprodb), mc(vprocache01), rmq(vpromq01), app(vproapp), we(vproweb).
  • 3 Services werden selbst gebaut (eigene Images + Dockerfiles): app, db, web.
  • 2 Services nutzen offizielle Images (direkt von Docker Hub): memcached und rabbitmq.
  • Wichtige Werte (DB-Name, User/Pass, MQ-User/Pass, Hostnamen/Ports) stehen in src/main/resources/application.properties .
Compose-Datei anlegen:

Im Projekt-Root eine Datei erstellen: compose.yml

Docker-Compose schreiben:
services:
# --- MySQL Datenbank ---
  vprodb:
    build:
      context: ./Docker-files/db # Dockerfile liegt im Ordner ./Docker-files/db
    image: meka237/profiledb # db-Image Repository in dockerhub push/pull
    container_name: vprodb
    ports:
      - "3306:3306" # MySQL Standard-Port
    volumes:
      - vprodbdata:/var/lib/mysql # Persistente Daten der Datenbank
    environment:
      - MYSQL_ROOT_PASSWORD=??? # Root-Passwort (application.properties)

# --- Memcached Cache ---
  vprocache01:
    image: memcached # Offizielles Memcached Image von DockerHub
    container_name: vprocache01
    ports:
      - "11211:11211" # Cache Service Port

# --- RabbitMQ Message Broker ---
  vpromq01:
    image: rabbitmq # Offizielles RabbitMQ Image von DockerHub
    container_name: vpromq01
    ports:
      - "5672:5672" # AMQP Port
    environment:
      - RABBITMQ_DEFAULT_USER=??? # Default User(application.properties)
      - RABBITMQ_DEFAULT_PASS=??? # Default Passwort(application.properties)

# --- Java App (Tomcat) ---
  vproapp:
    build:
      context: ./Docker-files/app # Dockerfile für Tomcat App
    image: meka237/profileapp
    container_name: vproapp
    ports:
      - "8080:8080" # Tomcat Application Port
    volumes:
      - vproappdata:/usr/local/tomcat/webapps # Persistente App-Daten

# --- Nginx Reverse Proxy ---
  vproweb:
    build:
      context: ./Docker-files/web # Dockerfile für Nginx Reverse Proxy
    image: meka237/profileweb
    container_name: vproweb
    ports:
      - "80:80" # HTTP Zugriff für Benutzer

volumes:
  vprodbdata: {} # Volume für MySQL Daten
  vproappdata: {} # Volume für Tomcat Webapps

10) Build & Run & Images auf Docker Hub

Ziel

Alle 5 Container der Profile-App lokal (VM) mit Docker Compose bauen und starten, danach die eigenen Images (App/DB/Web) nach Docker Hub pushen.

Warum?

  • Reproduzierbar: Ein Befehl startet die komplette Stack-Umgebung.
  • Schnell testbar: Lokale Validierung vor Cloud/CI/CD.
  • Wiederverwendbar: Images liegen zentral in Docker Hub.

Wie (Vorgehen)

  1. In die VM wechseln (Vagrant)
    • Im Projektordner zum Vagrant-Setup gehen und in die VM einloggen.
    • Root werden: sudo -i
    • In den /vagrant-Ordner wechseln (Shared Folder).
    • Sicherstellen, dass Docker-files/ und compose.yaml im gleichen Ordner liegen (z.B. /vagrant).
  2. Images bauen
    • docker compose build (liest compose.yaml, baut die Images mit build: context für App/DB/Web, und zieht die offiziellen Images für Memcached/RabbitMQ.)
  3. Images prüfen
    • docker images (zeigt, ob die Images meka237/profileapp, meka237/profiledb, meka237/profileweb gebaut wurden.)

    • Elastic Beanstalk Application – HTTP Not Secure
  4. Container starten
    • docker compose up -d (startet alle 5 Services im Hintergrund, erstellt Network + Volumes automatisch.)
    • (Pull 2 Images von DockerHub (rm und mc)), (2 Volumes hergestllt und 3 containers (app, db, web)) hergestllt
  5. Status prüfen
    • docker ps (zeigt laufende Container inkl. Ports.)
  6. App im Browser testen (über Nginx / Web-Container)
    • VM-IP ermitteln: ip addr show (suche die IP von enp0s8.)
    • Browser öffnen: http://<VM_IP>:80 (Traffic → Nginx → Tomcat-App.)
    • Elastic Beanstalk Application – HTTP Not Secure
  7. Bei Docker Hub einloggen
    • docker login (ggf. Device-Code/Browser-Confirm durchführen, bis Login Succeeded erscheint.)
  8. Images nach Docker Hub pushen
    • docker push meka237/profileapp
    • docker push meka237/profiledb
    • docker push meka237/profileweb
    • Elastic Beanstalk Application – HTTP Not Secure
  9. Cleanup
    • Container stoppen & entfernen: docker compose down
    • Volumes entfernen : docker volume rm vagrant_vproappdata
      docker volume rm vagrant_vprodbdata (oder docker volume prune.)
    • System aufräumen: docker system prune -a (löscht ungenutzte Images/Cache → spart Speicher.)

11) Zusammenfassung – Containerisierung der Java Web App „Profile“

Kurz erklärt

Bevor wir die Anwendung containerisieren, stellen wir zuerst eine reproduzierbare Linux-Umgebung bereit: eine Ubuntu-VM mit Vagrant. Darin installieren wir die Docker Engine und können anschließend den kompletten „Profile“-Stack als Container betreiben.

Im Projekt DevOps Projekt 1 wurde bewusst zunächst der manuelle Setup der einzelnen Services (klassische Installation und Konfiguration) gezeigt, um genau zu verstehen, welche Schritte Docker später automatisiert und vereinheitlicht. Aufbauend darauf folgt hier in diesem Projekt die eigentliche Containerisierung der Anwendung mithilfe von Dockerfiles, Docker Compose und Docker Hub.

Was ist das Ergebnis?

  • Verständnis: erst manuell (wie Services funktionieren), dann automatisiert (Container).
  • Einheitliches Setup: gleiche Umgebung für jeden Entwickler/Server.
  • Ein Befehl: startet das komplette System (App + Backend Services).
  • Portable Images: Upload nach Docker Hub für spätere Deployments/CI/CD.

Schema – kompletter Weg (VM → Docker → Docker Hub)

Developer Laptop
   │
   ├─ (A) Manuelles Setup (Lern-/Vergleichsteil)
   │     ├─ Java/Tomcat installieren + WAR deployen
   │     ├─ MySQL installieren + Schema importieren
   │     ├─ Memcached installieren
   │     ├─ RabbitMQ installieren
   │     └─ Nginx als Reverse Proxy konfigurieren
   │        → Ziel: verstehen, welche Abhängigkeiten/Configs später in Container wandern
   │
   ├─ (B) VM erstellen (Vagrant)
   │     ├─ vagrant up  → Ubuntu VM
   │     ├─ vagrant ssh → Zugriff auf VM
   │     └─ sudo -i     → Root (für Installation)
   │
   ├─ (C) Docker Engine installieren (in der VM)
   │     ├─ Docker Repo hinzufügen
   │     ├─ Docker Engine installieren
   │     ├─ User in docker-Gruppe (optional) → docker ohne sudo
   │     └─ docker --version / docker compose version (Check)
   │
   ├─ (D) Source Code + Container-Dateien vorbereiten
   │     ├─ Git clone (Profile Source Code)
   │     ├─ Branch: containers
   │     ├─ Docker-files/
   │     │     ├─ app/  (Tomcat Multi-Stage Dockerfile)
   │     │     ├─ db/   (MySQL Dockerfile + db_backup.sql)
   │     │     └─ web/  (Nginx Dockerfile + vproapp.conf)
   │     └─ compose.yaml (5 Services)
   │
   ├─ (E) Build & Run (Docker Compose in der VM)
   │     ├─ docker compose build
   │     │     ├─ baut: profileapp, profiledb, profileweb
   │     │     └─ pullt: memcached, rabbitmq (offizielle Images)
   │     ├─ docker images  (Check)
   │     └─ docker compose up -d
   │           → startet 5 Container + Network + Volumes
   │
   ├─ (F) Zugriff/Verifikation
   │     ├─ docker ps
   │     ├─ IP der VM (ip a → enp0s8)
   │     └─ Browser: http://VM-IP:80
   │           User → Nginx → Tomcat(App:8080)
   │
   └─ (G) Docker Hub (Images veröffentlichen)
         ├─ docker login
         ├─ docker push meka237/profileapp
         ├─ docker push meka237/profiledb
         └─ docker push meka237/profileweb
               → Images sind zentral verfügbar (Pull überall möglich)


Laufender Container-Stack (Docker Compose)
   ┌──────────────┐     ┌──────────────┐
   │   vproweb     │     │   vproapp     │
   │  Nginx :80    │──→  │ Tomcat :8080  │
   └──────────────┘     └──────────────┘
            │                   │
            │                   ├──→ vprodb :3306  (MySQL + Volume)
            │                   ├──→ vprocache01 :11211 (Memcached)
            │                   └──→ vpromq01 :5672 (RabbitMQ)
Containerisierung microservice App / Run on EC2-Instanz(AWS)