Bash docker image that exports your Bitwarden or Vaultwarden vault contents for backup.
50K+
Bitwarden_Export: Docker to backup your Bitwarden vault contents
This simple bash docker image uses the Bitwarden CLI to perform three backup tasks:
ps (details)The docker image provides the choice of creating unencrypted export files or password-encrypted export files. Attachments are not encrypted.
This docker image is compatible with Linux, MacOs or Windows. Please adapt docker-compose file to each OS.
This docker image requires the following:
Failed to unlock vault with BW_PASSWORD even though the password is right (vaultwarden#7693). bitwarden.com is not affected.Before using the image for the first time, you must edit the variables and volumes to provide authentication credentials and folder locations to save export files and attachments, as well as your organisation ID (if you want to export your organisation's vault; otherwise, leave it blank).
The docker image will prompt you for your password each time so that you don't have to save that within the docker image.
From a terminal window, simply type: bash bw_export.sh and follow the prompts.
The following are created by the docker image:
If you prefer to run it manually or just once, run the following command:
docker run --name bw-export -v ./export/data:/var/data -v ./export/attachments:/var/attachments -e "BW_CLIENTID=<CLIENT ID FROM BITWARDEN API>" -e "BW_CLIENTSECRET=<CLIENT SECRET FROM BITWARDEN API>" -e "BW_PASSWORD=<BITWARDEN PASSWORD>" 0netx/bw-export:latest
To run this container you must copy the docker-compose.yml from the repository.
services:
bw-export:
container_name: bw-export
image: 0netx/bw-export:latest
volumes:
- ./export:/var
environment:
- BW_CLIENTID=<CLIENT ID FROM BITWARDEN API>
- BW_CLIENTSECRET=<CLIENT SECRET FROM BITWARDEN API>
- BW_PASSWORD=<BITWARDEN PASSWORD>
# Optional: Own Vaultwarden/Bitarden selfhosted server
#- BW_URL_SERVER=<YOUR VAULTWARDER URL SERVER>
- OUTPUT_PATH=<Output path i.e. /var/data/ >
- ATTACHMENTS_PATH=<attachment path i.e. /var/attachments/ >
- EXPORT_PASSWORD=<Export password. Export will be encrypted with this password>
- BW_ORGANIZATIONS_LIST=<Organization list id, comma separated>
# Optional: Map the container's internal user to a user on the host machine
# - PUID=1000
# - PGID=1000
# Optional: Execution logging file. Please check output path is mapping inside a volume
# - FILE_LOG=/var/data/bw-export-logfile.log
# Optional: Keep the maximum number of exports defined. A maximum of X exports will be retained, the oldest exports will be deleted.
# - KEEP_LAST_BACKUPS=5
Configure each required variable and volumes.
Run the compose with docker compose up
Vaultwarden Export can be configured using the following environment variables.
You must report the following variables so that the container can authenticate itself in your vault. The API Keys for authentication can be obtained from Personal API Keys for CLI Authentication https://bitwarden.com/help/personal-api-key/
| Variable | Description |
|---|---|
BW_CLIENTID | It's client_id variable from your account |
BW_CLIENTSECRET | It's client_secret variable from your account |
BW_PASSWORD | Your Bitwarden/Vaultwarden password. |
EXPORT_PASSWORD | Encryption password you have chosen for exporting then vault. if you choose to use password-encryption to store your export files (recommended) be sure that you use a strong and memorable password! (don't just store it inside Bitwarden, because if you get locked out of your account you won't be able to restore your exports) |
OPTIONALS VARIABLES:
| Variable | Description |
|---|---|
BW_ORGANIZATIONS_LIST | If you want to make a backup of your organizations, set one or more organizations separated by comma. |
NOTIFICATION_URL | Shoutrrr notification url Service overview |
KEEP_LAST_BACKUPS | Optional: Keep the maximum number of exports defined. A maximum of X exports will be retained, the oldest exports will be deleted. |
FILE_LOG | Optional: Execution logging file. Please check output path is mapping inside a volume |
PING_URL | Optional: monitoring endpoint pinged when the export starts and when it finishes, carrying the outcome. For healthchecks, uptime-kuma or any dead man's switch. See Monitoring |
This container is based on an automation of the official Bitwarden CLI client. This script does not save, trace or store any information. The session is closed after the backup has been performed.
Every user on the host can read the command line of any process with ps,
including the processes inside a container, with Docker as with Podman. So the
script never passes a password as an argument to the Bitwarden CLI:
BW_PASSWORD) reaches bw unlock through an environment
variable (--passwordenv), which only the process owner and root can read;EXPORT_PASSWORD) reaches bw export through stdin, by
answering the CLI's own password prompt. The prompt is kept out of the log,
since it is redrawn once per character and would give away the length.Nothing has to be configured for this: both variables keep accepting either the value or the path of a file holding it.
If you wish you can configure each variable using docker secrets as in the example docker-compose.secrets.sample.yml
mkdir ./.secrets
echo BW_CLIENTID > ./.secrets/.bwclientid
echo BW_CLIENTSECRET > ./.secrets/.bwsecret
echo BW_PASSWORD > ./.secrets/.bwpassword
docker-compose.secrets.sample.yml with your variables and volumesSign up in Infisical free account
Create a project and create the variables bellow:
BW_CLIENTID=CLIENT ID FROM BITWARDEN APIBW_CLIENTSECRET=CLIENT SECRET FROM BITWARDEN APIBW_PASSWORD=BITWARDEN PASSWORDEXPORT_PASSWORD=EXPORT PASSWORD. Optional
*This is an example from Infisical Website


INFISICAL_TOKEN with previous generated token and INFISICAL_PATH with Secrets Path valueA service token carries its own environment and Secrets Path, and Infisical always applies them:
INFISICAL_PATHcannot point a service token anywhere else, and a wrong value is not reported as an error. If the export runs with the wrong secrets, check the token's scope in Infisical, notINFISICAL_PATH.When the same variable is set both in the compose file and in Infisical, the Infisical value is used.

In order to receive notifications on Telegram, we must inform the following variable by replacing the values of $BOT_TOKEN and $CHAT_ID with the token of our bot and the chat id of our user:
NOTIFICATION_URL=telegram://$BOT_TOKEN@telegram/?channels=$CHAT_ID
Example:
NOTIFICATION_URL=telegram://1009999999:AAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@telegram/?channels=19599999999
When export finish correctly, you should receive a notification:

The URL holds the bot token, so the container never prints it and hands it to
Shoutrrr through the environment rather than on its command line, where any
user on the host could read it with ps. If a notification cannot be sent, the
log says so without the address, and the backup's exit code is not affected.
If the Infisical secrets cannot be fetched, the container stops with exit code 1 and, when notifications are on, sends a message saying so with Infisical's own error.
The notification URL is the Gotify host followed by an application token:
NOTIFICATION_URL=gotify://$GOTIFY_HOST/$GOTIFY_TOKEN
Example:
NOTIFICATION_URL=gotify://gotify.example.com/gtfya.AbCdEf0123456789AbCdEf0123456789AbCdEf01234
If Gotify runs under a subpath, add it before the token (gotify://example.com/gotify/$GOTIFY_TOKEN). If it is served over plain HTTP instead of HTTPS, append ?disabletls=yes.
Both token formats are accepted: the legacy one from Gotify below 3.0.0 (15 characters starting with A) and the one issued by Gotify 3.0.0 and later (gtfya. prefix, around 50 characters). Support for the newer format is why this image builds Shoutrrr from the nicholas-fedor fork: the original containrrr project stopped at v0.8.0, which rejects any token that is not exactly 15 characters starting with A.
NOTIFICATION_URL tells a person what happened; PING_URL tells a machine. It
is pinged when the export starts and again when it finishes, carrying the
outcome, so a watchdog can tell a successful run from a failed one — and notice
a run that never happened at all.
PING_URL=https://hc-ping.com/<your-uuid>
That is everything healthchecks needs. The outcome is appended as a path
segment: /start when the export begins, /0 when it succeeds, and
/<exit code> when it fails.
Services that expect the outcome somewhere else use a placeholder. There are two, each with its own vocabulary:
| Placeholder | Start | Success | Failure |
|---|---|---|---|
{code} | start | 0 | the export's exit code |
{status} | not pinged | up | down |
A URL using {status} is not pinged at start: that vocabulary has no value
meaning "starting", and a push monitor has no concept of one.
# healthchecks, written out explicitly — same behaviour as the example above
PING_URL=https://hc-ping.com/<your-uuid>/{code}
# uptime-kuma push monitor
PING_URL=https://kuma.example.com/api/push/<token>?status={status}
# uptime-kuma, with the exit code in the message
PING_URL=https://kuma.example.com/api/push/<token>?status={status}&msg=exit-{code}
A trailing slash is removed before anything is appended, so
https://hc-ping.com/<uuid> and https://hc-ping.com/<uuid>/ behave the same.
A failed ping never fails the backup. It is reported as a warning and the container still exits with the export's own status code.
An unreachable monitor also cannot hold the container open for long. A refused connection or a hostname that does not resolve fails in about a second; a host that silently drops the packets costs at most eleven, and there are two pings in a run, so roughly twenty seconds in the worst case.
Please see the following link Bitwarden_Export Unraid Tempplate Readme
Docker runs all of its containers under the root user domain because it requires access to things like network configuration, process management, and your filesystem. This means that the processes running inside your containers also run as root. This kind of elevated access is not ideal for day-to-day use, and potentially gives applications the access to things they shouldn't (although, a strong understanding of volume and port mapping will help with this).
Another issue is file management within the container's mapped volumes. If the process is running under root, all files and directories created during the container's lifespan will be owned by root, thus becoming inaccessible by you.
Using the PUID and PGID allows our containers to map the container's internal user to a user on the host machine. All of our containers use this method of user mapping and should be applied accordingly.
When creating a container from one of our images, ensure you use the -e PUID and -e PGID options in your docker command:
docker create .... -e PUID=1000 -e PGID=1000 ....
Or, if you use docker-compose, add them to the environment: section:
environment:
- PUID=1000
- PGID=1000
It is most likely that you will use the id of yourself, which can be obtained by running the command below. The two values you will be interested in are the uid and gid.
id $user
Rootless Podman maps the container's uids onto your subordinate uid range, so
PUID=1000 inside the container is not uid 1000 on the host: exports land
owned by an unknown user, or cannot be written at all. Add the
compose.podman.yml override, which uses keep-id so your
uid means the same inside and outside the container:
podman compose -f docker-compose.yml -f compose.podman.yml up
With it, PUID/PGID behave exactly as under Docker. Without PUID, rootless
Podman already writes the files as you, since the container's root is your user.
The override is Podman only: Docker rejects keep-id.
On SELinux hosts (Fedora, RHEL) also add :z to the bind mounts, e.g.
./export:/var/data:z, or the container cannot read them.
Keep the maximum number of exports defined. A maximum of X exports will be retained, the oldest exports will be deleted.
environment:
- KEEP_LAST_BACKUPS=5
Each export is saved in the output folder with the template <DateTime>-bw-export.
When the variable KEEP_LAST_BACKUPS is set, the container will sort the folders and delete the oldest ones while keeping the number of folders indicated by the variable.

Earlier versions of this README and of docker-compose.yml mounted the
attachments folder on /var/attachment, but the image saves attachments to
/var/attachments. With that mount the export succeeded and the attachments
never reached the host folder: they went to an anonymous Docker volume
instead. Unraid's template already uses /var/attachments and is not affected.
What changes for you
You mount /var/attachment and do not set ATTACHMENTS_PATH: nothing
breaks. The image notices the old mount, saves the attachments there, and logs
a warning. Change the mount to /var/attachments when convenient:
volumes:
- ./export:/var/data
- ./export:/var/attachments
You mount /var/attachments, or set ATTACHMENTS_PATH: nothing changes.
That includes mounting both paths to different folders: /var/attachments
keeps being used.
You set ATTACHMENTS_PATH=/var/attachment without mounting it: the run
now fails with Could not find the folder in which to save the attachments.
Before, the files were written inside the container and lost with it. Mount a
folder there, or drop the variable.
Recovering the attachments saved so far
They are in the anonymous volume of your existing container, as long as it has
not been removed with docker rm -v or run with --rm (docker compose
keeps anonymous volumes when it recreates a container). Copy them out before
recreating it, replacing bw-export with your container's name and ./export
with your host folder:
docker run --rm --volumes-from bw-export -v ./export:/restore \
--entrypoint cp 0netx/bw-export:latest -a /var/attachments/. /restore/
Each run's attachments land in the same dated folder as its vault export. Under
Docker the copied files belong to root; add --user "$(id -u):$(id -g)" if you
need them to be yours (on SELinux hosts, also :z on the /restore mount).
git clone https://github.com/0neTX/Bitwarden_Export.git
cd Bitwarden_Export
docker build --no-cache -t bw-export --progress=plain .
This docker image is based on the script of @dh024/Bitwarden_Export that I used as a base and inspiration. Thank you very much.
Since the purpse of this docker image is to create a copy of all your passwords and secrets stored in Bitwarden, you should carefully inspect this docker image to ensure it meets your needs and that it will execute as you would expect. I provide no guarantees that it will work for you!
This project is licensed under the GPL 3.0 License. See the LICENSE file for details.
Content type
Image
Digest
sha256:fa3aa94a8…
Size
247.1 MB
Last updated
19 days ago
docker pull 0netx/bw-export