Manage Your Dotfiles So a New Machine Takes Minutes
13 Apr 2026The standard advice is to symlink dotfiles from a repo into your home directory with a script. That works until the script itself breaks, or you forget which files it manages. A bare git repo skips the symlinks and the script entirely — your home directory is the working tree.
Set it up
git init --bare "$HOME/.dotfiles"
alias dotfiles='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles config --local status.showUntrackedFiles no
That last line is the one people miss, and without it dotfiles status lists every single file in your home directory as untracked — useless noise. With it, dotfiles status only ever shows files you’ve already added.
Add the alias permanently to your shell config:
echo "alias dotfiles='git --git-dir=\$HOME/.dotfiles/ --work-tree=\$HOME'" >> ~/.zshrc
Use it exactly like git, just with a different name
dotfiles add ~/.zshrc ~/.gitconfig ~/.config/nvim/init.lua
dotfiles commit -m "add zsh and nvim config"
dotfiles push
No symlinks, no stow, no separate directory you have to remember to sync. The files live where every tool expects them, and dotfiles is just git pointed at a different .git directory (--git-dir) with your home directory as the working tree (--work-tree) instead of a repo subfolder.
Set up a remote
dotfiles remote add origin git@github.com:you/dotfiles.git
dotfiles push -u origin main
Use a private repo unless you’ve audited every file for tokens, API keys, and internal hostnames. ~/.aws/credentials living two directories over from ~/.zshrc is exactly the kind of thing that ends up in a commit by accident with dotfiles add . — never run add . with this setup, always add files by explicit path.
Bootstrap a new machine
git clone --bare git@github.com:you/dotfiles.git "$HOME/.dotfiles"
alias dotfiles='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
dotfiles checkout
The checkout step fails if any of the files it’s about to place already exist — a fresh macOS install always has a stock .bashrc or .zshrc sitting there. Back the conflicting files up and retry:
mkdir -p ~/.dotfiles-backup
dotfiles checkout 2>&1 | grep -E "^\s+" | awk '{print $1}' | \
xargs -I{} mv {} ~/.dotfiles-backup/{}
dotfiles checkout
That grabs the list of conflicting paths straight from git’s own error output and moves them aside in one pass, so you don’t do it file by file.
What to actually track
Track configs, not secrets and not machine-specific state:
.zshrc,.bashrc,.gitconfig,.vimrc/ nvim config,.tmux.conf.config/subfolders for tools you use across machines
Don’t track: .ssh/ private keys, .aws/credentials, anything with a token in it, shell history files, or per-machine state like .zsh_sessions.
Handle machine-specific differences without branching
# in .gitconfig
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
Git’s own includeIf handles the common case — a different email for work repos — without you maintaining separate dotfiles branches per machine. For anything more divergent, gate it in the shell config itself:
if [[ "$(hostname)" == "work-laptop" ]]; then
export JAVA_HOME=/usr/lib/jvm/work-jdk
fi
Why this beats a dotfiles/ folder with a symlink script
A symlink script is more code to maintain, and it’s one more thing that can be out of date or broken on a machine you haven’t touched in months. The bare-repo approach has zero moving parts beyond git itself, which is already installed everywhere you’d want your dotfiles anyway.