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
- Engineering merges PRs against
maincontinuously. - The release manager (BRE) cuts a release-candidate branch
rc-YYYY-MMfrommain. - Tags
YYYY.MM.xare created on the rc branch via.github/workflows/release.yml. - The publish workflow builds Docker images and uploads them.
- Self-hosted operators run
bitwarden.sh updateselfto 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(orpostgresql/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
MsSqlMigratorUtilityruns as a pre-deploy job against Azure SQL. EF migrations run for the corresponding providers in their environments. - Self-host — The bundled
migrate.ps1runs inside theapiorsetupcontainer at startup; the cycle is:setuppulls 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 —
/healthzand/healthz/extendedon 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.