Dotfiles Management — chezmoi vs yadm vs raw symlinks, Mac Edition
Comparing tools to cleanly replicate the same dev environment across machines, plus a practical chezmoi setup.
If you still copy-paste .zshrc, .gitconfig, and ~/.config/... files every time you get a new Mac, you need a dotfiles management tool. Set it up once, and a new machine restores its full configuration with a single command.
This guide compares three approaches (chezmoi · yadm · raw symlink + Git) and walks through the recommended chezmoi setup in detail.
TL;DR
| Tool | Strengths | Weaknesses | When to use |
|---|---|---|---|
| chezmoi (recommended) | Templates / per-machine branching / secrets integration / bidirectional apply | Learning curve (Go templates) | 1+ machine, mixed OS |
| yadm | Git wrapper, zero learning cost | Weak per-machine branching | Simple single-OS |
| raw symlink + Git | Zero dependencies, transparent | You write everything yourself | Minimalist |
1. Why a Dotfiles Tool
Putting .zshrc directly in a Git repo and symlinking it works. But these problems accumulate:
- Per-machine differences: Work Mac's
git user.emailvs personal. Hard-coded branching breaks. - Secrets: API keys / SSH config mixed in is dangerous. Easy to forget
.gitignore. - Bidirectional: If you edit
~/.zshrcdirectly, the repo drifts and you lose track of which is source of truth. - New machine: clone → install deps → symlink → permissions… every time.
A dotfiles tool wraps this into a consistent workflow.
2. Options Compared
chezmoi
Go-based CLI. Template engine (per-machine branching), secrets (1Password / Bitwarden integration), bidirectional (chezmoi diff / chezmoi apply).
yadm
A Git wrapper that treats dotfiles in ~ as a direct Git repository. Zero learning cost. Per-machine branching is supported but limited (alt files like .zshrc##os.Darwin).
Raw symlink + Git
Put files in ~/dotfiles/ and symlink with an install script. Most transparent, but you build every feature yourself.
Recommendation: chezmoi
For a single operator with 1–3 machines, chezmoi wins decisively. A one-hour learning investment pays back for life.
3. chezmoi Setup
3.1 Install
brew install chezmoi3.2 Prepare a GitHub Repo
Create a private dotfiles repo on GitHub (name is your choice). Empty is fine.
3.3 Initialize chezmoi
chezmoi init github.com/yourname/dotfiles
# Or, if SSH is available:
chezmoi init git@github.com:yourname/dotfiles.git~/.local/share/chezmoi/ becomes the working tree (the actual Git repo).
3.4 Add Files
# Put .zshrc under chezmoi management
chezmoi add ~/.zshrc
# Or multiple at once
chezmoi add ~/.gitconfig ~/.tmux.conf ~/.config/starship.tomlFiles are copied as ~/.local/share/chezmoi/dot_zshrc (leading dot → dot_ prefix).
3.5 Commit + Push
chezmoi cd # jump to the work tree
git add .
git commit -m "init dotfiles"
git push -u origin main
exit # back to where you were3.6 Restore on a New Machine
# On the new Mac
brew install chezmoi
chezmoi init --apply github.com/yourname/dotfilesDone. All dotfiles materialize in ~ and apply immediately.
4. Per-Machine Branching (Templates)
chezmoi's strongest feature. The same .gitconfig adapts to work vs personal:
~/.local/share/chezmoi/dot_gitconfig.tmpl
Contents:
[user]
name = Your Name
{{- if eq .chezmoi.hostname "Work-MacBook" }}
email = me@company.com
signingkey = AAAA....
{{- else }}
email = personal@example.com
{{- end }}
[core]
editor = nvim
autocrlf = inputThe .tmpl extension marks a template. chezmoi branches based on hostname when applying.
4.1 Available Variables
chezmoi data # print all context variablesCommonly used:
.chezmoi.os—darwin/linux/windows.chezmoi.hostname— machine name.chezmoi.arch—arm64/amd64- Custom — add with
chezmoi edit-config
4.2 OS-Specific Files
Rename dot_zshrc to dot_zshrc.tmpl and branch by OS:
{{ if eq .chezmoi.os "darwin" -}}
# macOS only
export HOMEBREW_PREFIX="/opt/homebrew"
eval "$($HOMEBREW_PREFIX/bin/brew shellenv)"
{{ else if eq .chezmoi.os "linux" -}}
# Linux only
export PATH="$HOME/.linuxbrew/bin:$PATH"
{{ end -}}
# Shared
alias ll='ls -lah'5. Secrets Integration
Never write API keys or tokens directly in dotfiles. Use chezmoi's secret-tool integration:
1Password CLI
brew install --cask 1password 1password-cli
op signindot_ssh/config.tmpl:
Host github.com-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
PreferredAuthentications publickey
dot_env.tmpl referencing secrets:
export ANTHROPIC_API_KEY={{ onepasswordRead "op://Private/Anthropic/api_key" }}
export GITHUB_TOKEN={{ onepasswordRead "op://Private/GitHub/token" }}
When you chezmoi apply, it fetches values from 1Password and writes a plaintext .env. That plaintext file is .gitignored, so it never enters the repo.
Bitwarden / pass / age
Each has official chezmoi integration — see docs.chezmoi.io.
6. Workflow
Daily
# After editing ~/.zshrc directly
chezmoi diff # see what changed
chezmoi add ~/.zshrc # propagate back to the repo
chezmoi cd && git commit -am "tweak zshrc" && git pushSync Another Machine
chezmoi update # git pull + applyPreview Changes
chezmoi diff # changes before applying
chezmoi apply -v # verbose applyEdit Source
chezmoi edit ~/.zshrc # edit source (= repo) and auto-apply7. yadm — The Lightest Alternative
If zero learning cost is your priority, yadm:
brew install yadm
yadm init
yadm add ~/.zshrc
yadm commit -m "init"
yadm remote add origin git@github.com:yourname/dotfiles.git
yadm push -u origin main
# Other machine
yadm clone git@github.com:yourname/dotfiles.gitCommands are identical to git (only the binary differs). Drawback: per-machine branching is limited to alt files like .zshrc##os.Darwin.
8. Raw Symlink + Git
Minimum dependencies:
mkdir ~/dotfiles && cd ~/dotfiles
git init
mv ~/.zshrc ~/dotfiles/zshrc
ln -s ~/dotfiles/zshrc ~/.zshrc
# install.sh
#!/usr/bin/env bash
for f in zshrc gitconfig tmux.conf; do
ln -sf "$HOME/dotfiles/$f" "$HOME/.$f"
doneClean, but you implement secrets, branching, and bootstrap yourself. Most people migrate to chezmoi within a year.
How to Verify
chezmoi diff→ no changes (everything applied)- Edit
~/.zshrcdirectly →chezmoi diffshows the diff - On a new machine (or clean VM)
chezmoi init --apply github.com/you/dotfiles→ all dotfiles restored - After modifying a template,
chezmoi apply -v→ different output based on hostname - With 1Password integration,
chezmoi applywrites plaintext to~/.env, but the repo doesn't contain it
Troubleshooting
chezmoi apply doesn't change anything
- Check
chezmoi diff. If empty, that's expected. - File permission differences can hide changes — try
chezmoi apply --force.
Template syntax errors
{{ ... }} is Go templates. Missing closing -}} and variable typos are common. Dry-run with chezmoi execute-template < dot_gitconfig.tmpl.
1Password integration fails
- After
op signin, session is valid for ~30 minutes by default. Runeval $(op signin)again. - Enable 1Password explicitly in
chezmoi.toml(chezmoi edit-config).
Missing SSH keys on a new machine
Never store SSH keys themselves in dotfiles (security). Generate new keys on the new machine and register them with GitHub. chezmoi should only manage configs like ~/.ssh/config.
~/.zshrc doesn't update via direct edit
Use chezmoi edit ~/.zshrc (edits source, auto-applies). Or edit source directly and chezmoi apply.
References
- Mac initial setup — Homebrew etc. before dotfiles
- Mac↔Win file sync — Syncthing for notes/projects, separate from dotfiles
- chezmoi (official)
- yadm
Changelog
- 2026-05-12: First draft. Three approaches compared + chezmoi practical setup (templates / secrets / workflow) + five troubleshooting cases.