# Self-hosting Firewatch > Install, configure, secure, back up, and operate Firewatch with Docker Compose. This is the machine-readable version of this Firewatch documentation page. ## Production readiness Firewatch includes the complete alert, schedule, escalation, incident, and notification paths needed for self-hosted paging. Before putting the deployment in a critical path, verify its notification providers, escalation behavior, backups, upgrades, and recovery procedures against your availability, security, and compliance requirements. ## Quick start Prerequisites are Docker Engine and Docker Compose v2. Clone the public repository, then run: ```bash 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: ```bash 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. ## Configuration and secrets 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. ### Database UI The default self-hosted stack does not include a database administration UI. For temporary troubleshooting, start the optional Adminer override: ```bash 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: ```bash 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 to set the organization time zone, confirm the public URL, and review optional delivery providers. 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 authenticated organization automation and is not a self-hosting bootstrap mechanism. `FIREWATCH_SELF_REGISTRATION_ENABLED` is a separate kill switch that takes precedence over the selected mode. It defaults to `true` so a new installation can create its first administrator. Set it to `false` after bootstrap when you want to guarantee invitation-only access. Turning it off does not affect sign-in or acceptance of administrator invitations. `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. ### Phone delivery with Twilio 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. ### Slack app 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 `/api/v1/organization/integrations/slack/callback` as the OAuth redirect URL. Enable **Interactivity & Shortcuts** and register `/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. ### Alert templates 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. ### Signed outbound webhooks 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](/docs/integrations/outbound-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. ### Runbook image storage 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 an operator 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. ## Data and backups 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): ```bash 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. ## Migrations and upgrades For a controlled deployment, run the target API image's migrations exactly once before starting new API or worker replicas: ```bash 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 [Firewatch architecture](/docs/concepts/architecture/). ## Network and process boundaries 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 and troubleshooting - `/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: ```bash 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. ## What belongs elsewhere Self-hosting runs the complete public Firewatch product. Managed-service infrastructure and operations do not change the portable product behavior described in these docs. There is no Helm chart yet; the [Helm packaging status](https://github.com/onfirewatch/firewatch/blob/main/deploy/helm/README.md) records that deliberate placeholder.