Wires the budget container to the OTel Collector via the shared external telemetry network. Endpoint, protocol, and service name come from env vars so the collector address is not baked into application code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Budget
A multi-user budget web app built around the 50/30/20 framework. Users manage Income and Outgo entries, view a Summary breakdown by type (Need / Want / Save), and can share budgets with other users in edit or view-only mode.
Stack: ASP.NET Core 10 + Vite/React (TypeScript) — served as a single Docker container. PostgreSQL via EF Core. OIDC authentication against auth.stwaddle.com.
Environment Variables
Backend (ASP.NET Core)
| Variable | Required | Description |
|---|---|---|
ConnectionStrings__DefaultConnection |
Yes | Full Npgsql connection string |
AUTH__AUTHORITY |
Yes | OIDC authority base URL (trailing slash required) |
AUTH__AUDIENCE |
Yes | Expected aud claim in access tokens — must match the scope's Resource value in the auth server |
ASPNETCORE_ENVIRONMENT |
No | Production or Development (default: Production in Docker) |
If ConnectionStrings__DefaultConnection is absent, the app falls back to assembling a connection string from individual variables:
| Variable | Default | Description |
|---|---|---|
POSTGRES_HOST |
localhost |
Database host |
POSTGRES_PORT |
5432 |
Database port |
POSTGRES_DB |
budget |
Database name |
POSTGRES_USER |
budget |
Database user |
POSTGRES_PASSWORD |
changeme |
Database password |
Frontend (Vite — baked in at build time)
These are compiled into the static assets during the Docker build and cannot be changed at runtime.
| Variable | Description |
|---|---|
VITE_AUTH_AUTHORITY |
OIDC authority base URL — must match AUTH__AUTHORITY |
VITE_AUTH_CLIENT_ID |
OIDC client ID registered in the auth server |
VITE_AUTH_REDIRECT_URI |
Full URL of the /callback route (e.g. https://budget.stwaddle.com/callback) |
To change frontend auth config after the image is built, rebuild the image with updated build args. See the Dockerfile.
Production Deployment
The service is defined in docker/docker-compose.yaml and managed alongside the rest of the stwaddle.com infrastructure.
docker-compose service
budget:
image: docker.stwaddle.com/budget:latest
environment:
- VIRTUAL_HOST=budget.stwaddle.com
- LETSENCRYPT_HOST=budget.stwaddle.com
- VIRTUAL_PORT=8080
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Host=apps-db;Port=5432;Database=budget;Username=postgres;Password=${APPS_DB_PASSWORD}
- AUTH__AUTHORITY=https://auth.stwaddle.com/
- AUTH__AUDIENCE=budget-api
depends_on:
- apps-db
- auth
networks:
- web
- apps-internal
restart: unless-stopped
.env entries
Add to the .env file on the server (alongside the existing entries):
BUDGET_VIRTUAL_HOST=budget.stwaddle.com
BUDGET_LETSENCRYPT_HOST=budget.stwaddle.com
APPS_DB_PASSWORD is already set in .env for the recipes app and is shared. The budget app gets its own budget database within the same Postgres instance — EF Core's MigrateAsync() creates it automatically on first startup.
Build and push
.\build-budget-image.ps1
This builds the multi-stage Docker image (Node → .NET SDK → ASP.NET runtime) and pushes it to docker.stwaddle.com/budget:latest.
The VITE_* build args default to the production values in the Dockerfile. To override:
docker build `
--build-arg VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/ `
--build-arg VITE_AUTH_CLIENT_ID=budget-client `
--build-arg VITE_AUTH_REDIRECT_URI=https://budget.stwaddle.com/callback `
-t docker.stwaddle.com/budget:latest .
docker push docker.stwaddle.com/budget:latest
DNS
Add an A record for budget.stwaddle.com pointing to the Linode server IP before deploying. The acme-companion will issue the Let's Encrypt certificate automatically. If DNS hasn't propagated when the container first starts:
docker compose restart acme-companion
Auth Server Setup
The budget app requires two registrations in the auth server admin panel at https://auth.stwaddle.com/Admin/ (requires an account with the Admin role).
1. Create the API Scope
Admin → Scopes → Create
| Field | Value |
|---|---|
| Name | budget_api |
| Display Name | Budget API |
| Resources | budget-api |
The Resources field is what gets written into the aud claim of access tokens. It must exactly match AUTH__AUDIENCE=budget-api set on the API container.
2. Register the Client Application
Admin → Sites → Create
| Field | Value |
|---|---|
| Client ID | budget-client |
| Display Name | Budget |
| Redirect URIs | https://budget.stwaddle.com/callback |
| Post-Logout Redirect URIs | https://budget.stwaddle.com |
| API Scopes | ✅ budget_api |
The client is always created as Public (no secret) with Authorization Code + PKCE + Refresh Token — this is enforced by the auth server for all SPA clients.
For local development, also add the dev URIs (you can edit the client after creation):
- Redirect URI:
http://localhost:5173/callback - Post-Logout URI:
http://localhost:5173
3. Grant Users Access
Users must be explicitly granted a role on the client before they can log in.
Admin → Sites → [Budget] → Roles → Grant
Assign any user who needs access at least the user role. Without a role entry the auth server returns access_denied at the authorize endpoint.
Token details
| Property | Value |
|---|---|
| Flow | Authorization Code + PKCE |
| Access token lifetime | 15 minutes |
| Refresh token lifetime | 14 days |
| Claims in access token | sub, email, name, role, aud (budget-api) |
The frontend uses oidc-client-ts with automatic silent renewal, so users stay logged in across the 15-minute access token window without re-authenticating.
Local Development
Prerequisites
- .NET 10 SDK
- Node 22
- PostgreSQL (or Docker)
Setup
-
Copy
.env.exampleto.envand fill in values for your local Postgres instance. -
Start the API:
cd src/Budget.Api dotnet runThe API listens on
http://localhost:5000by default. EF migrations run automatically on startup. -
Start the frontend dev server:
cd src/Budget.Client npm install npm run devVite proxies
/apirequests tohttp://localhost:5000. The app is available athttp://localhost:5173. -
Set the following in
src/Budget.Client/.env.local(Vite picks this up automatically, it is gitignored):VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/ VITE_AUTH_CLIENT_ID=budget-client VITE_AUTH_REDIRECT_URI=http://localhost:5173/callback -
Set the following in
src/Budget.Api/appsettings.Development.json:{ "AUTH__AUTHORITY": "https://auth.stwaddle.com/", "AUTH__AUDIENCE": "budget-api" }
The auth server's dev redirect URI (http://localhost:5173/callback) must be registered on the client as described in the Auth Server Setup section above.