Blog
Plain dark background with "Dotfiles" written on top
26 Mar 2023

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 dotfiles repository;
  • a private dotfiles-private repository 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!

SD

Simone

More posts