From 9413e53a0fb37385652263a2d87097734a677b01 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:57:03 +0000 Subject: [PATCH] Containerize Product service: multi-stage Dockerfile, /health & /ready, env config, .dockerignore --- src/.dockerignore | 34 ++++++++++++++++++ src/Services/Product/Product.API/Dockerfile | 35 +++++++++++++------ .../Product/Product.API/Product.API.csproj | 1 + src/Services/Product/Product.API/Program.cs | 18 ++++++++-- .../Product/Product.API/appsettings.json | 2 +- src/docker-compose.yml | 3 +- 6 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 src/.dockerignore diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..9ac4bd9 --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,34 @@ +# Build artifacts +**/bin/ +**/obj/ +**/out/ +**/publish/ + +# IDE / editor +.vs/ +.vscode/ +.idea/ +*.user +*.suo +*.swp + +# Version control +.git/ +.gitignore +**/.gitkeep + +# Docker +**/Dockerfile* +**/.dockerignore +docker-compose*.yml + +# Local config & secrets (never bake into the image) +**/appsettings.*.local.json +**/.env +**/.env.local +*.pfx +*.key + +# Logs & misc +**/*.log +**/*.md diff --git a/src/Services/Product/Product.API/Dockerfile b/src/Services/Product/Product.API/Dockerfile index 68798df..aebbf0b 100644 --- a/src/Services/Product/Product.API/Dockerfile +++ b/src/Services/Product/Product.API/Dockerfile @@ -1,24 +1,39 @@ -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base -WORKDIR /app -EXPOSE 5004 +# syntax=docker/dockerfile:1 +# ---- Build stage ---- FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src + +# Restore first (leverages layer caching) — copy only project files. COPY ["Services/Product/Product.API/Product.API.csproj", "Services/Product/Product.API/"] COPY ["Services/Product/Product.Domain/Product.Domain.csproj", "Services/Product/Product.Domain/"] COPY ["Services/Product/Product.Infrastructure/Product.Infrastructure.csproj", "Services/Product/Product.Infrastructure/"] COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"] COPY ["Shared/Shared.Infrastructure/Shared.Infrastructure.csproj", "Shared/Shared.Infrastructure/"] RUN dotnet restore "Services/Product/Product.API/Product.API.csproj" + +# Copy the rest of the sources and publish. COPY . . WORKDIR "/src/Services/Product/Product.API" -RUN dotnet build -c Release -o /app/build +RUN dotnet publish "Product.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 . -ENV ASPNETCORE_URLS=http://+:5004 + +# Config is externalized via environment variables. Sensible container defaults: +# - listen on 8080 inside the container (override with ASPNETCORE_HTTP_PORTS/ASPNETCORE_URLS) +# - production environment unless overridden +# No hosts, credentials, or connection strings are baked into the image. +ENV ASPNETCORE_ENVIRONMENT=Production \ + ASPNETCORE_HTTP_PORTS=8080 \ + DOTNET_RUNNING_IN_CONTAINER=true + +EXPOSE 8080 + +COPY --from=build /app/publish . + +# Run as the non-root user provided by the base image. +USER $APP_UID + ENTRYPOINT ["dotnet", "Product.API.dll"] diff --git a/src/Services/Product/Product.API/Product.API.csproj b/src/Services/Product/Product.API/Product.API.csproj index 68be876..83be2ca 100644 --- a/src/Services/Product/Product.API/Product.API.csproj +++ b/src/Services/Product/Product.API/Product.API.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Services/Product/Product.API/Program.cs b/src/Services/Product/Product.API/Program.cs index 73146ef..5b211ea 100644 --- a/src/Services/Product/Product.API/Program.cs +++ b/src/Services/Product/Product.API/Program.cs @@ -1,16 +1,19 @@ using Product.Infrastructure.Data; 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.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); +builder.Services.AddHealthChecks() + .AddDbContextCheck("database", tags: new[] { "ready" }); + var app = builder.Build(); if (app.Environment.IsDevelopment()) @@ -20,6 +23,17 @@ } app.MapControllers(); -app.MapHealthChecks("/healthz"); + +// Liveness: process is up and able to serve requests. No dependency checks. +app.MapHealthChecks("/health", new HealthCheckOptions +{ + Predicate = _ => false +}); + +// Readiness: dependencies (database) are reachable. +app.MapHealthChecks("/ready", new HealthCheckOptions +{ + Predicate = check => check.Tags.Contains("ready") +}); app.Run(); diff --git a/src/Services/Product/Product.API/appsettings.json b/src/Services/Product/Product.API/appsettings.json index b998b08..a841dca 100644 --- a/src/Services/Product/Product.API/appsettings.json +++ b/src/Services/Product/Product.API/appsettings.json @@ -6,6 +6,6 @@ } }, "ConnectionStrings": { - "DefaultConnection": "Host=localhost;Database=productdb;Username=postgres;Password=postgres" + "DefaultConnection": "" } } diff --git a/src/docker-compose.yml b/src/docker-compose.yml index bf66a7b..c88217a 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -60,12 +60,13 @@ services: context: . dockerfile: Services/Product/Product.API/Dockerfile ports: - - "5004:5004" + - "5004:8080" depends_on: - postgres - rabbitmq environment: - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_HTTP_PORTS=8080 - ConnectionStrings__DefaultConnection=Host=postgres;Database=productdb;Username=postgres;Password=postgres notification-service: