Skip to content

Self hosted Bitwarden password manager with Docker

Introduction

If you need a self hosted password manager then Bitwarden is a good choice. In this post I will show how to install Bitwarden with Let's Encrypt certificate using DNS challenge with Cloudflare (by default it is HTTP challenge).

OS used: Debian 12
Software used: Docker 27.4.0, Bitwarden 2024.12.1

Source

Prerequisites

Create new virtual machine for example using this tutorial Fast initialization of Debian VM using Ansible.

Bitwarden hardware requirements

I suggest following VM resources:

  • Disk size at least 16GB
  • RAM at least 2GB
  • CPU at least 2 cores

Get Let's Encrypt certificate

Bitwarden works only in HTTPS and needs valid certificate. You can obtain such certificate using instruction from post Obtaining Let's Encrypt certificate using Cloudflare.

After you followed the instruction Let's Encrypt certificate will be in folder /etc/letsencrypt/live/<your_domain_name>/.

Bitwarden installation

Install Docker

Bitwarden's official installation relies on Docker images, which is why you need to install Docker in your virtual machine. To install Docker run following command (taken from the Official Docker Docs how to Install Docker Engine on Debian):

$ sudo apt-get update && \
  sudo apt-get install ca-certificates curl && \
  sudo install -m 0755 -d /etc/apt/keyrings && \
  sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
  sudo chmod a+r /etc/apt/keyrings/docker.asc && \
  echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
  sudo apt-get update && \
  sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Create Bitwarden user

As recommended in official installation guide create Bitwarden user in order to isolate this applications from other processes and add this user to docker group.
Set strong password for this user.

$ sudo adduser bitwarden && \
  sudo usermod -aG docker bitwarden

Create Bitwarden directory

Create directory for Bitwarden:

$ folderpath=/opt/bitwarden && \
  sudo mkdir -m 700 ${folderpath} && \
  sudo chown bitwarden:bitwarden ${folderpath}

-m - set folder permissions

Install Bitwarden

Installation of Bitwarden, as stated in official documentation, should be performed as bitwarden user.

IMPORTANT Change user to bitwarden:

$ su - bitwarden

Enter Bitwarden folder:

$ cd /opt/bitwarden

Download Bitwarden installation script:

$ curl -Lso bitwarden.sh "https://func.bitwarden.com/api/dl/?app=self-host&platform=linux" && chmod 700 bitwarden.sh

Run the script:

./bitwarden.sh install

Answer the prompts in the installer:

  • Enter the domain name for your Bitwarden instance:
    • Enter domain name of your Bitwarden host.
  • Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n):
    • n - no, because it only uses HTTP challenge.
  • Enter the database name for your Bitwarden instance (ex. vault):
    • For this installation we will use name vault.
  • Enter your installation id:
    • You can retrive this id using a valid email at https://bitwarden.com/host. More information can be found here in official Bitwarden FAQ
  • Enter your installation key:
    • You can retrive this key using a valid email at https://bitwarden.com/host. More information can be found here in official Bitwarden FAQ
  • Enter your region (US/EU):
    • Enter your location. Only applicable if you're connecting a self-hosted account or organization to Bitwarden paid subscription.
  • Do you have a SSL certificate to use? (y/n):
    • y - yes, we will use certificate that was generated using certbot.
  • Is this a trusted SSL certificate (requires ca.crt, see docs)? (y/N):
    • n - no, certbot don't generate ca.crt.
  • Do you want to generate a self-signed SSL certificate? (y/n):
    • n - no, because you will use Let's Encrypt certificate.

Configure Bitwarden SSL

IMPORTANT Perform following actions as bitwarden user.

Create folder in which Bitwarden searches for certificate (if it's not already created):

$ mkdir -p /opt/bitwarden/bwdata/ssl/<your_domain_name>

-p - make directory structure

Docker containers can't read symlinks which certbot uses in certificate folder (/etc/letsencrypt/live) that is why we need to copy it to folder used by Bitwarden. To achieve this we will use the fact that certificate renewal command is run 2 times per day by certbot.timer (which invokes certbot.service) and certbot has option to run command after successful certificate renewal. Both timer and service are located in /usr/lib/systemd/system/.

Create script file in /etc/letsencrypt/renewal-hooks/deploy/ folder that will be run after a successful certificate renewal:

$ sudo vim /etc/letsencrypt/renewal-hooks/deploy/copy-certificate
/etc/letsencrypt/renewal-hooks/deploy/copy-certificate
cp /etc/letsencrypt/live/<your_domain_name>/fullchain.pem /opt/bitwarden/bwdata/ssl/<your_domain_name>/certificate.crt
cp /etc/letsencrypt/live/<your_domain_name>/privkey.pem /opt/bitwarden/bwdata/ssl/<your_domain_name>/private.key

Make file executable:

$ sudo chmod 744 /etc/letsencrypt/renewal-hooks/deploy/copy-certificate

Force certificate renewal:

$ sudo certbot renew --force-renewal

Check if certificate and certificate private key was copied to Bitwarden folder:

$ ls -la /opt/bitwarden/bwdata/ssl/<your_domain_name>/

Configure Bitwarden

IMPORTANT Perform following actions as bitwarden user.

Configure following values in ./bwdata/env/global.override.env file for proper email sending by Bitwarden. The following example will use values for Gmail.

$ vim /opt/bitwarden/bwdata/env/global.override.env
/opt/bitwarden/bwdata/env/global.override.env
globalSettings__mail__smtp__host=smtp.gmail.com
globalSettings__mail__smtp__port=587
globalSettings__mail__smtp__ssl=true
globalSettings__mail__smtp__username=[email protected]
globalSettings__mail__smtp__password=my-gmail-password
(...)
adminSettings__admins=[email protected]

adminSettings__admins - this is email address that has access to Bitwarden System Administrator Portal. You access System Administrator Portal through address https://<your_domain_name>/admin.

Note

When using Gmail with self-hosted services, it’s best to enable two-factor authentication on your Gmail account and create an application password for each service. Then, use this application password as your Gmail password when configuring the service.

Important note: When you change your Gmail account password, all application passwords will be deleted and you need to recreate them manually.

Run Bitwarden

IMPORTANT Perform following actions as bitwarden user.

After configuring Bitwarden you can start it. It will pull Docker images needed to run Bitwarden and run them as containers.

$ /opt/bitwarden/bitwarden.sh start

After the command completes, verify that the Docker containers are running:

$ docker ps

Firewall configuration

Currently on Linux, Docker manage firewall rules using iptables and dynamically creates rules that are needs at the system start. Debian by default uses nftables instead of iptables, but have compatibility layer enabled, so iptablescommands are still valid and Docker works correctly.

Docker creates its own firewall rules (such as forwarding traffic between containers), so it's better to disable your custom firewall rules in the system, and instead enable firewall for the whole virtual machine in Proxmox. For Bitwarden you would only need to open port 443 on the firewall.

Bitwarden web interface

Now you can access your Bitwarden instance at the following address: https://<your_domain_name>.

Register new account, log in and you have Bitwarden ready to use.

Update Bitwarden

IMPORTANT Change user to bitwarden:

$ su - bitwarden

Run following commands to update all containers and the database that Bitwarden uses:

$ /opt/bitwarden/bitwarden.sh updateself
$ /opt/bitwarden/bitwarden.sh update

Old Docker images can use some space so you can check it by using command:

$ docker images

To remove old images that are no longer used you can use command:

$ docker image prune

Update Docker using unattended-upgrades

Install unattended-upgrades package:

$ sudo apt install unattended-upgrades

You can enable Docker automatic upgrades by editing file /etc/apt/apt.conf.d/50unattended-upgrades:

$ sudo vim /etc/apt/apt.conf.d/50unattended-upgrades
/etc/apt/apt.conf.d/50unattended-upgrades
(...)
Unattended-Upgrade::Origins-Pattern {
        "origin=Debian,codename=${distro_codename}-updates";
        "origin=Debian,codename=${distro_codename},label=Debian";
        "origin=Debian,codename=${distro_codename},label=Debian-Security";
        "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";

        // --- Add these lines ---
        // Docker repository
        "origin=Docker,suite=${distro_codename},component=stable";
        // -----------------------

        (...)
};
(...)

Test if config is correct by running command:

$ sudo unattended-upgrades --dry-run --debug

If you see the following line in the command output, then there's likely a problem with your configuration (ensure you didn't make any typos):

Marking not allowed <apt_pkg.PackageFile object: filename:'/var/lib/apt/lists/download.docker.com_linux_debian_dists_bookworm_stable_binary-amd64_Packages'  a=bookworm,c=stable,v=,o=Docker,l=Docker CE arch='amd64' site='download.docker.com' IndexType='Debian Package Index' Size=230910 ID:12>