Tutorial: Let’s make our development lives better with dotfiles

An example from our dotfiles tutorial

You’re minding your own business, clicking through the repositories in a prominent developer’s GitHub profile, when you stumble across one called dotfiles. Curious, you click through. What’s this? A bunch of configuration files? “What’s .xinitrc?” you ask yourself aloud. “Why, exactly, are people putting their .vimrc files into version control, and then sharing them with everyone?”

These dotfiles aren’t just posturing, and they aren’t frivolous—for many, they’re essential to productive code development or administration in the terminal. And, if you take the time to set up a system for keeping and deploying them, you can instantaneously deploy a familiar desktop environment just about anywhere.

What’s a dotfile, exactly?

A dotfile is a configuration file, in plain text, with a name that begins with a full stop (.), or a dot. On Unix systems, the full stop hides the file when executing ls or when browsing using a file manager like Finder. Dotfiles thus remain hidden during typical use but can be exploited by power users to extraordinary ends.

Because dotfiles are written in plain text, they’re easily transferable between different machines, even those running completely different operating systems. As long as your operating system has git, for example, you can configure it using .gitconfig.

Traditionally, dotfiles only work on Unix-based systems—that is, macOS, Linux, and *BSD variants, plus others—and that’s the focus of this guide. Windows users using Cygwin or the Windows Subsystem for Linux may have some luck, but setup will be for you to figure out.

Why should I care about these random configuration files?

You probably have a handful of preferences when working on development or administration projects. Maybe you like vim just so, or only like one specific SublimeText theme and color scheme combination. Over time—quite a lot of time, in many cases—you’ve saved these choices into dozens of preferences dialogs by hitting Apply.

Imagine having to reinstall your operating system due to a hard drive failure or bad update—you’ve just lost all of those preferences.

Now, imagine downloading a repository with git and running a single command to put all those preferences back in place. Instead of taking hours to reconfigure all your favorite apps, a script will do the same in seconds.

That’s the power of dotfiles. And, best of all, they’re just waiting for you to start using them.

Where do I get started?

There are countless ways to configure each file, and just about as many methodologies for deploying dotfiles to a new machine. We’ll focus on the fundamentals:

  1. Establishing a dotfiles repository for the first time
  2. Creating a few dotfiles
  3. Making and saving changes
  4. Deploying dotfiles (with a Bash script)

Instead of telling you exactly how to set your dotfiles up, I want to focus on the fundamentals and give you some inspiration on how you want to establish your own repository.

An important note: You can find plenty of dotfile repositories online, but you should not merely clone one and deploy it to your machine—things could break, and you won’t know how to fix it. Or, the new environment will be so foreign that you can’t get around. Instead, explore these repositories for inspiration and bits of configuration magic that you can adapt to your dotfiles.

Another important note: Once we get started, we’ll be overwriting existing configuration files with logical (and functioning!) defaults. Be careful with what you overwrite, and, better yet, create backups!

Step 1. Establishing a dotfiles repository for the first time

Before you can start working with your own dotfiles repository, you need to create the directory and initialize it as a Git repository. You can name your folder anything you’d like, and put it wherever it’s convenient for you, but I think placing the .dotfiles directory in your home folder makes a lot of sense.

$ mkdir ~/.dotfiles
$ cd ~/.dotfiles
$ git init

You’re now set up with a local Git repository that will version control any changes.

For your repository to be shareable and accessible from multiple computers, you’ll want to connect this local repository to GitHub.

  1. Log into GitHub.
  2. Click on the plus sign + in the upper right-hand corner, and then New repository.
  3. Choose a name, add a description. Skip initializing a README, adding a .gitignore file, and choosing a license for now.
  4. Hit Create repository.

You should now have an empty repository on GitHub as well. Now to connect the two—look for the green Clone or download button on the right-hand side of the page and copy the text within the textarea—it should look something like [email protected]:joelhans/dotfiles.git. You want to add that to the git remote add origin command just below:

$ git remote add origin [email protected]:USER/NEW-REPOSITORY.git
$ git push -u origin master

If this doesn’t work, you may have to use HTTPS, or set up Git itself—check out GitHub’s handy documentation for more.

You should also initialize a README file with a single line of Markdown, and then add your changes and push them to GitHub.

$ echo "# Welcome to my dotfiles repository" > README.md
$ git add README.md
$ git commit -m "Added initial README."
$ git push -u origin master

At this point, your repository is ready to house some dotfiles! Let’s walk through a few fundamentals that you might be interested in.

Step 2. Creating a few dotfiles

Since we’re already working with Git, let’s create a few example Git-related dotfiles and add those to our repository.

.gitconfig

This file offers some basic configurations on how your local git installation should connect to GitHub or any other remote provider. You can also create aliases in this file as well—check out this post for examples and inspiration.

[user]
  name = Joel Hans
  email = [email protected]

[github]
  user = joelhans

[core]
  excludesfile = ~/.gitignore_global
  editor = nano
  filemode = false
  trustctime = false
    autocrlf = input

.gitignore_global

As referenced in the .gitignore file, the .gitignore_global file forces Git to “forget” about a few file extensions and directories so that they don’t get included in version control.

*~
._*
.DS_Store
.idea
.vscode
node_modules
bower_components
npm-debug.log

.zshrc

Let’s assume for a second that you followed my previous Bash-to-Zsh migration post and are all set up with Zsh and oh-my-zsh. Even though that pair gives you a good deal of built-in flexibility, you can always push customization further. I’ve taken the default Oh My Zsh script and stripped it down, and made a few small tweaks.

# Setting $PATH
export PATH=$HOME/bin:/usr/local/bin:$PATH

# Path to the oh-my-zsh installation.
export ZSH=$HOME/.oh-my-zsh

# ZSH theme to display.
ZSH_THEME="spaceship"

# Enable command auto-correction.
ENABLE_CORRECTION="true"

# Display red dots whilst waiting for completion.
COMPLETION_WAITING_DOTS="true"

# Disable marking untracked files as dirty.
DISABLE_UNTRACKED_FILES_DIRTY="true"

# History time stamps
HIST_STAMPS="yyyy-mm-dd"

# Oh-my-zsh plugins
plugins=( git docker cp zsh-syntax-highlighting )

# Spaceship settings
SPACESHIP_PROMPT_ORDER=( time user host dir git )
SPACESHIP_DOCKER_SHOW=false

# Sourcing oh-my-zsh and other shell helpers
source $ZSH/oh-my-zsh.sh
source $HOME/.zsh_exports
source $HOME/.zsh_aliases

See those last three lines, each beginning with source? Sourcing another file is like include() in PHP or #include in C—the file is gathered and executed as though it’s part of the original file. In this case, Zsh evaluates .zshrc and then seeks out any other sourced file, and evaluates those as well.

By separating different types of configuration into different files, we can keep our dotfiles small and easily navigable.

.zsh_exports

Both .zsh_exports and .zsh_aliases can be named anything you like—lots of other dotfile repositories name them simply .exports and .aliases—but I like this standard. Remember that when it comes to dotfiles, customization is key.

The .zsh_exports file establishes environment variables, which are used by scripts to open your preferred editor, for example.

# Set the shell
export SHELL=/bin/zsh

# Default editor
if [[ -n $SSH_CONNECTION ]]; then
  export EDITOR='nano'
else
  export EDITOR='nano'
fi

# SSH key
export SSH_KEY_PATH="~/.ssh/rsa_id"

# Prefer US English and use UTF-8
export LC_ALL="en_US.UTF-8"
export LANG="en_US"

.zsh_aliases

Once you get the hang of aliases, they can be addicting. They allow you to add arguments to common commands or create shortcuts to others, whether they’re a favorite of yours or a pain to type out.

# Common shortcuts
alias reload="source ~/.zshrc"
alias _="sudo -E"
alias dnf="sudo dnf"
alias rr="rm -rf"

# Directory traversal
alias ..="cd .."
alias ...="cd ../.."
alias ....="cd ../../.."
alias ~="cd ~"
alias -- -="cd -"

# Directory listing
# Colors courtesy https://github.com/mathiasbynens/dotfiles/blob/master/.aliases
if ls --color > /dev/null 2>&1; then # GNU `ls`
  colorflag="--color"
  export LS_COLORS='no=00:fi=00:di=01;31:ln=01;36:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.avi=01;35:*.fli=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.ogg=01;35:*.mp3=01;35:*.wav=01;35:'
else # macOS `ls`
  colorflag="-G"
  export LSCOLORS='BxBxhxDxfxhxhxhxhxcxcx'
fi
alias l="ls -lF ${colorflag}"
alias la="ls -laF ${colorflag}"
alias lsd="ls -lF ${co#lorflag} | grep --color=never '^d'"
alias ls="command ls ${colorflag}"

# IP addresses
alias ip="dig +short myip.opendns.com @resolver1.opendns.com"
alias ipl="hostname -I"
alias ips="ifconfig -a | grep -o 'inet6\? \(addr:\)\?\s\?\(\(\([0-9]\+\.\)\{3\}[0-9]\+\)\|[a-fA-F0-9:]\+\)' | awk '{ sub(/inet6? (addr:)? ?/, \"\"); print }'"

# Prevent ZSH from throwing autocorrect for weird-looking Sass commands
alias sass='nocorrect sass'

Other dotfiles and other “dotfiles”

Many Unix utilities, like vim, tmux, and screen, have dotfiles of their own. Start exploring to see what programs have dotfiles, and how you can configure/save them in your repository.

While not dotfiles in the traditional sense, many developer-centric programs store user preferences in plain text files in a variety of syntaxes. SublimeText and Atom, two widely-used code editors, both store user preferences files that you can store in your repository.

Step 3. Making and saving changes

Now that you have a few dotfiles in your repository, you want to start version controlling them. Every time you make a change to a dotfile, you should add it to the Git commit, write out a commit message that explains your change, and push it to your repository.

$ git add .     # for all changed files, or git add FILENAME to add a single file
$ git commit -m "Enter commit message here."
$ git push -u origin master

It’s that simple. Now that your repository contains a few unique dotfiles that are tracked and can be cloned to any new computer or server you boot up.

Step 4. Deploying dotfiles (with a Bash script)

Having a .dotfiles folder/repository with a handful of your unique configuration files does nothing without actually deploying them to your system. The basic, non-script version of deployment can be done on the command line in just a few steps, while the scripted version does everything in a batch using a single command.

Without a Bash script

In order for programs to pick up your dotfiles, you need to create symlinks between the expected location and your dotfile. In Linux/macOS, this is quite simple:

$ ln -sf "~/.dotfiles/.gitconfig" ~
$ ln -sf "~/.dotfiles/.gitignore_global" ~
$ ln -sf "~/.dotfiles/.zshrc" ~
$ ln -sf "~/.dotfiles/.zsh_exports" ~
$ ln -sf "~/.dotfiles/.zsh_aliases" ~

Rinse and repeat with any other dotfiles you might have.

The ln command, in conjunction with the -s argument, creates a symbolic link. The -f argument forces the creation of a symlink, overwriting any file that exists in the specified location. Here is where we start to enter the dangerous, overwriting-working-system-defaults part I mentioned before.

With ln -sf "~/.dotfiles/.gitconfig" ~, you’re basically saying to your computer, “If git wants to read ~/.gitconfig, please look at ~/.dotfiles/.gitconfig instead.” Once the symlink is in place, the program will begin reading its new configuration.

With a Bash script

A Bash script automates the installation process entirely, which is handly on for further deployments. Here’s a very simple example install.sh Bash script that would be placed in the .dotfiles folder.

#!/bin/bash

# Get dotfiles installation directory
DOTFILES_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

ln -sf "$DOTFILES_DIR/.gitconfig" ~
ln -sf "$DOTFILES_DIR/.gitignore_global" ~
ln -sf "$DOTFILES_DIR/.zshrc" ~
ln -sf "$DOTFILES_DIR/.zsh_exports" ~
ln -sf "$DOTFILES_DIR/.zsh_aliases" ~

You run the script by typing ./install.sh into your terminal, and it will create all the symlinks automatically.

But, once you bring Bash into the equation, you can do so much more than just creating symlinks. I have a somewhat-functional installation script in my own dotfiles repository that also installs Oh-my-Zsh, some plugins, and contains more dotfiles for other programs that I use frequently. It’s very much a work in progress, but that’s how all good projects are born.

It’s important to note here that once you’ve deployed symlinks, you don’t need to recreate them or re-run a bash script if you make changes to your .zshrc file, for example—the symlink will automatically connect to the newest version, which means your software will always have the latest and greatest setup.

Where do I go next?

When it comes to dotfiles, just about anywhere. That’s what makes them both exciting and thrilling.

On a personal level, I’m working my way through the rest of my most commonly used applications, like SublimeText, vim, and tmux, while also perusing the incredible backlog of dotfiles repositories for nuggets of wisdom. Here’s a handful of resources that have been invaluable so far:

The end goal here is to create your own perfect environment. Ultimately, it’s satisfying to feel at home no matter where you are. Building a massive, perfectly flexible dotfiles repository isn’t easy, but after only twenty minutes, you’ll have a solid foundation on which to continue building your little development empire.

Get 24GB RAM for $9.99/mo!

Save $3,960 with SSD Nodes versus competitors like Digital Ocean or Amazon Lightsail.

Snag limited-time prices: