build: add Dockerfile, compose deployment and CI pipeline
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
36d64ccf0b
commit
3b36dce605
8 files changed
+334
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
**/TestResults/
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
|
artifacts/
|
||||||
|
tests/
|
||||||
|
docs/
|
||||||
|
deploy/
|
||||||
|
tools/
|
||||||
|
**/*.user
|
||||||
|
**/appsettings.Development.json
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOTNET_NOLOGO: true
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
global-json-file: global.json
|
||||||
|
|
||||||
|
- name: Restore
|
||||||
|
run: dotnet restore FamilyPlanner.slnx
|
||||||
|
|
||||||
|
- name: Build (Warnungen sind Fehler)
|
||||||
|
run: dotnet build FamilyPlanner.slnx -c Release --no-restore
|
||||||
|
|
||||||
|
- name: Unit- und Komponententests
|
||||||
|
run: |
|
||||||
|
dotnet test --project tests/FamilyPlanner.UnitTests -c Release --no-build
|
||||||
|
dotnet test --project tests/FamilyPlanner.Web.Tests -c Release --no-build
|
||||||
|
|
||||||
|
- name: HTTPS-Entwicklungszertifikat (für Aspire-Integrationstests)
|
||||||
|
run: dotnet dev-certs https
|
||||||
|
|
||||||
|
- name: Integrationstests (Aspire + SQL-Server-Container)
|
||||||
|
run: dotnet test --project tests/FamilyPlanner.IntegrationTests -c Release --no-build
|
||||||
|
|
||||||
|
docker-images:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-and-test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
project: [FamilyPlanner.Api, FamilyPlanner.Web, FamilyPlanner.MigrationService]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Image bauen
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
build-args: PROJECT=${{ matrix.project }}
|
||||||
|
push: false
|
||||||
|
tags: familyplanner/${{ matrix.project }}:ci
|
||||||
|
cache-from: type=gha,scope=${{ matrix.project }}
|
||||||
|
cache-to: type=gha,mode=max,scope=${{ matrix.project }}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
# Gemeinsames Dockerfile für Api, Web und MigrationService.
|
||||||
|
# docker build --build-arg PROJECT=FamilyPlanner.Api -t familyplanner-api .
|
||||||
|
# Alternativ ohne Docker-Build: dotnet publish src/<Projekt> -c Release /t:PublishContainer
|
||||||
|
|
||||||
|
ARG DOTNET_VERSION=10.0
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} AS build
|
||||||
|
ARG PROJECT
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Zuerst nur Projektdateien kopieren, damit der Restore-Layer gecacht wird.
|
||||||
|
COPY global.json Directory.Build.props Directory.Packages.props .editorconfig ./
|
||||||
|
COPY src/FamilyPlanner.Domain/*.csproj src/FamilyPlanner.Domain/
|
||||||
|
COPY src/FamilyPlanner.Shared/*.csproj src/FamilyPlanner.Shared/
|
||||||
|
COPY src/FamilyPlanner.Infrastructure/*.csproj src/FamilyPlanner.Infrastructure/
|
||||||
|
COPY src/FamilyPlanner.ServiceDefaults/*.csproj src/FamilyPlanner.ServiceDefaults/
|
||||||
|
COPY src/FamilyPlanner.Api/*.csproj src/FamilyPlanner.Api/
|
||||||
|
COPY src/FamilyPlanner.MigrationService/*.csproj src/FamilyPlanner.MigrationService/
|
||||||
|
COPY src/FamilyPlanner.Web/*.csproj src/FamilyPlanner.Web/
|
||||||
|
COPY src/FamilyPlanner.Web.Client/*.csproj src/FamilyPlanner.Web.Client/
|
||||||
|
RUN dotnet restore "src/${PROJECT}/${PROJECT}.csproj"
|
||||||
|
|
||||||
|
COPY src/ src/
|
||||||
|
# Kein --no-restore: das SDK referenziert Microsoft.AspNetCore.App.Internal.Assets (blazor.web.js) erst,
|
||||||
|
# wenn Razor-Komponenten im Projekt liegen – das ist beim Restore-Layer oben noch nicht der Fall.
|
||||||
|
RUN dotnet publish "src/${PROJECT}/${PROJECT}.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION} AS final
|
||||||
|
ARG PROJECT
|
||||||
|
|
||||||
|
# curl nur für Docker-Health-Checks.
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/publish .
|
||||||
|
|
||||||
|
ENV ASPNETCORE_HTTP_PORTS=8080 \
|
||||||
|
DOTNET_RUNNING_IN_CONTAINER=true \
|
||||||
|
APP_DLL=${PROJECT}.dll
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Non-Root: der Benutzer "app" (UID 1654) ist in den Microsoft-Images vorhanden.
|
||||||
|
USER $APP_UID
|
||||||
|
|
||||||
|
ENTRYPOINT ["sh", "-c", "exec dotnet \"$APP_DLL\""]
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Kopieren nach deploy/.env und anpassen. Die .env-Datei niemals einchecken!
|
||||||
|
|
||||||
|
# --- Datenbank ---------------------------------------------------------------
|
||||||
|
# Mindestens 8 Zeichen, Groß-/Kleinbuchstaben, Ziffern und Sonderzeichen (SQL-Server-Vorgabe).
|
||||||
|
SQL_SA_PASSWORD=Bitte-Aendern-2026!
|
||||||
|
# SQL-Server-Edition: Express (kostenlos, bis 10 GB) | Developer | Standard ...
|
||||||
|
MSSQL_PID=Express
|
||||||
|
|
||||||
|
# --- Initialer Administrator (nur beim allerersten Start) -----------------------
|
||||||
|
# Das Passwort muss beim ersten Login geändert werden.
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=Erstes-Passwort-2026
|
||||||
|
ADMIN_EMAIL=
|
||||||
|
FAMILY_NAME=Familie Muster
|
||||||
|
|
||||||
|
# --- Netzwerk -------------------------------------------------------------------
|
||||||
|
# Ohne Caddy: Port des Web-Containers auf dem Host (127.0.0.1 = nur lokal bzw. für eigenen Reverse Proxy).
|
||||||
|
WEB_BIND=127.0.0.1
|
||||||
|
WEB_PORT=8080
|
||||||
|
# Mit Caddy (docker compose --profile proxy up -d): öffentlicher Domainname für das TLS-Zertifikat.
|
||||||
|
DOMAIN=familie.example.org
|
||||||
|
|
||||||
|
# --- Images ----------------------------------------------------------------------
|
||||||
|
# Optional: Registry-Präfix inkl. abschließendem Slash, z. B. ghcr.io/meinname/
|
||||||
|
REGISTRY=
|
||||||
|
TAG=latest
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Reverse Proxy mit automatischem HTTPS (Let's Encrypt). Domain kommt aus der Umgebungsvariable DOMAIN.
|
||||||
|
{$DOMAIN} {
|
||||||
|
encode zstd gzip
|
||||||
|
|
||||||
|
header {
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSockets (SignalR) werden automatisch durchgereicht.
|
||||||
|
reverse_proxy web:8080
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Produktivbetrieb auf einem Linux-Docker-Host.
|
||||||
|
#
|
||||||
|
# cp .env.example .env # Werte anpassen!
|
||||||
|
# docker compose up -d --build
|
||||||
|
# docker compose --profile proxy up -d --build # inkl. Caddy (HTTPS mit Let's Encrypt)
|
||||||
|
#
|
||||||
|
# Die Anwendung verlangt HTTPS (Secure-Cookies). Ohne Caddy entweder einen eigenen Reverse Proxy
|
||||||
|
# mit TLS davor setzen oder nur über http://localhost testen.
|
||||||
|
|
||||||
|
name: familyplanner
|
||||||
|
|
||||||
|
x-app-build: &app-build
|
||||||
|
context: ..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
|
||||||
|
x-connection-string: &connection-string
|
||||||
|
ConnectionStrings__familyplannerdb: "Server=sql,1433;Database=familyplannerdb;User Id=sa;Password=${SQL_SA_PASSWORD};TrustServerCertificate=True;Encrypt=True"
|
||||||
|
|
||||||
|
services:
|
||||||
|
sql:
|
||||||
|
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||||
|
# Das Image läuft als Benutzer "mssql" (non-root).
|
||||||
|
environment:
|
||||||
|
ACCEPT_EULA: "Y"
|
||||||
|
MSSQL_SA_PASSWORD: ${SQL_SA_PASSWORD:?SQL_SA_PASSWORD in .env setzen}
|
||||||
|
MSSQL_PID: ${MSSQL_PID:-Express}
|
||||||
|
TZ: Europe/Berlin
|
||||||
|
volumes:
|
||||||
|
- sql-data:/var/opt/mssql
|
||||||
|
- sql-backup:/var/opt/mssql/backup
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -b -Q 'SELECT 1' > /dev/null"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 20
|
||||||
|
start_period: 30s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
migrations:
|
||||||
|
image: ${REGISTRY:-}familyplanner-migrations:${TAG:-latest}
|
||||||
|
build:
|
||||||
|
<<: *app-build
|
||||||
|
args:
|
||||||
|
PROJECT: FamilyPlanner.MigrationService
|
||||||
|
environment:
|
||||||
|
<<: *connection-string
|
||||||
|
DOTNET_ENVIRONMENT: Production
|
||||||
|
Seed__AdminUserName: ${ADMIN_USERNAME:-admin}
|
||||||
|
Seed__AdminPassword: ${ADMIN_PASSWORD:?ADMIN_PASSWORD in .env setzen}
|
||||||
|
Seed__AdminEmail: ${ADMIN_EMAIL:-}
|
||||||
|
Seed__FamilyName: ${FAMILY_NAME:-}
|
||||||
|
depends_on:
|
||||||
|
sql:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
api:
|
||||||
|
image: ${REGISTRY:-}familyplanner-api:${TAG:-latest}
|
||||||
|
build:
|
||||||
|
<<: *app-build
|
||||||
|
args:
|
||||||
|
PROJECT: FamilyPlanner.Api
|
||||||
|
environment:
|
||||||
|
<<: *connection-string
|
||||||
|
ASPNETCORE_ENVIRONMENT: Production
|
||||||
|
TZ: Europe/Berlin
|
||||||
|
depends_on:
|
||||||
|
migrations:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 20s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: ${REGISTRY:-}familyplanner-web:${TAG:-latest}
|
||||||
|
build:
|
||||||
|
<<: *app-build
|
||||||
|
args:
|
||||||
|
PROJECT: FamilyPlanner.Web
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_ENVIRONMENT: Production
|
||||||
|
TZ: Europe/Berlin
|
||||||
|
# Service Discovery: Adresse der Api im Docker-Netz (YARP-BFF)
|
||||||
|
services__api__http__0: http://api:8080
|
||||||
|
ports:
|
||||||
|
- "${WEB_BIND:-127.0.0.1}:${WEB_PORT:-8080}:8080"
|
||||||
|
depends_on:
|
||||||
|
api:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fsS", "http://localhost:8080/alive"]
|
||||||
|
interval: 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 15s
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
profiles: ["proxy"]
|
||||||
|
image: caddy:2
|
||||||
|
environment:
|
||||||
|
DOMAIN: ${DOMAIN:-localhost}
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "443:443/udp"
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy-data:/data
|
||||||
|
- caddy-config:/config
|
||||||
|
depends_on:
|
||||||
|
web:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
sql-data:
|
||||||
|
sql-backup:
|
||||||
|
caddy-data:
|
||||||
|
caddy-config:
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# Erstellt ein Voll-Backup der Datenbank im Volume "sql-backup" und kopiert es zusätzlich nach ./backups.
|
||||||
|
# Aufruf aus dem Ordner deploy/: ./scripts/backup.sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
. ./.env
|
||||||
|
|
||||||
|
STAMP=$(date +%Y%m%d-%H%M%S)
|
||||||
|
FILE="familyplanner-${STAMP}.bak"
|
||||||
|
|
||||||
|
docker compose exec -T sql /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$SQL_SA_PASSWORD" -C -b \
|
||||||
|
-Q "BACKUP DATABASE [familyplannerdb] TO DISK = N'/var/opt/mssql/backup/${FILE}' WITH COMPRESSION, CHECKSUM, INIT"
|
||||||
|
|
||||||
|
mkdir -p backups
|
||||||
|
docker compose cp "sql:/var/opt/mssql/backup/${FILE}" "backups/${FILE}"
|
||||||
|
echo "Backup erstellt: backups/${FILE}"
|
||||||
|
|
||||||
|
# Alte Backups im Volume aufräumen (älter als 14 Tage).
|
||||||
|
docker compose exec -T sql sh -c "find /var/opt/mssql/backup -name 'familyplanner-*.bak' -mtime +14 -delete"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
# Stellt die Datenbank aus einer Backup-Datei wieder her.
|
||||||
|
# Aufruf aus dem Ordner deploy/: ./scripts/restore.sh backups/familyplanner-20261009-120000.bak
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ $# -ne 1 ] || [ ! -f "$1" ]; then
|
||||||
|
echo "Aufruf: $0 <backup-datei.bak>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
. ./.env
|
||||||
|
|
||||||
|
FILE=$(basename "$1")
|
||||||
|
|
||||||
|
echo "Stoppe Anwendung ..."
|
||||||
|
docker compose stop web api
|
||||||
|
|
||||||
|
docker compose cp "$1" "sql:/var/opt/mssql/backup/${FILE}"
|
||||||
|
docker compose exec -T sql /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$SQL_SA_PASSWORD" -C -b \
|
||||||
|
-Q "ALTER DATABASE [familyplannerdb] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; RESTORE DATABASE [familyplannerdb] FROM DISK = N'/var/opt/mssql/backup/${FILE}' WITH REPLACE, CHECKSUM; ALTER DATABASE [familyplannerdb] SET MULTI_USER;"
|
||||||
|
|
||||||
|
echo "Starte Anwendung ..."
|
||||||
|
docker compose up -d
|
||||||
|
echo "Wiederherstellung abgeschlossen."
|
||||||
Reference in new issue
Block a user