Development
How I use chezmoi to manage dotfiles across Macs
Dotfiles drift when real machines change. Here is how I use chezmoi to keep config, app settings, and helper scripts predictable across my Macs.
Syncing dotfiles across machines should be straightforward.
In practice, it drifts. Things get patched, scripts pile up, and after a while you’re not even sure how your own setup works anymore.
I ended up at chezmoi in a fairly practical way. I had a dotconfig setup that worked, but it was slightly manually patched together. Around the same time I got a new MacBook Pro while still wanting my development environment to stay consistent with my 2020 Intel iMac.
At some point I realized I didn’t fully remember how to maintain my own setup anymore.
Chezmoi is the first tool I have used that makes this workflow feel boring in the right way.
It gives me:
- a clean source-of-truth repo
- predictable application onto a machine
- support for private files and templates
- a sane way to pull local changes back into the repo when real apps edit files in place
That last part is what makes it usable long term.
What I want from dotfiles
I want this to feel predictable. And I want it to behave like Git, because that’s what I already understand.
I want:
- version-controlled config
- reproducible setup across machines
- a workflow that does not depend on manually rebuilding symlinks
- a way to manage files that apps rewrite directly
- a clean way to keep my own helper scripts and small CLI tools in sync too
Git alone does not solve that cleanly. A bare repo in $HOME or a symlink farm works for a while, then starts to feel fragile.
Chezmoi sits at a better level of abstraction. It handles mapping repo files into real home-directory paths without making me manage that translation manually.
What my setup looks like
Chezmoi stores files in a source repo with names that map to real paths.
At first the naming looks a bit odd:
dot_gitignore
private_dot_gitconfig
private_dot_config/private_git/private_ignore
private_dot_config/private_karabiner/private_karabiner.json
But once it clicks, it becomes readable enough that you stop thinking about it.
The basic workflow
The core commands are simple:
chezmoi init [email protected]:you/dotfiles.git
chezmoi add ~/.zshrc
chezmoi add ~/.gitconfig
chezmoi apply
chezmoi diff
The mental model is:
addbrings an existing file under managementapplywrites the source state onto the current machinediffshows drift between the source repo and the live system
That is already useful, but it is not the whole story.
The command that matters most: chezmoi re-add
This is the part most dotfiles guides under-explain.
Real apps edit config files in place. Sometimes I also tweak the live file first because it is faster than opening the source repo.
That is where chezmoi re-add matters.
Example:
chezmoi re-add ~/.config/karabiner/karabiner.json
This pulls the current live version of the file back into the chezmoi source repo.
That makes the workflow bidirectional:
- define config in the source repo
- apply it to the machine
- make real-world changes locally when needed
- sync those changes back into source with
re-add
That loop is what makes this practical.
Without it, the repo becomes a brittle idealized copy of your machine. With it, the repo stays close to reality.
A few examples from my setup
The useful parts of this workflow are not flashy files. They are the boring ones that actually need maintenance.
Git ignore rules
I keep a global Git ignore through ~/.gitignore.
That is the kind of file I want managed centrally because it follows me across machines.
Git-specific excludes
I also keep Git-specific ignore behavior under ~/.config/git/ignore.
That is a good example of a file that belongs in the same managed setup, but not necessarily in the same place as the global ignore.
Karabiner
Karabiner is where chezmoi re-add becomes especially useful.
Some config files are easier to tweak through the app or directly on the live machine. When that happens, I do not want to manually copy changes back into the source repo later. re-add gives me a clean way to sync the real state back into source.
That is much closer to how machine configuration actually evolves.
My own helper scripts
This also matters for the small scripts I use in day-to-day development.
For example, I have been building my own helpers around commit workflows, including scripts that reword commits or review recent commits for potential risks. Those are not exactly “dotfiles,” but they are part of the working environment I want to preserve across machines.
Once I started treating this as development-environment consistency rather than dotfiles aesthetics, chezmoi made a lot more sense.
What to avoid
A few mistakes seem easy to make here:
- editing live files and forgetting to sync them back
- using project-level
.gitignorefor personal machine noise - over-templating too early
- turning dotfiles into a framework project instead of a maintenance system
The goal is not to build a clever setup.
The goal is to make machine configuration boring to keep in sync.
Why I prefer this over ad hoc scripts
The value of chezmoi is not that it makes dotfiles look tidy on GitHub.
The value is that it gives me a repeatable maintenance loop:
- manage
- apply
- inspect drift
- sync changes back
Once that loop clicks, dotfiles stop feeling like a side project and start acting like infrastructure.
That’s the part I care about.
The takeaway
If you already tweak your shell, Git, editor, or keyboard config across multiple machines, the best reason to use chezmoi is not that it stores dotfiles in Git.
It is that it gives you a practical two-way workflow.
You can define config in a clean source repo, apply it predictably, and when the real machine changes, pull that reality back into source with chezmoi re-add.
That is what makes the setup hold up over time.
If this was useful, continue through the archive or follow the thread through related notes.