Open-Source Wikis

/

Bitwarden Server

/

Deployment

bitwarden/server

Deployment

This page covers how Bitwarden Server gets from a git push to a running container, plus the differences between cloud and self-host.

Container images

Every deployable service has its own Dockerfile. The release pipeline (.github/workflows/publish.yml, owned by @bitwarden/dept-bre) builds and pushes images to GitHub Container Registry under ghcr.io/bitwarden/<service> for each tagged release.

Production services in cloud:

  • admin, api, billing, events, eventsprocessor, identity, notifications, scim, sso.

The README badge bar reads from a metadata branch in the same repo; the publish pipeline writes per-service image hashes there. Production hashes are public; that file is also linked from the README's "Production Container Images" details block.

Release cycle

  1. Engineering merges PRs against main continuously.
  2. The release manager (BRE) cuts a release-candidate branch rc-YYYY-MM from main.
  3. Tags YYYY.MM.x are created on the rc branch via .github/workflows/release.yml.
  4. The publish workflow builds Docker images and uploads them.
  5. Self-hosted operators run bitwarden.sh updateself to pull the new tag.

Environments

Environment Notes
Development Local devs. Loaded by dotnet run. Allows Swagger UI + UseDeveloperExceptionPage.
QA Pre-prod cloud. appsettings.QA.json per service.
Production The US (bitwarden.com) and EU (bitwarden.eu) clusters. Plus the historical bitwarden.pw domain still in BitwardenCloudDomains.
SelfHosted Triggered by setting globalSettings.SelfHosted = true (the Setup utility writes it). Disables IP rate limiting + cloud-only health checks; enables /identity path base; switches push to relay-mode.

appsettings.<env>.json files per service override the defaults in appsettings.json. Sensitive values come from environment variables (cloud: Key Vault / app-config; self-host: global.override.env).

Self-host topology

The self-host bundle is a Docker Compose stack with the following long-running containers:

  • mssql (or postgresql / mysql / mariadb / sqlite-volume) — chosen by the operator at install time.
  • web — the static Web Vault assets served by nginx.
  • attachments — Azurite-style local blob storage.
  • api, identity, admin, notifications, events, eventsprocessor, icons.
  • Optional: sso, scim, redis.
  • nginx — reverse proxy mapping /identity/*, /api/*, /admin/*, /notifications/*, /events/*, /sso/*, /scim/*, /icons/*, / (Web Vault).

Configuration is generated by util/Setup/. Updates flow through bitwarden.sh updateself which docker pulls the new tags and recreates the stack. The .bwdata/ directory on the host carries the database files, attachments, mssql data, and global.override.env.

The nginx config templates live at util/Nginx/. The MSSQL container backup script is at util/MsSql/backup-db.sh.

Cloud topology (high level)

The cloud setup is a Kubernetes-like deployment of the same containers, fronted by a CDN:

  • Each service runs as multiple replicas behind an ingress.
  • Database is Azure SQL (Hyperscale).
  • Object storage is Azure Blob Storage.
  • Cache is Azure Cache for Redis.
  • Identity signing keys live in Azure Key Vault and are rotated by ASP.NET Data Protection.
  • Push uses Azure Notification Hub plus the SignalR-backed Notifications host.
  • Audit events flow into Cosmos DB / Azure Table Storage.

The exact topology is internal Bitwarden infra; the codebase only assumes the abstractions documented here are configured.

CI/CD workflows

See how-to-contribute/tooling for the full table. The deployment-relevant ones:

  • build.yml + test.yml — gate the merge.
  • test-database.yml — guarantees migrations are valid on all four engines.
  • scan.yml — security scanning.
  • publish.yml — builds & pushes Docker images, updates the metadata branch.
  • release.yml — cuts releases.
  • ephemeral-environment.yml — spins an environment per PR for QA.
  • load-test.yml — k6 perf tests.
  • _move_edd_db_scripts.yml — DBOps shared workflow for moving migration scripts during production rollouts.

Database migrations on deploy

  • Cloud — The MsSqlMigratorUtility runs as a pre-deploy job against Azure SQL. EF migrations run for the corresponding providers in their environments.
  • Self-host — The bundled migrate.ps1 runs inside the api or setup container at startup; the cycle is: setup pulls migrations, applies them, then services come up.
  • Schema changes must be forward-compatible for at least one release so the rolling deploy doesn't break existing pods.

Scaling characteristics

  • The API is horizontally scalable; cipher CRUD is dominated by the DB which is the hard limit.
  • Notifications can be scaled but each pod holds a slice of SignalR connections; sticky routing is required.
  • Identity is mostly stateless (persisted-grant store is in IDistributedCache).
  • EventsProcessor is the only service that should run as a singleton per region (or use lease-based scheduling) so the queue isn't double-consumed.

Observability

  • Logs — Serilog → Console → Sentry / Azure App Insights (cloud).
  • Metrics — emitted via App Insights and exported to Bitwarden's internal observability stack.
  • Health/healthz and /healthz/extended on cloud-hosted services.

Disaster recovery

  • DB backups are managed by Azure SQL automatic backups in cloud; self-host operators run their own backups.
  • Object storage is geo-redundant in cloud.
  • IdentityServer signing keys are persisted to Azure Blob (cloud) so they survive pod recreation.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Deployment – Bitwarden Server wiki | Factory