Skip to content

feat: migrate CargoAPI to .NET 10 and add reproducible Docker stack - #4

Merged
mkarson1997 merged 3 commits into
mainfrom
portfolio/net10-container-hardening
Sep 4, 2026
Merged

feat: migrate CargoAPI to .NET 10 and add reproducible Docker stack#4
mkarson1997 merged 3 commits into
mainfrom
portfolio/net10-container-hardening

Conversation

@mkarson1997

@mkarson1997 mkarson1997 commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Summary

Modernize the backend case study from out-of-support .NET 6 to .NET 10 LTS and make the full API + SQL Server development environment reproducible with Docker Compose.

Runtime and persistence

  • Target all solution projects at net10.0.
  • Upgrade EF Core packages to 10.0.11.
  • Pin the repository to the .NET 10 SDK toolchain with global.json.
  • Update GitHub Actions to build and test with .NET 10.

Containerized development

  • Add multi-stage Dockerfile using .NET 10 SDK/runtime images.
  • Add SQL Server + API docker-compose.yml.
  • Require the local SQL password through .env, with only a template committed.
  • Ignore .env in Git.
  • Wait for SQL Server health before starting the API.
  • Pass the connection string through environment configuration.
  • Enable migration-on-start only in the Compose environment.

Operational readiness

  • Add /health/live for process liveness.
  • Add /health/ready backed by Database.CanConnectAsync and HTTP 503 on database unavailability.
  • Make missing ConnectionStrings:DefaultConnection fail fast at startup.

Documentation

  • Rewrite Quick Start around docker compose up --build.
  • Document SDK-only development as an alternative.
  • Document health endpoints, migration behavior, environment boundaries and local URLs.

Review intent

The existing carrier-selection domain behavior is intentionally unchanged by this PR. Pricing semantics for below-range/gap values remain tracked separately in issue #3.

Merge only after the .NET 10 CI build and unit tests are green.

Summary by Sourcery

Modernize CargoAPI for .NET 10 and provide a reproducible, health-aware Docker development environment.

New Features:

  • Add Docker Compose support for a reproducible API and SQL Server development stack with health-gated startup and environment-based configuration.
  • Add liveness and database-readiness health endpoints.
  • Support optional database migration on application startup for containerized development.

Bug Fixes:

  • Fail fast when the default database connection string is not configured.

Enhancements:

  • Migrate all solution projects and EF Core dependencies to .NET 10 and EF Core 10.
  • Document Docker-based and SDK-based development workflows, health checks, migration behavior, and local service URLs without changing carrier-selection behavior.

Build:

  • Pin the repository to the .NET 10 SDK toolchain and add a multi-stage .NET 10 API container image.

CI:

  • Update GitHub Actions to restore, build, and test with .NET 10, and validate the Compose configuration and container image build.

Documentation:

  • Rewrite the README for .NET 10, Docker Compose setup, local SDK development, operational endpoints, and environment boundaries.

Chores:

  • Add Docker build context exclusions and protect local environment credentials from version control.

Copilot AI lite review requested due to automatic review settings September 4, 2026 10:54
@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR upgrades the backend and EF Core stack to .NET 10, pins the toolchain and CI, introduces a health-gated Docker Compose environment with SQL Server and opt-in migrations, adds fail-fast configuration and health endpoints, and updates documentation without changing carrier-selection domain behavior.

Sequence diagram for Compose startup and migration

sequenceDiagram
    participant Compose
    participant SQL as SQL Server
    participant API as CargoAPI API
    participant DB as CargoDb

    Compose->>SQL: Start container
    SQL-->>Compose: healthcheck succeeds
    Compose->>API: Start with Database__ApplyMigrations=true
    API->>DB: MigrateAsync()
    DB-->>API: Migration completes
    API-->>Compose: Serve HTTP on port 8080
Loading

Sequence diagram for API readiness checking

sequenceDiagram
    participant Client
    participant API as CargoAPI API
    participant DB as SQL Server

    Client->>API: GET /health/live
    API-->>Client: 200 OK
    Client->>API: GET /health/ready
    API->>DB: CanConnectAsync()
    alt database reachable
        DB-->>API: Connection succeeds
        API-->>Client: 200 OK ready
    else database unavailable
        DB-->>API: Connection fails
        API-->>Client: 503 Service Unavailable
    end
Loading

Flow diagram for connection configuration and startup validation

flowchart TD
    Config[ConnectionStrings:DefaultConnection] --> Validate{Configured?}
    Validate -->|no| Fail[Startup fails]
    Validate -->|yes| Services[Configure EF Core and Hangfire]
    Services --> Migration{Database:ApplyMigrations}
    Migration -->|true| Apply[MigrateAsync]
    Migration -->|false| Run[Start API]
    Apply --> Run
Loading

File-Level Changes

Change Details Files
Migrates the solution, dependency set, local SDK selection, and CI pipeline to .NET 10.
  • Retargets all projects to net10.0 and updates EF Core dependencies to 10.0.11.
  • Pins SDK resolution with global.json using the .NET 10.0.400 feature band.
  • Updates GitHub Actions restore, build, and test steps to use .NET 10 and solution-level restore/build.
.github/workflows/ci.yml
global.json
CargoAPI.API/CargoAPI.API.csproj
CargoAPI.Business/CargoAPI.Business.csproj
CargoAPI.DataAccess/CargoAPI.DataAccess.csproj
CargoAPI.Entities/CargoAPI.Entities.csproj
CargoAPI.Tests/CargoAPI.Tests.csproj
Adds a reproducible Docker Compose development stack for the API and SQL Server.
  • Builds the API with a .NET 10 multi-stage SDK/runtime Dockerfile.
  • Runs SQL Server with a persisted volume, environment-supplied SA password, and a healthcheck.
  • Starts the API only after SQL Server is healthy, injects its connection string, and enables migration-on-start only in Compose.
  • Adds .env templating and ignores local environment secrets from Git.
Dockerfile
docker-compose.yml
.dockerignore
.gitignore
.env.example
Adds startup configuration validation, database migration control, and operational health endpoints.
  • Fails fast when ConnectionStrings:DefaultConnection is missing and reuses the validated value for EF Core and Hangfire.
  • Applies EF migrations conditionally through Database:ApplyMigrations.
  • Adds liveness and database-backed readiness endpoints, returning 503 when SQL Server is unavailable.
CargoAPI.API/Program.cs
CargoAPI.API/appsettings.json
Reworks documentation around container-first development while preserving existing domain behavior.
  • Documents Docker Compose and SDK-only workflows, configuration boundaries, migration behavior, local URLs, and health checks.
  • Updates architecture, runtime, CI, security, and roadmap documentation for the new stack.
  • Clarifies that below-range and gap pricing semantics remain deferred to issue Define pricing behavior for orders below all configured desi ranges #3.
README.md

Possibly linked issues

  • #unknown: The PR directly fulfills the issue by upgrading .NET, adding Docker Compose, externalizing credentials, readiness checks, CI, and quick-start documentation.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="docker-compose.yml" line_range="29-30" />
<code_context>
+    environment:
+      ASPNETCORE_ENVIRONMENT: Development
+      ASPNETCORE_HTTP_PORTS: 8080
+      ConnectionStrings__DefaultConnection: >-
+        Server=sqlserver,1433;Database=CargoDb;User Id=sa;Password=${MSSQL_SA_PASSWORD};Encrypt=True;TrustServerCertificate=True;
+      Database__ApplyMigrations: "true"
+    depends_on:
</code_context>
<issue_to_address>
**issue (bug_risk):** The Compose-generated connection string is invalid when `MSSQL_SA_PASSWORD` contains a semicolon or another connection-string delimiter, because the interpolated password is inserted unquoted as `Password=${MSSQL_SA_PASSWORD}`. SQL Server accepts such passwords, so the API and migration step fail to connect for valid local passwords.

**Triggers:** When the local SQL password contains a semicolon or other characters requiring connection-string escaping.

**Suggested fix:** Constrain the documented password character set or generate the connection string with proper ADO.NET escaping/quoting instead of interpolating the raw password.

```suggestion
      ConnectionStrings__DefaultConnection: >-
        Server=sqlserver,1433;Database=CargoDb;User Id=sa;Password="${MSSQL_SA_PASSWORD}";Encrypt=True;TrustServerCertificate=True;
```
</issue_to_address>

### Comment 2
<location path="README.md" line_range="251-255" />
<code_context>
+
+### Requirements
+
+- .NET 10 SDK
+- SQL Server LocalDB, Express or another SQL Server instance
+- `dotnet-ef` 10.x for migration commands
+
+The repository includes `global.json` to keep local and CI SDK selection in the .NET 10 toolchain.
+
+Install EF tooling if needed:

 ```bash
-dotnet run --project CargoAPI.API
+dotnet tool install --global dotnet-ef --version 10.*
 ```
</code_context>
<issue_to_address>
**nitpick:** The SDK-only quick-start command runs with the committed LocalDB connection string unless the user separately overrides it, so the documented flow fails on Linux and fails for users whose SQL Server is Express or another instance rather than `(localdb)\MSSQLLocalDB`.

**Triggers:** When following the SDK-only instructions without configuring `ConnectionStrings__DefaultConnection` first.

**Suggested fix:** Make the SDK instructions require an explicit connection-string override, or provide platform-specific configuration and commands before `dotnet run`.

````suggestion
The committed `appsettings.json` contains only a LocalDB development connection string and does not contain a database password. Before applying migrations or running the API, explicitly override it for your SQL Server instance without editing tracked files:

```bash
export ConnectionStrings__DefaultConnection='Server=localhost,1433;Database=CargoDb;User Id=sa;Password=ChangeThis_LocalOnly_2026!;Encrypt=True;TrustServerCertificate=True'
```
````
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and the opt-in startup migration can change and persist the SQL Server schema, so reverting the application does not necessarily restore the database state, although the Compose database is local and can be recreated or repaired. The runtime and container changes otherwise introduce operational behavior that should be validated through normal deployment and startup checks.

Blocking findings: docker-compose.yml:30


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread docker-compose.yml
Comment on lines +29 to +30
ConnectionStrings__DefaultConnection: >-
Server=sqlserver,1433;Database=CargoDb;User Id=sa;Password=${MSSQL_SA_PASSWORD};Encrypt=True;TrustServerCertificate=True;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The Compose-generated connection string is invalid when MSSQL_SA_PASSWORD contains a semicolon or another connection-string delimiter, because the interpolated password is inserted unquoted as Password=${MSSQL_SA_PASSWORD}. SQL Server accepts such passwords, so the API and migration step fail to connect for valid local passwords.

Triggers: When the local SQL password contains a semicolon or other characters requiring connection-string escaping.

Suggested fix: Constrain the documented password character set or generate the connection string with proper ADO.NET escaping/quoting instead of interpolating the raw password.

Suggested change
ConnectionStrings__DefaultConnection: >-
Server=sqlserver,1433;Database=CargoDb;User Id=sa;Password=${MSSQL_SA_PASSWORD};Encrypt=True;TrustServerCertificate=True;
ConnectionStrings__DefaultConnection: >-
Server=sqlserver,1433;Database=CargoDb;User Id=sa;Password="${MSSQL_SA_PASSWORD}";Encrypt=True;TrustServerCertificate=True;

Comment thread README.md
Comment on lines +251 to 255
The committed `appsettings.json` contains only a LocalDB development connection string and does not contain a database password. You can override it without editing tracked files:

```text
Swagger: http://localhost:5246/swagger
Hangfire: http://localhost:5246/hangfire
ConnectionStrings__DefaultConnection=<your connection string>
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick: The SDK-only quick-start command runs with the committed LocalDB connection string unless the user separately overrides it, so the documented flow fails on Linux and fails for users whose SQL Server is Express or another instance rather than (localdb)\MSSQLLocalDB.

Triggers: When following the SDK-only instructions without configuring ConnectionStrings__DefaultConnection first.

Suggested fix: Make the SDK instructions require an explicit connection-string override, or provide platform-specific configuration and commands before dotnet run.

Suggested change
The committed `appsettings.json` contains only a LocalDB development connection string and does not contain a database password. You can override it without editing tracked files:
```text
Swagger: http://localhost:5246/swagger
Hangfire: http://localhost:5246/hangfire
ConnectionStrings__DefaultConnection=<your connection string>
```
The committed `appsettings.json` contains only a LocalDB development connection string and does not contain a database password. Before applying migrations or running the API, explicitly override it for your SQL Server instance without editing tracked files:
```bash
export ConnectionStrings__DefaultConnection='Server=localhost,1433;Database=CargoDb;User Id=sa;Password=ChangeThis_LocalOnly_2026!;Encrypt=True;TrustServerCertificate=True'
```

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

A few changes undermine the stated “reproducible” intent (SDK roll-forward and floating container image tags) and there is at least one concrete cleanup needed (unused health-check registration).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR upgrades CargoAPI from .NET 6 to .NET 10, updates EF Core accordingly, and introduces a Docker-based local development stack (API + SQL Server) with startup migration control and new health endpoints.

Changes:

  • Migrate solution projects and CI to .NET 10; upgrade EF Core packages to 10.0.11 and add global.json.
  • Add Dockerfile + Docker Compose stack (SQL Server + API) with .env-driven password and health-gated startup.
  • Add liveness/readiness endpoints and opt-in “migrate on startup” behavior controlled by configuration.
File summaries
File Description
README.md Updates docs for .NET 10, Docker Compose quick start, health endpoints, and migration behavior.
global.json Pins SDK selection to .NET 10 toolchain (with roll-forward behavior).
Dockerfile Adds multi-stage .NET 10 build/runtime container image.
docker-compose.yml Adds SQL Server + API local dev stack with healthcheck gating and env-based configuration.
.env.example Adds template for required local SQL password used by Compose.
.dockerignore Reduces Docker build context and avoids copying local secrets/build outputs.
.gitignore Ignores .env to prevent accidental secret commits.
.github/workflows/ci.yml Updates CI to use .NET 10 and simplifies restore/build/test steps.
CargoAPI.API/Program.cs Adds fail-fast connection string requirement, migration-on-start toggle, and health endpoints.
CargoAPI.API/appsettings.json Adds Database:ApplyMigrations default configuration.
CargoAPI.API/CargoAPI.API.csproj Targets net10.0 and upgrades EF Core design package.
CargoAPI.Business/CargoAPI.Business.csproj Targets net10.0.
CargoAPI.DataAccess/CargoAPI.DataAccess.csproj Targets net10.0 and upgrades EF Core packages.
CargoAPI.Entities/CargoAPI.Entities.csproj Targets net10.0.
CargoAPI.Tests/CargoAPI.Tests.csproj Targets net10.0.
Review details
  • Files reviewed: 14/15 changed files
  • Comments generated: 5
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Dockerfile
Comment on lines +1 to +2
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
Comment thread docker-compose.yml
Comment on lines +2 to +4
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
Comment thread global.json
Comment on lines +1 to +6
{
"sdk": {
"version": "10.0.400",
"rollForward": "latestFeature"
}
}
Comment thread CargoAPI.API/Program.cs
Comment on lines 31 to +33
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHealthChecks();
Comment thread Dockerfile
Comment on lines +4 to +10
COPY CargoAPI.sln ./
COPY CargoAPI.API/CargoAPI.API.csproj CargoAPI.API/
COPY CargoAPI.Business/CargoAPI.Business.csproj CargoAPI.Business/
COPY CargoAPI.DataAccess/CargoAPI.DataAccess.csproj CargoAPI.DataAccess/
COPY CargoAPI.Entities/CargoAPI.Entities.csproj CargoAPI.Entities/
COPY CargoAPI.Tests/CargoAPI.Tests.csproj CargoAPI.Tests/
RUN dotnet restore CargoAPI.API/CargoAPI.API.csproj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f1362f1f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/ci.yml
- name: Run unit tests
run: dotnet test CargoAPI.Tests/CargoAPI.Tests.csproj --configuration Release --no-restore --verbosity normal
- name: Test
run: dotnet test CargoAPI.Tests/CargoAPI.Tests.csproj --configuration Release --no-build --verbosity normal

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Build the test project before using --no-build

On a clean GitHub Actions runner, this test step fails because CargoAPI.sln does not include CargoAPI.Tests, so the preceding restore and build steps neither restore nor compile the test project. The documented --no-build behavior also implies --no-restore, leaving no test assembly or assets to execute; add the test project to the solution, build it explicitly, or remove --no-build.

Useful? React with 👍 / 👎.

@mkarson1997
mkarson1997 merged commit 3b92446 into main Sep 4, 2026
2 checks passed
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.

2 participants