I have configuration files scattered across my home directory: Hammerspoon, Helix, tmux, lazygit, Ghostty, zsh, and more. I wanted a simple way to back them up to a private GitHub repo without symlinks, without extra tools, and without accidentally tracking my entire home directory.
A bare Git repo does exactly this. No dependencies, no wrapper tools, just Git.
This post is part of my terminal workflow series.
How it works
A normal git init creates a .git folder in the current directory and tracks everything under it. A bare Git repo separates the Git database from the working tree. The database lives in ~/projects/.dotfiles, the working tree is your entire $HOME, but it only tracks files you explicitly add. Everything else is invisible to it.
Setup
Step 1: Create the bare repo
git init --bare ~/projects/.dotfilesStep 2: Add the alias
Add this to your ~/.zshrc:
alias dot='git --git-dir=$HOME/projects/.dotfiles --work-tree=$HOME'Reload your shell:
source ~/.zshrcStep 3: Hide untracked files
This is the key step. Without it, dot status would list every single file in your home directory.
dot config status.showUntrackedFiles noStep 4: Create the remote repo
Create a private repo on GitHub (e.g. dotfiles), then:
dot branch -M mainStep 5: Add your config files
Only the files you explicitly add get tracked. Nothing else.
dot add ~/.zshrcdot add ~/.config/tmux/tmux.confdot add ~/.config/helix/config.tomldot add ~/.config/helix/languages.tomldot add ~/.config/lazygit/config.ymldot add ~/.config/ghostty/configdot add ~/.hammerspoon/init.luadot add ~/Library/LaunchAgents/com.local.capslock-remap.plistCommit and push:
dot commit -m "initial dotfiles"dot push -u origin mainStep 6: One command to back up everything
Add this function to your ~/.zshrc:
dotpush() { dot add -u dot commit -m "update $(date +%Y-%m-%d)" dot push}dot add -u stages changes only to files that are already tracked. It will never pick up new files, only updates and deletions to files you previously added. So if you create a new config file, you still need to dot add it once manually. After that, dotpush handles it.
Reload your shell:
source ~/.zshrcFrom now on, run dotpush and all your tracked configs get committed and pushed with today’s date.
Adding new files later
Whenever you set up a new tool or change where a config lives:
dot add ~/.config/new-tool/config.tomldot commit -m "add new-tool config"dot pushAfter that first dot add, the file is tracked and dotpush will pick up future changes automatically.
Checking what is tracked
dot ls-filesThis lists every file the repo knows about. Useful to verify you have not missed anything, or accidentally added something you should not.
To see what changed since the last commit:
dot statusdot diffRestoring on a new Mac
Step 1: Clone the bare repo
Step 2: Add the alias
alias dot='git --git-dir=$HOME/projects/.dotfiles --work-tree=$HOME'Step 3: Checkout
dot checkoutIf this fails with an error about existing files (e.g. macOS creates a default .zshrc), back them up first:
mkdir -p ~/projects/.dotfiles-backupdot checkout 2>&1 | grep "^\t" | sed 's/^\t//' | while read -r file; do mkdir -p ~/projects/.dotfiles-backup/$(dirname "$file") mv "$file" ~/projects/.dotfiles-backup/"$file"donedot checkoutThis moves conflicting files to ~/projects/.dotfiles-backup so you can compare them later.
Step 4: Hide untracked files
dot config status.showUntrackedFiles noStep 5: Verify
dot ls-filesAll your configs are in place. Install your tools (brew install --cask hammerspoon, etc.) and everything picks up its config automatically.
Full restore script
For convenience, save this as a gist or keep it in your repo README. Run it on a fresh Mac after installing Git and setting up SSH keys:
alias dot='git --git-dir=$HOME/projects/.dotfiles --work-tree=$HOME'
# Back up any conflicting filesmkdir -p ~/projects/.dotfiles-backupdot checkout 2>&1 | grep "^\t" | sed 's/^\t//' | while read -r file; do mkdir -p ~/projects/.dotfiles-backup/$(dirname "$file") mv "$file" ~/projects/.dotfiles-backup/"$file"done
dot checkoutdot config status.showUntrackedFiles no
echo "Dotfiles restored. Conflicts (if any) moved to ~/projects/.dotfiles-backup"What not to track
Do not add files that contain secrets: SSH keys, API tokens, .env files. The repo is private, but secrets do not belong in Git. If you need to back up secrets, use a password manager or encrypted storage.
Also avoid large or machine-specific files like caches, logs, or anything under ~/Library that is not a LaunchAgent or a config plist you explicitly manage.
File locations
~/projects/.dotfiles/ # bare Git database (hidden, do not touch)~/.zshrc # contains the dot alias and dotpush functionThe whole point is that your config files stay exactly where they are. No symlinks, no special directories, no wrapper tools. Just Git.