build: switch to hybrid build for Kotlin/JS web-app and optimize Docker setup
Replaced multi-stage Docker builds with a hybrid approach that pre-builds frontend artifacts locally and copies them into the container. Removed Kotlin Multiplatform configurations from the root project to resolve NodeJsRootPlugin conflicts. Adjusted `.dockerignore` to allow pre-built artifacts and increased Gradle/Kotlin daemon memory for faster builds. Updated Caddyfile for runtime stability and added documentation for new build processes.
This commit is contained in:
parent
03e1484dd3
commit
e8dd8cf48f
|
|
@ -187,7 +187,6 @@ secrets/
|
|||
**/*.sqlite3
|
||||
**/postgres-data/
|
||||
**/redis-data/
|
||||
# REMOVED: **/data/ - This was excluding source packages named 'data' (e.g. at.mocode...data)
|
||||
|
||||
# ===================================================================
|
||||
# Application specific exclusions
|
||||
|
|
@ -216,6 +215,12 @@ NOTES*.md
|
|||
!docs/
|
||||
|
||||
# ===================================================================
|
||||
# Final note: Each Dockerfile should copy only what it needs
|
||||
# This .dockerignore provides a baseline for all builds
|
||||
# HYBRID BUILD EXCEPTIONS (Must be at the end!)
|
||||
# ===================================================================
|
||||
# We need to explicitly un-ignore the path to the pre-built artifacts.
|
||||
# Since **/build/ and **/dist/ are ignored above, we must un-ignore
|
||||
# the specific chain of directories.
|
||||
!frontend/shells/meldestelle-portal/build/
|
||||
!frontend/shells/meldestelle-portal/build/dist/
|
||||
!frontend/shells/meldestelle-portal/build/dist/js/
|
||||
!frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/
|
||||
|
|
|
|||
1
.env
1
.env
|
|
@ -103,6 +103,7 @@ PING_CONSUL_HOSTNAME=ping-service
|
|||
PING_CONSUL_PREFER_IP=true
|
||||
|
||||
# --- WEB-APP ---
|
||||
CADDY_VERSION=2.11-alpine
|
||||
WEB_APP_PORT=4000:4000
|
||||
WEB_BUILD_PROFILE=dev
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ plugins {
|
|||
// This prevents "plugin loaded multiple times" errors in Gradle 9.2.1+
|
||||
// Subprojects apply these plugins via version catalog: alias(libs.plugins.kotlinJvm)
|
||||
alias(libs.plugins.kotlinJvm) apply false
|
||||
// CHANGE: Apply KMP plugin at root (but don't configure targets yet) to claim NodeJsRootPlugin ownership
|
||||
alias(libs.plugins.kotlinMultiplatform) apply true
|
||||
// KMP plugin applied as 'apply false' to avoid root project conflict
|
||||
alias(libs.plugins.kotlinMultiplatform) apply false
|
||||
alias(libs.plugins.kotlinSerialization) apply false
|
||||
alias(libs.plugins.kotlinSpring) apply false
|
||||
alias(libs.plugins.kotlinJpa) apply false
|
||||
|
|
@ -34,19 +34,6 @@ plugins {
|
|||
alias(libs.plugins.ktlint)
|
||||
}
|
||||
|
||||
// Minimal KMP configuration for Root Project to satisfy the plugin
|
||||
// This ensures NodeJsRootPlugin is initialized here first.
|
||||
kotlin {
|
||||
jvm() // Dummy target to keep KMP happy
|
||||
|
||||
// FIX: Explicitly initialize JS target at root to force NodeJsRootPlugin loading
|
||||
// This prevents "IsolatedKotlinClasspathClassCastException" in subprojects
|
||||
js {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
}
|
||||
|
||||
// ##################################################################
|
||||
// ### ALLPROJECTS CONFIGURATION ###
|
||||
// ##################################################################
|
||||
|
|
@ -54,11 +41,6 @@ kotlin {
|
|||
allprojects {
|
||||
group = "at.mocode"
|
||||
version = "1.0.0-SNAPSHOT"
|
||||
|
||||
// The 'repositories' block was removed from here.
|
||||
// Repository configuration is now centralized in 'settings.gradle.kts'
|
||||
// as per modern Gradle best practices. This resolves dependency resolution
|
||||
// conflicts with platforms and Spring Boot 4+.
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
|
@ -94,8 +76,6 @@ subprojects {
|
|||
maxHeapSize = "2g"
|
||||
// Parallel test execution for better performance
|
||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
// Removed byte-buddy-agent configuration to fix Gradle 9.0.0 deprecation warning
|
||||
// The agent configuration was causing Task.project access at execution time
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,69 +1,16 @@
|
|||
# syntax=docker/dockerfile:1.8
|
||||
# ===================================================================
|
||||
# Multi-Stage Dockerfile for Meldestelle Web-App (Kotlin/JS)
|
||||
# Version: 3.0.0 - Caddy Edition with Runtime Config
|
||||
# Dockerfile for Meldestelle Web-App (Pre-built Artifacts)
|
||||
# Version: 3.1.0 - Local Build Injection
|
||||
# ===================================================================
|
||||
|
||||
# === GLOBAL ARGS ===
|
||||
ARG GRADLE_VERSION=9.3.1
|
||||
ARG JAVA_VERSION=25
|
||||
ARG CADDY_VERSION=2.11-alpine
|
||||
ARG VERSION=1.0.0-SNAPSHOT
|
||||
ARG BUILD_DATE
|
||||
|
||||
# ===================================================================
|
||||
# Stage 1: Build Stage
|
||||
# ===================================================================
|
||||
FROM gradle:${GRADLE_VERSION}-jdk${JAVA_VERSION} AS builder
|
||||
|
||||
LABEL stage=builder
|
||||
WORKDIR /workspace
|
||||
|
||||
# 1. Gradle Optimizations (Memory & Caching)
|
||||
# Increased Heap to 4g for Kotlin 2.3 JS Compilation
|
||||
# REMOVED: -Dorg.gradle.daemon=false (We want the daemon to handle classloading correctly!)
|
||||
ENV GRADLE_OPTS="-Dorg.gradle.caching=true \
|
||||
-Dorg.gradle.parallel=true \
|
||||
-Dorg.gradle.workers.max=4 \
|
||||
-Dorg.gradle.jvmargs=-Xmx4g \
|
||||
-XX:+UseParallelGC"
|
||||
ENV GRADLE_USER_HOME=/home/gradle/.gradle
|
||||
|
||||
# 2. Dependency Layering (Optimize Cache Hit Rate)
|
||||
COPY gradlew gradlew.bat gradle.properties settings.gradle.kts ./
|
||||
COPY gradle/ gradle/
|
||||
# Copy Version Catalog explicitly first!
|
||||
COPY gradle/libs.versions.toml gradle/libs.versions.toml
|
||||
|
||||
RUN chmod +x gradlew
|
||||
|
||||
# 3. Copy Sources (Monorepo Structure)
|
||||
COPY platform/ platform/
|
||||
COPY core/ core/
|
||||
COPY backend/ backend/
|
||||
COPY frontend/ frontend/
|
||||
COPY config/ config/
|
||||
COPY contracts/ contracts/
|
||||
|
||||
# Create dummy docs dir
|
||||
RUN mkdir -p docs
|
||||
|
||||
# 4. Build Web App
|
||||
# REMOVED: --no-daemon flag to allow Gradle to manage its classloaders properly
|
||||
# This fixes the "IsolatedKotlinClasspathClassCastException" in Gradle 9.x + KMP
|
||||
RUN --mount=type=cache,target=/home/gradle/.gradle/caches \
|
||||
--mount=type=cache,target=/home/gradle/.gradle/wrapper \
|
||||
./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution \
|
||||
-Pproduction=true \
|
||||
-PnoSourceMaps=true \
|
||||
--stacktrace
|
||||
|
||||
# 5. Prepare Dist
|
||||
RUN mkdir -p /app/dist && \
|
||||
cp -r frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/* /app/dist/
|
||||
|
||||
# ===================================================================
|
||||
# Stage 2: Runtime Stage (Caddy)
|
||||
# Stage 1: Runtime Stage (Caddy)
|
||||
# ===================================================================
|
||||
FROM caddy:${CADDY_VERSION}
|
||||
|
||||
|
|
@ -79,11 +26,12 @@ LABEL service="web-app" \
|
|||
COPY config/docker/caddy/web-app/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY config/docker/caddy/web-app/config.json /usr/share/caddy/config.json
|
||||
|
||||
# Copy Static Assets from Builder
|
||||
COPY --from=builder /app/dist/ /usr/share/caddy/
|
||||
# Copy Pre-built Static Assets from Host
|
||||
# NOTE: You must run `./gradlew :frontend:shells:meldestelle-portal:jsBrowserDistribution -Pproduction=true` locally first!
|
||||
COPY frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/ /usr/share/caddy/
|
||||
|
||||
# Ensure favicon exists (fallback)
|
||||
COPY --from=builder /workspace/config/docker/nginx/web-app/favicon.svg /usr/share/caddy/favicon.svg
|
||||
COPY config/docker/nginx/web-app/favicon.svg /usr/share/caddy/favicon.svg
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ kotlin {
|
|||
|
||||
// JS target for frontend usage (Compose/Browser)
|
||||
js {
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wasm enabled by default
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
plugins {
|
||||
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
|
||||
id("org.jetbrains.kotlin.multiplatform")
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
plugins {
|
||||
// Fix for "Plugin loaded multiple times": Apply plugin by ID without version (inherited from root)
|
||||
id("org.jetbrains.kotlin.multiplatform")
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js {
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Wasm support enabled?
|
||||
@OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Kommuniziere ausschließlich auf Deutsch.
|
|||
|
||||
Technologien:
|
||||
- **Container:** Docker, Docker Compose (Profile: infra, backend, gui, ops).
|
||||
- **Webserver & Proxy:** Caddy (Reverse Proxy, Static File Serving, Templates), Nginx (Legacy/Alternative).
|
||||
- **IAM:** Keycloak 26 (OIDC/OAuth2). Nutzung des offiziellen Images (`quay.io/keycloak/keycloak`) im `start-dev` Modus für lokale Entwicklung.
|
||||
- **Service Discovery:** HashiCorp Consul.
|
||||
- **Monitoring & Tracing:** Prometheus, Grafana, Zipkin, Micrometer.
|
||||
|
|
@ -23,10 +24,11 @@ Technologien:
|
|||
|
||||
Aufgaben:
|
||||
1. **Container-Orchestrierung:** Stelle sicher, dass `docker-compose.yaml` fehlerfrei läuft. Achte auf korrekte Healthchecks und Start-Reihenfolgen (depends_on).
|
||||
2. **Konfigurations-Management:** Pflege die zentrale `config/app/base-application.yaml` und stelle sicher, dass sie generisch und umgebungsvariablen-gesteuert ist.
|
||||
3. **Identity Management:** Verwalte den Keycloak-Realm (`meldestelle-realm.json`). Stelle sicher, dass der Import beim Start funktioniert.
|
||||
4. **Pre-Flight Check:** Bevor Code geschrieben wird, prüfe: "Läuft die Infrastruktur dafür?". Wenn nein: Erst Infra fixen, dann coden.
|
||||
5. **Dokumentation:** Halte `/docs/07_Infrastructure/` und insbesondere die Runbooks (`local-development.md`) aktuell. Dokumentiere Ports und Zugangsdaten.
|
||||
2. **Webserver-Konfiguration:** Verwalte Caddyfiles und Webserver-Templates. Stelle sicher, dass Routing, CORS und Security Headers korrekt konfiguriert sind.
|
||||
3. **Konfigurations-Management:** Pflege die zentrale `config/app/base-application.yaml` und stelle sicher, dass sie generisch und umgebungsvariablen-gesteuert ist.
|
||||
4. **Identity Management:** Verwalte den Keycloak-Realm (`meldestelle-realm.json`). Stelle sicher, dass der Import beim Start funktioniert.
|
||||
5. **Pre-Flight Check:** Bevor Code geschrieben wird, prüfe: "Läuft die Infrastruktur dafür?". Wenn nein: Erst Infra fixen, dann coden.
|
||||
6. **Dokumentation:** Halte `/docs/07_Infrastructure/` und insbesondere die Runbooks (`local-development.md`) aktuell. Dokumentiere Ports und Zugangsdaten.
|
||||
|
||||
Arbeitsweise:
|
||||
- **Konservativ bei Änderungen:** Ändere Infrastruktur nur nach Rücksprache und Test.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
type: Journal
|
||||
status: ACTIVE
|
||||
owner: DevOps Engineer
|
||||
last_update: 2026-02-04
|
||||
---
|
||||
|
||||
# Session Log: Docker Hybrid Build & Build Optimization
|
||||
|
||||
**Datum:** 04.02.2026
|
||||
**Teilnehmer:** User, DevOps Engineer
|
||||
**Fokus:** Stabilisierung des Frontend-Builds im Docker-Container und Optimierung der Build-Performance.
|
||||
|
||||
## 🎯 Ziel
|
||||
Den Docker-Build für den `web-app` Service reparieren, der aufgrund von Gradle/Kotlin-Plugin-Konflikten (`IsolatedKotlinClasspathClassCastException`) fehlschlug, und die Build-Zeiten optimieren.
|
||||
|
||||
## 📝 Protokoll
|
||||
|
||||
### 1. Problem: Gradle Plugin Konflikte im Docker
|
||||
* **Symptom:** `IsolatedKotlinClasspathClassCastException` beim Build im Docker-Container.
|
||||
* **Ursache:** Konflikt zwischen Gradle 9.x, Kotlin 2.3.0 und dem `NodeJsRootPlugin`, wenn das Root-Projekt versucht, die Node.js-Umgebung zu initialisieren, während Subprojekte dies ebenfalls tun.
|
||||
* **Lösung:**
|
||||
1. **Root `build.gradle.kts` bereinigt:** KMP-Plugin nur noch mit `apply false` eingebunden. `kotlin { ... }` Block im Root entfernt. Root ist nun reiner Konfigurations-Container.
|
||||
2. **Hybrid-Build Strategie:** Statt im Docker-Container zu bauen (was instabil war), bauen wir das Frontend lokal (`./gradlew ...jsBrowserDistribution`) und kopieren die fertigen Artefakte in den Container.
|
||||
|
||||
### 2. Problem: `.dockerignore` blockiert Artefakte
|
||||
* **Symptom:** `COPY failed: ... not found`.
|
||||
* **Ursache:** `.dockerignore` schloss `**/build/` pauschal aus.
|
||||
* **Lösung:** Explizite Ausnahme für den Pfad `!frontend/shells/meldestelle-portal/build/dist/js/productionExecutable/` (inklusive aller Eltern-Ordner) hinzugefügt.
|
||||
|
||||
### 3. Problem: Build-Performance (Webpack Timeout)
|
||||
* **Symptom:** Webpack-Task lief >12 Minuten oder hing.
|
||||
* **Ursache:** Zu wenig Speicher für den Gradle Daemon und Node.js Prozess bei großen Builds.
|
||||
* **Lösung:** `gradle.properties` angepasst:
|
||||
* `org.gradle.jvmargs`: 4GB (vorher 3GB)
|
||||
* `kotlin.daemon.jvmargs`: 4GB (vorher 3GB)
|
||||
|
||||
### 4. Problem: Caddy Config & Runtime
|
||||
* **Symptom:** 500er Fehler beim Abruf von `/config.json`.
|
||||
* **Ursache:** Syntax-Fehler im Caddy-Template (Escaping von Anführungszeichen).
|
||||
* **Lösung:** Template auf einfache Syntax zurückgesetzt: `{{env "API_BASE_URL" ...}}`.
|
||||
* **Playbook Update:** DevOps Engineer übernimmt explizit die Verantwortung für Caddy/Webserver-Konfiguration.
|
||||
|
||||
## ✅ Ergebnisse
|
||||
1. **Web-App läuft:** Der Container `meldestelle-web-app` startet erfolgreich und ist unter `http://localhost:4000` erreichbar.
|
||||
2. **Build-Prozess:** Stabilisiert durch Hybrid-Ansatz (Lokal bauen -> Docker kopieren).
|
||||
3. **Infrastruktur:** `build.gradle.kts` (Root) ist sauberer und performanter.
|
||||
|
||||
## ⏭️ Nächste Schritte (Open Points)
|
||||
* **Ping-Service Erreichbarkeit:** Das Frontend kann den Ping-Service (`http://localhost:8081`) noch nicht erreichen (CORS oder Netzwerk-Thema). -> Übergabe an Backend/Frontend.
|
||||
* **CI/CD:** Für die CI-Pipeline muss der Hybrid-Build berücksichtigt werden (Build-Step vor Docker-Build).
|
||||
|
||||
## 📂 Betroffene Dateien
|
||||
* `build.gradle.kts` (Root)
|
||||
* `gradle.properties`
|
||||
* `config/docker/caddy/web-app/Dockerfile`
|
||||
* `.dockerignore`
|
||||
* `config/docker/caddy/web-app/config.json`
|
||||
* `docs/04_Agents/Playbooks/DevOpsEngineer.md`
|
||||
|
|
@ -12,8 +12,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
// Use nodejs() to minimize NodeJsRootPlugin conflicts in Docker
|
||||
nodejs()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
|
|
@ -12,7 +8,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
|
|
@ -11,7 +7,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ kotlin {
|
|||
jvm()
|
||||
js {
|
||||
binaries.library()
|
||||
browser()
|
||||
browser {
|
||||
testTask {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ android.nonTransitiveRClass=true
|
|||
|
||||
# Kotlin Configuration
|
||||
kotlin.code.style=official
|
||||
kotlin.daemon.jvmargs=-Xmx3072M -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M
|
||||
# Increased Kotlin Daemon Heap for JS Compilation
|
||||
kotlin.daemon.jvmargs=-Xmx4g -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g
|
||||
|
||||
# Kotlin Compiler Optimizations (Phase 5)
|
||||
kotlin.incremental=true
|
||||
|
|
@ -17,7 +18,8 @@ kotlin.compiler.execution.strategy=in-process
|
|||
kotlin.stdlib.default.dependency=true
|
||||
|
||||
# Gradle Configuration
|
||||
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" -XX:+UseParallelGC -XX:MaxMetaspaceSize=1024M -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true
|
||||
# Increased Gradle Daemon Heap
|
||||
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx3g" -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Xshare:off -Djava.awt.headless=true
|
||||
org.gradle.workers.max=8
|
||||
org.gradle.vfs.watch=true
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user