Skip to main content

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.

MethodMechanismNotes
Symlinks (GNU stow)Store dotfiles in a separate directory and symlink them into $HOMESimple, but symlink management gets messy
Bare git repositoryKeep only .git in $HOME and use home as the worktreeNo extra tooling, but higher risk of mistakes
chezmoiSeparates source and target, offers templates and diff-based applyIdempotency, templates, and diff review built in
dotbot / home-manager, etc.Declarative dotfile managementLearning 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.

  1. To change a setting → edit dotfiles/ in the repo and apply
  2. If you already edited the home side → add to 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.

References​