You’ve Been Hand-Editing Dotfiles on Each Machine. Stop That.
Your ~/.bashrc is slightly different on your laptop, desktop, and server. Your Neovim config has that one tweak you made at 2 AM that you can never quite remember. Your Caddy config exists only on the production box because you rebuilt it six months ago and forgot to save it anywhere.
This is how people lose their configs. Not to catastrophe, but to entropy.
Dotfile managers solve this problem in radically different ways. Three contenders keep showing up in /r/homelab and Discord: chezmoi, GNU Stow, and the bare git repo. Each one is correct for someone, and wrong for someone else. Here’s how to pick, and why each approach exists.
The Three Approaches
1. GNU Stow: Symlinks and Simplicity
Stow is the oldest of the three. It’s been around since the 1990s. It does one thing and does it well: symlink files from a dotfiles repo into your home directory.
Init:
mkdir -p ~/dotfilescd ~/dotfilesgit initmkdir -p config/nvim config/zsh# Copy your existing configs into the tree matching home structurecp ~/.config/nvim/init.lua config/nvim/cp ~/.zshrc config/zsh/git add .git commit -m "Initial dotfiles"Install on a new machine:
cd ~git clone <your-repo> dotfilescd dotfilesstow config # symlinks ~/.config/nvim -> ~/dotfiles/config/nvimWhat Stow actually does: It mirrors your home directory tree structure inside the repo, then creates symlinks from ~ to the repo files. If you have dotfiles/config/nvim/init.lua, Stow creates ~/.config/nvim/init.lua → ~/dotfiles/config/nvim/init.lua.
Pros:
- Zero moving parts. No daemon, no templating engine, no secrets vault.
- You know exactly what’s happening: symlinks.
- Minimal learning curve. Five minutes to understand.
- Tiny. Pure shell script, no dependencies.
- Works across anything that respects symlinks: Linux, macOS, WSL.
Cons:
- Can’t template. If your Neovim config path differs by OS (or you need to inject a local API key), you’re stuck with manual find-replace or shell script wrappers.
- Symlink conflicts are clunky to debug. If
~/.config/nvimalready exists (or is a symlink to somewhere else), Stow fails. - No built-in secrets handling. If you commit an API key, it’s in the git history forever.
- Machine-specific configs need separate branches or manual file exclusions.
When Stow shines:
- You’re living the “same setup on all machines” dream. Same OS, same tools, same paths.
- Your dotfiles are simple enough that templating feels like overkill.
- You want the paranoia of knowing that Stow is just making symlinks—no hidden processing.
- You’re bootstrapping a new machine and want the fastest path.
2. chezmoi: Templating and Secrets
chezmoi is the newcomer. It treats your dotfiles like infrastructure-as-code. You define templates, variables, and secrets, and chezmoi renders them during install.
Init:
chezmoi init# This creates ~/.local/share/chezmoi/ — the source directorycd ~/.local/share/chezmoi
# Add your configs as templates:chezmoi add ~/.zshrcchezmoi add ~/.config/nvim/init.luachezmoi add ~/.ssh/config # chezmoi will prompt you to template secretsWhat you see in the source repo:
~/.local/share/chezmoi/ dot_zshrc dot_config/nvim/init.lua dot_ssh/encrypted_config .chezmoi.yaml.tmplThe dot_ prefix is chezmoi’s syntax for “this should be a file in ~”. It’s stripped during apply.
Template example:
hostname: {{ .hostname }}os: {{ .chezmoi.os }}Then in your files:
# ~/.zshrc template{{ if eq .chezmoi.os "darwin" }}export HOMEBREW_PREFIX="/opt/homebrew"{{ end }}
export EDITOR="nvim"Install on a new machine:
chezmoi init <your-repo>chezmoi apply # renders templates, decrypts secrets, applies everythingPros:
- Multi-machine sync done right. Different EDITOR on macOS vs Linux? Template it. Different user on your server? Ask during init.
- Secrets aren’t stored in plaintext. chezmoi encrypts sensitive files (SSH keys, API keys) at rest using GPG or age encryption.
- Zero-install on fresh OS. chezmoi can bootstrap itself. One command pulls everything down and applies it.
- Diff before apply.
chezmoi diffshows you what will change before you hit apply. - Idempotent. Run
chezmoi applya thousand times, same result. - Executable scripts. You can add shell scripts that run during apply (e.g., install Homebrew before symlinking).
Cons:
- Learning curve. Templates, variables, encryption keys, GPG config—there’s a lot.
- Heavier dependency. chezmoi is a binary (Go app), not a shell script.
- GPG/age setup is a hassle if you’re not already using them.
- Overkill if all your machines are identical.
When chezmoi shines:
- You manage machines with different OSes or configs (home laptop + work server + Raspberry Pi home lab box).
- You have secrets that shouldn’t live in the repo (API keys, SSH passphrases, database passwords).
- You want to bootstrap a brand-new machine with one command and have everything Just Work™.
- You’re versioning your entire home directory (or at least all the important parts).
3. Bare Git Repo: The Minimalist’s Choice
The bare repo approach is the anti-tool. You create a bare git repo in a hidden directory and use a shell alias to manage it like a normal repo—except you don’t need to cd into a dotfiles folder. Everything lives in ~.
Init:
git init --bare ~/.dotfilesalias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'dotfiles config --local status.showUntrackedFiles nodotfiles add ~/.zshrc ~/.config/nvim/init.luadotfiles commit -m "Initial dotfiles"dotfiles remote add origin <your-repo>dotfiles push -u origin mainInstall on a new machine:
alias dotfiles='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'git clone --bare <your-repo> ~/.dotfilesdotfiles checkout mainWhat’s happening: You have a hidden bare repo (.dotfiles/) that tracks files scattered across your entire home directory. The alias lets you run git commands as if you’re in ~, but the actual repo metadata lives in .dotfiles/.
Pros:
- No dependencies. Just git and shell.
- No symlinks. Files live in their real locations. Debugging is straightforward.
- Natural workflow. It’s exactly like managing a code repo, except your working directory is
~. - Minimal magic. The tool doesn’t do anything. It just tracks files.
Cons:
- No templating. Different machines need different setups? You need custom scripts or separate branches.
- No secrets encryption by default. You can add git-crypt as an addon, but it’s not built-in.
- Merge conflicts hurt. If you edit
~/.zshrcon two machines and try to sync, you get a real merge conflict—in your home directory. - Silent failures. If there’s a file conflict on checkout (you have
~/.bashrcthat the repo wants to claim), git will refuse to checkout. Not intuitive for beginners. - All files exposed. Everything in your home directory can be tracked. One
git add .mistake and you’ve committed/tmp/,.ssh/, and private keys.
When bare repos shine:
- You’re a git power user and prefer the Unix philosophy: do one thing (version control), do it well.
- All your machines are the same (same user, same OS, same paths).
- You want maximum transparency and minimal dependencies.
- Your dotfiles are truly immutable across environments (no machine-specific tweaks).
Workflow Comparison: A Day in the Life
Scenario: You update your Neovim config on your laptop, then need it on your desktop 30 minutes later.
GNU Stow:
# On laptop: edit ~/dotfiles/config/nvim/init.luacd ~/dotfilesgit add config/nvim/init.luagit commit -m "Add indent settings"git push
# On desktop:cd ~/dotfilesgit pull# Done. Symlinks already point to the right place.chezmoi:
# On laptop: edit a config, then applychezmoi edit ~/.config/nvim/init.lua # opens in $EDITORchezmoi applychezmoi git add -A && chezmoi git commit -- -m "Add indent settings" && chezmoi git push
# On desktop:chezmoi update# chezmoi pulls, rerenders templates, applies changesBare repo:
# On laptop: edit ~/.config/nvim/init.lua directlydotfiles add ~/.config/nvim/init.luadotfiles commit -m "Add indent settings"dotfiles push
# On desktop:dotfiles pull# Done. Files are already in place.All three get you there. Bare repo and Stow are fastest. chezmoi adds the templating step but gains multi-machine safety.
Secrets: The Critical Difference
Let’s say you have an SSH key or an API token that needs to live in your dotfiles.
GNU Stow: Don’t. Seriously. Either:
- Keep secrets in a
.gitignore’d file and source it from your shell config. - Use a separate tool like
passor1passwordCLI to inject secrets. - Manually manage them per machine (gross, but it works).
chezmoi:
chezmoi add ~/.ssh/id_ed25519# chezmoi detects it's a sensitive file (or you mark it encrypted_*)# and encrypts it with GPG/agechezmoi git add -A && chezmoi git commit -- -m "Add encrypted ssh key" && chezmoi git push# The key is encrypted at rest. Safe to push.
# On another machine with your GPG key:chezmoi apply# chezmoi decrypts on apply using your GPG keyBare repo: Same as Stow. You need git-crypt or manual secret management. Not built-in.
If you have secrets, chezmoi wins by default. Bare repo and Stow force you to do extra work.
Multi-Machine Sync: The Real Test
You have a laptop (macOS), a desktop (Ubuntu), and a Raspberry Pi (Raspberry Pi OS). They all need the same Neovim config, but:
- macOS Homebrew prefix is
/opt/homebrewinstead of/usr/local. - The server user is
root, not your normal user. - The Raspberry Pi doesn’t need a full LazyVim config—just minimal vi stuff.
GNU Stow: You need conditional symlinks or script wrappers. It can work, but you’re fighting the tool. Typically you’d create separate branches per machine or manually exclude files.
chezmoi: This is exactly what it’s built for.
data: hostname: {{ .chezmoi.hostname }} os: {{ .chezmoi.os }} username: {{ .chezmoi.username }}Then in your configs:
{{ if eq .chezmoi.os "darwin" }}export HOMEBREW_PREFIX="/opt/homebrew"{{ else if eq .chezmoi.os "linux" }}export HOMEBREW_PREFIX="/usr/local"{{ end }}Files can be templated or entirely skipped per machine.
Bare repo: Branches or manual exclusion lists. Workable, but tedious. You’d probably end up with a ~/.machine-config directory that you source into shell configs based on hostname.
The Decision Tree
Use GNU Stow if:
- You’re managing 1–3 machines with identical setups.
- You value simplicity and minimal dependencies.
- All your machines run the same OS.
- You don’t have secrets in your dotfiles (or you’re comfortable managing them separately).
Use chezmoi if:
- You have multiple machines with different configurations.
- You have secrets (API keys, SSH keys, database passwords) that need to stay private.
- You want one-command bootstrap from scratch.
- You can spend 30 minutes learning the templating syntax.
Use bare repo if:
- You’re a git expert and want maximum transparency.
- All machines have identical setups and you don’t need templating.
- You prefer “just git, nothing else” philosophy.
- You’re willing to roll your own secrets management (git-crypt, etc.).
The Honest Take
Here’s the thing: all three work. I’ve shipped production configs with each. Stow is stable and ancient. chezmoi is modern and solves the hard problems. Bare repo is minimalist and honest.
If you’re starting today and have more than one machine, pick chezmoi. The learning curve is real, but you’ll save yourself three months of manual sync pain. If you’re a bare-repo person, you already know it and you’re not reading this article.
Stow wins if you want to introduce one coworker to dotfile management without teaching them git-crypt or templates. It’s the gateway drug.
Stop hand-editing files on each machine. Pick one, set it up once, and let your future self thank you at 2 AM when you need to restore a borked config.