Managing Dotfiles with chezmoi
A technique for managing dotfiles — .zshrc, .gitconfig, and friends — in git and deploying them across machines. This page focuses on chezmoi, a representative tool for the job.
What are dotfiles?
Dotfiles are configuration files in your home directory whose names begin with a dot.
- Shell:
.zshrc/.bashrc/.profile - Git:
.gitconfig/.gitignore_global - Editor:
.vimrc/.config/nvim/, etc.
They are the backbone of your environment, but because they are scattered throughout $HOME, they are awkward to track in git as-is.
Why plain git tracking is not enough
Turning your entire home directory into a git repository tends to pull in unintended files. The common solutions compare as follows.
| Method | Mechanism | Notes |
|---|---|---|
| Symlinks (GNU stow) | Store dotfiles in a separate directory and symlink them into $HOME | Simple, but symlink management gets messy |
| Bare git repository | Keep only .git in $HOME and use home as the worktree | No extra tooling, but higher risk of mistakes |
| chezmoi | Separates source and target, offers templates and diff-based apply | Idempotency, templates, and diff review built in |
| dotbot / home-manager, etc. | Declarative dotfile management | Learning curve per ecosystem |
chezmoi is a single Go binary with these strengths:
- Separates the source directory (managed reality) from the home directory (target)
- Idempotent application via
diff/apply - Templates (
{{ }}) for per-machine variations - Integration with password managers
Core concepts: source and target
chezmoi manages two states: the source state and the target state.
- Source state: the
dotfiles/directory in your repository (the managed reality) - Target state: your actual home directory (
~/.zshrc, etc.)
The convention is to prefix filenames with dot_. dot_zshrc corresponds to ~/.zshrc.
dotfiles/
├── dot_zshrc → ~/.zshrc
├── dot_bashrc → ~/.bashrc
├── dot_profile → ~/.profile
└── dot_gitconfig → ~/.gitconfig
apply moves from source to target; add imports from target (a real file in $HOME) back into the source.
Common workflow
Operating through a thin wrapper script (chezmoi.sh) saves you from specifying the source directory every time.
chezmoi diff # Review the diff that would be applied (do this first)
chezmoi apply # Apply the source to your home
chezmoi add ~/.zshrc # Import a change from home into the source
chezmoi edit dot_zshrc # Edit the source
# Example wrapper script (chezmoi.sh)
exec chezmoi --source "$(dirname "$0")/dotfiles" "$@"
The operating principle is: never edit the home side directly.
- To change a setting → edit
dotfiles/in the repo andapply - If you already edited the home side →
addto import it (always review the diff)
This keeps home-only changes out of the repository and preserves reproducibility.
Separating machine-specific and secret settings
Tracking everything in git risks leaking secrets and machine-specific values. The standard approach is to source a .local file at the end of the main dotfile.
# End of .zshrc
[ -f "$HOME/.zshrc.local" ] && source "$HOME/.zshrc.local"
Keep only a template (zshrc.local.example) in the repository and create the real file on each machine. Put secrets such as tokens in this .local file (with chmod 600 if needed).
Keeping Git credentials out of dotfiles
When managing .gitconfig, the key point is to avoid putting credentials in dotfiles. You can reuse the credentials held by the GitHub CLI (gh) as a credential helper, keeping only the configuration in your dotfiles while the actual token lives with the tool.
# Example ~/.gitconfig
[init]
defaultBranch = main
[credential "https://github.com"]
helper =
helper = !gh auth git-credential
gh auth git-credential uses the credentials managed by gh as a Git credential helper. The actual token lives under ~/.config/gh/, never in your dotfiles.