Skip to content

One local config file for both dotnet run and Docker - #113

Merged
rghvgrv merged 1 commit into
mainfrom
feat/single-dev-appsettings
Aug 7, 2026
Merged

One local config file for both dotnet run and Docker#113
rghvgrv merged 1 commit into
mainfrom
feat/single-dev-appsettings

Conversation

@rghvgrv

@rghvgrv rghvgrv commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Yes — one file works for both. #112's separate appsettings.Docker.json is gone.

cp src/SubVora.Api/appsettings.Development.example.json src/SubVora.Api/appsettings.Development.json
# edit it: set Jwt:Secret to `openssl rand -base64 48`

docker compose up -d --build            # whole stack, API on :8080
# or
docker compose up -d db mailpit         # dependencies only
dotnet run --project src/SubVora.Api    # API on :5271

Why this is better than two files, not just tidier

The two files each got their own generated signing key, so a token minted by dotnet run was rejected by the container and vice versa — two local environments that could never share a session. One file makes the key shared by construction.

Verified in both directions against the running pair:

users/me on :5271 with the CONTAINER's token   200
users/me on :8080 with the HOST's token        200

How

dotnet run reads appsettings.Development.json directly. Compose mounts that same file into the container as /app/appsettings.Docker.json — the name that environment loads.

The environment stays Docker, not Development. Program.cs:214 skips UseHttpsRedirection only for Docker, and the container has no HTTPS port to redirect to. Switching would have broken it.

The two overrides, and why neither is a secret

Setting Why it can't be shared
ConnectionStrings__Default Inside a container localhost is the container. Uses the same throwaway password already spelled out on the db service.
Smtp__Host The catcher is mailpit there, localhost here.

The signing key is not among them — it never leaves the mounted file, which was the original requirement.

Two things fixed on the way

Mailpit's 1025 is now published. Only 8025 was, so dotnet run couldn't reach SMTP at the localhost:1025 its own config pointed at — host-side mail silently went nowhere. Both paths now deliver:

host mail reached mailpit    2 total

appsettings.Development.json is no longer tracked. It was committed with only a Logging section, which is why every local edit to it has shown as an uncommitted diff for this entire session. Now gitignored with appsettings.Development.example.json as the template — matching how the Docker file was already handled — and still dockerignored, so no key reaches an image layer.

Verified

container (8080)   health Healthy · register 202 · users/me 200 · mail delivered
dotnet run (5271)  health Healthy · register 202 · login ok · mail delivered
tokens             interchangeable both ways

Application.Tests      28 passed
Infrastructure.Tests   98 passed
Api.Tests              92 passed

Unchanged gotcha

Create appsettings.Development.json before the first docker compose up. Missing file → Docker creates a directory at the mount path and the API fails on an empty connection string. Noted in the README and in a compose comment.

#112 introduced appsettings.Docker.json alongside appsettings.Development.json,
which meant maintaining the same settings twice. Worse, each got its own
generated signing key, so a token minted by `dotnet run` was rejected by the
container and vice versa - two local environments that could not share a
session.

There is now one gitignored appsettings.Development.json. `dotnet run` reads it
directly; compose mounts it into the container as appsettings.Docker.json, which
is the name that environment loads.

The environment stays Docker rather than becoming Development, because
Program.cs skips UseHttpsRedirection only for Docker and the container has no
HTTPS port to redirect to.

Two settings cannot be shared and are overridden in compose. Neither is a
secret: the database host, because inside a container localhost is the
container, using the same throwaway password already spelled out on the db
service; and the SMTP host, because the catcher is reachable as `mailpit` there
and `localhost` here. The signing key is not among them - it stays in the file.

Also publishes mailpit's 1025, so `dotnet run` can reach SMTP at the
localhost:1025 the config already pointed at. Only 8025 was published before, so
host-side mail went nowhere.

appsettings.Development.json was tracked with only a Logging section, which is
why every local change to it showed as an uncommitted diff. It is now gitignored
with appsettings.Development.example.json committed as the template, matching how
the Docker file was already handled, and still dockerignored so no key reaches an
image layer.

Verified with both running against the same file: tokens are accepted in both
directions, and mail from each reaches mailpit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rghvgrv rghvgrv self-assigned this Aug 7, 2026
@rghvgrv
rghvgrv merged commit 69563f9 into main Aug 7, 2026
2 checks passed
@rghvgrv
rghvgrv deleted the feat/single-dev-appsettings branch August 7, 2026 07:23
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