Skip to content

Containerize ApiGateway: hardened image + health/ready endpoints#80

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1783565621-containerize-apigateway
Open

Containerize ApiGateway: hardened image + health/ready endpoints#80
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1783565621-containerize-apigateway

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

Summary

Containerizes the ApiGateway (YARP reverse proxy) into a hardened, self-contained image and adds standard liveness/readiness probes. Scoped to the gateway only — no other services touched.

ApiGateway/Dockerfile — replaced the plain SDK-build/aspnet-run Dockerfile with a hardened multi-stage build:

  • Restore layer copies only ApiGateway.csproj (no project refs) before dotnet publish.
  • Runtime stage runs as non-root USER $APP_UID (uid 1654).
  • apt-get install curl in the runtime stage (not in the aspnet base image) to back the HEALTHCHECK.
  • HEALTHCHECK targets /health (liveness), not /ready, to avoid restart loops on dependency blips.
  • Internal port hardcoded to 5000 (ASPNETCORE_URLS=http://+:5000); remap externally with -p.

ApiGateway/Program.cs — split the single /healthz mapping into three probes:

// liveness (process up, no dependency checks)
app.MapHealthChecks("/health",  new HealthCheckOptions { Predicate = _ => false });
app.MapHealthChecks("/healthz", new HealthCheckOptions { Predicate = _ => false }); // preserved alias
// readiness — stateless proxy has no backing store to gate on, so returns 200
app.MapHealthChecks("/ready",   new HealthCheckOptions { Predicate = c => c.Tags.Contains("ready") });

No DbContext / EF Core health check added — this is a stateless proxy, not a data service. /healthz kept as a liveness alias for fleet consistency.

src/.dockerignore (new, at build-context root) — excludes bin/, obj/, secrets, and other artifacts so stale local builds never poison the image.

Downstream cluster addresses in appsettings.json (http://identity-service:5001/, etc.) are compose DNS names, not secrets — left as-is. The existing api-gateway compose block already matches (context ., dockerfile ApiGateway/Dockerfile, port 5000) — verified, no edit needed.

Verification

  • dotnet build ApiGateway/ApiGateway.csproj -c Release — succeeds.
  • docker build — image builds.
  • docker run (no DB): /health → 200 Healthy, /ready → 200 Healthy, /healthz → 200 Healthy.
  • docker inspect health status → healthy.
  • docker exec ... iduid=1654(app) (non-root).
  • docker compose config — valid.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/48ee2b9dc84d49bc97a29fbe1901ff34
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 1 potential issue.

Open in Devin Review

Comment thread src/ApiGateway/Program.cs

// Liveness: process is up; no dependency checks.
app.MapHealthChecks("/health", new HealthCheckOptions { Predicate = _ => false });
app.MapHealthChecks("/healthz", new HealthCheckOptions { Predicate = _ => false });

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.

🔍 Behavioral change: /healthz endpoint now skips all registered health checks

Previously, /healthz was mapped without options (app.MapHealthChecks("/healthz")) which means it would run ALL registered health checks. The new code at src/ApiGateway/Program.cs:16 uses Predicate = _ => false, which skips all checks and always returns Healthy.

Right now this is a no-op difference because builder.Services.AddHealthChecks() at line 8 registers no actual checks. However, if someone later adds a health check (e.g., a downstream service probe), /healthz will silently ignore it. This is likely intentional given the comments, but it's a contract change on an existing endpoint that other infrastructure (e.g., Kubernetes liveness probes) may already depend on.

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. /healthz is deliberately kept as a liveness alias (Predicate = _ => false) alongside the new /health (liveness) and /ready (readiness) split, following the fleet convention across the other services in this repo. Liveness probes should reflect only "is the process up" and must not gate on dependency checks — otherwise a transient downstream blip would cause liveness restart loops. Any future dependency probes should be tagged ready and will surface via /ready, which is the endpoint orchestrators/LBs should use for readiness gating. No registered checks change behavior today (none are registered), so this is a no-op now and the intended contract going forward.

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