What follows is a brief description of the techniques I currently use to
organize my personal configuration files, often called “dot files.” In general,
most of your configurations are stored in a folder in your home directory
.config. This directory can be changed, but in my opinion it’s a
In theory, every program you configure in Linux/UNIX will store its config files
.config, making it very easy to migrate your configs to new computers or
track that folder with a version control system like
git. Tracking your
git allows you to easily clone the repository onto all
your computers and keep your configs synchronized across them. You can browse
your git history to look at any old versions of your configs and use branches to
maintain different versions of some configs. For example, my desktop doesn’t
need a battery indicator, but my laptop does.
Unfortunately, some popular programs don’t support this standard either because
of incompetence, ignorance, or the program is just older than the XDG
standard and can’t be updated due to legacy reasons. This
vim (but not neovim),
git itself, and most
shells. Additionally, you might track more than just configurations such as an
icon pack or a
few fonts which would instead be (correctly) stored under
The solution is to use symbolic links (or symlinks for short). They are one of
the two types of links on UNIX and function similarly to “shortcuts” on Windows.
A symlink is a regular text file that contains the name of another file or
directory and has a file system flag of
l. This is what indicates to
programs that it is a symlink rather than a regular file. The standard way to
create one is with
$ touch /tmp/a $ ln -s /tmp/a /tmp/b $ echo "hello world" > /tmp/a $ cat /tmp/b hello world
In this example
/tmp/b is a symlink that points to
Whenever a program attempts to open a symlink it will instead seamlessly open
the file that the symlink is pointing towards. Using this, we can store our
configuration files anywhere we like and create symlinks in the “expected”
places that point to our real files. Personally, I organize all my configs into
a folder called
dots with each program having its own subdirectory – like below.
$ tree -a ~/dots dots ├── beets │ └── .config │ └── beets │ └── config.yaml ├── dunst │ └── .config │ └── dunst │ └── dunstrc ├── git │ └── .gitconfig ├── i3 │ ├── .config │ │ └── i3 │ │ └── config │ └── .xinitrc ├── user-dirs │ └── .config │ ├── user-dirs.dirs │ └── user-dirs.locale └── zsh ├── .local │ └── share │ └── zsh │ └── plugins │ └── kota-prompt │ ├── .git │ ├── kota-prompt.plugin.zsh │ ├── kota-prompt.zsh │ ├── LICENSE │ └── README.md └── .zshrc
Most have their own
.config folder. Everything is arranged so that the
contents of these folders can be symlinked to my home directory. The file at
/home/kota/.config/i3/config is actually a symlink that points to
/home/kota/dots/i3/.config/i3/config. It’s a little bit confusing, but this
allows me to easily track of all of my dot files with
git. A little bit of
tooling makes it quite easy to manage.
stow [ options ] package ...
Stow is a fairly popular and old unix tool that is used to “manage farms of symbolic links,” which is exactly what we’re dealing with. It was originally created to help with manual software installs (and excels at that), but it also happens to be an amazing tool for managing your dot files.
stow deals with “packages,” which are directories arranged like the ones in my
dots folder from above (zsh, user-dirs, i3 are all
stow “packages”). It has
options for “installing,” “removing,” or “updating” these packages. Installing a
package simply means creating a bunch of symlinks in a certain location that
point to the corresponding files in the package. I think the easiest way to
explain this is with an example.
This is how I install my i3 configs on a new computer.
cd ~/dots stow -t /home/kota i3
-t option tells
stow which folder all the symlinks are relative towards,
or rather, where to actually install the package. Basically, if running
cp -r i3/* /home/kota/ would copy all the files to the correct location, then
/home/kota is the correct directory for the
stow defaults to creating symlinks, but if one or more of the directories exists
it works around them. For example, if
.config/i3 doesn’t exist
stow will make a
symlink for that folder that points to the one in your i3 package. However, if
that folder does exist,
stow will create a symlink for
I’m going to assume you know the basics of
git (add, commit, push, pull, clone),
but if you haven’t used it much or need a refresher take a look at the first few
chapters of the git book.
There are 2 distinct “issues” you might run into with your new
stow managed, dots folder. I addressed the first earlier. What happens
if you want to have slightly differing configs between your computers? You
might want to use a different colorscheme on one computer, or not show a
battery indicator on your desktop. Whatever the case, there are 3 ways of
The most obvious is using
git branches. This approach is pretty clean and easy
to understand, but there is one annoying downside: if you update a config on one
branch, most of the time you’ll want to update it on all your branches. So, this
approach involves a lot of remembering to merge and keeping all your branches up
to date. If you have like 6+ computers this quickly becomes a huge pain in the
ass, especially if they use almost the exact same configs for everything.
The second approach, which is even worse than the first (but requires slightly
git knowledge), is to have multiple repos, one for each computer. I did
this for a while, but it has all the downsides of the first approach with more
manual merges and none of the upsides.
The last approach, which I use currently, is having one repo and one branch, but
different packages for the differing software. For example, if you have a sway
config for your laptop and a different one for your desktop, you would have two
sway-desktop. You still need to remember to copy
over changes that you want to make to both configs, but seeing them both
physically in the directory makes it easier to remember. Plus, for all the
software that doesn’t have differing configs, you only store one copy.
The second distinct problem that comes up is how to handle “plugins” inside your
configs. Specifically, I’m thinking of
zsh plugins, but most other
software (except browsers) works similarly.
For zsh I made a little prompt plugin. To
use it, you simply clone that repo to
~/.local/share/zsh/plugins/ or something
similar and add a line like this to your zsh config
However, now that you’ve added that line to your zsh config, you need to
re-install that plugin whenever you setup a new computer. You could “solve” this
.local/share/zsh to your
stow package and tracking the whole thing
git. That works well enough for small packages, but for bigger ones it
really bloats your repo and now you need to manually update your plugins.
Let’s be honest you’re not gonna remember to do that.
git actually comes to the rescue here with a lovely command called
git submodule. This command allows you to specify other repositories as
dependencies for your repository. For example,
git submodule add https://github.com/tpope/vim-surround neovim/.config/nvim/pack/plugins/start/vim-surround is the command I ran from
~/dots repo to install Tim Popes lovely vim-surround
plugin. It will clone that repository
into the location you specified, and now you can use the other
commands to update or remove your plugins. You also need to make a commit that
adds the submodule – similar to when you update a
.gitignore. The git book
covers that command in detail in chapter
One thing to note is that now, when you clone your dots onto a new computer, you
need to use the
--recursive argument with
git clone. For me, the command is
git clone --recursive https://git.sr.ht/~kota/dots. When you want to update
your plugins you can run
git submodule update --remote --merge.