Deployment als Docker-Container
Ein Container liefert Startseite und REST-API über Port 8080 aus: Das Dockerfile baut das Frontend mit Node, legt das Ergebnis in die statischen Ressourcen des Backends und packt beides mit Maven zum Jar; die Laufzeit- stufe enthält nur ein JRE und das Jar, ausgeführt als Benutzer swyx. Die beiden Build-Stufen laufen auf $BUILDPLATFORM, also nativ auf dem bauenden Rechner - JS-Bundle und Jar sind architekturunabhängig. Nur das Laufzeit-Image wird für die Zielarchitektur gezogen, was beim Bauen für linux/amd64 auf einem ARM-Mac die Emulation spart. docker_push.sh liest die Version aus backend/pom.xml, baut für linux/amd64 und pusht in die Registry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# Build-Ergebnisse und Abhängigkeiten kommen aus dem Image-Build, nicht vom Host.
|
||||||
|
backend/target/
|
||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Lokale Konfiguration
|
||||||
|
frontend/.env
|
||||||
|
frontend/.env.local
|
||||||
|
|
||||||
|
# Tooling / OS / VCS
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
README.md
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# SwyxWeb als ein Container: Das React-Frontend wird gebaut, in die statischen
|
||||||
|
# Ressourcen des Backends gelegt und zusammen mit ihm in ein Jar gepackt.
|
||||||
|
# Ausgeliefert wird am Ende alles über den einen Port des Backends (8080).
|
||||||
|
#
|
||||||
|
# Der eingebaute SwyxTray-Mock (/ws und /api/mock/**) läuft NICHT mit: seine
|
||||||
|
# Bohnen hängen am Spring-Profil "mock", und das setzt dieses Image nicht.
|
||||||
|
#
|
||||||
|
# Die beiden Build-Stufen laufen bewusst auf $BUILDPLATFORM, also nativ auf dem
|
||||||
|
# bauenden Rechner: JS-Bundle und Jar sind architekturunabhängig. Nur die
|
||||||
|
# Laufzeitstufe wird für die Zielarchitektur gezogen. Ein Build mit
|
||||||
|
# --platform linux/amd64 auf einem ARM-Mac läuft dadurch ohne Emulation.
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. Frontend bauen (Vite -> frontend/dist)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM --platform=$BUILDPLATFORM node:22-alpine AS frontend
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Erst die Abhängigkeiten, damit diese Schicht im Cache bleibt, solange sich
|
||||||
|
# package.json/package-lock.json nicht ändern.
|
||||||
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY frontend/ ./
|
||||||
|
# VITE_WS_URL nur setzen, wenn die Adresse fest ins Bundle soll; im Normalfall
|
||||||
|
# kommt sie zur Laufzeit aus /api/config (siehe README).
|
||||||
|
ARG VITE_WS_URL
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Backend bauen (Spring Boot 4.1, Java 21) inkl. Frontend als static/
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM --platform=$BUILDPLATFORM maven:3.9-eclipse-temurin-21 AS backend
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
# Abhängigkeiten vorab auflösen – eigene Schicht, unabhängig vom Quelltext.
|
||||||
|
COPY backend/pom.xml ./
|
||||||
|
RUN --mount=type=cache,target=/root/.m2 \
|
||||||
|
mvn -B -q dependency:go-offline
|
||||||
|
|
||||||
|
COPY backend/src ./src
|
||||||
|
COPY --from=frontend /build/dist/ ./src/main/resources/static/
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.m2 \
|
||||||
|
mvn -B -DskipTests clean package \
|
||||||
|
&& cp target/*.jar /build/app.jar
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. Laufzeit-Image
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
FROM eclipse-temurin:21-jre-alpine AS runtime
|
||||||
|
|
||||||
|
# Nicht als root laufen.
|
||||||
|
RUN addgroup -S swyx && adduser -S -G swyx swyx
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=backend --chown=swyx:swyx /build/app.jar ./app.jar
|
||||||
|
|
||||||
|
USER swyx
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Adresse der SwyxTray-App zur Laufzeit überschreibbar (Spring Relaxed Binding):
|
||||||
|
# -e APP_WEBSOCKET_HOST=192.168.180.135 -e APP_WEBSOCKET_PORT=17654
|
||||||
|
# -e APP_WEBSOCKET_PATH=/ws -e APP_WEBSOCKET_SECURE=false
|
||||||
|
#
|
||||||
|
# SPRING_PROFILES_ACTIVE bleibt bewusst leer – der SwyxTray-Mock bliebe sonst
|
||||||
|
# beim Ausrollen erreichbar. Für eine Vorführung ohne Telefonanlage lässt er
|
||||||
|
# sich einzeln einschalten: -e SPRING_PROFILES_ACTIVE=mock
|
||||||
|
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0"
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||||
|
CMD wget -q -O /dev/null http://127.0.0.1:8080/api/config || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"]
|
||||||
Executable
+112
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
readonly SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# Paket-Registry des Gitea auf gitea.appcreation.de; der Pfad ist dort
|
||||||
|
# <Host>/<Besitzer>/<Image>.
|
||||||
|
readonly REGISTRY_IMAGE="gitea.appcreation.de/appcreation/swyxweb"
|
||||||
|
readonly BACKEND_DIR="${SCRIPT_DIR}/backend"
|
||||||
|
readonly PLATFORM="linux/amd64"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Verwendung:
|
||||||
|
./docker_push.sh [x.y.z] [--dry-run]
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
./docker_push.sh 0.9.13
|
||||||
|
./docker_push.sh
|
||||||
|
./docker_push.sh 0.9.13 --dry-run # nur bauen, nicht pushen
|
||||||
|
|
||||||
|
Voraussetzungen:
|
||||||
|
- Docker Buildx ist installiert
|
||||||
|
- Login zur Registry wurde bereits ausgeführt:
|
||||||
|
docker login gitea.appcreation.de
|
||||||
|
|
||||||
|
Ohne Versionsargument wird die Version aus backend/pom.xml verwendet.
|
||||||
|
|
||||||
|
Gebaut wird das Dockerfile im Wurzelverzeichnis: Es übersetzt Frontend *und*
|
||||||
|
Backend selbst, ein vorheriger lokaler Maven- oder npm-Lauf ist also nicht
|
||||||
|
nötig. Der SwyxTray-Mock ist im Image nicht enthalten (Profil "mock").
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
echo "Fehler: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_command() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || fail "'$1' wurde nicht gefunden."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Die Version steht in backend/pom.xml; ausgelesen wird sie mit dem Maven-
|
||||||
|
# Wrapper des Backends (backend/mvnw), nicht mit einem global installierten Maven.
|
||||||
|
resolve_pom_version() {
|
||||||
|
[[ -x "${BACKEND_DIR}/mvnw" ]] || fail "'${BACKEND_DIR}/mvnw' wurde nicht gefunden oder ist nicht ausführbar."
|
||||||
|
|
||||||
|
local version
|
||||||
|
version="$(
|
||||||
|
cd "${BACKEND_DIR}" && ./mvnw -q -DforceStdout help:evaluate -Dexpression=project.version \
|
||||||
|
| awk 'NF { last = $0 } END { print last }'
|
||||||
|
)"
|
||||||
|
|
||||||
|
[[ -n "${version}" ]] || fail "Version konnte nicht aus der pom.xml ermittelt werden."
|
||||||
|
echo "${version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSION=""
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "${arg}" in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=true
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
fail "Unbekannte Option '${arg}'."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[[ -z "${VERSION}" ]] || fail "Es ist nur eine Versionsnummer erlaubt."
|
||||||
|
VERSION="${arg}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -n "${VERSION}" ]] || VERSION="$(resolve_pom_version)"
|
||||||
|
|
||||||
|
# -SNAPSHOT ist zugelassen, weil die pom.xml zurzeit 0.0.1-SNAPSHOT trägt.
|
||||||
|
if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?$ ]]; then
|
||||||
|
fail "Versionsnummer muss das Format x.y.z oder x.y.z-SNAPSHOT haben (war: '${VERSION}')."
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_command docker
|
||||||
|
docker buildx version >/dev/null 2>&1 || fail "Docker Buildx ist nicht verfügbar."
|
||||||
|
|
||||||
|
cd "${SCRIPT_DIR}"
|
||||||
|
|
||||||
|
if [[ "${DRY_RUN}" == true ]]; then
|
||||||
|
echo "Baue Image ${REGISTRY_IMAGE}:${VERSION} für ${PLATFORM} (dry run, kein Push) ..."
|
||||||
|
docker buildx build \
|
||||||
|
--platform "${PLATFORM}" \
|
||||||
|
-f "${SCRIPT_DIR}/Dockerfile" \
|
||||||
|
-t "${REGISTRY_IMAGE}:${VERSION}" \
|
||||||
|
"${SCRIPT_DIR}"
|
||||||
|
echo "Fertig gebaut, nicht gepusht: ${REGISTRY_IMAGE}:${VERSION}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Baue und pushe Image ${REGISTRY_IMAGE}:${VERSION} für ${PLATFORM} ..."
|
||||||
|
docker buildx build \
|
||||||
|
--platform "${PLATFORM}" \
|
||||||
|
-f "${SCRIPT_DIR}/Dockerfile" \
|
||||||
|
-t "${REGISTRY_IMAGE}:${VERSION}" \
|
||||||
|
--push \
|
||||||
|
"${SCRIPT_DIR}"
|
||||||
|
|
||||||
|
echo "Fertig: ${REGISTRY_IMAGE}:${VERSION}"
|
||||||
Reference in New Issue
Block a user