Introduction
Your dotfiles those hidden .-prefixed configuration files scattered across your home directory are the muscle memory of your environment. They hold your shell aliases, your editor settings, your Git identity, your terminal theme. Lose them and a fresh machine feels like someone elseโs computer. Version them, and any machine becomes yours in a single clone.
This guide covers two battle-tested, Git-based strategies for managing them:
Info
Both approaches use plain Git no extra runtime, no proprietary format. They differ only in how the files reach your home directory: the bare repository checks them out in place, while the modular repository symlinks them in from a clone you control.
| Strategy | How files land in $HOME | Best when |
|---|---|---|
| Bare Git repository | Checked out directly into home | You want zero tooling and zero symlinks |
| Modular repo + bootstrap | Symlinked from a normal clone | You want modularity, opt-in modules, and man pages |
Pick whichever fits your taste both are shown below in full.
Prerequisites
Youโll be pushing your configuration to a private remote, so set up SSH first if you havenโt. My companion guide walks through it end to end: Git SSH Keys for GitHub, GitLab, and Bitbucket on Linux. New to the shell in general? Start with Introduction to Linux CLI.
Tip
Set your Git identity before committing anything, so the history is attributed correctly from the very first commit:
git config --global user.name 'YOUR_NAME'Strategy 1: The bare Git repository
The classic trick: initialize a bare repository in a discrete folder ($HOME/.dotfiles) and point its work-tree at $HOME. No symlinks, no copying your real home directory becomes the work-tree.
Initial setup
- Create a bare Git repository in your home directory:
git init --bare $HOME/.dotfiles- Add a
configalias to your shell profile so you can run Git against that repo from anywhere. Pick the tab for your shell:
alias config='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'alias config='git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME'- Tell Git not to show every untracked file in
$HOME(which would be thousands), soconfig statusstays readable:
config config --local status.showUntrackedFiles noWith the config alias in place, you now drive your dotfiles with ordinary Git commands just type config where youโd normally type git.
Adding and committing dotfiles
Version-controlling a file is exactly like a normal repo, using config instead of git:
config add .vimrc .bashrc .zshrcconfig commit -m 'Add shell and editor config'Then publish to a private remote for safekeeping and easy sync. Use the SSH remote so you never type credentials:
config branch -M mainconfig push -u origin mainWarning
Treat this repo like any other public-facing thing: never commit secrets.
Keep API tokens, SSH private keys, and .netrc-style credentials out of it
add them to a .gitignore (tracked in the repo) before your first push.
Replicating your environment on a new machine
On a fresh box, add the config alias to your shell profile, then clone the repo as bare into $HOME/.dotfiles:
Now check the files out into $HOME. If files like .bashrc already exist, Git refuses to overwrite them this one-liner backs up any conflicts, then checks out cleanly:
mkdir -p .dotfiles-backup && \config checkout 2>&1 | egrep "\s+\." | awk '{print $1}' | \xargs -I{} mv {} .dotfiles-backup/{}config checkout -fconfig config --local status.showUntrackedFiles noThatโs it your environment is restored, and config status is clean.
Strategy 2: A modular repo + a bootstrap script
The bare-repo trick is elegant, but everything lives as one flat checkout. Once your config grows aliases, shell functions, plugins, per-app configs, even your own CLI tools youโll want structure and opt-in modules. The approach I actually run does exactly that: a normal Git repo of organized modules, symlinked into place by an idempotent setup script.
You can browse the real thing here: github.com/MKAbuMattar/dotfiles.
Repository layout
Directorydotfiles/
Directory.aliases/ per-tool alias modules (35 modules)
- โฆ
Directory.utils/ per-tool shell function libraries (17 modules)
- โฆ
Directory.plugins/ zsh plugins + Python CLI plugins
- โฆ
Directory.zsh/ core zsh settings (options, completion, keybindings)
- โฆ
Directory.config/ third-party app configs (kitty, btop, mpv, โฆ)
- โฆ
Directory.docs/ markdown man-page sources (the source of truth)
- โฆ
Directory.man/ generated roff man pages
- โฆ
Directory.scripts/ build & maintenance scripts
- โฆ
Directory.agents/ Claude Code skills used to author this repo
- โฆ
- .zshrc the entry point you opt modules in/out from
- setup one-shot bootstrap (idempotent; safe to re-run)
- README.md
Instead of dumping files in $HOME, each subtree is symlinked into ~/.config/.dotfiles, and ~/.zshrc is symlinked to the repoโs .zshrc. The clone stays the single source of truth edit a file in the repo and your live config updates instantly, because it is the same file.
Quick start
- Clone the repository somewhere stable (not directly in
$HOME):
cd ~/Work/dotfiles- Run the bootstrap script. Itโs idempotent re-running it detects existing correct symlinks and skips them, and prompts before overwriting anything else:
./setup- Reload your shell (or just open a new terminal):
source ~/.zshrcHereโs roughly what a first run looks like:
What the bootstrap does
- Symlinks the repo (or its individual subtrees) into
~/.config/.dotfiles. - Links
~/.zshrcto the repoโs.zshrc. - Builds a modular
~/.gitconfig[include]block from every*.gitconfigshipped in.config/gitconfig/. - Generates the man pages from
.docs/and refreshes theaproposindex. - Verifies that
gitandzshare installed, and backs up any pre-existing non-symlink target before replacing it.
Note
Because the bootstrap only ever creates symlinks and backs up conflicts before
touching them, itโs safe to re-run after every git pull no clobbering, no
surprises.
Opting modules in and out
The payoff of a modular layout: you choose what loads. The arrays in .zshrc are the control panel comment a line out and that module simply doesnโt load:
UTILS=("clipboard" "fedora" "git" "npm" "python") # shell functionsPLUGINS=("aws" "docker" "fzf" "git" "kubectl") # completion / integrationALIASES=("docker" "exa" "general" "git" "npm") # short command aliasesAfter editing, reload with Ctrl + C then source ~/.zshrc, or just open a new terminal.
Self-documenting: man pages for everything
Every module ships an AWS-style markdown man page, compiled to real roff pages so man <module> and apropos <keyword> work for your own config exactly like they do for system tools:
Built against reusable skills
The scripts in this repo arenโt ad-hoc. The setup bootstrap and the Python CLI tools are written against two agent skills I maintain shared specs that encode strict Bash and Python patterns plus a validator, so quality stays consistent across machines and contributors. You can grab both for your own scripting from my skills repository: github.com/MKAbuMattar/skills.
linux-script-developerstrict Bash patterns + a validator thesetupscript passes at 100%.python-script-developerstrict Python patterns + a validator every shipped Python plugin passes at 100%.
Info
Want the same guardrails on your own scripts? Pull the skills from
github.com/MKAbuMattar/skills theyโre
the exact specs (patterns + validators) the setup script and every Python
plugin in the dotfiles are built and checked against.
If you fork the dotfiles and add a script, run the matching validator to keep it in line:
bash .agents/skills/linux-script-developer/scripts/validate-script.sh ./your-script.shpython3 .agents/skills/python-script-developer/scripts/validate-script.py ./your-script.pyTip
Once your shell is dialed in, give the terminal itself some polish a prompt, colors, icons. My guide on customizing the terminal with Starship pairs perfectly with a modular dotfiles setup.
Which should you choose?
Use the bare Git repository (Strategy 1). Thereโs nothing to install and nothing to symlink your home directory is the work-tree, and config is just git with a different --git-dir. Itโs the fastest path from โunmanagedโ to โversioned and synced.โ
Use the modular repo + bootstrap (Strategy 2). Splitting aliases, functions, plugins, and app configs into separate files keeps everything navigable, and a setup script makes a new machine reproducible in one command. The symlink model also means editing the repo updates your live config instantly.
Yes. Your files are already in Git either way. Move them into a structured layout, write (or borrow) a setup script that symlinks them, and point your shell at the new location. Nothing about the bare approach locks you in.
Never commit credentials. Keep a tracked .gitignore that excludes things like ~/.ssh/id_*, .netrc, and any *.env files, and load real secrets from a separate, untracked file your shell sources only if it exists (e.g. [[ -f ~/.secrets ]] && source ~/.secrets).
Conclusion
Whichever strategy you pick, the win is the same: your environment stops being a fragile, one-of-a-kind artifact and becomes something reproducible, reviewable, and one git clone away. Start with the bare repo if you want to be versioned in five minutes; graduate to a modular repo with a bootstrap script when your config earns the structure. Either way, the next machine you sign in to will already feel like home.
References
- The best way to store your dotfiles: A bare Git repository (Atlassian)
- MKAbuMattar/dotfiles the modular repo + bootstrap shown above
- MKAbuMattar/skills the linux- and python-script-developer skills
- Hacker News Discussion on Dotfiles Management
- Managing Dotfiles With a Bare Git Repository
- Dotfiles.github.io A guide to dotfiles on GitHub
- Using Git and GitHub to manage your dotfiles (Anish Athalye)
- Ask HN: How do you manage your dotfiles?
- Git Bare Repository A Better Way To Manage Dotfiles (Dries Vints)
- ArchWiki: Dotfiles
- GitHub Does Dotfiles (GitHub Blog)
- GNU Stow for dotfiles management (Alternative approach)
- Chezmoi Manage your dotfiles across multiple diverse machines, securely (Popular dotfiles manager tool)
- YADM Yet Another Dotfiles Manager (Another popular tool)
- Understanding Git โbare repositories
- Stack Overflow: What is a bare git repository?