# Environment Strategy > **NOTE**: image tag and namespace reflect the post-rename state (B10 done). The container name and compose service name are still `flights` — that rename is B6/B11 (consumer cutover), tracked separately. ## Environments | Environment | Where it runs | Audience | Image tag (post-B10) | |-------------|---------------|----------|----------------------| | **Development** | Local workstation (`dotnet run` from the repo root, or `docker run` against a local PG) | Engineers | none — built ad-hoc | | **Edge production** | Each customer-owned edge device (Jetson Orin / OrangePI / operator-PC), one container per device | Operators, autopilot, UI | `azaion/missions:dev-arm` / `stage-arm` / `main-arm` per device tier | There is **no centralized staging environment** in the suite. Each edge device is its own deployment; the `stage` image tag is for pre-prod customer demo devices, not for a separate cloud staging cluster. ## Configuration sources (precedence) `Program.cs` resolves each setting in this order: 1. `IConfiguration` (defaults: appsettings.json + ASPNETCORE_-prefixed env vars) 2. `Environment.GetEnvironmentVariable(...)` (legacy fallback for unprefixed env) 3. **Hardcoded dev fallback** (last resort) ### `DATABASE_URL` | Environment | Value source | Resolved value | |-------------|--------------|----------------| | Development (no env set) | hardcoded fallback | `Host=localhost;Database=azaion;Username=postgres;Password=changeme` | | Development (env set) | env var | Whatever the engineer sets (URL form OR raw Npgsql key=value form both work) | | Edge production | env var passed via docker compose | `postgresql://postgres:${PG_LOCAL_PASSWORD}@postgres-local/azaion` (per `../../../suite/_docs/00_top_level_architecture.md`) | **`ConvertPostgresUrl` helper** parses the URL form into Npgsql key=value form. **Does NOT URL-decode user/password** — credentials with `@`, `:`, `/`, `%` will be mis-parsed. `07_host` Caveats #4 calls this out. Mitigation: avoid those characters in the local PG password (the standard suite provisioning does), or pass a raw Npgsql key=value string instead of a URL. ### `JWT_SECRET` | Environment | Value source | Resolved value | |-------------|--------------|----------------| | Development (no env set) | hardcoded fallback | `development-secret-key-min-32-chars!!` (**well-known; never use in production**) | | Development (env set) | env var | Whatever the engineer sets | | Edge production | env var (compose `env_file` or per-device-provisioned secret) | The shared HMAC secret used by `admin` + every backend service on the device. **Rotation is suite-coordinated** — see `architecture.md` § Security | **Critical foot-gun (ADR-005 carry-forward)**: there is **no runtime gate** that blocks startup with the dev fallback in production. A misconfigured production deploy will silently boot with the well-known dev secret. The CMMC L2 scorecard tracks the broader fix at suite level (AZ-487 / AZ-494). ## Other configuration These settings are **NOT** environment-overridable today (carry-forward improvements): | Setting | Current behavior | Should it differ between dev and prod? | |---------|------------------|----------------------------------------| | Swagger UI mount | Always on | Yes — production should gate on `IsDevelopment()` (ADR-005) | | CORS policy | `AllowAnyOrigin/Method/Header` always | Yes — production should restrict to the suite's reverse-proxy origin | | HTTPS redirection | None — assumes upstream TLS termination | No — the suite's reverse proxy handles TLS; this is correct | | Logging verbosity | ASP.NET Core defaults (Information+) | Probably not at this scale; `LogLevel:Default = Warning` could be useful in prod | | Migrator `DROP TABLE IF EXISTS` (B9 one-shot) | Runs every startup; idempotent on already-cleaned devices | No — the idempotent design means this is safe everywhere | ## Edge compose excerpt (suite-wide pattern, post-B10) Per `../../../suite/_docs/00_top_level_architecture.md` § Edge compose: ```yaml services: missions: # was: flights (pre-B10) image: ${REGISTRY_HOST}/azaion/missions:${BRANCH:-main}-arm container_name: missions restart: unless-stopped depends_on: postgres-local: condition: service_healthy environment: DATABASE_URL: postgresql://postgres:${PG_LOCAL_PASSWORD}@postgres-local/azaion JWT_SECRET: ${JWT_SECRET} ports: - "5002:8080" networks: - azaion-edge ``` The actual file lives in the suite repo (`../../../suite/_infra/_compose/`) — the snippet here is illustrative. ## Secrets management - **Local dev**: hardcoded fallbacks in `Program.cs` (the dev-secret values listed above). - **Edge production**: env vars sourced from a per-device `.env` file (created at provisioning) OR a local secrets manager (per-customer choice — the suite supports both patterns). The `JWT_SECRET` is suite-wide (one value across all backend services on the device). - **Rotation**: changing `JWT_SECRET` invalidates every issued token until new ones are minted. Coordinated procedure across the device's backend services + UI re-login. There is **no online rotation** — every backend must be restarted with the new secret simultaneously. ## Network / port layout - Container `EXPOSE 8080`; bound HTTP only (no TLS in this service). - Edge compose maps `5002:8080` per the suite convention. - Reverse proxy (Caddy fronting the suite per `../../../suite/_docs/00_top_level_architecture.md`) terminates TLS upstream. - No outbound network calls except to `postgres-local` (DB). ## Restart / lifecycle - Container restart policy: `unless-stopped` in production compose (manually-stopped containers stay stopped; crash → auto-restart). - Watchtower polls the registry and triggers re-create on new image digest. - `flight-gate` (suite component) gates container restart so it does not happen mid-mission. Once the active mission completes, Watchtower's queued restart goes through. - `missions` itself does not implement graceful shutdown beyond ASP.NET Core's defaults — in-flight HTTP requests are allowed to complete; idle connections are closed. ## Backup / disaster recovery - **Out of scope for this service.** PostgreSQL backup is a per-device, suite-level concern (not per-service). Each edge device runs `postgres-local`; backup cadence and offsite replication are decided at provisioning time and documented at suite level (currently informally). - **No application-level export** — the only way to read mission / waypoint data out of the device is through the API or `pg_dump` against `postgres-local`.