Overview
Dotfiles are the per-user configuration that follows a developer across machines: shell, editor, git, terminal, language toolchains. A good dotfile setup boots a new laptop in under an hour. A bad one stays scattered across $HOME with secrets mixed in. The rules below cover the XDG layout, sync tooling, public/private splits, and the rule that distinguishes a dotfile from an env var. They pair with config-files for project-level config.
Prefer ~/.config/ over $HOME for new tools
The XDG Base Directory Specification puts per-user config under ~/.config/<tool>/. Use it for everything that supports it.
~/.config/git/configinstead of~/.gitconfig(git supports both since 1.7.12).~/.config/nvim/init.luafor Neovim.~/.config/zsh/.zshrcplusZDOTDIR=~/.config/zshin/etc/zsh/zshenv(or~/.zshenv).~/.config/starship.toml,~/.config/alacritty/alacritty.toml,~/.config/wezterm/wezterm.lua.
$HOME stays clean and the entire config tree is one directory away. Tools that still demand top-level dotfiles (.bashrc, .profile, .ssh/config) keep their old paths; do not fight the toolchain.
Use chezmoi or stow to sync across machines
A dotfiles git repo plus a sync tool beats every shell-script symlink installer. Pick one:
- chezmoi. Template engine, machine-specific overrides via
.chezmoi.toml.tmpl, secret integration with 1Password and Bitwarden, and anapplymodel. Best for heterogeneous fleets (work laptop, personal laptop, Linux server). - GNU stow. Pure symlinks, no templating. Each tool’s config lives in its own directory and
stow zshsymlinks the contents into$HOME. Best when machines are similar and templating is overkill.
Both produce a single source of truth in a git repo. New machine bootstrap reduces to git clone plus one command.
Never commit secrets to a dotfiles repo
A dotfiles repo is usually public or semi-public; it is the wrong place for API keys, tokens, or private SSH keys.
- Store secrets in a password manager (1Password CLI, Bitwarden CLI,
pass,gopass). - Reference them from dotfiles via chezmoi templates or shell
command substitution:export OPENAI_API_KEY="$(op read 'op://Personal/OpenAI/token')". - If a secret lands in git history, treat it as compromised. Rotate and force-push only after running
git filter-repoorbfg. See git. - Run
gitleaks detectas a pre-commit hook on the dotfiles repo.
.gitignore is not enough. A scanner is.
Split public from private
Some dotfiles are safe to publish; others contain machine identifiers, internal hostnames, or work-only aliases. Split them.
- Public repo: shell prompt, editor config, generic aliases, public language settings. Visible to recruiters and collaborators.
- Private repo: work email in
git/config, internal SSH hosts in~/.ssh/config, company VPN scripts. Visible only to the author.
chezmoi handles this natively with private_ file prefixes and per-source configuration. With stow, use two separate repos and stow them into the same target.
Never blend the two. A single repo with secrets behind .gitignore is one accidental git add -A from a leak.
Per-machine config goes in env vars; per-user config goes in dotfiles
The rule that ends most “which file should this go in?” debates:
- If the value changes per machine, it is an env var. Hostname, work username, monitor DPI, machine-specific paths. Set them in
~/.config/zsh/local.zsh(gitignored), or in~/.zshenvoutside the dotfiles repo. - If the value is the same across every machine, it is a dotfile. Editor preferences, shell aliases, prompt theme, git user name.
- If the value is a secret, it is in the password manager, referenced from the dotfile or env file.
The corollary: a dotfile that hardcodes /Users/alice/work is wrong. Use $HOME or $WORK_DIR and set WORK_DIR per machine.
Bootstrap script earns its keep
A bootstrap.sh (or install.sh) at the dotfiles repo root takes a fresh machine and installs the toolchain. Keep it small and idempotent.
#!/usr/bin/env bash
set -euo pipefail
# Install Homebrew (macOS) or apt packages (Linux).
# Install chezmoi.
# Run `chezmoi init --apply <github-user>`.
# Install language toolchains (rustup, mise, fnm).The script should be re-runnable. A second invocation should change nothing on a healthy machine. See shell for the script-writing rules.
Organize the repo by tool, not by file type
A dotfiles repo’s directory layout should mirror the destination structure under ~/.config/.
dotfiles/
├── README.md
├── bootstrap.sh
├── git/
│ └── config
├── nvim/
│ └── init.lua
├── zsh/
│ ├── .zshrc
│ └── aliases.zsh
└── starship/
└── starship.toml
When the layout mirrors the target, stow git Just Works and chezmoi templates stay readable. Avoid lumping everything into a flat files/ directory; the structure is the documentation.