
Dotfiles repository with submodule
My dotfiles repository setup with a submodule
I recently published my dotfiles on GitHub, and I wanted to talk about how I decided to manage them.
What are dotfiles?
In short, dotfiles are configuration files that are usually found in the home directory of Unix-like systems such as Linux, macOS, and WSL on Windows. They’re called dotfiles because their filenames usually (but not always) start with a dot (.).
Here are a few common examples:
.zshrc/.bashrc— Configuration files for your shell if you use ZSH or Bash..gitconfig— Your personal Git configuration, containing things like your name, email, and other Git settings..ssh/config— Configuration for your SSH hosts, including authentication methods, ports, usernames, hostnames, and more.
Why create a repository?
Dotfiles are something you build over time. You tweak them, fix annoyances, and gradually improve your workflow. There isn’t a universal configuration that’s perfect for everyone—dotfiles are personal.
Now imagine losing all of that because your laptop suddenly dies.
A less dramatic, but much more common, scenario is having multiple computers and wanting the same configuration everywhere. That’s where a Git repository comes in handy.
You make a change on one machine, push it, then pull it on the others. If something breaks, Git history makes it easy to track down exactly what changed.
Now that we’ve covered the what and the why, let’s talk about the how.
Creating a dotfiles repository
First of all, you’ll need a GitHub account. If you don’t have one already, go create one.
I also recommend setting up SSH authentication with GitHub, so you won’t have to enter your credentials every time you use Git.
Note: I still use master as my default branch instead of main. If your repositories use main, simply replace master with main in the commands below.
The setup described here uses two repositories:
- a public
dotfilesrepository; - a private
dotfiles-privaterepository for sensitive files (such as SSH configuration).
The private repository will be added as a Git submodule inside the public one, allowing everything to be managed from a single directory.
On GitHub
Create two repositories:
dotfiles(public)dotfiles-private(private)
On your machine
Open a terminal, move to your home directory:
cd $HOME
Clone your public repository as “.dotfiles”:
git clone git@github.com:<username>/dotfiles.git .dotfiles
Enter the repository and add the private one as a submodule:
cd .dotfiles
git submodule add git@github.com:<username>/dotfiles-private.git private
You should now see a new .gitmodules file and a private/ directory.
Enter the submodule and check out its master branch:
cd private
git checkout master
Return to the main repository:
cd ..
Configure the submodule to always track the master branch:
git config -f .gitmodules submodule.private.branch master
Then configure updates to merge changes instead of leaving the submodule in a detached HEAD state:
git config -f .gitmodules submodule.private.update merge
Finally, commit and push everything:
git commit -am "Initial commit"
git push
Making changes
Let’s start by adding something simple, like your ~/.zshrc.
Copy it into your dotfiles repository:
cp ~/.zshrc ~/.dotfiles
Commit it:
git add .
git commit -m "Add .zshrc"
To push your changes, use:
git push --recurse-submodules=on-demand
Why –recurse-submodules=on-demand?
When your repository contains submodules, Git needs to make sure those submodules are pushed before updating the parent repository.
The --recurse-submodules=on-demand flag does exactly that: it pushes any modified submodules first, then pushes the main repository.
If you make changes inside the private submodule, the workflow is slightly different.
Inside private/:
git add .
git commit -m "Edit file X"
Then return to the main repository, where Git tracks the updated submodule reference:
cd .. # We are now inside .dotfiles/
git add .
git commit -m "Update private submodule"
git push --recurse-submodules=on-demand
Cloning the repository
When cloning a repository that contains submodules, you’ll want to use:
git clone --recurse-submodules --shallow-submodules git@github.com:<username>/dotfiles.git .dotfiles
The --recurse-submodules option clones all submodules automatically.
The --shallow-submodules option clones each submodule with only its latest commit instead of the full Git history, making the clone faster.
Pulling changes
Pulling updates also requires a slightly different command:
git pull --recurse-submodules
This makes sure both the main repository and its submodules stay in sync.
Wrapping up
This is the basic setup I use to manage my dotfiles. In the future I might write another post showing what’s actually inside them, but that’s a topic for another day.
Thanks for reading!
Simone