Skip to content
Firewatch
Get started
Site

Firewatch Cloud deployment contract

The public firewatch repository builds the complete portable product. The private firewatch-cloud repository deploys and operates it, and must consume the contract below instead of copying product source or introducing a separate Cloud/Enterprise implementation.

This document is the interface between those repositories. Values shown here are configuration names and examples, not cloud resources. The public repository does not contain AWS Terraform, managed-cloud deployment code, paid-plan logic, or customer-specific secrets.

The private firewatch-cloud repository owns:

  • AWS infrastructure-as-code and production networking;
  • multi-region failover;
  • managed PostgreSQL and queue provisioning;
  • billing and subscription systems;
  • customer provisioning and internal administration;
  • managed SMS, voice, email, and push-provider configuration;
  • disaster-recovery procedures and exercises;
  • cloud deployment pipelines;
  • private operational dashboards.
  • paid authentication adapters such as enterprise SAML and their customer connection configuration.

The public repository owns all portable product behavior, public database migrations, the REST and message contracts, API-client generation, and the API, worker, and web image definitions. A private extension may own migrations only for its own namespaced schema and must not alter public tables or migration history. A change needed by both deployment models belongs in public product code.

A release produces three OCI images from the public repository:

Image Process Internal port Persistent filesystem
firewatch/api:<version> ASP.NET Core REST API TCP 8080 None
firewatch/worker:<version> .NET background worker None None
firewatch/web:<version> Static React assets and Nginx proxy TCP 8080 None

The eventual registry hostname is intentionally not promised yet. Release automation should publish a SemVer tag, an immutable source-revision tag, and an OCI manifest digest. Cloud deployments must pin a digest, record the product version, and promote the same digest between environments. latest is not a deployment contract.

API and worker images from a release must be deployed at compatible versions. The web image should normally match them. Rollbacks remain constrained by the database migration notes below.

Cloud may build additive API and web overlays for private extensions. The API overlay must pin the public firewatch/api base image by digest, copy extension artifacts under /app/extensions, and select each assembly explicitly through Firewatch__Extensions__Assemblies__N. It must not replace public binaries, copy public source into a fork, or own migrations for public tables. A private web build may fill the public product-extension registry while compiling from a pinned public checkout. The public API remains responsible for account linking, organization authorization, and session creation after an extension validates an external identity.

.NET configuration uses the standard double-underscore environment mapping. Secrets must come from the deployment platform’s secret store, never from an image or source-controlled environment file.

Variable Required Meaning
ConnectionStrings__Firewatch Yes Npgsql connection string for the shared Firewatch PostgreSQL database
Queueing__Provider Worker only Postgres for self-hosting or Sqs for the managed worker transport
Database__MigrateOnStartup API only Local convenience only; keep false in managed/multi-replica environments
Auth__RegistrationMode API only Disabled, Bootstrap, Open, or Provisioned; managed Cloud must use Provisioned
Auth__PublicBaseUrl API only Canonical external HTTPS origin used to construct invitation and account-recovery links
Auth__RequireEmailVerification API only Managed Cloud must use true; self-hosted bootstrap defaults to false
SelfHosting__Enabled API only Enables the self-hosted setup guide; managed Cloud must use false
Auth__Passkeys__ServerDomain API only Optional WebAuthn RP ID override; otherwise inferred from the canonical public URL
Auth__DataProtection__CertificatePath Managed Cloud Path to a mounted PKCS#12 certificate used to encrypt the shared Data Protection key ring
Auth__DataProtection__CertificatePassword Managed Cloud secret Password for the mounted Data Protection certificate
Auth__DataProtection__PreviousCertificates__N__Path Certificate rotation Mounted PKCS#12 certificate previously used to encrypt keys (N starts at 0)
Auth__DataProtection__PreviousCertificates__N__Password Rotation secret Password for the matching previous certificate
Auth__Provisioning__Enabled Cloud provisioning only Must be true to expose paid-customer provisioning; defaults to false
Auth__Provisioning__Authority Cloud provisioning only OIDC issuer trusted to mint workload access tokens
Auth__Provisioning__Audience Cloud provisioning only Audience that uniquely identifies the Firewatch provisioning API
Auth__Provisioning__RequiredScope Cloud provisioning only Required workload-token scope; must remain firewatch.provision
Features__UserProfileEditing API only Self-hosted defaults to true; managed Cloud should set false
Firewatch__Extensions__Assemblies__N Cloud API overlay Absolute path to a trusted private extension assembly; N starts at 0
Email__Enabled API and worker Enables account and incident email; keep disabled until the selected provider is valid
Email__Provider API and worker Smtp for portable/self-hosted delivery or Ses for the managed AWS adapter
Email__FromAddress API and worker Verified sender address; defaults to noreply@onfirewatch.com
Email__FromName API and worker Sender display name; defaults to Firewatch
Email__Smtp__Host API and worker SMTP server hostname
Email__Smtp__Port API and worker SMTP port, normally 587 for STARTTLS or 465 for implicit TLS
Email__Smtp__Security API and worker None, StartTls, or SslOnConnect; production must use an encrypted mode
Email__Smtp__Username API and worker secret Optional SMTP username; configure together with a password
Email__Smtp__Password API and worker secret Optional SMTP password; source from the deployment secret store
Email__Ses__ConfigurationSetName API and worker Optional SESv2 configuration set; Cloud supplies its transactional set
Runbooks__Provider API only FileSystem for Compose or S3 for managed object storage
Runbooks__StoragePath API only Persistent directory required by the FileSystem provider
Runbooks__S3__BucketName API only Private S3 bucket required by the S3 provider
Runbooks__S3__KeyPrefix API only Object prefix; defaults to runbooks
Twilio__Enabled API and worker Enables phone verification and configured incident phone channels
Twilio__AccountSid API and worker secret Managed Twilio account SID
Twilio__AuthToken API and worker secret Managed Twilio auth token
Twilio__FromPhoneNumber API and worker SMS/voice-capable E.164 sender
Twilio__IncidentSmsEnabled Worker only Sends incident SMS to verified primary phone numbers; defaults to true
Twilio__IncidentVoiceEnabled Worker only Enables automated incident calls; defaults to false
Slack__Enabled API and worker Enables workspace installation, interactive alerts, and lifecycle synchronization
Slack__ClientId API and worker secret Hosted Slack app client ID
Slack__ClientSecret API and worker secret Hosted Slack app client secret
Slack__SigningSecret API and worker secret Verifies timestamped Slack interaction signatures
Slack__Scopes API and worker Bot scopes; defaults to chat:write,im:write,users:read,users:read.email
Logging__LogLevel__Default No Baseline structured-log level; defaults to Information
OTEL_SERVICE_NAME Recommended Stable service identity such as firewatch-api or firewatch-worker
ASPNETCORE_HTTP_PORTS API only API listen port; image default is 8080

Managed Firewatch Cloud uses Email__Provider=Ses and the SESv2 API with noreply@onfirewatch.com under a verified domain identity. The AWS SDK obtains credentials from the compute workload role. The SMTP adapter remains the self-hosted default so operators can supply any standards-compliant mail server.

The product appsettings default is Auth__RegistrationMode=Disabled, while the self-hosted Compose stack uses Bootstrap. Bootstrap permits the first administrator registration and then closes automatically. Open deliberately exposes ongoing public account registration. Managed Firewatch Cloud sets the mode to Provisioned: public registration remains unavailable, while invitation acceptance and the authenticated provisioning workflow remain available. The web application discovers this capability from the API; the cloud deployment must not build a different web image just to hide registration.

Deployment-specific product behavior is exposed to the web application through fine-grained session capabilities and is enforced again by the API. The web app must not infer behavior from an environment name or a generic Cloud flag. Features__UserProfileEditing=false lets managed deployments keep names under central identity administration. Future deployment-specific behavior should use the same capability pattern rather than branching directly on hosting mode.

Auth__PublicBaseUrl must be the single canonical public origin, for example https://app.firewatch.example, without an internal service name or path. The API must not derive security-sensitive links from an untrusted Host or forwarded header. It is also the default WebAuthn origin and source for the passkey RP ID. Cloud ingress must redirect HTTP to HTTPS and forward only headers assigned by trusted proxies. An RP ID override is a long-lived security boundary: changing it invalidates the use of passkeys registered under the previous RP ID.

The private cloud control plane starts provisioning only after the corresponding payment or subscription transition is durable. It obtains a short-lived OIDC client-credentials/workload token whose audience matches Auth__Provisioning__Audience and whose scope includes firewatch.provision. The product provisioning endpoint must also be restricted to private ingress. Network isolation supplements token validation; it does not replace it. Customer organization API keys, user access tokens, and a shared static bootstrap secret are not valid provisioning credentials.

Each request carries a stable provisioningId idempotency key plus a distinct externalCustomerId, organization name, and initial administrator’s first name, last name, and normalized email address. In one database transaction, the public product creates or retrieves the organization, creates the pending user when needed, grants the organization-administrator membership, and issues a single-use, expiring invitation. A durable provisioning-operation row serializes requests with the same key and returns the same identifiers and invitation token when an identical request is retried; GET /api/v1/provisioning/operations/{id} replays that result. Reusing a key with different input is a conflict, not a second customer. The private control plane retains the key and result so a payment event, timeout, or process restart can be retried safely.

Provisioning never supplies or receives a plaintext password. Invitation tokens are high-entropy; their validation value is stored only as a hash. The stable provisioning replay value is separately encrypted by the Data Protection key ring, expires, and becomes unusable after its first successful redemption. The managed email provider delivers the token in the URL fragment, which is not sent in the HTTP request, and the SPA removes it from browser history immediately. The token must not be placed in application logs. Accepting an invitation is not public self-registration and remains available in Provisioned mode.

The authority and audience are ordinary configuration, but the OIDC client credential is a cloud-control-plane secret and is never injected into the Firewatch API. Issuer signing-key rotation must be supported through OIDC discovery. Changing the issuer or audience is a deployment-contract change and must be coordinated with the control plane.

OpenTelemetry instrumentation hooks are registered, but this skeleton does not bundle an exporter. Consequently OTEL_EXPORTER_OTLP_ENDPOINT is not an active runtime setting yet. A future public-core release must add and document an exporter before the private deployment repository relies on that variable.

PostgreSQL-provider settings require no AWS credentials. The provider uses the same database and connection string as durable application state.

The optional PostgreSQL tuning keys are Queueing__Postgres__BatchSize (default 20), Queueing__Postgres__MaximumAttempts (default 10), Queueing__Postgres__VisibilityTimeout (default 00:02:00), and Queueing__Postgres__BaseRetryDelay (default 00:00:05). Change them only with queue-latency, handler-runtime, and database-capacity evidence; they do not alter at-least-once semantics.

For SQS, the adapter configuration path is Queueing__Sqs. A managed deployment supplies at least Queueing__Sqs__QueueUrl and an AWS region through the normal AWS SDK configuration (AWS_REGION or workload metadata). Credentials should use the workload identity/role credential chain; a non-AWS compute platform may use a dedicated, least-privilege runtime key until the worker moves to AWS. MaxNumberOfMessages, WaitTimeSeconds, VisibilityTimeoutSeconds, VisibilityHeartbeatSeconds, MaximumReceiveCount, and RetryBaseDelaySeconds are configurable. Runtime receive/visibility settings must match the queue and dead-letter policy provisioned by the cloud stack.

Queueing__Sqs__MessageGroupId defaults to firewatch and is used only for a FIFO queue URL. The stable Firewatch message ID is also the FIFO deduplication ID, but consumers must still implement durable idempotency beyond SQS’s deduplication window.

The SQS provider is operational end to end. Product transactions still write an outbox row in PostgreSQL; a relay publishes the stable envelope to SQS and marks that row sent only after AWS acknowledges it. SQS workers long-poll, renew visibility while a handler runs, delete only after the handler and durable processed-message record commit, and leave failures for retry/redrive. A PostgreSQL advisory transaction lock prevents concurrent duplicate deliveries from executing the same handler. This transactional-outbox boundary is intentional: SQS replaces database polling as the managed worker transport, but cannot atomically replace the database outbox.

The web bundle defaults to same-origin API calls. At runtime, FIREWATCH_API_UPSTREAM tells its Nginx proxy where to forward /api, /health, and /openapi; the default used by Compose is http://api:8080. This keeps the same versioned web image portable across hostnames and environments.

VITE_API_BASE_URL remains a development build argument, but authenticated production web deployments require the same-origin runtime proxy. The API does not enable credentialed cross-origin browser requests. This avoids cross-site cookie and CSRF ambiguity and keeps one portable web image across environments.

Future configuration additions should be optional with safe defaults during a compatibility window. Renaming or removing a variable is a deployment-contract change and requires release notes.

Firewatch requires a supported PostgreSQL database reachable by both API and worker. Managed Cloud also uses the private S3 bucket selected through Runbooks__Provider=S3; the database stores asset metadata while S3 stores object bytes. Application containers remain disposable. Operators own encryption, credentials, capacity, high availability, backups, point-in-time recovery, and restore testing for both stores.

ASP.NET Core Data Protection keys are persisted in the shared Firewatch PostgreSQL database so cookies and security tokens remain valid across API replicas and container replacement. Managed deployments must use a stable Data Protection application name for every API replica and configure Auth__DataProtection__CertificatePath to a mounted PKCS#12 certificate whose password comes from Auth__DataProtection__CertificatePassword. The API fails startup in managed Provisioned mode when this protector is absent. The private cloud repository owns access policy, rotation, backup, and recovery for that protecting key. Database encryption alone does not replace key-ring protection, and neither the key ring nor its protecting private key may be baked into an image. During certificate rotation, the new certificate becomes the primary protector and every still-needed old certificate is configured under PreviousCertificates; remove an old decrypt certificate only after every key, session, and protected provisioning token that used it has passed its retention window.

Run migrations as a one-off release task before increasing the new application version’s replica count:

Terminal window
docker run --rm \
--env ConnectionStrings__Firewatch="<secret connection string>" \
firewatch/api:<version> --migrate

The API image entry point is dotnet Firewatch.Api.dll, so --migrate invokes the product’s migration-only mode, then the migration hook for every configured trusted extension, and exits nonzero on failure. Private migrations must use a separate schema and migration-history table. Managed deployments keep Database__MigrateOnStartup=false to prevent replica races. Compose sets it to true only so a clean local volume starts with one command.

Migration policy is forward-first and expand/contract:

  1. Back up and verify restore capability before a production schema change.
  2. Apply a migration compatible with the currently running version.
  3. Roll out API and workers gradually; check readiness and processing lag.
  4. Remove old columns or compatibility code only in a later release.

Never infer that rolling back an image reverses a migration. Release notes must call out any irreversible operation and minimum PostgreSQL version.

The API contract is rooted at /api/v1. A native iOS client and the web client consume the same routes. API version removal requires a separately documented deprecation window.

Endpoint Use
GET /health/live Process liveness; no dependency details
GET /health/ready Traffic readiness; includes PostgreSQL connectivity
GET /openapi/v1.json Versioned OpenAPI document

The platform should terminate TLS, preserve or assign X-Request-ID, set forwarded headers from trusted proxies only, and route API readiness independently from the web container’s /web-health/ready. Health responses must not be used as authenticated administrative endpoints.

The public API applies per-account or per-invitation authentication throttles and Identity lockout, but deliberately does not trust arbitrary forwarded client-IP headers. Cloud ingress must additionally apply per-client/IP limits to login, registration, and invitation endpoints before traffic reaches the shared web proxy; a replica-wide proxy-address bucket would let one caller deny service to every tenant.

The platform sends SIGTERM and allows at least 30 seconds for API shutdown and 45 seconds for worker shutdown before SIGKILL. On shutdown:

  • the API stops accepting new requests and finishes in-flight requests;
  • the worker stops claiming new work, honors cancellation, and either completes or leaves the current item eligible for retry;
  • an interrupted or visibility-expired message may be delivered again, so handlers remain idempotent.

API and worker replica counts are independent. PostgreSQL-provider workers rely on database claims to avoid concurrent ownership; SQS workers rely on visibility leases. Scaling does not change message identity or delivery guarantees.

Before promoting a public release, the private deployment pipeline should:

  1. verify each image signature/digest and its matching source version;
  2. run the migration task once and retain its logs;
  3. wait for API readiness before routing traffic;
  4. verify worker health, outbox age, retry volume, and queue depth;
  5. exercise the versioned system test-job flow in a non-production environment;
  6. confirm structured logs and request/message correlation reach the managed observability system;
  7. retain the prior image digests and follow the migration-aware rollback plan.

Cloud-only dashboards, alarms, runbooks, release orchestration, provider credentials, and disaster-recovery automation stay private. Fixes to product behavior discovered through those systems flow back to this public repository.