Every developer eventually accepts that their dotfiles are a real piece of personal infrastructure. The shell config, the editor config, the git config, the tmux conf, the ssh config — these accumulate years of small decisions, and re-configuring them on a fresh machine is a half-day of pain you should never have to repeat.
Three reasonable ways to manage them in 2026: GNU stow, chezmoi, and a custom bash bootstrap script. I’ve shipped each on my main machines for ~12 months at a time. Here’s the honest comparison — what each one is good at, where each one bites, and the one I ultimately settled on.
stow — the simplest one
GNU stow has been around since 1996. It’s a 30-year-old Perl script that does one thing: given a directory of subfolders, it creates symlinks from your home directory into those folders.
~/dotfiles/
├── zsh/
│ └── .zshrc # will become ~/.zshrc symlink
├── git/
│ ├── .gitconfig # → ~/.gitconfig
│ └── .gitignore_global
├── tmux/
│ └── .tmux.conf
└── nvim/
└── .config/
└── nvim/
└── init.lua # → ~/.config/nvim/init.lua
# Apply all:
cd ~/dotfiles && stow zsh git tmux nvimPros: Trivial mental model. Your dotfiles repo IS your home directory. Symlinks mean editing ~/.zshrc edits the file inside the repo — git diff just works. Restoring on a fresh machine is git clone && stow *.
Cons: No templating — if your .gitconfig needs different email addresses for work vs personal, you can’t express that. No conditional logic. No secrets. The minute you have one machine-specific tweak, stow doesn’t help — you fork the file, or you give up and copy by hand.
When stow is right: You have one machine, or all your machines are identical. You’re not putting anything sensitive in your dotfiles. You like that the tool is so dumb it can’t surprise you.
chezmoi — the smart one
chezmoi is a Go binary that turns dotfile management into a templated, encrypted, multi-machine system. It treats each machine as a separate target with its own values for things like email, hostname, OS.
# Install
brew install chezmoi
chezmoi init # creates ~/.local/share/chezmoi
# Tell it about an existing dotfile:
chezmoi add ~/.zshrc
# It copies to ~/.local/share/chezmoi/dot_zshrc
# Use templates: filename .gitconfig.tmpl
[user]
name = {{ .name }}
email = {{ .email }}
# chezmoi data per machine in ~/.config/chezmoi/chezmoi.toml:
# [data]
# name = "Rehmat"
# email = "rehmat@5683.me"
# (different machine, different file → different email)
chezmoi apply # render templates and write to ~/.zshrc, ~/.gitconfig etc.Pros: Per-machine templating is the killer feature. Built-in encryption (gpg / age) for secrets-y dotfiles like .aws/credentials or API tokens. chezmoi diff previews changes before applying. chezmoi update pulls from git and applies in one command.
Cons: The mental model is heavier. Files in your repo are renamed (.zshrc becomes dot_zshrc, executable scripts get prefixed executable_, encrypted files get encrypted_). The repo doesn’t look like a home directory anymore — it looks like chezmoi’s internal representation. Editing means chezmoi edit ~/.zshrc, not directly editing the repo file.
When chezmoi is right: You have multiple machines that diverge meaningfully. You want to keep secrets in your dotfiles repo without making it private. You don’t mind learning a new tool’s mental model.
A custom bash bootstrap — the “I’ll write 50 lines myself” one
Just a Git repo with your dotfiles and a single script that copies / symlinks them into place. No tools beyond bash, ln, mkdir.
#!/usr/bin/env bash
# bootstrap.sh
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
mkdir -p ~/.config ~/.config/nvim
ln -sfn "$HERE/zsh/.zshrc" ~/.zshrc
ln -sfn "$HERE/git/.gitconfig" ~/.gitconfig
ln -sfn "$HERE/tmux/.tmux.conf" ~/.tmux.conf
ln -sfn "$HERE/nvim" ~/.config/nvim
# Per-machine: source local file if it exists
[ -f ~/.zshrc.local ] || cp "$HERE/zsh/.zshrc.local.template" ~/.zshrc.local
echo "Done. Edit ~/.zshrc.local for machine-specific config."Pros: Total control. Zero dependencies. The script is documentation. Per-machine differences via a sourced ~/.zshrc.local file the bootstrap doesn’t touch.
Cons: You re-implement everything chezmoi gives you. The first time you want gpg-encrypted secrets in the repo, you write your own ad-hoc encrypt-on-commit hook. The first time you want OS-conditional behavior, you write your own case $OSTYPE branches. By month 6, your bootstrap has accidentally become a worse chezmoi.
When this is right: Your dotfiles are simple, you don’t have secrets, you have only one or two machines. You hate dependencies more than you love features.
The honest comparison after a year on each
- Friction to start. stow < bash < chezmoi.
- Friction at month 6 when you have 3 machines and want different git emails. chezmoi < bash (with a sourced local file) < stow (which can’t do this).
- Friction when restoring on a brand-new laptop. Tied. All three are
git cloneand one command. - Friction when collaborating with future-you who has forgotten how this works. stow < bash < chezmoi. stow is so simple there’s nothing to forget.
- Secrets handling. chezmoi < bash (manual gpg) < stow (none).
The setup I actually run
chezmoi, with one twist: I keep boring dotfiles (no secrets, no per-machine variance) as straight files, and only use templating where I actually need it. About 80% of my files have no {{ }} at all — they’re just normal text that chezmoi happens to manage. The other 20% (.gitconfig with per-machine email, .ssh/config with work-vs-personal keys, an encrypted ~/.aws/credentials) earn the templating overhead.
The migration from stow to chezmoi was a quiet evening. From bash bootstrap to chezmoi was painful only because I’d built three years of bash crud I had to translate. Lesson: pick the right tool early; the cost of switching grows with the size of the repo.
If you’re starting today: stow if you have one machine and no secrets, chezmoi if you have two or more machines and any secrets at all. Skip the custom bash bootstrap unless you really enjoy reinventing everything — chezmoi has already solved the problems you’re about to discover.
Photo: Person holding a Git sticker by Real Tough Candy on Pexels.
