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/config instead of ~/.gitconfig (git supports both since 1.7.12).
  • ~/.config/nvim/init.lua for Neovim.
  • ~/.config/zsh/.zshrc plus ZDOTDIR=~/.config/zsh in /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 an apply model. 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 zsh symlinks 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-repo or bfg. See git.
  • Run gitleaks detect as 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 ~/.zshenv outside 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.