Skip to content
Firewatch
Get started
Site

Self-hosting Firewatch

Firewatch is under active development and is not yet a production-stable release. The core alert, schedule, escalation, incident, and notification paths exist, but operators must evaluate and test the current release against their own availability, security, and compliance requirements before relying on it for paging.

Prerequisites are Docker Engine and Docker Compose v2. Clone the public repository, then run:

Terminal window
docker compose up --build

The services are:

Service Default host address Purpose
web http://localhost:3000 React application and same-origin API proxy
api http://localhost:8080 Versioned REST API, OpenAPI, health
worker None Outbox/queue processing in an independent process
postgres 127.0.0.1:5432 Durable state and default queue/outbox provider

Confirm readiness:

Terminal window
docker compose ps
curl --fail http://localhost:8080/health/live
curl --fail http://localhost:8080/health/ready

The first Compose boot sets Database__MigrateOnStartup=true on the single API container. This is a local/simple-hosting convenience so docker compose up --build works against a clean volume. It is not the managed or multi-replica migration strategy.

The initial boot needs no mail server. Self-hosted administrator registration does not send a verification email, and the email notification channel remains off until SMTP is configured. Before using the stack outside a disposable local machine, copy .env.example to the ignored .env and change at least POSTGRES_PASSWORD. Do not commit .env, cloud credentials, provider tokens, connection strings, or database dumps.

The local HTTP stack uses FIREWATCH_ENVIRONMENT=Development so ASP.NET can issue session and antiforgery cookies on http://localhost. Any deployment reachable by other machines must terminate HTTPS, set FIREWATCH_ENVIRONMENT=Production, and set FIREWATCH_PUBLIC_BASE_URL to its public https:// origin. Production mode deliberately refuses to issue these cookies over plain HTTP. It also refuses to start with the documented development database password or an empty database password, and requires a canonical HTTPS public origin.

The default FIREWATCH_QUEUE_PROVIDER=Postgres requires no AWS account. API and worker use the same ConnectionStrings__Firewatch value assembled by Compose. The SQS adapter is intended for a managed configuration and is not required for self-hosting.

AWS SNS/SQS alert ingestion is independent of the internal queue provider. Self-hosters can keep FIREWATCH_QUEUE_PROVIDER=Postgres and use the Alert sources → AWS SNS / SQS setup generated in the UI. The generated Lambda forwards SNS or SQS event batches to Firewatch using an organization API key; no AWS credentials are stored by Firewatch.

The default self-hosted stack does not include a database administration UI. For temporary troubleshooting, start the optional Adminer override:

Terminal window
docker compose -f docker-compose.yml -f docker-compose.adminer.yml up -d adminer

Open http://localhost:8081 and sign in with:

Field Value
System PostgreSQL
Server postgres
Username the .env POSTGRES_USER value
Password the .env POSTGRES_PASSWORD value
Database the .env POSTGRES_DB value

The override’s ADMINER_BIND_ADDRESS defaults to 127.0.0.1. Keep that loopback binding: Adminer has direct database access and must not be exposed to the public internet or routed through the product UI. For a remote self-hosted server, leave it on loopback and use an SSH tunnel when access is needed:

Terminal window
ssh -L 8081:127.0.0.1:8081 your-server

Then browse to http://localhost:8081 on the administrator’s machine. Change ADMINER_PORT if port 8081 is already in use. Pin updates by changing ADMINER_VERSION, reviewing the release, and recreating the service.

Compose defaults to FIREWATCH_REGISTRATION_MODE=Bootstrap, which permits exactly the first self-hosted administrator to create an organization and then closes registration automatically. No ownership email is sent in this mode. The administrator chooses a discoverable passkey (recommended) or a password of at least 12 characters, then follows the in-product setup guide. Additional users join through administrator invitations. Open deliberately enables ongoing public registration, while Disabled closes it; existing users can still sign in. Provisioned is reserved for the managed-cloud control-plane workflow and is not a self-hosting bootstrap mechanism.

FIREWATCH_USER_PROFILE_EDITING_ENABLED=true allows signed-in users to update their own first and last names and is the self-hosted default. Set it to false when those identity attributes are managed centrally; the API will advertise the restriction to the web app and reject profile-update requests directly.

Firewatch sends incident email and account-security notifications through a configurable SMTP server. Both are disabled by default. The setup guide reports whether SMTP is active and generates the required .env block. Configure the FIREWATCH_SMTP_* values and use a sender domain your SMTP service is authorized to send as. StartTls is the normal setting for port 587; SslOnConnect supports implicit TLS, commonly on port 465. Store SMTP passwords as deployment secrets rather than committing them.

After changing deployment variables, run docker compose up -d so the API and worker receive the same configuration. During first-run setup, use Re-check on the relevant step. After setup, verify provider status under the applicable organization settings page. Mailpit remains available only as an explicit development override; it is not part of the self-hosted stack.

Set FIREWATCH_TWILIO_ENABLED=true and supply the account SID, auth token, and an SMS/voice-capable E.164 sender in FIREWATCH_TWILIO_*. Users can then verify one primary E.164 phone number under Settings → Profile. Verified numbers receive incident SMS by default. Automated voice calls are disabled unless FIREWATCH_TWILIO_INCIDENT_VOICE_ENABLED=true because calls are more intrusive and can have materially different provider costs. These credentials are global deployment secrets and are never exposed to users.

Set FIREWATCH_SLACK_ENABLED=true with a Slack app client ID, client secret, and signing secret to make Organization → Integrations → Add to Slack available. Register <FIREWATCH_PUBLIC_BASE_URL>/api/v1/organization/integrations/slack/callback as the OAuth redirect URL. Enable Interactivity & Shortcuts and register <FIREWATCH_PUBLIC_BASE_URL>/api/v1/integrations/slack/interactions as its request URL. Firewatch requests chat:write, im:write, users:read, and users:read.email; synchronization looks up only existing, verified Firewatch member emails and stores their immutable Slack user IDs. The worker opens a bot DM and sends each incident escalation to the mapped on-call responder. Responders can acknowledge or resolve from the DM. Firewatch verifies Slack’s timestamped HMAC signature, updates every delivered alert when status changes, and posts the lifecycle event in the message thread. It never guesses from a display name and does not import the Slack directory. Unmatched members continue to use their other verified notification methods.

Organization administrators can customize email subject/body, SMS, voice, Slack, and webhook wording under Organization → Alert templates. Templates use a small allow-listed {{variable.name}} syntax; they cannot execute code, read configuration or secrets, or emit raw HTML. Firewatch validates every template before saving and keeps built-in defaults available through Restore defaults. The settings are stored in PostgreSQL, so every API and worker replica sees the same version without custom container images.

Configure outbound alert endpoints under Organization → Integrations. Firewatch creates a separate HMAC-SHA256 signing secret for each endpoint, encrypts it at rest with the shared Data Protection key ring, and displays it only on creation or rotation. Receivers validate the timestamp, delivery ID, raw body, and X-Firewatch-Signature; see Outbound alert webhooks for the exact payload and a verification example.

Outbound destinations require HTTPS and public network addresses by default. For a reviewed internal self-hosted receiver, set FIREWATCH_WEBHOOK_ALLOW_PRIVATE_NETWORKS=true; set FIREWATCH_WEBHOOK_ALLOW_HTTP=true only when plain HTTP is also explicitly required. These values must be the same on the API (configuration validation) and worker (delivery), which the Compose file handles.

Set FIREWATCH_PUBLIC_BASE_URL to the external origin through which users reach the web application, including https:// in a real deployment and no trailing path. Firewatch uses this value for verification and invitation links and infers the passkey Relying Party ID from its host. Normally leave FIREWATCH_PASSKEY_SERVER_DOMAIN empty. Set it only when deliberately sharing the RP ID across trusted subdomains, and never serve untrusted content below that domain. Do not set it to the API container name or rely on an arbitrary incoming Host header.

WebAuthn requires HTTPS outside the browser’s special localhost development case. Production must keep the public URL, TLS origin, reverse-proxy host, and RP ID stable; changing the RP ID makes previously registered passkeys unusable.

Users can register up to ten named passkeys from Settings → Authentication. Firewatch prevents a passkey-only account from deleting its final credential; operators should encourage at least two credentials on separate devices. Email account recovery is not enabled yet. The intended self-hosted policy is a short-lived verified-email link that permits setting a password, followed by sign-in and passkey re-enrollment. Managed deployments may disable that route in favor of administrator or enterprise identity-provider recovery.

Account passwords, invitation tokens, recovery tokens, external-provider client secrets, and Data Protection key-protection material are secrets. Do not place them in .env under source control. Firewatch stores its shared Data Protection key ring in PostgreSQL so logins survive API restarts and multiple replicas; a production operator is responsible for database encryption, access control, backups, and any configured certificate or key used to protect that key ring.

The web image sends same-origin API calls through its Nginx proxy. Compose sets FIREWATCH_API_UPSTREAM=http://api:8080; operators can change that internal URL without rebuilding the static assets.

Images uploaded to Firewatch-authored runbooks are stored by the API through a provider-neutral asset-store interface. The open-source deployment uses durable filesystem storage at Runbooks__StoragePath; Compose mounts the firewatch-runbooks named volume at /app/data/runbooks. This keeps local and self-hosted installations lightweight—MinIO or another object-storage service is not required—and survives ordinary container replacement.

Compose runs a short-lived runbook-storage-init container before the API to give the API’s non-root account ownership of this volume. The official image uses UID/GID 1654; FIREWATCH_CONTAINER_UID and FIREWATCH_CONTAINER_GID exist only for operators who deliberately build a custom image with a different runtime account.

Do not point this setting at a temporary container directory. A non-Compose installation should mount a persistent filesystem at the configured path. The public Firewatch.Storage.S3 adapter lets a managed deployment select S3 without changing the runbook API, editor, or database model. Self-hosting keeps the filesystem provider by default so the one-command install does not require an AWS account.

PostgreSQL data lives in the firewatch-postgres named volume. Runbook image objects live in the separate firewatch-runbooks named volume. Routine container replacement does not remove either volume. Treat the database as the authoritative metadata state, including outbox and idempotency records, and back up the image volume alongside it so Markdown image references remain usable.

Create a logical backup to the host (the output file may contain sensitive data):

Terminal window
docker compose exec -T postgres \
pg_dump --format=custom --username=firewatch --dbname=firewatch \
> firewatch-backup.dump

Production operators should additionally automate encrypted backups, point-in-time recovery, retention, off-host copies, and restore drills. A backup that has not been restored successfully is not a recovery plan.

Never run docker compose down --volumes unless permanent deletion of the local database and uploaded runbook images is intended.

For a controlled deployment, run the target API image’s migrations exactly once before starting new API or worker replicas:

Terminal window
docker compose run --rm api --migrate
docker compose up --detach --no-build

For a source checkout, rebuild with docker compose up --build. For eventual published releases, set FIREWATCH_VERSION to a versioned image tag and pin an image digest in higher-assurance environments.

Before upgrading:

  1. read release notes and minimum PostgreSQL requirements;
  2. take and verify a backup;
  3. run the one-off migration command;
  4. start API and worker at matching versions;
  5. wait for readiness and exercise the test-job smoke flow;
  6. review worker retries and structured logs.

Rolling an image back does not reverse its database migration. Follow the expand/contract compatibility policy in Cloud deployment contract.

Only the web/API ports need ingress. PostgreSQL is published on loopback by the development Compose file for local tools; remove that host-port mapping on a remote server. The worker has no public port.

Before any real deployment exists, place web/API traffic behind a TLS-terminating reverse proxy, restrict database access, use a secrets manager, and configure resource limits and log retention. Forward request IDs and only trust forwarded headers from the known proxy.

Apply per-client rate limiting at that trusted ingress for authentication routes. Firewatch enforces per-account/invitation throttles and password lockout itself, but it does not trust unconfigured forwarded IP headers or impose one shared proxy-IP bucket that could lock out every user behind the web container.

API and worker can be scaled independently after migrations are separated from startup. Graceful shutdown windows should be at least 30 seconds for API and 45 seconds for worker. At-least-once delivery means a worker termination can cause a message retry; idempotent handlers make that safe.

  • /health/live answers whether the API process is alive.
  • /health/ready includes PostgreSQL and gates dependent Compose services.
  • /web-health/ready checks only the web container.

Inspect structured logs and service state:

Terminal window
docker compose ps
docker compose logs --follow --tail=200 api worker postgres

If the API is live but not ready, start with PostgreSQL health and the connection string. If jobs stay pending, verify the worker is healthy, uses the same database and provider, and can claim outbox work. Stable message IDs in worker logs are the primary correlation key for retries.

Self-hosting runs the complete public core. The private Firewatch Cloud repository adds AWS infrastructure, managed providers, billing, provisioning, private operations, and disaster recovery around these same images. It will not contain a second product implementation. See Cloud deployment contract for the exact boundary.

There is no Helm chart yet; the Helm packaging status records that deliberate placeholder.