From 2f2e5b1f562f0b84ef2caf8fcb7c59d2e2fb9ea5 Mon Sep 17 00:00:00 2001 From: Gulum Date: Sat, 6 Jun 2026 07:25:55 +0200 Subject: [PATCH] OPS-1: Dockerfiles (net10 API + nginx frontend) + production config - GerbilManagerWebAPI/Dockerfile: net7->net10 multi-stage; build context=repo root (includes ServiceDefaults); port 8080 (.NET 10 container default) - gerbil-manager-web/Dockerfile: drop VITE_API_BASE_URL build-arg; nginx proxies /api/* to api:8080 (strip prefix), /scalar, /openapi/ -- one published port 80 - gerbil-manager-web/src/api/client.ts: default API_BASE_URL /api (relative), no hostname coupling at build time; Aspire still injects absolute LAN URL in dev - GerbilManagerWebAPI/Program.cs: run EF Migrate() at startup always (not Dev-only) - GerbilManager.ServiceDefaults/Extensions.cs: expose /health + /alive in all environments (compose healthcheck requires them in Production) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- GerbilManager.ServiceDefaults/Extensions.cs | 18 ++++------ GerbilManagerWebAPI/Dockerfile | 31 ++++++++-------- GerbilManagerWebAPI/Program.cs | 7 ++-- gerbil-manager-web/Dockerfile | 39 ++++++++++++++++----- gerbil-manager-web/src/api/client.ts | 7 ++-- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/GerbilManager.ServiceDefaults/Extensions.cs b/GerbilManager.ServiceDefaults/Extensions.cs index 97642a1..2a36702 100644 --- a/GerbilManager.ServiceDefaults/Extensions.cs +++ b/GerbilManager.ServiceDefaults/Extensions.cs @@ -108,19 +108,13 @@ public static class Extensions public static WebApplication MapDefaultEndpoints(this WebApplication app) { - // Adding health checks endpoints to applications in non-development environments has security implications. - // See https://aka.ms/aspire/healthchecks for details before enabling these endpoints in non-development environments. - if (app.Environment.IsDevelopment()) + // Health checks always exposed — compose healthchecks and Aspire both rely on them. + // Trusted home LAN only (no public exposure, no auth — see board constraint). + app.MapHealthChecks(HealthEndpointPath); + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions { - // All health checks must pass for app to be considered ready to accept traffic after starting - app.MapHealthChecks(HealthEndpointPath); - - // Only health checks tagged with the "live" tag must pass for app to be considered alive - app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions - { - Predicate = r => r.Tags.Contains("live") - }); - } + Predicate = r => r.Tags.Contains("live") + }); return app; } diff --git a/GerbilManagerWebAPI/Dockerfile b/GerbilManagerWebAPI/Dockerfile index 1268601..3f78f6e 100644 --- a/GerbilManagerWebAPI/Dockerfile +++ b/GerbilManagerWebAPI/Dockerfile @@ -1,20 +1,23 @@ -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -WORKDIR /app -EXPOSE 80 +# Build context: repo root (includes GerbilManager.ServiceDefaults). +# docker build -f GerbilManagerWebAPI/Dockerfile -t gerbilmanager-api . -ENV ASPNETCORE_URLS=http://+:80 - -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0 AS build -ARG configuration=Release +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src -COPY ["GerbilManagerWebAPI/GerbilManagerWebAPI.csproj", "GerbilManagerWebAPI/"] -RUN dotnet restore "GerbilManagerWebAPI/GerbilManagerWebAPI.csproj" -COPY . . -WORKDIR "/src/GerbilManagerWebAPI" -ARG configuration=Release -RUN dotnet publish "GerbilManagerWebAPI.csproj" -c $configuration -o /app/publish /p:UseAppHost=false -FROM base AS final +# Restore: project files only for layer-cache efficiency. +COPY GerbilManager.ServiceDefaults/GerbilManager.ServiceDefaults.csproj GerbilManager.ServiceDefaults/ +COPY GerbilManagerWebAPI/GerbilManagerWebAPI.csproj GerbilManagerWebAPI/ +RUN dotnet restore GerbilManagerWebAPI/GerbilManagerWebAPI.csproj + +# Full source copy + publish. +COPY GerbilManager.ServiceDefaults/ GerbilManager.ServiceDefaults/ +COPY GerbilManagerWebAPI/ GerbilManagerWebAPI/ +WORKDIR /src/GerbilManagerWebAPI +RUN dotnet publish GerbilManagerWebAPI.csproj -c Release -o /app/publish /p:UseAppHost=false + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final WORKDIR /app +# .NET 10 containers default to ASPNETCORE_HTTP_PORTS=8080 bound on all interfaces. +EXPOSE 8080 COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "GerbilManagerWebAPI.dll"] diff --git a/GerbilManagerWebAPI/Program.cs b/GerbilManagerWebAPI/Program.cs index c4bfc3e..5b62239 100644 --- a/GerbilManagerWebAPI/Program.cs +++ b/GerbilManagerWebAPI/Program.cs @@ -43,12 +43,9 @@ app.MapDefaultEndpoints(); app.MapOpenApi(); app.MapScalarApiReference(); -if (app.Environment.IsDevelopment()) -{ - // Aspire provisions an empty database in dev — bring the schema up to date. - using var scope = app.Services.CreateScope(); +// Apply EF migrations at startup (no-op if schema is current; safe for single-instance deploy). +using (var scope = app.Services.CreateScope()) scope.ServiceProvider.GetRequiredService().Database.Migrate(); -} app.UseCors(LanCorsPolicy); diff --git a/gerbil-manager-web/Dockerfile b/gerbil-manager-web/Dockerfile index 6c758fa..722b55e 100644 --- a/gerbil-manager-web/Dockerfile +++ b/gerbil-manager-web/Dockerfile @@ -1,25 +1,48 @@ -# Build-Stufe: Vite-Produktionsbuild +# Build context: repo root (Dockerfile liest aus gerbil-manager-web/). +# docker build -f gerbil-manager-web/Dockerfile -t gerbilmanager-frontend . +# +# Produktion: kein VITE_API_BASE_URL nötig — client.ts fällt auf '/api' zurück, +# nginx proxyt /api/* transparent zum API-Container (keine Host-Kopplung zur Build-Zeit). + FROM node:22-alpine AS build WORKDIR /app COPY gerbil-manager-web/package.json gerbil-manager-web/package-lock.json ./ RUN npm ci COPY gerbil-manager-web/ . -# Basis-URL der API (zur Build-Zeit eingebettet; Browser erreicht die API über den Host) -ARG VITE_API_BASE_URL=http://localhost:80 -ENV VITE_API_BASE_URL=$VITE_API_BASE_URL RUN npm run build -# Laufzeit-Stufe: statische Auslieferung über nginx FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html -# SPA-Fallback: alle Pfade auf index.html umleiten (react-router) + +# nginx: SPA-Fallback + Reverse-Proxy für /api, /scalar, /openapi zum API-Container. RUN printf 'server {\n\ - listen 3000;\n\ + listen 80;\n\ root /usr/share/nginx/html;\n\ index index.html;\n\ +\n\ + # API-Aufrufe: /api/* -> API-Container /* (Praefix wird entfernt)\n\ + location /api/ {\n\ + proxy_pass http://api:8080/;\n\ + proxy_set_header Host $host;\n\ + proxy_set_header X-Real-IP $remote_addr;\n\ + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\ + }\n\ +\n\ + # Scalar API-Doku und OpenAPI-Spec direkt vom Backend\n\ + location /scalar {\n\ + proxy_pass http://api:8080/scalar;\n\ + proxy_set_header Host $host;\n\ + }\n\ + location /openapi/ {\n\ + proxy_pass http://api:8080/openapi/;\n\ + proxy_set_header Host $host;\n\ + }\n\ +\n\ + # SPA-Fallback: alle anderen Pfade laden index.html (React Router)\n\ location / {\n\ try_files $uri $uri/ /index.html;\n\ }\n\ }\n' > /etc/nginx/conf.d/default.conf -EXPOSE 3000 + +EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/gerbil-manager-web/src/api/client.ts b/gerbil-manager-web/src/api/client.ts index 8f6eab6..0536118 100644 --- a/gerbil-manager-web/src/api/client.ts +++ b/gerbil-manager-web/src/api/client.ts @@ -2,11 +2,12 @@ import { de } from '../strings/de' /** * Basis-URL der GerbilManagerWebAPI. - * Per VITE_API_BASE_URL konfigurierbar (z. B. in Docker / Aspire); - * Standard ist das lokale Dev-Profil der API (launchSettings.json, Profil "http"). + * Dev (Aspire): VITE_API_BASE_URL wird von AppHost zur Laufzeit gesetzt (LAN-IP + Port). + * Produktion (Docker/nginx): kein Env-Var nötig — nginx proxyt /api/* zum API-Container, + * daher funktioniert der relative Pfad /api auf jedem Host ohne Build-Zeit-Kopplung. */ export const API_BASE_URL: string = - import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:5179' + import.meta.env.VITE_API_BASE_URL ?? '/api' export class ApiError extends Error { readonly status: number | null