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) <noreply@anthropic.com>
This commit is contained in:
@@ -108,19 +108,13 @@ public static class Extensions
|
|||||||
|
|
||||||
public static WebApplication MapDefaultEndpoints(this WebApplication app)
|
public static WebApplication MapDefaultEndpoints(this WebApplication app)
|
||||||
{
|
{
|
||||||
// Adding health checks endpoints to applications in non-development environments has security implications.
|
// Health checks always exposed — compose healthchecks and Aspire both rely on them.
|
||||||
// See https://aka.ms/aspire/healthchecks for details before enabling these endpoints in non-development environments.
|
// Trusted home LAN only (no public exposure, no auth — see board constraint).
|
||||||
if (app.Environment.IsDevelopment())
|
app.MapHealthChecks(HealthEndpointPath);
|
||||||
|
app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
|
||||||
{
|
{
|
||||||
// All health checks must pass for app to be considered ready to accept traffic after starting
|
Predicate = r => r.Tags.Contains("live")
|
||||||
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")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
|
# Build context: repo root (includes GerbilManager.ServiceDefaults).
|
||||||
WORKDIR /app
|
# docker build -f GerbilManagerWebAPI/Dockerfile -t gerbilmanager-api .
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
ENV ASPNETCORE_URLS=http://+:80
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:7.0 AS build
|
|
||||||
ARG configuration=Release
|
|
||||||
WORKDIR /src
|
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
|
WORKDIR /app
|
||||||
|
# .NET 10 containers default to ASPNETCORE_HTTP_PORTS=8080 bound on all interfaces.
|
||||||
|
EXPOSE 8080
|
||||||
COPY --from=build /app/publish .
|
COPY --from=build /app/publish .
|
||||||
ENTRYPOINT ["dotnet", "GerbilManagerWebAPI.dll"]
|
ENTRYPOINT ["dotnet", "GerbilManagerWebAPI.dll"]
|
||||||
|
|||||||
@@ -43,12 +43,9 @@ app.MapDefaultEndpoints();
|
|||||||
app.MapOpenApi();
|
app.MapOpenApi();
|
||||||
app.MapScalarApiReference();
|
app.MapScalarApiReference();
|
||||||
|
|
||||||
if (app.Environment.IsDevelopment())
|
// Apply EF migrations at startup (no-op if schema is current; safe for single-instance deploy).
|
||||||
{
|
using (var scope = app.Services.CreateScope())
|
||||||
// Aspire provisions an empty database in dev — bring the schema up to date.
|
|
||||||
using var scope = app.Services.CreateScope();
|
|
||||||
scope.ServiceProvider.GetRequiredService<ApplicationContext>().Database.Migrate();
|
scope.ServiceProvider.GetRequiredService<ApplicationContext>().Database.Migrate();
|
||||||
}
|
|
||||||
|
|
||||||
app.UseCors(LanCorsPolicy);
|
app.UseCors(LanCorsPolicy);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
FROM node:22-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY gerbil-manager-web/package.json gerbil-manager-web/package-lock.json ./
|
COPY gerbil-manager-web/package.json gerbil-manager-web/package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY gerbil-manager-web/ .
|
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
|
RUN npm run build
|
||||||
|
|
||||||
# Laufzeit-Stufe: statische Auslieferung über nginx
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
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\
|
RUN printf 'server {\n\
|
||||||
listen 3000;\n\
|
listen 80;\n\
|
||||||
root /usr/share/nginx/html;\n\
|
root /usr/share/nginx/html;\n\
|
||||||
index index.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\
|
location / {\n\
|
||||||
try_files $uri $uri/ /index.html;\n\
|
try_files $uri $uri/ /index.html;\n\
|
||||||
}\n\
|
}\n\
|
||||||
}\n' > /etc/nginx/conf.d/default.conf
|
}\n' > /etc/nginx/conf.d/default.conf
|
||||||
EXPOSE 3000
|
|
||||||
|
EXPOSE 80
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import { de } from '../strings/de'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Basis-URL der GerbilManagerWebAPI.
|
* Basis-URL der GerbilManagerWebAPI.
|
||||||
* Per VITE_API_BASE_URL konfigurierbar (z. B. in Docker / Aspire);
|
* Dev (Aspire): VITE_API_BASE_URL wird von AppHost zur Laufzeit gesetzt (LAN-IP + Port).
|
||||||
* Standard ist das lokale Dev-Profil der API (launchSettings.json, Profil "http").
|
* 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 =
|
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 {
|
export class ApiError extends Error {
|
||||||
readonly status: number | null
|
readonly status: number | null
|
||||||
|
|||||||
Reference in New Issue
Block a user