From 0b0e5e88f212cd90b0a1d4b1d868b86da5a3c050 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 02:55:20 +0000 Subject: [PATCH] Containerize Notification service (hardened image + health probes) --- src/.dockerignore | 19 +++++++++++++ .../Notification/Notification.API/Dockerfile | 28 ++++++++++++------- .../Notification.API/Notification.API.csproj | 5 ++-- .../Notification/Notification.API/Program.cs | 21 ++++++++++++-- .../Notification.API/appsettings.json | 3 -- 5 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 src/.dockerignore diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..e4ffc3c --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,19 @@ +**/bin/ +**/obj/ +**/out/ +**/publish/ +**/.vs/ +**/.vscode/ +**/.idea/ +**/*.user +**/.git/ +**/.gitignore +**/Dockerfile* +**/.dockerignore +**/docker-compose*.yml +**/.env +**/.env.* +**/appsettings.*.local.json +**/secrets.json +**/*.log +**/.DS_Store diff --git a/src/Services/Notification/Notification.API/Dockerfile b/src/Services/Notification/Notification.API/Dockerfile index 252f690..352848f 100644 --- a/src/Services/Notification/Notification.API/Dockerfile +++ b/src/Services/Notification/Notification.API/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base -WORKDIR /app -EXPOSE 5005 +# syntax=docker/dockerfile:1 +# ---- Build stage ---- FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src +# Copy ONLY .csproj files first (one COPY per referenced project) for layer-cached restore COPY ["Services/Notification/Notification.API/Notification.API.csproj", "Services/Notification/Notification.API/"] COPY ["Services/Notification/Notification.Domain/Notification.Domain.csproj", "Services/Notification/Notification.Domain/"] COPY ["Services/Notification/Notification.Infrastructure/Notification.Infrastructure.csproj", "Services/Notification/Notification.Infrastructure/"] @@ -11,14 +11,22 @@ COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contract COPY ["Shared/Shared.Infrastructure/Shared.Infrastructure.csproj", "Shared/Shared.Infrastructure/"] RUN dotnet restore "Services/Notification/Notification.API/Notification.API.csproj" COPY . . -WORKDIR "/src/Services/Notification/Notification.API" -RUN dotnet build -c Release -o /app/build +RUN dotnet publish "Services/Notification/Notification.API/Notification.API.csproj" \ + -c Release -o /app/publish /p:UseAppHost=false -FROM build AS publish -RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false - -FROM base AS final +# ---- Runtime stage ---- +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final WORKDIR /app -COPY --from=publish /app/publish . +# curl is required by the HEALTHCHECK below and is NOT in the aspnet image +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl \ + && rm -rf /var/lib/apt/lists/* +# Fixed container-internal port; nothing host/secret specific is baked in. +# Remap externally with `docker run -p :5005`. ENV ASPNETCORE_URLS=http://+:5005 +EXPOSE 5005 +COPY --from=build /app/publish . +USER $APP_UID +HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \ + CMD curl -fsS "http://localhost:5005/health" || exit 1 ENTRYPOINT ["dotnet", "Notification.API.dll"] diff --git a/src/Services/Notification/Notification.API/Notification.API.csproj b/src/Services/Notification/Notification.API/Notification.API.csproj index 25b5fb0..277d26c 100644 --- a/src/Services/Notification/Notification.API/Notification.API.csproj +++ b/src/Services/Notification/Notification.API/Notification.API.csproj @@ -7,11 +7,12 @@ - - + + + diff --git a/src/Services/Notification/Notification.API/Program.cs b/src/Services/Notification/Notification.API/Program.cs index 6219c9c..348a4fb 100644 --- a/src/Services/Notification/Notification.API/Program.cs +++ b/src/Services/Notification/Notification.API/Program.cs @@ -3,13 +3,15 @@ using Notification.Infrastructure.Data; using Notification.Infrastructure.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -builder.Services.AddHealthChecks(); +builder.Services.AddHealthChecks() + .AddDbContextCheck("database", tags: new[] { "ready" }); builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); @@ -23,7 +25,16 @@ using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureCreated(); + try + { + db.Database.EnsureCreated(); + } + catch (Exception ex) + { + // Liveness must not depend on the database being reachable at startup; + // readiness (/ready) reflects DB availability instead. + app.Logger.LogWarning(ex, "Database initialization skipped; database unavailable at startup."); + } } if (app.Environment.IsDevelopment()) @@ -33,6 +44,10 @@ } app.MapControllers(); -app.MapHealthChecks("/healthz"); +// Liveness: process is up; no dependency checks. +app.MapHealthChecks("/health", new HealthCheckOptions { Predicate = _ => false }); +app.MapHealthChecks("/healthz", new HealthCheckOptions { Predicate = _ => false }); +// Readiness: dependencies (DB) reachable -> 200, else 503. +app.MapHealthChecks("/ready", new HealthCheckOptions { Predicate = c => c.Tags.Contains("ready") }); app.Run(); diff --git a/src/Services/Notification/Notification.API/appsettings.json b/src/Services/Notification/Notification.API/appsettings.json index 335c5c3..0c208ae 100644 --- a/src/Services/Notification/Notification.API/appsettings.json +++ b/src/Services/Notification/Notification.API/appsettings.json @@ -4,8 +4,5 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - }, - "ConnectionStrings": { - "DefaultConnection": "Host=localhost;Database=notificationdb;Username=postgres;Password=postgres" } }