> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Vaultwarden

> Deploy Vaultwarden on Control Plane using the Template Catalog. Self-hosted, Bitwarden-compatible password manager with a SQLite database on a persistent volume, served over HTTPS on the canonical endpoint. Covers registration lockdown, the optional admin panel and SMTP, and scheduled volume-snapshot backups.

## Overview

Vaultwarden is a lightweight, self-hosted password manager (AGPL-3.0) that is compatible with all official Bitwarden clients — browser extensions, desktop apps, and mobile apps. This template deploys a single stateful workload with its SQLite database on a persistent volume, served over HTTPS on the canonical `*.cpln.app` endpoint. Registration is open on a fresh install so you can onboard immediately; [lock it down](#important-notes) once your users are on board.

### Architecture

* **Vaultwarden** — A single-replica stateful workload serving the web vault, APIs, and websocket live-sync on port `80`. Its public `DOMAIN` is derived from the canonical endpoint at boot, so clients and passkeys bind to the correct URL with no manual configuration.
* **SQLite on a persistent volume** — All state (accounts, vault items, attachments, sends, and the RSA key that signs login sessions) lives in a SQLite database on the workload's volume set; there is no external database dependency.

### What Gets Created

* **Stateful Vaultwarden Workload** — A single replica serving the web vault, APIs, and websocket notifications on port `80`.
* **Volume Set** — A 10 GiB persistent volume mounted at `/data` holding the SQLite database, attachments, sends, and RSA signing keys. Scheduled snapshots protect the data, and a final snapshot is kept on delete.
* **Start-Script Secret** — An opaque secret whose boot script sets `DOMAIN` from the canonical endpoint.
* **Identity & Policy** — An identity bound to the workload with a least-privilege policy granting `reveal` access to exactly the secrets it mounts (the start script, plus your admin and SMTP secrets only when you configure them).

<Note>
  This template does not create a GVC. You must deploy it into an existing GVC.
</Note>

## Prerequisites

None for a default install — no secrets, cloud accounts, or external resources are required. The two secrets below are optional and only needed for the corresponding features.

* **Admin panel (`/admin`)** — Create, **before installing**, an [opaque secret](https://docs.controlplane.com/reference/secret#opaque) (`encoding: plain`) holding the argon2 hash of your admin token. Generate the hash with `docker run --rm -it vaultwarden/server:1.36.0 /vaultwarden hash` and store the full `$argon2...` output **as-is** — do not double the `$` signs (that is docker-compose escaping and will break login here). Set the secret's name in `admin.tokenSecretName`.
* **Authenticated SMTP** — Create a [dictionary secret](https://docs.controlplane.com/reference/secret#dictionary) with exactly two keys, `username` and `password`, for your mail server, and set its name in `smtp.authSecretName`. Only needed when your relay requires authentication.

Install the template using your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

The default `values.yaml` for this template:

```yaml theme={null}
image: vaultwarden/server:1.36.0

resources:
  cpu: 500m
  memory: 512Mi
  minCpu: 125m
  minMemory: 256Mi

volumeset:
  capacity: 10 # GiB (minimum 10) — SQLite database, attachments, sends, and RSA keys

# ─── Backup ───────────────────────────────────────────────────────────────────
# Scheduled, crash-consistent snapshots of the data volume, managed by the
# platform (no cloud account or bucket required). SQLite recovers cleanly from a
# snapshot via its WAL. A final snapshot is always taken on uninstall.
backup:
  enabled: true # periodic snapshots of the data volume
  schedule: "0 3 * * *" # cron in UTC — default daily at 03:00 (hourly is the most frequent the platform allows)
  retention: 7d # how long each snapshot is kept (e.g. 7d, 720h, 30d)

# ─── Sign-ups & Invitations ───────────────────────────────────────────────────
# Registration is OPEN by default so a fresh install is immediately usable.
# After onboarding your users, lock down: set allowed=false or restrict
# registration with domainsWhitelist.
signups:
  allowed: true # open registration on the public endpoint — turn off after onboarding
  domainsWhitelist: [] # email domains that may register even when allowed=false, e.g. [mycompany.com]
  verify: false # require email verification at registration (needs smtp.host)

invitations:
  allowed: true # existing org owners can invite users; invited emails may register

# ─── Admin Panel (/admin — disabled by default) ───────────────────────────────
# To enable, create BEFORE install an opaque secret (encoding: plain) holding
# the argon2 hash of your admin token:
#   docker run --rm -it vaultwarden/server:1.36.0 /vaultwarden hash
admin:
  tokenSecretName: "" # name of your pre-created opaque secret (e.g. my-vaultwarden-admin-token); empty = /admin disabled

# ─── SMTP (optional — invites, verification, 2FA email) ───────────────────────
smtp:
  host: "" # e.g. smtp.example.com; empty = all email features off
  port: 587
  security: starttls # options: starttls, force_tls, off
  from: "" # sender address (required when host is set)
  authSecretName: "" # pre-created dictionary secret with keys `username` and `password` (e.g. my-vaultwarden-smtp-auth); empty = unauthenticated relay

# ─── Privacy ──────────────────────────────────────────────────────────────────
icons:
  disableDownload: false # set true to stop the server fetching site favicons (outbound requests reveal stored-site domains); clients then show letter placeholders

customDomain: "" # full URL of a custom domain (e.g. https://vault.example.com); empty = canonical *.cpln.app endpoint

# ─── Access ───────────────────────────────────────────────────────────────────
publicAccess:
  enabled: true # Bitwarden clients (browser/desktop/mobile) connect via the canonical *.cpln.app HTTPS endpoint

internalAccess: # internal firewall scope — keep closed for a password vault unless in-GVC callers need the API
  type: none # options: none, same-gvc, same-org, workload-list
  workloads: [] # used with workload-list
```

### Vaultwarden Instance

* `image` — The Vaultwarden container image.
* `resources` — CPU and memory for the Vaultwarden container.
* `volumeset.capacity` — Volume size in GiB (minimum 10) for the SQLite database, attachments, sends, and RSA keys.

### Backup

Scheduled, crash-consistent snapshots of the data volume, managed by the platform — no cloud account or bucket is required. See [Backing Up](#backing-up) for how snapshots and restores work.

* `backup.enabled` — Take periodic snapshots of the data volume (default `true`).
* `backup.schedule` — Cron expression in UTC (default `0 3 * * *`, daily at 03:00). The platform does not accept schedules more frequent than hourly.
* `backup.retention` — How long each snapshot is kept, e.g. `7d`, `720h`, `30d`.

### Sign-ups & Invitations

* `signups.allowed` — Open registration on the public endpoint (default `true` so a fresh install is usable). **Turn this off after onboarding** — see [Important Notes](#important-notes).
* `signups.domainsWhitelist` — Email domains that may register even when `signups.allowed` is `false`, e.g. `[mycompany.com]`. This also gates who can be invited.
* `signups.verify` — Require email verification at registration. Requires `smtp.host`.
* `invitations.allowed` — Allow existing organization owners to invite users; invited emails may then register.

### Admin Panel

* `admin.tokenSecretName` — Name of a pre-created opaque secret holding the argon2 hash of your admin token (see [Prerequisites](#prerequisites)). Empty (default) leaves the `/admin` panel disabled.

### SMTP

Configure an outbound mail server to send invitations, verification, and 2FA emails.

* `smtp.host` — Mail server hostname. Empty (default) disables all email features.
* `smtp.port` — SMTP port (default `587`).
* `smtp.security` — `starttls`, `force_tls`, or `off`.
* `smtp.from` — Sender address; required when `smtp.host` is set.
* `smtp.authSecretName` — Name of a pre-created dictionary secret with `username` and `password` keys (see [Prerequisites](#prerequisites)). Empty means an unauthenticated relay.

### Privacy

* `icons.disableDownload` — Set to `true` to stop the server fetching site favicons. Those outbound requests reveal which sites users store; with downloads off, clients show letter placeholders instead.

### Domain

* `customDomain` — Full URL of a custom domain, e.g. `https://vault.example.com`. Empty (default) uses the canonical `*.cpln.app` endpoint. See the domain caution in [Important Notes](#important-notes) before changing this on a live vault.

### Access

* `publicAccess.enabled` — Serve Vaultwarden over HTTPS on the canonical `*.cpln.app` endpoint (default). Bitwarden clients require HTTPS, so keep this enabled unless you front the vault another way. When disabled, external requests are blocked at the edge and only in-GVC callers reach it per `internalAccess`.
* `internalAccess.type` — Internal firewall scope of the Vaultwarden workload. Keep this closed (`none`, the default) for a password vault unless in-GVC callers need the API:

| Type            | Description                                                            |
| --------------- | ---------------------------------------------------------------------- |
| `none`          | No internal access (default).                                          |
| `same-gvc`      | Allow access from all workloads in the same GVC.                       |
| `same-org`      | Allow access from all workloads in the same organization.              |
| `workload-list` | Allow access only from workloads listed in `internalAccess.workloads`. |

## Connecting

| What                   | Value                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------- |
| Web vault (public)     | `https://<canonical>.cpln.app` — `status.canonicalEndpoint` of `{release}-vaultwarden` |
| Bitwarden apps         | Set the self-hosted server URL to the same `https://<canonical>.cpln.app`              |
| Admin panel (optional) | `https://<canonical>.cpln.app/admin` — log in with your plaintext admin token          |
| Internal (if opened)   | `http://{release}-vaultwarden.{gvc}.cpln.local:80`                                     |
| Login                  | The account you register in the web vault (registration is open by default)            |

### Clients and Live Sync

Point any official Bitwarden client at the canonical endpoint via its self-hosted server URL, then log in with an account registered in the web vault. Live sync across open clients rides a websocket on the same HTTPS endpoint and works through the canonical endpoint with no extra configuration.

## Backing Up

Backups are **scheduled volume snapshots** managed by the platform — no cloud account or bucket is required. On the configured cron schedule the platform takes a crash-consistent snapshot of the data volume; SQLite recovers cleanly from one via its write-ahead log. Snapshots are pruned automatically after `backup.retention`, and a final snapshot is always taken when the release is uninstalled.

* **Default schedule** — Daily at 03:00 UTC (`backup.schedule: "0 3 * * *"`), kept for 7 days (`backup.retention: 7d`).
* **Minimum interval** — The platform does not accept schedules more frequent than hourly.

List the snapshots for the release's volume set:

```bash theme={null}
cpln volumeset snapshot get {release}-vaultwarden-data --gvc {gvc} -o yaml
```

<Note>
  Snapshots live in the platform storage layer alongside the volume, not off-site. They protect against data corruption and accidental changes, but losing the whole GVC would lose them too. For off-cluster durability, restore into a fresh install elsewhere or export vault data separately.
</Note>

## Restoring a Backup

Restore is **in-place** on the release's own volume set: the platform provisions a fresh volume from the chosen snapshot and swaps it in, then the workload restarts to remount it (about 90 seconds).

<Warning>
  Restoring reverts the volume to the exact snapshot state — any vault changes made after that snapshot are lost — and restarts the single-replica workload, so the vault is briefly unavailable during the swap.
</Warning>

<Steps>
  <Step title="Find the snapshot to restore">
    List snapshots and note the `name`, `location`, and `volumeIndex` of the one you want:

    ```bash theme={null}
    cpln volumeset snapshot get {release}-vaultwarden-data --gvc {gvc} -o yaml
    ```
  </Step>

  <Step title="Restore it in place">
    Provision a fresh volume from the snapshot and swap it in. The workload restarts to remount it:

    ```bash theme={null}
    cpln volumeset snapshot restore {release}-vaultwarden-data \
      --snapshot-name <snapshot-name> \
      --location <location> \
      --volume-index 0 \
      --gvc {gvc}
    ```
  </Step>

  <Step title="Verify">
    Once the workload is ready again, log in to the web vault and confirm your items are present and decrypt correctly.
  </Step>
</Steps>

## Important Notes

* **Lock down registration after onboarding.** Sign-ups are open by default so the install works immediately — which means anyone who can reach the public endpoint can register. Once your users are on board, set `signups.allowed=false`, or restrict with `signups.domainsWhitelist` (whitelisted domains can still register with sign-ups off).
* **Vault contents are end-to-end encrypted** with each user's master password, and master passwords are unrecoverable. Without SMTP configured, password-hint emails are off, so a forgotten master password means a lost vault.
* **Admin panel settings override Helm values.** Saving settings in `/admin` writes `/data/config.json`, which silently wins over environment variables from then on. If a values change does not take effect, delete that file to return control to values.
* **Store the admin token hash raw.** Put the argon2 output (starting with `$argon2`) into the secret as-is — do not double the `$` signs; that escaping is for docker-compose only and breaks login here.
* **Do not change the domain casually.** Passkey/WebAuthn logins are bound to the exact URL; switching between the canonical endpoint and a custom domain breaks them until re-registered.
* **Single instance only** — upstream does not support running multiple instances, so the workload is pinned to 1 replica with no `replicas` knob. A restart is a short full outage (about a minute); Bitwarden apps keep a local offline copy of the vault, so users can still read their vault during it.
* **Backups are in-platform, not off-site** — scheduled snapshots and the final snapshot on uninstall live in the platform storage layer next to the volume; losing the whole GVC would lose them too. See [Backing Up](#backing-up).

## External References

<CardGroup cols={2}>
  <Card title="Vaultwarden on GitHub" icon="github" href="https://github.com/dani-garcia/vaultwarden">
    Upstream source repository
  </Card>

  <Card title="Vaultwarden Wiki" icon="book" href="https://github.com/dani-garcia/vaultwarden/wiki">
    Official documentation wiki
  </Card>

  <Card title="Enabling the Admin Page" icon="gear" href="https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page">
    How to configure and secure the /admin panel
  </Card>

  <Card title="SMTP Configuration" icon="envelope" href="https://github.com/dani-garcia/vaultwarden/wiki/SMTP-configuration">
    Configure outbound email for invites and verification
  </Card>

  <Card title="Hardening Guide" icon="shield" href="https://github.com/dani-garcia/vaultwarden/wiki/Hardening-Guide">
    Upstream recommendations for locking down an instance
  </Card>

  <Card title="Vaultwarden Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/vaultwarden">
    View the source files, default values, and chart definition
  </Card>
</CardGroup>
