A friend mentioned the BitWarden password manager to me yesterday and I had to confess that I'd never heard of it. I started researching it and was impressed by what I found: it's free, open-source, feature-packed, fully cross-platform (with Windows/Linux/MacOS desktop clients, Android/iOS mobile apps, and browser extensions for Chrome/Firefox/Opera/Safari/Edge/etc), and even offers a self-hosted option.
I wanted to try out the self-hosted setup, and I discovered that the official distribution works beautifully on an n1-standard-1
1-vCPU Google Compute Engine instance - but that would cost me an estimated $25/mo to run after my free Google Cloud Platform trial runs out. And I can't really scale that instance down further because the embedded database won't start with less than 2GB of RAM.
I then came across this comment on Reddit which discussed in somewhat-vague terms the steps required to get BitWarden to run on the free e2-micro
instance, and also introduced me to the community-built vaultwarden project which is specifically designed to run a BW-compatible server on resource-constrained hardware. So here are the steps I wound up taking to get this up and running.
bitwarden_rs -> vaultwarden
When I originally wrote this post back in September 2018, the containerized BitWarden solution was called bitwarden_rs
. The project has since been renamed to vaultwarden
, and I've since moved to the hosted version of BitWarden. I have attempted to update this article to account for the change but have not personally tested this lately. Good luck, dear reader!
Spin up a VM
Easier said than done, but head over to https://console.cloud.google.com/ and fumble through:
- Creating a new project (or just add an instance to an existing one).
- Creating a new Compute Engine instance, selecting
e2-micro
for the Machine Type and ticking the Allow HTTPS traffic box. - (Optional) Editing the instance to add an ssh-key for easier remote access.
Configure Dynamic DNS
Because we're cheap and don't want to pay for a static IP.
- Log in to the Google Domain admin portal and create a new Dynamic DNS record. This will provide a username and password specific for that record.
- Log in to the GCE instance and run
sudo apt-get update
followed bysudo apt-get install ddclient
. Part of the install process prompts you to configure things... just accept the defaults and move on. - Edit the
ddclient
config file to look like this, substituting the username, password, and FDQN from Google Domains:
1$ sudo vi /etc/ddclient.conf
2 # Configuration file for ddclient generated by debconf
3 #
4 # /etc/ddclient.conf
5
6 protocol=googledomains,
7 ssl=yes,
8 syslog=yes,
9 use=web,
10 server=domains.google.com,
11 login='[USERNAME]',
12 password='[PASSWORD]',
13 [FQDN]
sudo vi /etc/default/ddclient
and make sure thatrun_daemon="true"
:
1# Configuration for ddclient scripts
2# generated from debconf on Sat Sep 8 21:58:02 UTC 2018
3#
4# /etc/default/ddclient
5
6# Set to "true" if ddclient should be run every time DHCP client ('dhclient'
7# from package isc-dhcp-client) updates the systems IP address.
8run_dhclient="false"
9
10# Set to "true" if ddclient should be run every time a new ppp connection is
11# established. This might be useful, if you are using dial-on-demand.
12run_ipup="false"
13
14# Set to "true" if ddclient should run in daemon mode
15# If this is changed to true, run_ipup and run_dhclient must be set to false.
16run_daemon="true"
17
18# Set the time interval between the updates of the dynamic DNS name in seconds.
19# This option only takes effect if the ddclient runs in daemon mode.
20daemon_interval="300"
- Restart the
ddclient
service - twice for good measure (daemon mode only gets activated on the second go because reasons):
- After a few moments, refresh the Google Domains page to verify that your instance's external IP address is showing up on the new DDNS record.
Install Docker
Steps taken from here.
- Update
apt
package index:
1$ sudo apt-get update
- Install package management prereqs:
1$ sudo apt-get install \
2 apt-transport-https \
3 ca-certificates \
4 curl \
5 gnupg2 \
6 software-properties-common
- Add Docker GPG key:
1$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
- Add the Docker repo:
1$ sudo add-apt-repository \
2 "deb [arch=amd64] https://download.docker.com/linux/debian \
3 $(lsb_release -cs) \
4 stable"
- Update apt index again:
1$ sudo apt-get update
- Install Docker:
1$ sudo apt-get install docker-ce
Install Certbot and generate SSL cert
Steps taken from here.
- Install Certbot:
1$ sudo apt-get install certbot
- Generate certificate:
1$ sudo certbot certonly --standalone -d [FQDN]
- Create a directory to store the new certificates and copy them there:
1$ sudo mkdir -p /ssl/keys/
2$ sudo cp -p /etc/letsencrypt/live/[FQDN]/fullchain.pem /ssl/keys/
3$ sudo cp -p /etc/letsencrypt/live/[FQDN]/privkey.pem /ssl/keys/
Set up vaultwarden
Using the container image available here.
- Let's just get it up and running first:
1$ sudo docker run -d --name vaultwarden \
2 -e ROCKET_TLS={certs='"/ssl/fullchain.pem", key="/ssl/privkey.pem"}' \
3 -e ROCKET_PORT='8000' \
4 -v /ssl/keys/:/ssl/ \
5 -v /bw-data/:/data/ \
6 -v /icon_cache/ \
7 -p 0.0.0.0:443:8000 \
8 vaultwarden/server:latest
- At this point you should be able to point your web browser at
https://[FQDN]
and see the BitWarden login screen. Click on the Create button and set up a new account. Log in, look around, add some passwords, etc. Everything should basically work just fine. - Unless you want to host passwords for all of the Internet you'll probably want to disable signups at some point by adding the
env
optionSIGNUPS_ALLOWED=false
. And you'll need to setDOMAIN=https://[FQDN]
if you want to use U2F authentication:
1$ sudo docker stop vaultwarden
2$ sudo docker rm vaultwarden
3$ sudo docker run -d --name vaultwarden \
4 -e ROCKET_TLS={certs='"/ssl/fullchain.pem",key="/ssl/privkey.pem"'} \
5 -e ROCKET_PORT='8000' \
6 -e SIGNUPS_ALLOWED=false \
7 -e DOMAIN=https://[FQDN] \
8 -v /ssl/keys/:/ssl/ \
9 -v /bw-data/:/data/ \
10 -v /icon_cache/ \
11 -p 0.0.0.0:443:8000 \
12 vaultwarden/server:latest
Install vaultwarden as a service
So we don't have to keep manually firing this thing off.
- Create a script to stop, remove, update, and (re)start the
vaultwarden
container:
1$ sudo vi /usr/local/bin/start-vaultwarden.sh
2 #!/bin/bash
3
4 docker stop vaultwarden
5 docker rm vaultwarden
6 docker pull vaultwarden/server
7
8 docker run -d --name vaultwarden \
9 -e ROCKET_TLS={certs='"/ssl/fullchain.pem",key="/ssl/privkey.pem"'} \
10 -e ROCKET_PORT='8000' \
11 -e SIGNUPS_ALLOWED=false \
12 -e DOMAIN=https://[FQDN] \
13 -v /ssl/keys/:/ssl/ \
14 -v /bw-data/:/data/ \
15 -v /icon_cache/ \
16 -p 0.0.0.0:443:8000 \
17 vaultwarden/server:latest
18$ sudo chmod 744 /usr/local/bin/start-vaultwarden.sh
- And add it as a
systemd
service:
1$ sudo vi /etc/systemd/system/vaultwarden.service
2 [Unit]
3 Description=BitWarden container
4 Requires=docker.service
5 After=docker.service
6
7 [Service]
8 Restart=always
9 ExecStart=/usr/local/bin/vaultwarden-start.sh
10 ExecStop=/usr/bin/docker stop vaultwarden
11
12 [Install]
13 WantedBy=default.target
14$ sudo chmod 644 /etc/systemd/system/vaultwarden.service
- Try it out:
1$ sudo systemctl start vaultwarden
2$ sudo systemctl status vaultwarden
3 ● bitwarden.service - BitWarden container
4 Loaded: loaded (/etc/systemd/system/vaultwarden.service; enabled; vendor preset: enabled)
5 Active: deactivating (stop) since Sun 2018-09-09 03:43:20 UTC; 1s ago
6 Process: 13104 ExecStart=/usr/local/bin/bitwarden-start.sh (code=exited, status=0/SUCCESS)
7 Main PID: 13104 (code=exited, status=0/SUCCESS); Control PID: 13229 (docker)
8 Tasks: 5 (limit: 4915)
9 Memory: 9.7M
10 CPU: 375ms
11 CGroup: /system.slice/vaultwarden.service
12 └─control
13 └─13229 /usr/bin/docker stop vaultwarden
14
15 Sep 09 03:43:20 vaultwarden vaultwarden-start.sh[13104]: Status: Image is up to date for vaultwarden/server:latest
16 Sep 09 03:43:20 vaultwarden vaultwarden-start.sh[13104]: ace64ca5294eee7e21be764ea1af9e328e944658b4335ce8721b99a33061d645
Conclusion
If all went according to plan, you've now got a highly-secure open-source full-featured cross-platform password manager running on an Always Free Google Compute Engine instance resolved by Google Domains dynamic DNS. Very slick!