BLUF: This repository contains scripts and configurations for deploying a self-hosted Vaultwarden instance with Cloudflare Tunnel integration and (optional) DuckDNS custom subdomain via Docker Compose.
Vaultwarden is an alternative implementation of the Bitwarden server API, written in Rust, and compatible with upstream Bitwarden clients.
Bitwarden/Vaultwarden is capable of password management via web app, desktop app, browser extension, mobile app, or CLI.
DuckDNS is a free dynamic DNS service. It allows users to create custom domain names that automatically update to point to a specific IP address, which is particularly useful for home servers, remote access, or hosting services with changing IP addresses. Users can choose a subdomain under duckdns.org and configure it to always point to their current IP address, making it easier to access their network or servers remotely.
If you have a registered domain, you can further customize your Bitwarden experience by modifying the server URLs (instructions provided further down).
- Linux/macOS server (or WSL instance if running Windows)
- Docker installed
- Cloudflare account
- DuckDNS account + registered subdomain and token <OR> personal registered domain
- Basic understanding of networking and Docker concepts
- Docker Compose Setup: Deploys Vaultwarden and Cloudflare Tunnel containers
- (Optional) DuckDNS subdomain: Allows for secure access to your vault instance without relying on an IP address
- Cloudflare Tunnel: Provides secure access without exposing ports
- Cloudflare Domain: Add you own registered domain or DuckDNS subdomain to your Cloudflare dashboard to resolve traffic to your Vaultwarden instance behind the Cloudflare Tunnel
- (Optional) Nginx Configuration: Nginx reverse proxy for SSL termination (useful if deploying as part of a larger infrastructure, i.e., you already have other containers deployed as part of your infrastructure)
- Admin Token Generation: Securely creates admin dashboard access
- Clone this repository:
git clone https://github.com/dynamic-stall/vaultwarden-docker
cd vaultwarden-docker
- Create
.env
fromexample
file (be sure to add your personalized variables):
cp .env.example .env
Required environment variables:
VAULT_NAME
: Vaultwarden container and hostname (used to createDOMAIN_URL
)VAULT_VOLUME
: Persistent volume for vaultwarden container and automated back-up locationsVAULT_PORT
: Local port for Vaultwarden (default: 8443)TUNNEL_TOKEN
: Cloudflare Tunnel token (obtained from your personal Cloudflare account)SMTP_*
: Email configuration variables (default: smtp.gmail.com)DDNS_DOMAIN
: (Optional) Your DuckDNS subdomain (i.e.,example.ddns.com
; used to createDOMAIN_URL
)DDNS_TOKEN
: (Optional) DuckDNS subdomain token (obtained from your personal DuckDNS account)DOMAIN_NAME
: Either your DuckDNS subdomain or your personal registered domain name (used to createDOMAIN_URL
)ADMIN_TOKEN
: Generated byadmin-token-create.sh
(NOTE: More on DOMAIN_NAME
variable setup further down)
- (Optional) If deploying with Nginx, you will need to either have your own SSL certificates (i.e.,
private.key
andcertificate.crt
) or thedeploy-nginx.sh
script will generate them for you.
Copy the config/nginx/openssl.cnf.example
file and update with your own values:
cd config/nginx
cp openssl.cnf.example openssl.cnf
NOTE: Before running the deploy-nginx.sh
script, be sure to have the absolute paths to your SSL certificates.
- The following will be auto-configured for you:
- SSL private key and self-signed certificate
vaultwarden.conf
Nginx configuration file- Nginx server configuration
(NOTE II: If skipping Nginx and SSL configuration, simply run the deploy-standalone.sh
script.)
- Visit DuckDNS and sign in using your preferred OAuth provider
-
Create a subdomain (i.e., vault.duckdns.org)
-
Copy your token from the DuckDNS dashboard
- Modify these variables to your
.env
file:
DDNS_DOMAIN=<your-chosen-subdomain>
DDNS_TOKEN=<your-duckdns-token>
- NOTE If you own
example.duckdns.org
, then setDDNS_DOMAIN=example
.
The DuckDNS container will automatically:
- Update your IP address every 5 minutes
- Maintain your subdomain registration
- Handle logging and retry logic
- Keep your subdomain active (domains remain valid as long as they're updated once every 30 days)
- No additional maintenance is required as long as the container remains running.
If you have a registered domain, you can skip the DuckDNS setup and modify the vw-compose.yml
file:
- Remove the DuckDNS service block:
# Remove this entire block
duckdns:
image: linuxserver/duckdns
# ...
- Update the
DOMAIN_NAME
variable in.env
, either commenting out or deleting the firstDOMAIN_NAME
variable referencing the DuckDNS subdomain:
Get rid of:
DOMAIN_NAME="${DDNS_DOMAIN}.duckdns.org"
...and modify the following variable:
# Remove the "#" and enter your personal registered domain
DOMAIN_NAME="example.com"
NOTE: Regardless of whether you use DuckDNS or bring your own domain, the final DOMAIN_URL
variable will be correctly formatted for use in later configurations:
DOMAIN_URL="https://${VAULT_NAME}.${DOMAIN_NAME}"
-
Login to your Cloudflare account
-
Navigate to Account Home → Domains
-
Select the blue
+ Add a domain
button to enter your domain name: either your full DuckDNS subdomain or your personal registered domain. Leave default settings as they are.
- In your DNS dashboard, you should see four records: two A records (containing your current public IP address) and two MX records (ignore these...).
- Save this page for later; we'll come back to it for the next step...
-
Login to your Cloudflare Zero Trust dashboard
-
Navigate to Networks → Tunnels
-
Create a new tunnel:
-
Click Create a tunnel.
-
Select Cloudflared on the left.
-
Name your tunnel (the name is only important to you). Click Save on the bottom-right.
-
Under "Install and run a connector", copy the
docker run
command provided. Paste this command in a secure note-taking environment to see the full token. -
Copy this token and paste it into your
.env
file, updating theTUNNEL_TOKEN
variable.
# Cloudflare Configuration TUNNEL_NAME="${VAULT_NAME}-tunnel" # NOTE: this variable is for the Docker container name TUNNEL_TOKEN="<your-cloudflare-tunnel-token>"
- Back to your Cloudflare dashboard, click Save on the bottom-right.
-
-
Configure the tunnel's Public Hostname:
- Subdomain: Ensure this value matches your chosen
VAULT_NAME
value in your.env
file - Domain: Either your personal registered domain name (i.e.,
example.com
) or your DuckDNS subdomain (i.e.,example.duckdns.org
) - Path: (Leave this field blank)
- Type: HTTP
- URL: localhost:8443 (or your chosen
VAULT_PORT
value, if you changed it)
- Subdomain: Ensure this value matches your chosen
- NOTE: If you are using a DuckDNS subdomain, you will not be able to add your subdomain to your Cloudflare dashboard for longer than 28 days (without NS record verification, which isn't possible with DuckDNS). You can still enter a "custom domain name" in the Public Hostname section. Be sure to hit the ENTER key when populating this value so it doesn't disappear. A warning for custom domains will appear. It means nothing configuration-wise, but be mindful:
-
(Optional) Configure the tunnel's Private Network:
- Cloudflare offers an extensive array of features available at free-tier. If you plan on utilizing WARP with your Zero Trust account (which I recommend), you can implement more robust access policies as well as custom private networks/IPs to access remote resources without requiring a public domain
- For the sake of simplicity, these instructions are beyond the scope of this GitHub project, but I suggest you check them out on your own, especially Cloudflare Access -- which can secure access to your self-hosted applications and more via various authentication and posture-assessment methods (i.e., Google OIDC for single sign-on with your Gmail account, restricting access to resources based on public IP or country, and much, much more).
-
Click Save tunnel in the bottom-right.
Run one of the main deployment scripts:
If configuring SSL and Nginx, run:
./deploy-nginx.sh
This will:
- Generate admin token
- Create SSL certificates
- Configure Nginx
- Launch Docker containers
- (Optionally,) configure Bitwarden CLI
If deploying without nginx, run:
./deploy-standalone.sh
This will:
- Generate admin token
- Launch Docker containers
- (Optionally,) configure Bitwarden CLI
-
Navigate to the admin panel of your Vaultwarden instance (i.e.,
vault.example.com/admin
) -
Use the Admin token you set to authenticate
-
From the admin page, you can create users for general access and password management (access the Web Vault at, i.e.,
vault.example.com/#/login
)
-
Open Bitwarden desktop app
-
Expand the "Accessing" dropdown menu below your login email
- Select "Self-hosted" and enter your server URL (ex:
https://vault.example.com
)
- Use your created user's master password to login
The main deployment scripts will (optionally) build a Docker container which will allow you to run the Bitwarden CLI -- while also pre-configuring it to use your custom domain.
NOTE: If you would prefer a host installation of the CLI, you can use the scripts/cli-host-config.sh
script instead of the Docker deployment.
Reference the below for manual configuration of the CLI:
# Set server URL
bw config server https://vault.example.com
# Configure individual endpoints
bw config server \
--api https://vault.example.com/api \
--identity https://vault.example.com/identity \
--web-vault https://vault.example.com \
--icons https://vault.example.com/icons \
--notifications https://vault.example.com/notifications
If you would like to register your SSL certificate with Cloudflare (or another certificate authority) and obtain an Origin CA certificate, you will first need to generate a certificate signing request (CSR). Use the command below (replace with your private key name, if you brought your own):
openssl req -new -key private.key -out request.csr -config config/nginx/openssl.cnf
After you have your CSR, use it to obtain the Origin CA certificate by following the official documentation. Once you have registered your SSL certificate with Cloudflare and configured your setup to use them, you can discard the self-signed certificate, certificate.crt
, IF it is no longer being used in your deployment...
.
├── config/
│ ├── docker/
│ │ ├── cli.Dockerfile
| | └── vw-compose.yml
│ └── nginx/
| ├── vaultwarden.conf.template
│ └── openssl.cnf.example
├── deploy-nginx.sh
├── deploy-standalone.sh
├── .env.example
├── .gitignore
├── README.md # this file
└── scripts/
├── admin-token-create.sh
├── bw-cli-config.sh
├── cli-config-host.sh
├── docker-custom-net.sh
├── nginx-config.sh
└── ssl-cert-create.sh
- Always use strong passwords
- Keep your system updated
- Regularly backup the
/opt/bitwarden
directory (automated by default) - Monitor logs for suspicious activity
- Use 2FA where possible
Back-ups are AUTOMATED via the vw_backup
container as part of the vw-compose.yml
file. Default is set to daily at 5 AM.
For manual back-ups, you can back up the /opt/vaultwarden
directory:
tar -czf backup.tar.gz /opt/vaultwarden
To update Vaultwarden:
docker compose pull
docker compose up -d
- Check container logs:
docker logs <vault_container_name>
docker logs <tunnel_container_name>
- Verify Nginx configuration:
nginx -t
- Check Cloudflare tunnel status:
docker logs <tunnel_container_name>
Pull requests are welcome. For major changes, please open an issue first.