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: