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.
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.
- Log into GitHub.
- Click on the plus sign + in the upper right-hand corner, and then New repository.
- Choose a name, add a description. Skip initializing a README, adding a .gitignore file, and choosing a license for now.
- 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 git@github.com:joelhans/dotfiles.git. You want to add that to the git remote add origin command just below:
$ git remote add origin git@github.com: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 = joel.g.hans@gmail.com [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
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 favourite 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.