Husband. Father. Software engineer. Ubuntu Linux user.
I’ve been managing my dotfiles with Chezmoi for nearly five years now, and I love it!
Chezmoi fundamentally changed my approach to dotfiles with templating, making it
easy to keep my dotfiles working on multiple machines and even multiple
operating systems. And Chezmoi’s integration with password managers made it
easy to keep important identity files like id_rsa synced and backed up while
remaining secure. Overall I’ve been really happy with my dotfiles setup, but in
the last couple months I started noticing some pain points. Every time I synced
my dotfiles, I got prompted for my Bitwarden password. For a long time this
didn’t bother me, but I found myself getting increasingly annoyed at having
to authenticate to my password manager, and sometimes even re-authenticate with
2-factor authentication, when I knew my secrets hadn’t changed. I realized I
was actually avoiding updating my dotfiles because I didn’t want to deal with
the password manager – which is exactly the opposite of what I want. So I
finally decided to do something about it.
I’ve been using Bitwarden as my personal password manager for more than seven years! Back when I started, it was a simple, no-nonsense, open-source password manager with an intuitive interface. I like Bitwarden a lot and I’m very happy with it overall, even though it’s lost some of its original charm as it’s become more popular over the years. But the CLI is terrible.
The CLI doesn’t really manage any vault state for you, so the only way to keep
it unlocked is to set an env variable. There might be some reasons for designing
things to work that way, but from a UX perspective it sucks. It means
something as simple as unlocking the vault to run a series of bw get commands
in your terminal involves copying and pasting a key from the unlock command. The
UX is bad enough that I wrote my own
alias
(yes, that’s a link to my dotfiles 😄) to unlock the vault for the current
session in a single command. But, frustratingly, even this doesn’t work half the
time! If my OTP has expired after 30 days (coincidentally, about the frequency I
update my dotfiles), the CLI just exits with an unhelpful error message. While
I’m glad Bitwarden at least offers a CLI, the interface is laughably bad for my
usage. And the fact that I end up having to redo 2-factor OTP auth every other
time I update my dotfiles is just icing on the cake for a very bad UX.
(Now that it’s 2026, I should probably just vibe-code a much more robust script for myself to login with more graceful error handling. Maybe I will! But that’s a topic for another blog post. This post is about a better password management UX for Chezmoi, so let’s get on with it!)
One of the most important and useful features of Chezmoi is dotfile templates (using Go text/template). This is what allows you to customize dotfiles per-machine from the same source. It also allows you to reference secrets from your password manager using a template function. When I started with Chezmoi, the appeal of integrating with Bitwarden was a big draw for me. I became enchanted by the idea of completely setting up a new machine with a single command, and Chezmoi’s Bitwarden integration was an important building block. Chezmoi is an excellent dotfiles manager, and with all the different tools and hooks for customization (templates, settings, chezmoidata, hooks, etc) it’s nearly infinitely configurable. This flexibility is both good and bad. It makes Chezmoi a good solution to a wide variety of needs, but it also removes the simplicity offered by an opinionated view. And definitely gives you all the tools you need to shoot yourself in the foot.
In my case, I shot myself in the foot by setting up my dotfiles to unnecessarily require my Bitwarden password every time they’re applied, which in turn makes it more toilsome to update them. I only recently started to realize that this integration was making me go through the song and dance of unlocking my vault to sync my dotfiles (including OTP), and that was adding enough toil that updating my dotfiles became a burden. This isn’t entirely Chezmoi’s fault; if anything the Bitwarden CLI UX is mostly to blame. But in hindsight, I think Chezmoi makes it a little too easy to create a setup where you do need to unlock your password vault every time you sync your dotfiles, and I do think we can do better than that. So let’s get to it.
My desired UX was simple: I wanted to be able to sync my dotfiles without unlocking my password manager. I started thinking about this problem from first principles, with all options on the table.
I considered getting rid of any secrets managed by my dotfiles. That’s not a bad option. I really only have a small handful of secrets in my dotfiles, and it wouldn’t be terrible to manually copy them out of my password manager on a one-off basis. This would be a fine option, but let’s see if we can do better.
I considered using a different password manager. Perhaps one of the fifteen options Chezmoi supports is better than Bitwarden, and has a CLI with a better UX that doesn’t constantly log me out. I only considered this option briefly. Aside from the CLI, I’m happy with Bitwarden, and I don’t want to migrate all my passwords to a solution that might not be that much better. This option doesn’t really solve the root problem of my dotfiles setup prompting me for a password every time I run it.
I considered encrypting my secrets and storing them in my (public) dotfiles git repo. This is actually a really appealing option, and was a strong contender. Especially since Chezmoi has a guide to only request a passphrase the first time chezmoi is run, and that’s basically exactly what I wanted to do. In fact, this became a source of inspiration for the solution I finally landed on. But ultimately, I decided not to pursue this option because I don’t like the idea of making my secrets publicly available in encrypted form. Even if the encryption is very good, it feels like you’re asking for someone to try to break it. And if you make a mistake configuring something, you’re toast.
I considered configuring or modifying Chezmoi to avoid requesting a passphrase every time dotfiles are updated/applied. That’s the root problem, and this is the behavior I really wanted. But as I thought about how this might work, I realized there’s a fundamental flaw to a solution that screws with Chezmoi’s template behavior like that. Chezmoi’s job is to ensure the target state matches the source state when you apply your dotfiles. If it’s skipping files, it’s not really doing it’s job. Sure, hypothetically, you could design something where a configuration setting makes it ignore any files with a certain function in the template, but that’s not an elegant solution. It breaks Chezmoi’s operating model. That’s a dirty hack that would probably be ugly to implement. But what if you could achieve similar behavior without breaking chezmoi’s assumptions?
What if, instead of skipping files that call Bitwarden functions in their template, you could just replace the Bitwarden functions with a function call that didn’t require a password? What if you could actually pre-fetch any secrets that you need, and find a way to cache them, and provide them to Chezmoi when it runs? That’s exactly what I ended up doing, using functionality that already exists in Chezmoi!
Chezmoi has supported a .chezmoidata directory for a long time.
If any .chezmoidata/ directories exist in the source state, all files within them are interpreted as structured static data in the given formats. This data can then be used in templates.
The files can be JSON, JSONC, TOML, or YAML. Importantly, they don’t actually need to be checked in to your dotfiles repository! They just need to exist in that source state directory when Chezmoi is applying templates. This is a perfect solution for storing secrets that change infrequently since it makes the data available for Chezmoi without a password prompt. I had a little hesitation at first about putting secrets in a plain text file like this, but there’s really no reason to worry about that. Chezmoi is generating dotfiles, so any secrets it’s using in templates will obviously be visible in plain text to the owner of those dotfiles. It’s perfectly fine to store them in a YAML file in the dotfiles source directory.
I wrote a short
script
that fetches secrets from Bitwarden CLI and writes them to a secrets.yml file
in .chezmoidata. I checked the script in to my dotfiles, and also added
secrets.yml to my .gitignore to ensure I never accidentally commit the
secrets. Now I only need to authenticate to Bitwarden once, and the secrets will
be cached where Chezmoi can use them so I don’t need to authenticate again
unless the secrets change (which is pretty infrequent). If the secrets do
change, I’ll notice (because something won’t authenticate correctly), and I’ll
just run the script again to install the latest secrets. Once the secrets are
installed, I can access them using normal Chezmoi templates. (I “namespaced”
mine with kasm because I felt like it, but that’s not really required.) The
solution feels nice and elegant because instead of working against Chezmoi,
we’re using it’s existing framework to let it do what it’s already good at –
sync the target state to match the source.
I’ve been running this setup for about a week, and the improvement from not needing to authenticate every time I update my dotfiles is real. I not only removed a little bit of toil, I removed a friction point to making changes to my dotfiles, and I’m using them more effectively because of it!
It might seem counter-intuitive at first that having a separate command is
actually better, but after experiencing the difference I’m quite confident that
it is, at least for me. While there’s definitely an appeal to having a single
command (chezmoi update) that puts your machine in exactly the correct state,
including not only dotfiles but also secrets and apps and other things, in some
ways that’s an illusion that doesn’t account for the complexity and toil of
typing in a password to perform updates, or dealing with failed authentication.
If you like what I’ve built, I hope you steal it for your own dotfiles and you’re welcome to do so! Attribution is always appreciated but not strictly required. My dotfiles are public, so you can see my secrets script and commit where I implemented everything. If you’re not using Bitwarden, it should be trivial to update the script to call any other CLI tool to get secrets. And if you’re not already using Chezmoi, I think you should check it out! But a similar approach will probably work with a wide variety of dotfiles tools.
👋 Hi, I'm Mike! I'm a husband, I'm a father, and I'm a staff software engineer at Strava. I use Ubuntu Linux daily at work and at home. And I enjoy writing about Linux, open source, programming, 3D printing, tech, and other random topics. I'd love to have you follow me on X or LinkedIn to show your support and see when I write new content!
I run this blog in my spare time. There's no need to pay to access any of the content on this site, but if you find my content useful and would like to show your support, buying me a coffee is a small gesture to let me know what you like and encourage me to write more great content!
You can also support me by visiting LinuxLaptopPrices.com, a website I run as a side project.