Handling secrets

We strongly recommend not putting any sensitive information such as passwords or private keys into your repository. This page describes the helpers available in BundleWrap to manage those secrets without checking them into version control.

Most of the functions described here return lazy Fault objects.


When you initially ran bw repo create, a file called .secrets.cfg was put into the root level of your repo. It's an INI-style file that by default contains two random keys BundleWrap uses to protect your secrets.

You should never commit .secrets.cfg. Immediately add it to your .gitignore or equivalent.

Derived passwords

In some cases, you can control (i.e. manage with BundleWrap) both ends of the authentication process. A common example is a config file for a web application that holds credentials for a database also managed by BundleWrap. In this case, you don't really care what the password is, you just want it to be the same on both sides.

To accomplish that, just write this in your template (Mako syntax shown here):

database_user = "foo"
database_password = "${repo.vault.password_for("my database")}"

In your bundle, you can then configure your database user like this:

postgres_roles = {
    "foo": {
        'password': repo.vault.password_for("my database"),

It doesn't really matter what string you call password_for() with, it just has to be the same on both ends. BundleWrap will then use that string, combine it with the default key called generate in your .secrets.cfg and derive a random password from that.

This makes it easy to change all your passwords at once (e.g. when an employee leaves or when required for compliance reasons) by rotating keys.

However, it also means you have to guard your .secrets.cfg very closely. If it is compromised, so are all your passwords. Use your own judgement.

"Human" passwords

As an alternative to password_for(), which generates random strings, you can use human_password_for().It generates strings like Wiac-Kaobl-Teuh-Kumd-40. They are easier to handle for human beings. You might want to use them if you have to type those passwords on a regular basis.

Static passwords

When you need to store a specific password, you can encrypt it symmetrically:

$ bw debug -c "print(repo.vault.encrypt('my password'))"

You can then use this encrypted password in a template like this:

database_user = "foo"
database_password = "${repo.vault.decrypt("gAAAA[...]mrVMA==")}"


You can also encrypt entire files:

$ bw debug -c "repo.vault.encrypt_file('/my/secret.file', 'encrypted.file'))"
Encrypted files are always read and written relative to the data/ subdirectory of your repo.

If the source file was encoded using UTF-8, you can then simply pass the decrypted content into a file item:

files = {
    "/secret": {
        'content': repo.vault.decrypt_file("encrypted.file"),

If the source file is binary however (or any encoding other than UTF-8), you must use base64:

files = {
    "/secret": {
        'content': repo.vault.decrypt_file_as_base64("encrypted.file"),
        'content_type': 'base64',

Key management

Multiple keys

You can always add more keys to your .secrets.cfg, but you should keep the defaults around. Adding more keys makes it possible to give different keys to different teams. By default, BundleWrap will skip items it can't find the required keys for.

When using .password_for(), .decrypt() etc., you can provide a key argument to select the key:

repo.vault.password_for("some database", key="devops")

Rotating keys

This is applicable mostly to .password_for(). The other methods use symmetric encryption and require manually updating the encrypted text after the key has changed.

You can generate a new key by running bw debug -c "print(repo.vault.random_key())". Place the result in your .secrets.cfg. Then you need to distribute the new key to your team and run bw apply for all your nodes.