What is Vaultwarden?
In the realm of digital security, managing passwords securely is paramount. Enter Vaultwarden is a self-hosted password manager designed for individuals and organizations seeking robust encryption and full control over their sensitive data. Unlike cloud-based alternatives, this self-hosted password manager empowers users to safeguard passwords and confidential information on their own servers, ensuring maximum security and privacy.
Installing Vaultwarden with Docker
Setup overview
In this article, I will offer a detailed guide, taking you step by step through the process of elevating the configurations to their optimal level. This involves configuring all the recommended security enhancements, such as setting up Vaultwarden self-hosted password manager to operate under a non-root user and following the most robust security practices.
Create a new user
To enhance security, begin by creating a new, unprivileged Linux user specifically for Vaultwarden. This mitigates risks associated with running applications as root.
sudo adduser vaultwarden
Install docker and docker compose
For docker rootless installation script to work correctly, we have to ssh to the user.
Now, we can execute the automated docker rootless script setup as the newel y created vaultwarden user.
You can get the automated script from my previous article:
As we will be using docker-compose to deploy the vaultwarden instance, we have to install docker-compose package as well using:
pip3 install docker-compose
docker-compose Vaultwarden setup
For better management, we have to create a new docker-compose.yml file instead of starting Vaultwarden from the cli.
Fix the data dir permissions
Executing Vaultwarden as root within the container is straightforward and doesn't demand any special configurations, given that root possesses the authority to write to any directory.
Running as non-root user is another story. The data directory where all the data will be written should be mounted to the host system to be able to backup our data. This can be done using:
volumes:
- /home/vaultwarden/data:/data
The directory /home/vaultwarden/data on our host system will be linked to the /data directory within the container through mounting. However, a concern arises: Docker mounts this as root, which results in Vaultwarden encountering failure. This failure occurs because Vaultwarden doesn't operate as root, leading to a "permission denied" error when attempting to write to the /data directory within the container.
To address this problem, we can use another lightweight Linux image. This service will operate with sudo privileges exclusively during system startup, enabling the assignment of appropriate permissions to the /data directory.
---
version: "3.7"
services:
change-data-vol-owner:
image: alpine
user: root
group_add:
- 1007
volumes:
- /home/vaultwarden/data:/data
command: ["sh", "-c", "mkdir -p /data/logs && chown -R 1007:1007 /data"]
This action will assign the ownership of /data to the user and group with the IDs 1007:1007.
And finally we can let Vaultwarden depend on the change-data-vol-owner service using:
depends_on:
change-data-vol-owner:
condition: service_completed_successfully
Create the user inside the container
As we will be running vaultwarden process as 1007:1007, we will not have a user assigned to this PUID/PGID by default inside the container. To do so , we have to build vaultwarden based on the official image and create this user.
This can be done by uing a Dockerfile
FROM vaultwarden/server:1.31.0
RUN echo "vaultwarden:x:1007:1007::/home:/bin/sh" >> /etc/passwd \
&& echo "vaultwarden:x:1007:" >> /etc/group
Deploy Vaultwarden
A new service for Vaultwarden can be created as follows:
---
version: "3.7"
services:
change-data-vol-owner:
image: alpine
user: root
group_add:
- 1007
volumes:
- /home/vaultwarden/data:/data
command: ["sh", "-c", "mkdir -p /data/logs && chown -R 1007:1007 /data"]
vaultwarden:
build:
context: .
dockerfile: Dockerfile
container_name: vaultwarden
user: "1007:1007"
environment:
- TZ=Europe/Brussels
- ROCKET_ENV=prod
- ROCKET_PORT=8080
- DATA_FOLDER=/data
volumes:
- /home/vaultwarden/data:/data
ports:
- "127.0.0.1:8081:8080"
depends_on:
change-data-vol-owner:
condition: service_completed_successfully
volumes:
data:
The environment variables:
- TZ is the Time Zone. You can get it by running cat /etc/timezone.
- DATA_FOLDER define the data directory inside the container.
- ROCKET_PORT is the port used by Vaultwarden within the container. It's essential to choose a port number higher than 1024 for this purpose. This ensures that Vaultwarden, operating as a non-root user, can successfully bind to the chosen port.
As for the ports section:
ports:
- "127.0.0.1:8081:8080"
This will make sure that Vaultwarden will be available at 127.0.0.1:8081 in the host system.
Furthermore, it's essential to consistently obtain the latest tag from the official Docker website. In my situation, I'm utilizing the 1.29.1 tag.
Vaultwarden hardening and optimization
Read only file system
Implement a read-only file system to prevent unauthorized modifications.
read_only: true
Limiting the memory
Set memory limits to optimize resource management and prevent over-allocation. .I've set it to 500 Mb by default using:
deploy:
resources:
limits:
memory: 500M
Reduce the write operations
Minimize write operations to prolong hardware lifespan, especially on devices like Raspberry Pi. This strategy aims to prolong the SD card's lifespan. To achieve this, I've taken the following steps:
- I've mounted several temporary disks as tmpfs file systems.
- I've disabled Docker logging using the following method:
tmpfs:
- /var/run
- /var/log:mode=1775
- /var/tmp:mode=1777
- /tmp
logging:
driver: none
Enable admin page
To enable admin page https://yourdomain.com/admin , we have to generate an admin token first using:
echo -n "MySecretPassword" | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1 | sed 's#\$#\$\$#g'
With MySecretPassword as password (make sure to use a very strong password). This will generate a token similar to:
$$argon2id$$v=19$$m=19456,t=2,p=1$$Y1NtOFduRmE3UEhDY3UwTUR4QjBZSURoMlZRZFBSSHpjWTVrRVZ3Slpqdz0$$xLFtcvI6z1hnd7i3DlmEaP8l83BYBeEfm9RqdGHk0aA
We can generate this using docker as well but you need the Vaultwarden container to be running first:
docker exec -it vaultwarden /vaultwarden hash
Then finally we can use this value in the docker compose file:
- ADMIN_TOKEN=$$argon2id$$v=19$$m=19456,t=2,p=1$$Y1NtOFduRmE3UEhDY3UwTUR4QjBZSURoMlZRZFBSSHpjWTVrRVZ3Slpqdz0$$xLFtcvI6z1hnd7i3DlmEaP8l83BYBeEfm9RqdGHk0aA
Disable signups
After creating the initial account, we can disabled both signups and invitations. Given that I am the only user of this instance, this action effectively prevents unauthorized individuals from creating accounts on my server.
This can be done with the below:
- SIGNUPS_ALLOWED=false
- SIGNUPS_VERIFY=true
- INVITATIONS_ALLOWED=false
- SHOW_PASSWORD_HINT=false
Setup 2fa
Upon successfully logging in, it's strongly advised to promptly activate two-factor authentication (2FA) to safeguard your account comprehensively. This can be configured at https://yourdomain.com/#/settings/security/two-factor
Set login rate limit
By default, Vaultwarden has brute-force attacks prevention's but we can modify the default values as well if needed:
- LOGIN_RATELIMIT_MAX_BURST=10
- LOGIN_RATELIMIT_SECONDS=60
- ADMIN_RATELIMIT_MAX_BURST=10
- ADMIN_RATELIMIT_SECONDS=60
- LOGIN_RATELIMIT_MAX_BURST denotes the upper limit of requests permissible in a burst during login or two-factor authentication attempts, while still adhering to the average set in LOGIN_RATELIMIT_SECONDS.
- LOGIN_RATELIMIT_SECONDS indicates the mean duration, in seconds, between consecutive login requests originating from the same IP address. Once this threshold is reached, Vaultwarden initiates rate limiting for login attempts.
- ADMIN_RATELIMIT_MAX_BURST same as LOGIN_RATELIMIT_MAX_BURST but for the admin page only.
- ADMIN_RATELIMIT_SECONDS same as LOGIN_RATELIMIT_SECONDS but for admin page only.
Configure logs
Maintaining logs is a recommended practice, as they prove invaluable for troubleshooting, monitoring server health, and responding to critical failures and security threats.
We can add the following:
- EXTENDED_LOGGING=true
- LOG_LEVEL=info
- LOG_FILE=/data/logs/access.log
Putting everything together
A final docker-compose.yml should look like:
---
version: "3.7"
services:
change-data-vol-owner:
image: alpine
user: root
group_add:
- 1007
volumes:
- /home/vaultwarden/data:/data
command: ["sh", "-c", "mkdir -p /data/logs && chown -R 1007:1007 /data"]
vaultwarden:
build:
context: .
dockerfile: Dockerfile
container_name: vaultwarden
user: "1007:1007"
read_only: true
environment:
- ADMIN_TOKEN=$$argon2id$$v=19$$m=19456,t=2,p=1$$Y1NtOFduRmE3UEhDY3UwTUR4QjBZSURoMlZRZFBSSHpjWTVrRVZ3Slpqdz0$$xLFtcvI6z1hnd7i3DlmEaP8l83BYBeEfm9RqdGHk0aA
# - SIGNUPS_ALLOWED=false
# - SIGNUPS_VERIFY=true
# - INVITATIONS_ALLOWED=false
- SHOW_PASSWORD_HINT=false
- LOG_LEVEL=info
- EXTENDED_LOGGING=true
- LOG_FILE=/data/logs/access.log
- TZ=Europe/Brussels
- ROCKET_ENV=prod
- ROCKET_PORT=8080
- DATA_FOLDER=/data
- LOGIN_RATELIMIT_MAX_BURST=10
- LOGIN_RATELIMIT_SECONDS=60
- ADMIN_RATELIMIT_MAX_BURST=10
- ADMIN_RATELIMIT_SECONDS=60
volumes:
- /home/vaultwarden/data:/data
ports:
- "127.0.0.1:8081:8080"
deploy:
resources:
limits:
memory: 500M
tmpfs:
- /var/run
- /var/log:mode=1775
- /var/tmp:mode=1777
- /tmp
logging:
driver: none
depends_on:
change-data-vol-owner:
condition: service_completed_successfully
volumes:
data:
Finally, to deploy Vaultwarden you can execute:
docker-compose up -d
The container can be stopped with this command:
docker-compose down
Configure reverse proxy
Vaultwarden cannot function over HTTP; HTTPS is an absolute requirement. To fulfill this need, we can configure Caddy as a reverse proxy that serves an automatically generated Let's Encrypt certificate.
I've already created a full article about this subject:
Automated script
All above steps have been automated for you. The script will do the following:
- Create a new Linux user where Vaultwarden will be deployed.
- Automatically install docker in rootless mode.
- Automatically install docker compose.
- Configure Vaultwarden docker file.
- Add cronjobs to start Vaultwarden automatically after reboot.
You can find the script in my GitHub repository.
The only two variables that you may change ( if needed ) are:
#MODIFY IF NEEDED
VAULTWARDEN_USER_ACCOUNT="vaultwarden"
#ONLY USE PORT HIGHER THAN 1024 OR FEW MODIFICATIONS WILL BE REQUIRED
VAULTWARDEN_PORT=8081
With:
- VAULTWARDEN_USER_ACCOUNT represents the user account freshly created by the script, serving as the host for both the Vaultwarden container and its associated data.
- VAULTWARDEN_PORT designates the host system's port through which Vaultwarden will be accessible.
Self-hosting Vaultwarden offers compelling benefits, including heightened security through data control, enhanced privacy, and customization options. By managing your own server, you gain offline access to passwords, control over updates, and resource efficiency. While self-hosting demands technical proficiency and ongoing maintenance, it presents an opportunity to learn, integrate with other tools, and tailor the service to your preferences.
Feel free to ask any questions you may have in the comments section, and I'll be happy to assist you.