Managing Dotfiles

Time is Precious

“'Time,’ he told me, ‘is both precious and worthless. It is eternal and fleeting. Like clay, you may make of it what you will if your hands possess the skill to do so.'” ― Anthony Ryan, The Black Song

I spend a lot of time figuring out how to save time. I'm constantly tweaking and configuring my setup. According to the xkcd comic below, I do end up saving more time than I spend for the most part. For those times when it doesn't quite work out that way, I find comfort in the saying "the time you enjoy wasting, is not wasted time".

Graph showing when you spend more time than you save

Backing up your configuration is something that doesn't take a lot of time to do now, but can save you a lot of time in the future. Imagine getting a new computer. You get to start fresh without gigabytes of random files and applications slowing you down, but at the same time, have to start fresh and reconfigure everything to work just as you want it to. Because my configuration is all backed up, when I get on a new machine I can just clone my repo, run a script, and the most important applications are installed and configured the way I want saving me countless hours. This post is another example of something that can save time in the future by showing you how you could backup your dotfiles.

A Dotfile Repository

When I was looking to backup my dotfiles for the first time, I tried a couple of different approaches. To start, Github has a huge list of different packages. I tried a couple of options from that list and I also read this article from Atlassian about setting up a bare repository in your $HOME directory. In the end, all these options just seemed like overkill and just didn't work with my normal flow. Here is how I set up my repository.

  1. Create a git repository at ~/.dotfiles
  2. Move dotfiles out of $HOME and into ~/.dotfiles
  3. Write a script to create symlinks for folders and files back to their necessary locations

That script for me looks something like this:

ln -sf $PWD/doom.d ~/.doom.d
ln -sf $PWD/git/gitconfig ~/.gitconfig
ln -sf $PWD/hammerspoon ~/.hammerspoon
ln -sf $PWD/iterm2_config ~/.iterm2_config
ln -sf $PWD/pylintrc ~/.pylintrc
ln -sf $PWD/irbrc ~/.irbrc
ln -sf $PWD/rubocop.yml ~/.rubocop.yml
ln -sf $PWD/tmux.conf ~/.tmux.conf
ln -sf $PWD/vim ~/.vim
ln -sf $PWD/zsh/zshrc ~/.zshrc
# ...

This approach is easy to understand, doesn't require extra packages, and I don't have to learn any new commands. It also gets all the benefits of Git, one of which is that it is easily shareable with other people.

Using a Brewfile

The dotfile repo gets me a long way to have a consistent configuration across machines and makes it easy to revert bad changes or set up new machines. Another thing that makes setting up a new machine easier is something called a Brewfile. If you are familiar with Ruby, it's like a Gemfile but for anything installed through brew. For example, with the Brewfile below (a small sample of the one in my dotfiles repo), I can install GUI applications, command-line tools, and fonts all with the command brew bundle.

cask 'alfred'
cask 'docker'
cask 'google-chrome'
cask 'spotify'

brew 'emacs-plus@28', args: ['--without-imagemagick']
brew 'kubectx'
brew 'pyenv'
brew 'rbenv'
brew 'ripgrep'
brew 'starship' # terminal theme
brew 'tldr' # tldr of the man pages

tap 'homebrew/cask-fonts'
cask 'font-droidsansmono-nerd-font-mono'
cask 'font-office-code-pro'
cask 'font-fira-code'

Scripts for Everything Else

Anything else that I'd want to install (there are only a few things in this category) can be set up using small scripts like the ones below. For example, the following script will clone Doom Emacs, create the folder structure that I use, and install global Ruby Gems and NPM packages that use.

install_doomemacs () {
  if [[ ! -f ~/.emacs.d/bin/doom ]]; then
    echo "Installing doom emacs..."
    git clone ~/.emacs.d -b develop
    echo "Skipping doom emacs install..."

install_file_structure () {
  mkdir -p ~/Documents/projects
  mkdir -p ~/Documents/personal
  mkdir -p ~/Documents/work

install_gems () {
  bundle install --system --gemfile=~/.dotfiles/setup-scripts/Gemfile

install_node_packages () {
  bash ~/.dotfiles/setup-scripts/

The code block below shows a script to install a set of node packages.

#! /usr/bin/env bash

for package in ${packages[@]}; do
  npm install -g $package

You can always get carried away and end up spending more time than you save while automating or configuring anything. I love knowing that the time I have spent getting things set up the way that makes me the most productive wouldn't be lost if I had to start over on a new machine tomorrow.