This setup allows for managing configuration files (dotfiles) directly in the
$HOME directory using Git, without the need for symlinks, specialized
management tools, or messy directory structures.
The core of the system is a bare Git repository located at ~/.dotfiles/.
Unlike a standard repository, a bare repo doesn't have a default working
directory. We manually point its "working tree" to $HOME using a simple shell
alias.
Add this to your .zshrc:
alias dot='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'The shell automatically sources all *.zsh files in ~/.zshrc.d/. Modular configurations include:
shell-power.zsh: (Tracked) Common interactive enhancements (fzf, zoxide, eza).mac-local.zsh: (Tracked) Mac-specific PATH and environment overrides.secrets.zsh: (Untracked) Private API keys.
This setup uses modular Brewfiles to share configuration between Linux and macOS:
~/.Brewfile.shared: CLI tools used on both platforms (e.g.,bat,eza,fzf).~/.Brewfile.mac: macOS-specific GUI apps and development tools.~/.Brewfile.linux: Linux-specific Flatpaks and system fonts.
A custom Zsh function is included in ~/.zshrc to sync your environment:
brewall # Updates brew and installs tools from shared + platform-specific filesBecause the working tree is your entire $HOME directory, a standard git status would attempt to list every single untracked file you own—downloads, cache, temporary files, everything. We call this "Listing the Universe."
To prevent this, we employ a double-layered defense:
- Untracked Filter: We tell Git to ignore untracked files by default in the local config:
dot config --local status.showUntrackedFiles no
- Global "Ignore All": We use a
*wildcard in~/.dotfiles/info/excludeto ignore everything by default, and then explicitly "un-ignore" only the files we want to track (e.g.,!.zshrc).
This ensure that dot status remains lightning-fast and only shows changes to files you have explicitly chosen to track. It's the difference between a curated collection and a digital hoard.
Because this is a bare repository with the working tree at $HOME, some critical configuration lives inside the ~/.dotfiles/ directory itself and cannot be tracked by Git. When setting up a new machine, you must manually recreate these:
- Untracked Filter: To keep
dot statusclean, ignore untracked files:dot config --local status.showUntrackedFiles no
- Secret Exclusion: Add
.zshrc.d/secrets.zshto the local Git exclude list:echo ".zshrc.d/secrets.zsh" >> $HOME/.dotfiles/info/exclude
- Secrets Content: Manually recreate
~/.zshrc.d/secrets.zshwith your API keys and private tokens. This file is sourced by~/.zshrcbut ignored by Git.
This environment is enhanced with modern CLI replacements:
zoxide(z): A smartercdcommand. Usez <fragment>orzifor interactive selection.fzf: Fuzzy finder for history (Ctrl+r) and files (Ctrl+t).eza: A modernls(aliased tolsanda). Features icons and Git status integration.bat: A moderncatwith syntax highlighting and line numbers.tmux: Terminal multiplexer with custom navigation and|/-splits. See cheat sheet.
- Zero Symlinks: Files live in their natural locations (e.g.,
~/.zshrc,~/.gitconfig). You don't need to manage a complex tree of symlinks or use tools like GNU Stow. - Gemini Settings: Your coding style and preferences are tracked in .gemini/GEMINI.md.
- Native Git Experience: Since
dotis just a standard Git command with specific flags, all your existing Git knowledge applies (dot status,dot add,dot commit,dot diff, etc.). - Portable & Lightweight: This works on any system with Git installed. No Python, Ruby, or Node.js dependencies are required.
- Clean Workflow: Only files you explicitly
dot addare tracked. The rest of your home directory remains invisible to Git.
dot add ~/.zshrc
dot commit -m "Add zshrc to dotfiles"dot status
dot diffdot push origin main
dot pull origin main- Clone the repo as a bare repository:
git clone --bare <your-repo-url> $HOME/.dotfiles
- Define the alias in your current shell session:
alias dot='/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'
- Checkout the content into your home directory. If you have existing
configuration files, this may fail. To resolve it, back them up first:
mkdir -p ~/.dotfiles-backup && \ dot checkout 2>&1 | grep -E "^\s+\." | awk '{print $1}' | \ xargs -I{} mv $HOME/{} ~/.dotfiles-backup/{} && \ dot checkout
After the initial checkout and brewall, perform these final steps:
- Set Default Shell:
chsh -s /bin/zsh - Initialize Volta:
volta install node@latest - Initialize Rust:
rustup-init(follow the prompts) - Verify Setup: Open a new terminal and ensure the Starship prompt appears.
Curious how this setup came to be? Check out the Great Dotfile Migration History for the full story.