Add Docker support: multi-stage build, provider-switchable compose, hardened env handling#1
Add Docker support: multi-stage build, provider-switchable compose, hardened env handling#1Rashik004 wants to merge 17 commits into
Conversation
Captures three defects in the pre-bootstrap docker flow and the implementation plan to fix them. DOCKER-INVESTIGATION.md will be removed in the final task once the flow is verified working.
The mcr.microsoft.com/dotnet/aspnet:10.0 image ships a non-root 'app' user at UID 1654 and 'ubuntu' at UID 1000. The previous groupadd -g 1000 collided with 'ubuntu' and broke every build. Drop the manual user creation and adopt Microsoft's documented pattern of reusing the pre-baked app user.
context: . resolves relative to the compose file location (docker/), but Dockerfile lives at repo root, so every build failed with 'failed to read dockerfile'. Use context: .. so the README invocation 'docker compose -f docker/compose.X.yaml up -d' works without extra flags.
Compose looks for .env next to the compose file by default. With
.env at repo root and compose files under docker/, --project-directory .
is required for ${JWT_SECRET_KEY} interpolation. Also correct the
'non-root UID 1000' claim — the runtime user is whatever UID the
base image's app user has (currently 1654).
The previous change of context: . → context: .. was incorrect once the README documented --project-directory . for .env discovery. With --project-directory ., compose resolves context relative to the project directory (repo root), so context: . correctly points at the repo-root Dockerfile. context: .. would resolve to the parent of the repo root and break the build.
Task 4 verification surfaced a logic conflict between Task 2 (context: ..) and Task 3 (--project-directory .). The compose context revert restores the original 'context: .' which works correctly with the documented --project-directory . invocation.
Defects documented in DOCKER-INVESTIGATION.md are fixed in the prior commits on this branch. The README now reflects the working invocation.
Only the SQLite docker path was end-to-end verified on this branch. Postgres and SqlServer compose files build but full container startup with migrations was not exercised.
The pre-existing DOCKER-PLAN.md design doc still claimed the container would run a manually-created appuser at UID 1000. The implementation actually reuses the base image's pre-baked 'app' user at UID 1654 because UID 1000 collides with the 'ubuntu' user shipped in mcr.microsoft.com/dotnet/aspnet:10.0. Update lines 73 and 83 so the design doc no longer contradicts the Dockerfile and README.
…ration - Dockerfile: project-scoped restore (skip test projects), drop wget+apt layer, switch healthcheck to bash /dev/tcp builtin - compose: add :? required-var assertions for POSTGRES_*, MSSQL_SA_PASSWORD, JWT_SECRET_KEY with copy-from-.env.example hints - compose.sqlserver: replace sqlcmd healthcheck with TCP probe (avoids brittle /opt/mssql-tools*/bin path that has changed twice) - init-project: atomic .env create (set -C / FileMode.CreateNew) with umask 077 / restricted ACL; randomize SQL Server password category-char positions instead of predictable Aa1! prefix - select-db-provider: detect provider mismatch when compose.yaml already trimmed to a different provider than requested - .gitignore: switch to .env.* allowlist with !.env.example exception - .dockerignore: also exclude src/tests/
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8ff6d49f6
ℹ️ 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".
There was a problem hiding this comment.
Pull request overview
Adds first-class Docker support to the starter Web API, aiming to make the template runnable via Docker Compose (pre- and post-bootstrap) while keeping provider trimming and bootstrap scripts in sync.
Changes:
- Introduces a multi-stage
Dockerfile(non-root runtime, HTTP-only on 8080, healthcheck). - Adds provider-specific Compose stacks (
docker/compose.{sqlite,postgres,sqlserver}.yaml) and extendsselect-db-providerto trim to a singledocker/compose.yaml. - Extends
init-projectto optionally generate a hardened repo-root.env, and updates ignores / docs / architecture tests accordingly.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
Dockerfile |
Multi-stage build/publish and runtime hardening (non-root, healthcheck). |
docker/compose.sqlite.yaml |
SQLite compose stack using repo-root .env vars and a named volume for /data. |
docker/compose.postgres.yaml |
Postgres stack with healthcheck + required env var assertions. |
docker/compose.sqlserver.yaml |
SQL Server stack with TCP health probe + required env var assertions. |
scripts/select-db-provider.sh |
Adds compose-shard trimming/rename step (to docker/compose.yaml). |
scripts/select-db-provider.ps1 |
Same compose trimming logic for PowerShell. |
scripts/init-project.sh |
Adds --no-env-file and .env generation for Docker Compose. |
scripts/init-project.ps1 |
Adds -NoEnvFile and .env generation + ACL hardening notes. |
scripts/rename-project.sh |
Ensures Docker/YAML files participate in rename find/replace. |
scripts/rename-project.ps1 |
Same: include YAML + Dockerfile in rename content pass. |
src/tests/Starter.WebApi.Tests.Architecture/DockerArtifactsTests.cs |
Architecture-level checks that Docker artifacts exist. |
README.md |
Documents pre-/post-bootstrap Docker flows and endpoints. |
.gitignore |
Ignores .env* files while explicitly keeping .env.example. |
.env.example |
Template for required Docker Compose environment variables. |
.dockerignore |
Shrinks build context; excludes secrets/tests/etc. |
DOCKER-PLAN.md |
Captures implementation plan/constraints for Docker support. |
docs/superpowers/plans/2026-05-09-fix-docker-pre-bootstrap.md |
Detailed plan doc for pre-bootstrap Docker flow fixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $extras = @( | ||
| (Pick-Char 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') | ||
| (Pick-Char 'abcdefghijklmnopqrstuvwxyz') | ||
| (Pick-Char '0123456789') | ||
| (Pick-Char '!@#%^*-_+=') | ||
| ) |
| git_tracked() { git -C "$ROOT_DIR" ls-files --error-unmatch "$1" >/dev/null 2>&1; } | ||
|
|
||
| if [[ ! -d "$COMPOSE_DIR" ]]; then | ||
| tag SKIP "docker/ directory absent — skipping compose trim" | ||
| elif [[ ! -f "$KEEP_PATH" ]]; then | ||
| # Idempotency: if compose.yaml exists and no shards remain, already trimmed |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
…hs in select-db-provider Agent-Logs-Url: https://github.com/Rashik004/starter-web-api/sessions/9a65981d-6f12-4696-939b-03ffc2a17a0c Co-authored-by: Rashik004 <4425665+Rashik004@users.noreply.github.com>
Applied all unresolved review feedback in commit
|
Summary
Adds first-class Docker support to the starter: a multi-stage
Dockerfile, three provider-specificdocker/compose.*.yamlstacks (Sqlite, Postgres, SqlServer), and matchinginit-project/select-db-providerscript extensions to generate.envfiles and trim compose shards. Final commit (harden(docker): ...) tightens env validation, healthchecks, and secret generation based on a pre-landing review.What changed
Image / runtime
Dockerfile— SDK image restores + publishes; aspnet runtime image hosts the published output as non-root userapp.src/Host/Starter.WebApi/Starter.WebApi.csproj) — pulls only the host project + transitive module references. Skips three test projects (xunit, NetArchTest, Mvc.Testing, FluentAssertions) that never reach the runtime image.wget-based check (which required anapt-get installlayer) with a bash/dev/tcpbuiltin probe of/health/live. Cuts one image layer and the apt-cache cleanup.COPY --chown=app:appbeforeUSER app, withmkdir -p /data && chown -R app:app /app /datafor the writable volume mount point.Compose stacks
docker/compose.sqlite.yaml,compose.postgres.yaml,compose.sqlserver.yaml— one stack per supported provider, selectable at init time.${VAR:?error message}form so missing values fail fast atdocker compose upwith an actionable hint (copy .env.example to .env).sqlcmd(whose binary path under/opt/mssql-tools*/bin/has shifted twice across MSFT image revisions) totimeout 3 bash -c '</dev/tcp/127.0.0.1/1433'. Trade-off documented in35d2ff8: TCP listener can come up before the engine accepts logins; EF connection retry on the api side absorbs the brief race.Scripts
scripts/init-project.{ps1,sh}— generate.envwith provider-appropriate keys (JWT, CORS_ORIGIN, plus POSTGRES_* or MSSQL_SA_PASSWORD).set -C+umask 077; PowerShell usesFileMode.CreateNew+FileShare.None. Both fail loudly if a racing writer exists; neither overwrites an existing.env.chmod 600reminder.Aa1!<entropy>prefix with random-position injection of one upper / lower / digit / symbol character into a base64 entropy string. Same complexity guarantee, no prefix signature.scripts/select-db-provider.{ps1,sh}— when compose.yaml has already been trimmed to a single provider, detect a mismatch between the trimmedDatabase__Provider:and the requested provider and abort with recovery instructions instead of silently shipping the wrong DB.Ignore lists
.gitignore— switch to.env.*allowlist with!.env.exampleexception so all real env files are blocked while the example template stays committed..dockerignore— also excludesrc/tests/.Pre-landing review notes
A
/reviewpass produced 5 informational findings, 0 critical:bashis present in the aspnet base image (true for default Debian variant; would break on-chiseledtags).grep -q \" 200 \"matches Kestrel's status line but is mildly brittle for non-default response shapes.sqlcmd(already documented in35d2ff8).$RANDOM-modulo-position bias is negligible for the alphabets used; password entropy still comes from openssl/urandom.Database__Provider:line fromcompose.yaml.None block landing.
Test plan
docker build -t starter-webapi .succeeds and produces an image withoutwget/apt cache layers.docker compose -f docker/compose.sqlite.yaml --env-file .env upstarts; healthcheck transitions tohealthywithin 60 s;/health/livereturns 200.compose.postgres.yamlandcompose.sqlserver.yaml(with valid.envfrominit-project).docker compose ... upwith a missingJWT_SECRET_KEYfails immediately with the:?hint message.init-project.sh -Provider SqlServer(and.ps1) emit aMSSQL_SA_PASSWORDthat satisfies SQL Server complexity rules (upper, lower, digit, symbol, 16+ chars) and contains noAa1!prefix.init-projectagainst an existing.envaborts without overwriting..envACL grants only the current user; on POSIX, mode is0600.select-db-provideragainst an already-trimmedcompose.yamlfor a different provider exits non-zero with the recovery instruction.dotnet test src/tests/Starter.WebApi.Tests.Architecture).