Dotfiles are those configuration files that live in your home directory
— .bashrc, .zshrc, .vimrc, and countless others that define your
development environment. Over the years, I've tried various approaches to
manage them: from manual copying between machines, to symlink-based tools,
to dedicated dotfile managers. Each approach had its drawbacks.
A few years ago, I discovered a technique using a bare Git repository that solved all my pain points. Since then, I've used this setup across multiple machines, switching between macOS and Linux, and it has worked flawlessly.
The Problem with Common Approaches
Most dotfile management tools rely on storing your dotfiles in a dedicated
directory (like ~/dotfiles) and then creating symlinks to their actual
locations in your home directory. While this works, it introduces complexity:
- You need to maintain symlink scripts
- Tracking new files requires updating your setup
- Some applications don't handle symlinks well
- It's not immediately obvious which files are managed
I wanted something simpler: treat my home directory as a Git repository without
the usual mess of having a .git folder cluttering my actual home directory.
The Solution: A Bare Repository
A bare Git repository stores all Git metadata but no working tree. By
combining this with Git's --work-tree flag, we can track files in our
home directory while keeping the Git metadata tucked away in a hidden folder.
Here's the approach:
- Create a bare repository in
~/.cfg - Create a
configalias that operates on this repository - Use the alias just like you'd use
git
Initial Setup
Starting fresh on a new machine is straightforward:
# Create the bare repository
git init --bare $HOME/.cfg
# Create an alias to interact with the repository
alias config='/usr/bin/git --git-dir=$HOME/.cfg --work-tree=$HOME'
# Hide untracked files (optional but recommended)
config config --local status.showUntrackedFiles no
# Add the alias to your shell config so it persists
echo "alias config='/usr/bin/git --git-dir=$HOME/.cfg --work-tree=$HOME'" >> $HOME/.bashrc
Now you can start tracking your dotfiles:
# Add files you want to track
config add .zshrc
config add .vimrc
config add .gitconfig
# Commit them
config commit -m "Initial dotfiles commit"
# Add your remote repository (create one on GitHub/GitLab first)
config remote add origin git@github.com:<username>/dotfiles.git
# Push your changes
config push -u origin main
Restoring on a New Machine
When setting up a new machine, you clone your dotfiles in one command:
# Clone the repository
git clone --bare git@github.com:<username>/dotfiles.git $HOME/.cfg
# Create the alias (before using it)
alias config='/usr/bin/git --git-dir=$HOME/.cfg --work-tree=$HOME'
# Checkout the actual content from the repository
config checkout
# Hide untracked files
config config --local status.showUntrackedFiles no
If checkout fails because some files already exist, you can back them up:
mkdir -p ~/.config-backup
config checkout 2>&1 | egrep "\s+\." | awk {'print $1'} | xargs -I{} mv {} .config-backup/{}
config checkout
Daily Usage
Once set up, you use config exactly like you'd use git:
# Check status
config status
# Add a new file
config add .bashrc
# Commit changes
config commit -m "Update bash configuration"
# Push to remote
config push
# Pull changes from another machine
config pull
# View history
config log
# See differences
config diff
The beauty of this approach is that it's just Git — no learning curve beyond the alias.
Real-World Experience
I've been using this setup for several years now, and it has survived:
- Multiple machine switches: Moved between laptops at least four times
- Parallel machines: Simultaneously used a MacBook for personal work and a Linux workstation
- Cross-platform: Shared dotfiles between macOS and Linux with conditional logic where needed
- Collaboration: Kept work and personal configs separate with different branches
The key advantages I've experienced:
- No symlinks: Files live where they're expected
- Selective tracking: Only track what you explicitly add
- Standard Git workflow: Use branches, tags, and all Git features
- Easy sharing: Push to a private repository, clone anywhere
- Minimal overhead: Just an alias and a hidden directory
Considerations
A few things to keep in mind:
Security: Your dotfiles may contain sensitive information. Use a
private repository and consider using tools like git-crypt for extra
sensitive data, or simply don't commit files with secrets.
Platform differences: If you use both macOS and Linux, you might need conditional logic in your shell configs:
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS-specific settings
else
# Linux-specific settings
fi
Untracked files: Setting status.showUntrackedFiles no means
config status won't show untracked files. This is intentional
— otherwise, your entire home directory would appear as untracked.
When you want to add new files, you'll need to specify them explicitly.
Conclusion
This bare repository approach has proven to be the most robust dotfile management solution I've used. It leverages Git's power without introducing additional complexity or dependencies. The setup takes five minutes, and then you can forget about it — it just works.
If you're tired of fighting with dotfile managers or manually syncing configurations between machines, give this approach a try. It might just be the last dotfile setup you'll ever need.