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.
- Reproduzierbar: Setup über Dockerfiles + docker-compose statt manueller Installation
- Portabel: Gleiche Laufzeitumgebung unabhängig von Betriebssystem/Server
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).
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).
Alle Images stammen aus dem offiziellen Docker Hub und werden entweder direkt verwendet oder über eigene Dockerfiles angepasst.
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
-
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 -
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 - Im Projekt einen
-
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
-
Vagrant User zur Docker-Gruppe hinzufügen
- Docker läuft als Root-Service. Ohne diese Einstellung müsste jeder
Docker-Befehl mit
sudoausgeführt werden.
usermod -aG docker vagrant newgrp docker - Docker läuft als Root-Service. Ohne diese Einstellung müsste jeder
Docker-Befehl mit
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 Tomcat → Container 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)
-
Docker-Ordnerstruktur im Projekt anlegen
profile/ └── src/ └── vagrant/ └── Docker-files/ └── app/ └── Dockerfile -
Basis-Images auf Docker Hub auswählen
maven:3.9.9-eclipse-temurin-21-jammy→ Build-Stagetomcat:10-jdk21→ Runtime-Stage
Wir bauen diese Images nicht „from scratch“, weil sie bereits gepflegt und standardisiert sind.
-
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 imtarget/-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. -
Stage 1 – BUILD (Maven)
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)
-
Erstelle im Projektordner
Docker-files/einen Unterordnerdb/.
Warum? So liegen DB-Dateien (SQL, Dockerfile) sauber getrennt von der App. -
Kopiere die Datei
db_backup.sqlinDocker-files/db/.
Warum? Diese Datei initialisiert später automatisch das Datenbankschema + Daten. -
Verwende das offizielle Image
mysql:8.0.33von Docker Hub. -
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
-
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).
1) Projektstruktur vorbereiten
Docker-files/
├── app/
│ └── Dockerfile
└── db/
├── Dockerfile
└── db_backup.sql
2) Basis-Image auswählen
3) Dockerfile schreiben (DB Image)
Dieses Dockerfile baut ein MySQL-Image, das beim ersten Start die Datenbank automatisch initialisiert.
Warum genau diese ENV-Werte?
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)
-
Docker-Ordnerstruktur im Projekt anlegen
profile/ └── src/ └── vagrant/ └── Docker-files/ └── app/ └── db/ └── web/ └── Dockerfile └── nginvproapp.conf -
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:
serverNginx 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; } } -
Basis-Images auf Docker Hub auswählen
nginx
-
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
-
Basis: offizielles Nginx Image (enthält Nginx + Standardstruktur)
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
- Account auf https://hub.docker.com erstellen
- 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.
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
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)
-
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/undcompose.yamlim gleichen Ordner liegen (z.B./vagrant).
-
Images bauen
-
docker compose build(liestcompose.yaml, baut die Images mitbuild: contextfür App/DB/Web, und zieht die offiziellen Images für Memcached/RabbitMQ.)
-
-
Images prüfen
-
docker images(zeigt, ob die Imagesmeka237/profileapp,meka237/profiledb,meka237/profilewebgebaut wurden.)
-
-
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
-
-
Status prüfen
-
docker ps(zeigt laufende Container inkl. Ports.)
-
-
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.)
-
VM-IP ermitteln:
-
Bei Docker Hub einloggen
-
docker login(ggf. Device-Code/Browser-Confirm durchführen, bis Login Succeeded erscheint.)
-
-
Images nach Docker Hub pushen
docker push meka237/profileappdocker push meka237/profiledbdocker push meka237/profileweb
-
Cleanup
-
Container stoppen & entfernen:
docker compose down -
Volumes entfernen :
docker volume rm vagrant_vproappdata
docker volume rm vagrant_vprodbdata(oderdocker volume prune.) -
System aufräumen:
docker system prune -a(löscht ungenutzte Images/Cache → spart Speicher.)
-
Container stoppen & entfernen:
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)