Overview

Dotfiles are the per-user config that follows a developer across machines: shell, editor, git, terminal. A good setup rebuilds a new laptop with one command and keeps secrets out of the repo. The rules cover the XDG layout, sync tools, secrets, per-machine values, and bootstrap. Project config is in config-files.

Prefer ~/.config/ for tools that support it

The XDG Base Directory spec puts per-user config under ~/.config/<tool>/.

  • ~/.config/git/config instead of ~/.gitconfig (git has read it since 1.7.12).
  • ~/.config/nvim/init.lua, ~/.config/starship.toml, ~/.config/alacritty/alacritty.toml, ~/.config/wezterm/wezterm.lua.
  • Zsh: set ZDOTDIR=~/.config/zsh in ~/.zshenv, then keep .zshrc under it.

Tools that still demand top-level files (.bashrc, .profile, .ssh/config) keep their paths.

Sync with chezmoi or stow

  • chezmoi keeps a source tree and applies it. It supports templates, per-machine differences (.chezmoi.toml.tmpl, templated .chezmoiignore), password-manager integration, and encrypted files. Use it across mixed machines.
  • GNU stow symlinks directories from the repo into a target. It has no templating. Use it when machines are alike.

Both reduce a new-machine bootstrap to git clone plus one command.

Lay out stow packages to mirror the target path

Each stow package contains the path as it should appear under the target, so stow nvim links ~/.config/nvim/init.lua only when the package holds that path.

dotfiles/
├── git/.config/git/config
├── nvim/.config/nvim/init.lua
├── zsh/.zshenv
└── zsh/.config/zsh/.zshrc

Run stow git nvim zsh from the repo root. A package of nvim/init.lua would link ~/init.lua, which is the usual stow mistake. chezmoi uses its own source naming (dot_zshrc, private_dot_ssh/) instead.

Keep secrets out of the repo

  • Keep secrets in a password manager (1Password CLI, Bitwarden CLI, pass, gopass) and reference them at use: export OPENAI_API_KEY="$(op read 'op://Personal/OpenAI/token')".
  • Run a scanner as a pre-commit hook: gitleaks git --pre-commit --staged (gitleaks deprecated detect and protect in 8.19.0).
  • A leaked secret is compromised. Rotate it, then clean history with git filter-repo or BFG; see git.

A .gitignore entry is not protection. A scanner is.

Keep work and personal config in separate repos

Public dotfiles hold prompt, editor, and generic aliases. Private ones hold work email in git config, internal SSH hosts, and VPN scripts. Do not mix them in one repo with the private parts gitignored, which is one git add -A away from a leak. With chezmoi, private_ only sets restrictive file permissions; it does not make a file private to the repo, so use a separate repo or .chezmoiignore per machine for that.

Route values by how they vary

  • Same on every machine: a dotfile (editor settings, aliases, prompt, git user name).
  • Different per machine: an environment variable set in a gitignored file such as ~/.config/zsh/local.zsh, sourced if present. Hardcoding /Users/alice/work is wrong; use $WORK_DIR.
  • A secret: the password manager, referenced from either.

Make the bootstrap script idempotent

A bootstrap.sh at the repo root installs the package manager, chezmoi, and the toolchain, then runs chezmoi init --apply <github-user>. A second run on a healthy machine should change nothing. Script conventions are in shell.