Development guide
Toolchain
Section titled “Toolchain”Install the following tools on the development host:
- .NET 10 SDK;
- Node.js 24 or another version satisfying the root
enginesfield, with the npm version recorded bypackageManager; - Docker Engine with the Compose v2 plugin;
- Git; and
- optionally
psqlfor database inspection.
Initial setup
Section titled “Initial setup”From the repository root, run the reproducible setup command:
dotnet tool restore && dotnet restore Firewatch.sln && npm ciRe-run the setup command whenever the lock files, tool manifest, or project graph changes.
Run the complete stack
Section titled “Run the complete stack”docker compose up --buildOpen http://localhost:3000. The API is also published at http://localhost:8080. Compose waits for PostgreSQL readiness, starts the API and its local migration step, waits for API readiness, then starts the independent worker and web processes.
The base stack bootstraps the first self-hosted administrator without an email round trip and leaves email delivery disabled. To test verification and SMTP delivery with Mailpit at http://localhost:8025, start the explicit development override:
docker compose -f docker-compose.yml -f docker-compose.mailpit.yml up --buildUseful commands:
docker compose psdocker compose logs --follow api workerdocker compose downdocker compose down preserves the PostgreSQL named volume. Use
docker compose down --volumes only when you intentionally want to erase local
Firewatch data.
Run processes directly
Section titled “Run processes directly”Keep infrastructure in Docker:
docker compose -f docker-compose.yml -f docker-compose.mailpit.yml up -d postgres mailpitIn one terminal, configure a host-reachable database and run the API:
export ConnectionStrings__Firewatch='Host=localhost;Port=5432;Database=firewatch;Username=firewatch;Password=change-me-for-non-local-use'export Database__MigrateOnStartup=trueexport Auth__PublicBaseUrl=http://localhost:5173export Email__Enabled=trueexport Email__Smtp__Host=localhostexport Email__Smtp__Port=1025export Email__Smtp__Security=Nonedotnet run --project src/Firewatch.ApiIn a second terminal, export the same connection string, select the local queue provider, then run:
export ConnectionStrings__Firewatch='Host=localhost;Port=5432;Database=firewatch;Username=firewatch;Password=change-me-for-non-local-use'export Queueing__Provider=Postgresdotnet run --project src/Firewatch.WorkerIn a third terminal, start Vite:
npm run devOpen http://localhost:5173 for the direct Vite workflow.
Vite is the only direct development server. The React application still uses the
HTTP API through its same-origin development proxy; it never imports backend
implementation code. VITE_DEV_API_PROXY_TARGET changes the proxy target from
its http://localhost:8080 default while preserving same-origin browser calls.
Set VITE_API_BASE_URL only when intentionally bypassing that proxy for a
different public API origin.
Exercise the smoke flow
Section titled “Exercise the smoke flow”With the API and worker running:
curl --include \ --request POST \ --header 'Content-Type: application/json' \ --data '{"name":"documentation-smoke-test"}' \ http://localhost:8080/api/v1/system/test-jobsThe response is 202 Accepted and includes an identifier and status URL. Replace
<id> below with the returned identifier:
curl http://localhost:8080/api/v1/system/test-jobs/<id>docker compose logs workerThe status eventually becomes Completed, and the worker log includes the stable
message identifier. Retrying delivery does not create a second completion.
Tests and quality checks
Section titled “Tests and quality checks”Run backend checks:
dotnet format Firewatch.sln --verify-no-changes --no-restoredotnet build Firewatch.sln --configuration Releasedotnet test Firewatch.sln --configuration Release --no-buildIntegration tests use Testcontainers with real PostgreSQL behavior. Docker must be running; SQLite is intentionally not used as a substitute.
Run frontend checks:
npm run frontend:checkOr run the constituent commands:
npm run lintnpm run format:checknpm run typechecknpm testnpm run buildnpm run api:checkOpenAPI and TypeScript client generation
Section titled “OpenAPI and TypeScript client generation”The committed openapi/firewatch.v1.json document is the language-neutral API
contract. After changing API routes or contracts, first regenerate the document
from the ASP.NET Core project using the repository’s deterministic OpenAPI
generation command, then generate the TypeScript types:
dotnet build src/Firewatch.Api/Firewatch.Api.csproj --configuration Releasenpm run api:generatenpm run api:checkopenapi-typescript is used because it is maintained, produces small static
types without a proprietary runtime, and composes with the maintained
openapi-fetch client. Generated files under
packages/api-client/src/generated must not be manually edited. The handwritten
package wrapper is the stable import surface consumed by apps/web.
npm run api:check regenerates to a temporary file and compares bytes, so CI
fails when the checked-in client does not match the checked-in OpenAPI document.
Both files must be committed with an API change.
A future iOS repository can feed the same OpenAPI file to Apple’s Swift OpenAPI Generator (with versions pinned by that repository). No iOS application or Swift-specific API is created here.
EF Core migrations
Section titled “EF Core migrations”Restore the pinned local dotnet-ef tool before migration work:
The design-time fallback matches the unchanged .env.example database. Export
ConnectionStrings__Firewatch first when using different credentials or a
different database.
dotnet tool restoreCreate a migration after changing the EF model:
dotnet ef migrations add <MigrationName> \ --project src/Firewatch.Infrastructure \ --startup-project src/Firewatch.Api \ --output-dir Persistence/MigrationsApply it to the configured database:
dotnet ef database update \ --project src/Firewatch.Infrastructure \ --startup-project src/Firewatch.ApiVerify that the model and migration snapshot agree, as backend CI does:
dotnet ef migrations has-pending-model-changes \ --project src/Firewatch.Infrastructure \ --startup-project src/Firewatch.ApiContainerized deployments should use the API image’s one-off migration mode:
docker compose run --rm api --migrateDo not enable automatic startup migrations for a multi-replica or managed deployment. See the cloud deployment contract for the expand/contract policy.
Common problems
Section titled “Common problems”- Readiness returns 503: inspect
docker compose logs postgres apiand verifyConnectionStrings__Firewatchand the PostgreSQL health check. - Integration tests cannot start: verify
docker infoworks from the same terminal and that the Docker daemon is running. - The web page loads but API calls fail: for Vite, check its
/apiproxy or an explicitly setVITE_API_BASE_URL; for the web container, checkFIREWATCH_API_UPSTREAMand API readiness. - Registration says email setup is required: the API is configured to
require verification. Start the Mailpit override or configure
Email__Enabled, the SMTP settings, andAuth__PublicBaseUrl. - Generated client check fails: regenerate with
npm run api:generate, review the API diff, and commit the generated file together with the schema change.