EnglishPolski
The simplest way to manage dotfiles
Linux

The simplest way to manage dotfiles

People fall into two groups: those who make backups, and those who will start making them.

Of course, I don’t wish anyone loosing their data, but if you ever have to start from scratch, it’s good to be able to quickly restore your daily workflow.

And since that workflow largely relies on dotfiles (at least mine), it’s worth having an easy way to back them up—and at the same time, keep them in sync across machines you use daily.

There are dedicated tools for this, like GNU Stow, which works by creating symbolic links. But I use git anyway. So why complicate things?

Bare repo

A bare repository is the key to this whole setup.

If you read the git documentation, you’ll find that a bare repo is mainly used for synchronizing and sharing code between machines, not for direct development work.

Which is exactly what we need.

As a reminder — a standard git repository consists of a .git directory containing the full history, and a working directory with the current version of the code.

A bare repo contains only the .git structure.

To make things clean, a bare repo is pure history, without any working files.

If we combine that with using the home directory as the working tree, we get a perfect solution.

Repository in the home directory

So your home directory becomes the working tree. We now need the place to store the history.

Let’s create a new repository:

git init --bare $HOME/.dotfiles

The first question I’d ask here: why .dotfiles instead of .git? The name can be anything, but it’s good to keep it hidden to avoid accidental modifications.

As for the name—if you use a custom prompt (e.g. via a terminal theme or OHMYZSH), naming it .git will make the prompt display branch info. I don’t want that.

Now if we check its contents:

ls $HOME/.dotfiles
COMMIT_EDITMSG  HEAD  branches  config  description  hooks  index  info  logs  objects  refs 

You’ll see the standard .git structure. That’s expected.

But if you try:

git status

You’ll get:

fatal: not a git repository (or any of the parent directories): .git

That’s because git looks for .git in the current directory and it’s not there. Exactly as intended.

Fortunately, git allows specifying both the repository directory and the working tree via –git-dir and –work-tree.

Let’s use that and define an alias:

alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'

From now on, use dot instead of git for dotfiles:

dot status

One more important step—create a remote repository (e.g. on GitHub). Make it private. And don’t store secrets there.

Set the default branch:

dot remote add origin <github_url>
dot branch -M main

That’s the minimal setup, but already enough to manage dotfiles. Let’s add a first file:

dot add .zshrc
dot commit -m "initial commit - .zshrc"
dot push -u origin main

Done. Your first dotfile is safe.

Hiding files and keeping things clean

Managing dotfiles matters, so it’s worth adding a few improvements.

Firstly, this repo should contain only what’s necessary. No junk.

To avoid accidentally adding unwanted files, configure the repo to ignore untracked files by default:

dot config --local status.showUntrackedFiles no

Now dot status will show only tracked files, not everything in your home directory.

When adding a new file, you need to do it explicitly:

dot add .config/nvim/init.vim
dot commit -m "add nvim config"
dot push

How not to lose changes?

If you’re tracking changes, you should commit and push them regularly. To help remember, I added a reminder in .zshrc:

# check if dotfiles are committed
if [[ -n $(dot status --porcelain) ]]; then
    echo "⚠️ Dotfiles not committed ⚠️"
fi 

Now every terminal session will warn you if there are uncommitted changes.

You could automate commits with cron, but this is simple and effective enough.

Small timesaver

With this alias, running dot without arguments shows the repo status. Passing a command executes it normally:

alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME "${@:-status}"'

Synchronization

So how do you sync this on a new machine?

Clone the repo as bare into your home directory:

git clone --bare <github_url> $HOME/.dotfiles

Set the alias again:

alias dot='git --git-dir=$HOME/.dotfiles --work-tree=$HOME "${@:-status}"'

Final step—checkout:

dot checkout --force

Why –force?

If there are existing files in your home directory (and there probably are), git won’t allow checkout because files would be overwritten.

Since we want that, we use –force and proceed without prompts.

Done.

On the second machine, it’s also worth hiding untracked files as before.

From here—just commit, push, and regularly run:

dot pull --rebase

Summary

I’ve been using this approach long enough to trust it. It’s simple, fast, and requires only what you already use—git. No extra tools, no complexity.

Simplicity is the ultimate sophistication. – Leonardo da Vinci

Back to Top