Skip to content

Containerize Order service: multi-stage Dockerfile, /health + /ready, env config#78

Open
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1783564330-containerize-order-service
Open

Containerize Order service: multi-stage Dockerfile, /health + /ready, env config#78
devin-ai-integration[bot] wants to merge 2 commits into
mainfrom
devin/1783564330-containerize-order-service

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Containerizes the Order service (src/Services/Order/Order.API) with a hardened image, dedicated liveness/readiness probes, and fully externalized configuration.

  • Multi-stage Dockerfile (sdk:10.0 build → aspnet:10.0 runtime): layer-cached restore, dotnet publish, and a slim final stage. Runs as the non-root app user (USER $APP_UID, uid 1654) and declares a container HEALTHCHECK hitting /health.
  • Health endpoints in Program.cs:
    • /health — liveness, tagged live (trivial self check, always up while the process runs).
    • /ready — readiness, tagged ready, backed by AddDbContextCheck<OrderDbContext>() so it returns 503 until Postgres is reachable and 200 once it is.
    builder.Services.AddHealthChecks()
        .AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
        .AddDbContextCheck<OrderDbContext>("orderdb", tags: ["ready"]);
    app.MapHealthChecks("/health", new() { Predicate = c => c.Tags.Contains("live") });
    app.MapHealthChecks("/ready",  new() { Predicate = c => c.Tags.Contains("ready") });
    (Adds package Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore.)
  • Externalized config: removed the hardcoded ConnectionStrings:DefaultConnection (Host=localhost;...;Password=postgres) from appsettings.json. The connection string now comes solely from the environment (ConnectionStrings__DefaultConnection), matching how docker-compose.yml already wires it. No hosts/credentials are baked into the image; the listen port is also env-driven (APP_PORT / ASPNETCORE_URLS).
  • .dockerignore added at the build-context root (src/) to exclude bin/, obj/, .git/, IDE files, and local .env/appsettings.*.local.json secrets from the build context.

Note: the previous /healthz route is replaced by /health + /ready.

Verification

Built the image and ran the container:

  • docker build -f Services/Order/Order.API/Dockerfile -t order-service:test . → success.
  • No DB configured: GET /health200 Healthy, GET /ready503 Unhealthy.
  • With a postgres:16-alpine container on a shared network: GET /health200, GET /ready200 Healthy.
  • docker inspect HEALTHCHECK status → healthy; docker exec ... iduid=1654(app) (non-root).

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/1bb4867c148e483a853062ef04e8065d
Requested by: @mbatchelor81


Open in Devin Review

@mbatchelor81 mbatchelor81 self-assigned this Jul 9, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Open in Devin Review

Comment thread src/Services/Order/Order.API/Dockerfile Outdated
Comment on lines +31 to +34
app.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Health endpoint path inconsistent with all other services in the repo

All other services in this repo (Identity, Customer, Product, Notification, ApiGateway) expose their health check at /healthz (see e.g. src/Services/Customer/Customer.API/Program.cs:23, src/Services/Identity/Identity.API/Program.cs:23). This PR changes Order to use /health and /ready instead. If there is any shared infrastructure (load balancer rules, Kubernetes ingress, monitoring dashboards) that probes /healthz across all services, the Order service will appear unhealthy. The Dockerfile HEALTHCHECK on line 40 correctly targets /health to match the new code, but the divergence from the fleet-wide convention is worth a deliberate decision.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /health + /ready split was an explicit requirement for this task (separate liveness and readiness probes). Valid point about fleet consistency though — if there's shared infra probing /healthz, I can add /healthz back as an alias mapping to the liveness check so Order stays compatible while still exposing the new /health and /ready endpoints. Want me to add that alias here, or would you rather roll /health+/ready out across the other services separately?

Comment on lines +17 to +19
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
.AddDbContextCheck<OrderDbContext>("orderdb", tags: new[] { "ready" });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Container HEALTHCHECK only runs the always-healthy self check, never detects database failures

The Dockerfile HEALTHCHECK curls /health (src/Services/Order/Order.API/Dockerfile:40), which is mapped with Predicate = check => check.Tags.Contains("live") at src/Services/Order/Order.API/Program.cs:33. The only check tagged "live" is the self-check that unconditionally returns HealthCheckResult.Healthy() (line 18). This means the Docker-level health check will never detect a database outage. The DB check is only on /ready (tagged "ready"). This is a valid liveness-vs-readiness separation pattern for Kubernetes, but in a plain Docker or docker-compose deployment (which is what this repo uses — see src/docker-compose.yml), there is no orchestrator querying /ready, so database failures will go completely undetected by the container runtime.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. The Docker HEALTHCHECK targets /health (liveness) on purpose: a container's health status should reflect whether the process itself is alive, not whether a downstream dependency is up. Tying the HEALTHCHECK to the DB (/ready) would cause the container to be marked unhealthy — and potentially restart-looped — during a transient Postgres outage, even though the app is fine and would recover on its own once the DB returns. /ready is still exposed for orchestrators/LBs that want to gate traffic on dependency health. If you'd prefer the compose deployment to also surface DB reachability at the container level (e.g. for depends_on: condition: service_healthy), I can point the HEALTHCHECK at /ready instead — let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant