Dotfile Management Strategies
Purpose: Read this when choosing or applying stow, chezmoi, yadm, bare Git, Brewfile, or XDG migration patterns for personal dotfiles.
Contents
- GNU Stow
- chezmoi
- yadm
- Bare Git repository
- Brewfile management
- XDG Base Directory specification
- Selection guide
GNU Stow
Recommended for a single machine or a simple shared layout.
Directory Structure
~/dotfiles/
├── zsh/
│ ├── .zshrc
│ └── .config/
│ └── zsh/
├── nvim/
│ └── .config/
│ └── nvim/
├── ghostty/
│ └── .config/
│ └── ghostty/Rule: each package mirrors the target path under $HOME.
Commands
stow -d ~/dotfiles -t ~ zsh
cd ~/dotfiles && stow */
stow -d ~/dotfiles -t ~ -D zsh
stow -d ~/dotfiles -t ~ -R zsh
stow -d ~/dotfiles -t ~ -n -v zsh.stow-local-ignore
\.git
\.gitignore
\.gitmodules
README\.md
LICENSE
^\.DS_StoreMulti-Machine Pattern
~/dotfiles/
├── zsh/
├── nvim/
├── zsh-work/
├── zsh-personal/
└── linux-specific/cd ~/dotfiles && stow zsh nvim zsh-workchezmoi
Recommended for multiple machines, template logic, or secret integration.
Workflow
chezmoi init
chezmoi add ~/.zshrc
chezmoi edit ~/.zshrc
chezmoi diff
chezmoi applyGo Template Example
{{ if eq .chezmoi.os "darwin" -}}
eval "$(/opt/homebrew/bin/brew shellenv)"
{{ end -}}Config Example
[data]
name = "Your Name"
email = "{{ if eq .chezmoi.hostname "work-laptop" }}work@company.com{{ else }}personal@email.com{{ end }}"Secret Management
{{ onepasswordRead "op://vault/item/field" }}
{{ (bitwarden "item" "My SSH Key").notes }}
chezmoi add --encrypt ~/.ssh/id_rsa
{{ (keyring "service-name" "username") }}Naming Conventions
| Prefix | Meaning | Example |
|---|---|---|
dot_ |
Leading dot | dot_zshrc |
private_ |
0600 permissions |
private_dot_ssh/ |
executable_ |
Executable file | executable_dot_local/bin/script |
modify_ |
Modify existing file | modify_dot_zshrc |
create_ |
Create if missing | create_dot_gitconfig |
symlink_ |
Create symlink | symlink_dot_config/nvim |
.tmpl |
Template | dot_zshrc.tmpl |
yadm
Basic Workflow
yadm init
yadm add ~/.zshrc ~/.config/nvim/init.lua
yadm commit -m "initial dotfiles"
yadm remote add origin <repo-url>
yadm push -u origin main
yadm clone <repo-url>Alt Files
~/.config/zsh/aliases.zsh##os.Darwin
~/.config/zsh/aliases.zsh##os.Linux
~/.config/zsh/aliases.zsh##h.work-laptop
~/.config/zsh/aliases.zsh##defaultBootstrap Script
#!/bin/bash
if ! command -v brew &>/dev/null; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
brew bundle --file="$HOME/Brewfile"
nvim --headless "+Lazy! sync" +qaBare Git Repository
Minimal approach when you want Git only and no templates.
Setup
git init --bare $HOME/.dotfiles
alias dotfiles='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles config --local status.showUntrackedFiles noWorkflow
dotfiles add ~/.zshrc
dotfiles commit -m "add zshrc"
dotfiles push
git clone --bare <repo-url> $HOME/.dotfiles
dotfiles checkoutLimitation: no templates, no secret management, and weak multi-machine support.
Brewfile Management
Structure
tap "homebrew/bundle"
tap "homebrew/cask-fonts"
brew "git"
brew "neovim"
brew "tmux"
brew "starship"
brew "sheldon"
brew "mise"
brew "ripgrep"
brew "fd"Commands
brew bundle
brew bundle --file=~/dotfiles/Brewfile
brew bundle dump --force
brew bundle check
brew bundle cleanup
brew bundle cleanup --forceTips
- Keep
Brewfilein the dotfiles repo. - Prefer
brew bundle dump --describewhen you want comments. - Split
BrewfileandBrewfile.workif machines differ. - Run
brew bundle checkin bootstrap scripts or CI.
XDG Base Directory Specification
Variables
| Variable | Default | Purpose |
|---|---|---|
XDG_CONFIG_HOME |
~/.config |
Config files |
XDG_DATA_HOME |
~/.local/share |
Data files |
XDG_STATE_HOME |
~/.local/state |
State and history |
XDG_CACHE_HOME |
~/.cache |
Cache |
XDG_RUNTIME_DIR |
/run/user/$UID |
Runtime sockets and pipes |
Tool Support
| Tool | XDG support | Location |
|---|---|---|
neovim |
Native | $XDG_CONFIG_HOME/nvim/ |
ghostty |
Native | $XDG_CONFIG_HOME/ghostty/ |
tmux |
Since 3.1 |
$XDG_CONFIG_HOME/tmux/tmux.conf |
starship |
Native | $XDG_CONFIG_HOME/starship.toml |
zsh |
Manual | ZDOTDIR=$XDG_CONFIG_HOME/zsh |
bash |
No | ~/.bashrc, ~/.bash_profile |
Migration Steps
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
export ZDOTDIR="$XDG_CONFIG_HOME/zsh"
mkdir -p "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME"Selection Guide
| Scenario | Recommended | Reason |
|---|---|---|
| Single machine, simple setup | GNU Stow |
Transparent symlinks |
| Multiple machines, different configs | chezmoi |
Templates and conditionals |
| Git power user, minimal tooling | bare Git repo | No extra dependency |
Existing yadm user |
yadm |
Familiar workflow |
| Fewer than ~10 files | Manual symlinks or bare Git | Lower overhead |
Decision Flow
Need machine-specific configs?
├── Yes -> Need secrets management?
│ ├── Yes -> chezmoi
│ └── No -> chezmoi or yadm
└── No -> Need more than ~10 config files?
├── Yes -> GNU Stow
└── No -> Manual symlinks or bare GitOutput Routing Table (SKILL.md excerpt)
| Signal | Approach | Primary output | Read next |
|---|---|---|---|
zsh, bash, fish, shell, aliases |
Shell configuration | Shell config files | reference/personal-environment/shell-configs.md |
ghostty, alacritty, kitty, wezterm, terminal |
Terminal configuration | Terminal config file | |
neovim, vim, nvim, zed, editor |
Editor configuration | Editor config files | reference/personal-environment/editor-configs.md |
tmux, starship, powerlevel10k, prompt |
Multiplexer/prompt setup | tmux.conf or starship.toml | |
dotfiles, stow, chezmoi, yadm |
Dotfile management | Manager config + symlinks | reference/personal-environment/dotfile-management.md |
startup, slow, performance, benchmark |
Startup optimization | Benchmark results + optimized config | reference/personal-environment/shell-config-anti-patterns.md |
audit, anti-pattern, review config |
Config audit | Audit report with findings | Domain-specific anti-pattern reference |
mise, asdf, homebrew, brew |
Package management | Brewfile or mise config | reference/personal-environment/dotfile-management.md |
secret, leak, gitleaks, security |
Secret scanning setup | Pre-commit hook config + scan results | reference/personal-environment/dotfile-security-anti-patterns.md |
bootstrap, new machine, onboarding |
Bootstrap automation | Idempotent setup script + verification | reference/personal-environment/dotfile-management.md |
applescript, jxa, osascript, apple events, mac automation, finder, system events |
macOS app automation | Runnable script + TCC permission setup | reference/personal-environment/applescript-patterns.md |
ui scripting, gui scripting, accessibility, no dictionary |
UI scripting fallback | System Events-based script | reference/personal-environment/ui-scripting.md |
| unclear environment request | Environment scan + recommendation | SCAN results + plan | reference/personal-environment/shell-configs.md |
Routing rules:
- If the request mentions shell or startup time, read
reference/personal-environment/shell-configs.md. - If the request mentions editor or neovim, read
reference/personal-environment/editor-configs.md. - If the request mentions audit or anti-patterns, read the relevant anti-pattern reference.
- Always run SCAN phase before making changes.