6.9 KiB
Docker Deployment Design
Date: 2026-03-21 Project: BudgetApp
Overview
Deploy BudgetApp to a home server running Nginx Proxy Manager (NPM) and an existing MySQL instance. Two Docker containers are orchestrated via docker-compose: one for the .NET 8 API and one for the Vue 3 frontend served by nginx.
Architecture
┌─────────────────────────────────────────────┐
│ docker-compose.yml │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ budgetapp-web │ │ budgetapp-api │ │
│ │ (nginx + Vue) │──▶ (.NET 8 API) │ │
│ │ port 3100:80 │ │ port 8080:8080 │ │
│ └──────────────────┘ └──────────────────┘ │
│ internal network: budgetapp │
└─────────────────────────────────────────────┘
│ │
▼ ▼
NPM proxy host seth.local MySQL
(one entry) (existing)
budgetapp-webexposes port 3100 to host — NPM points herebudgetapp-webnginx proxies/api/→http://budgetapp-api:8080over internal Docker network. The/api/prefix is preserved (no trailing slash onproxy_pass) because both controllers use[Route("api/[controller]")].budgetapp-apiexposes port 8080 (optional, for direct access/debugging)- Both containers share a Docker bridge network named
budgetapp - API connects to the existing MySQL at
seth.local budgetapp-webhasdepends_on: budgetapp-api(condition: service_started) — nginx will retry failed upstream connections, so waiting for the process to start is sufficient without requiring a full health-check gate
Files to Create
| File | Purpose |
|---|---|
Dockerfile.api |
Multi-stage .NET 8 build — solution root as context |
Dockerfile.web |
Multi-stage Node/Vite build + nginx runtime |
docker-compose.yml |
Wires containers, network, port mappings, env vars |
nginx.web.conf |
nginx config — serves static files, proxies /api/ to API container |
.env |
Gitignored secrets file — DB connection string |
.dockerignore |
Excludes node_modules/, dist/, bin/, obj/, docs/ from build context |
Files to Modify
| File | Change |
|---|---|
appsettings.json |
Connection string already cleared; env var override used at runtime |
BudgetApp.Api/Program.cs |
Remove or guard UseHttpsRedirection() — see below |
BudgetApp.Web/src/backend/ApiConnector.ts |
Change hardcoded https://localhost:7244/api/Transactions to relative path /api/Transactions |
Required Application Code Changes
1. ApiConnector.ts — Relative API URL
Currently ApiConnector.ts hardcodes https://localhost:7244/api/Transactions. In Docker this would be baked into the Vite bundle and the browser would try to call localhost:7244 instead of going through nginx. Change to a relative path /api/Transactions so all API calls go through nginx's proxy.
Any future frontend calls to other endpoints (e.g. UploadController at api/Upload/csv) must also use relative paths following the same pattern.
2. Program.cs — Disable HTTPS Redirection in Production
app.UseHttpsRedirection() is currently active unconditionally. Inside Docker, TLS is terminated at NPM — the API container receives plain HTTP on port 8080. With HTTPS redirection active, nginx's proxy call to http://budgetapp-api:8080 would receive a 307 redirect and fail. Guard or remove the middleware:
if (!app.Environment.IsProduction())
{
app.UseHttpsRedirection();
}
Container Details
budgetapp-api
- Base images:
mcr.microsoft.com/dotnet/sdk:8.0(build) →mcr.microsoft.com/dotnet/aspnet:8.0(runtime) - Build context: Solution root (required — API references sibling projects)
- Publish target:
BudgetApp.Api/BudgetApp.Api.csproj - Runtime port: 8080 (the
aspnet:8.0image defaults to port 8080; noASPNETCORE_URLSoverride required) - Health check:
GET /ping(existing endpoint returns{ok: true}) - Environment variables:
ASPNETCORE_ENVIRONMENT=ProductionConnectionStrings__MainDatabase— injected from.envvia docker-compose
- Note: Swagger UI is disabled in Production (gated behind
IsDevelopment()inProgram.cs). Access/swaggerby temporarily settingASPNETCORE_ENVIRONMENT=Developmentif needed.
budgetapp-web
- Base images:
node:20-alpine(build) →nginx:alpine(runtime) - Build context:
BudgetApp.Web/ - Build output:
dist/copied into nginx image - Runtime port: 80 (mapped to host port 3100)
- API routing: nginx proxies
location /api/→http://budgetapp-api:8080(prefix preserved — controllers use[Route("api/[controller]")]) - SPA routing:
try_files $uri $uri/ /index.htmlfor Vue Router - Depends on:
budgetapp-api
Secrets Management
A .env file at the solution root (gitignored) holds the DB connection string using the exact ASP.NET Core env var key format to avoid any mapping indirection in docker-compose:
ConnectionStrings__MainDatabase=Server=seth.local;User Id=budget;Password=<password>;Database=budget
docker-compose passes this directly as the environment variable to the API container. ASP.NET Core's built-in env var configuration provider resolves double-underscores as nested keys automatically.
docker-compose Environment Block (API service)
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__MainDatabase: ${ConnectionStrings__MainDatabase}
Build & Deploy
# On the server, in the project directory
docker-compose up -d --build
# View logs
docker-compose logs -f
# Restart after code changes
docker-compose up -d --build
NPM Setup (Manual)
After containers are running, add one proxy host in Nginx Proxy Manager:
- Domain:
budget.yourdomain.com(or local DNS name) - Forward hostname/IP:
<host-ip> - Forward port:
3100
No second proxy host needed — API traffic is routed internally by the web container's nginx.
Notes
- CORS: The API has an unconditional
AllowAllCORS policy. Since all browser traffic reaches the API through nginx's internal proxy, the browser never makes a direct cross-origin request to the API — CORS does not apply to the primary flow. Tightening the CORS policy will have no effect on security in this deployment. .envis already covered by.gitignore— the root.gitignorealready includes.env, so no additional entry is needed.