New machine? Reinstall? Your perfect dev environment should be one command away. Here’s how to manage dotfiles properly.

The Problem

You spend hours configuring:

  • Shell (zsh, bash)
  • Editor (vim, nvim, VS Code)
  • Git config
  • SSH config
  • Tmux
  • Aliases and functions

Then you get a new laptop and do it all again. Badly.

The Basic Solution

Put dotfiles in a Git repo, symlink them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Create repo
mkdir ~/dotfiles
cd ~/dotfiles
git init

# Move configs
mv ~/.zshrc ~/dotfiles/zshrc
mv ~/.vimrc ~/dotfiles/vimrc
mv ~/.gitconfig ~/dotfiles/gitconfig

# Create symlinks
ln -sf ~/dotfiles/zshrc ~/.zshrc
ln -sf ~/dotfiles/vimrc ~/.vimrc
ln -sf ~/dotfiles/gitconfig ~/.gitconfig

# Push to GitHub
git remote add origin git@github.com:username/dotfiles.git
git push -u origin main

GNU Stow makes symlinks manageable:

dotfizvgtlsiimehmtusx../.zvgtsiimhmturrcxcco.ncfoingf
1
2
3
4
5
6
7
8
cd ~/dotfiles
stow zsh      # Creates ~/.zshrc → ~/dotfiles/zsh/.zshrc
stow vim
stow git
stow tmux

# Remove symlinks
stow -D zsh

Installation Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash
# install.sh

set -e

DOTFILES="$HOME/dotfiles"

# Clone if not exists
if [ ! -d "$DOTFILES" ]; then
    git clone https://github.com/username/dotfiles.git "$DOTFILES"
fi

cd "$DOTFILES"

# Install packages
if command -v apt &> /dev/null; then
    sudo apt update
    sudo apt install -y zsh vim tmux stow
elif command -v brew &> /dev/null; then
    brew install zsh vim tmux stow
fi

# Stow everything
for dir in */; do
    stow "$dir"
done

# Change shell
chsh -s $(which zsh)

echo "Done! Restart your terminal."

Handling Sensitive Data

Don’t commit secrets.

Option 1: Separate File

1
2
3
4
5
6
# .zshrc
source ~/.secrets  # Not in repo

# .secrets (gitignored)
export API_KEY="xxx"
export DB_PASSWORD="yyy"

Option 2: Git-crypt

1
2
git-crypt init
echo "secrets/** filter=git-crypt diff=git-crypt" >> .gitattributes

Files matching the pattern are encrypted in the repo.

Option 3: Environment Variables

Load from password manager or secrets service at runtime.

Machine-Specific Configs

Conditional in Shell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# .zshrc
if [[ "$OSTYPE" == "darwin"* ]]; then
    # macOS
    alias ls='ls -G'
    export PATH="/opt/homebrew/bin:$PATH"
else
    # Linux
    alias ls='ls --color=auto'
fi

if [[ "$(hostname)" == "work-laptop" ]]; then
    export HTTP_PROXY="http://proxy.corp:8080"
fi

Separate Stow Packages

dotfizzzzlssssehhhhs---mlwaiocnr/ukx###ssstttooowwwooonnnlllyyyooanntmlwaiocnrukx
1
2
3
stow zsh
[[ "$OSTYPE" == "darwin"* ]] && stow zsh-mac
[[ "$OSTYPE" == "linux"* ]] && stow zsh-linux

Essential Configs

Git

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# .gitconfig
[user]
    name = Your Name
    email = you@example.com

[core]
    editor = vim
    excludesfile = ~/.gitignore_global

[alias]
    st = status
    co = checkout
    br = branch
    ci = commit
    lg = log --oneline --graph --decorate

[pull]
    rebase = true

[init]
    defaultBranch = main

Shell Aliases

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# .zshrc or .bashrc
alias ll='ls -la'
alias ..='cd ..'
alias ...='cd ../..'
alias g='git'
alias k='kubectl'
alias d='docker'
alias dc='docker compose'

# Quick edits
alias zshrc='$EDITOR ~/.zshrc && source ~/.zshrc'
alias vimrc='$EDITOR ~/.vimrc'

SSH Config

#HHHooo.sssstttsAIHUIHUIFhddgosdposdodeiseerseercKnttrnotrnwoethNtdNtanyiuagiadirfstbmitmetdiTi.etyepyAgoecFlFgAsogi1oiegOmil9ylnente2etnlh.tyu~1~yb/6/eyy..8.seecs.sssos1smh.h/1/g0pi0rtohdub

Tmux

#subsss#bb#benieeeiii.tbntttSnnRntidpddedm-n---llugdCgggi|-orx-ta.pCambhssdscr-oaipppooebsussallcunfesetniiorfine-oettncxdirs--fe-onywwi-Cpnd-iigf-relnniaexiddlfmooei1iwwxt~-/1h.0t0m0u0x.conf\;display"Reloaded!"

Bootstrap Script Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/bin/bash
# bootstrap.sh - One command setup

set -euo pipefail

REPO="https://github.com/username/dotfiles.git"
DOTFILES="$HOME/dotfiles"

echo "=== Dotfiles Bootstrap ==="

# Detect OS
if [[ "$OSTYPE" == "darwin"* ]]; then
    OS="mac"
    # Install Homebrew
    if ! command -v brew &> /dev/null; then
        /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    fi
    brew install git stow zsh vim tmux fzf ripgrep
else
    OS="linux"
    sudo apt update
    sudo apt install -y git stow zsh vim tmux fzf ripgrep
fi

# Clone dotfiles
if [ -d "$DOTFILES" ]; then
    cd "$DOTFILES" && git pull
else
    git clone "$REPO" "$DOTFILES"
fi

cd "$DOTFILES"

# Backup existing configs
backup_dir="$HOME/.dotfiles_backup/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"

for file in .zshrc .vimrc .gitconfig .tmux.conf; do
    if [ -f "$HOME/$file" ] && [ ! -L "$HOME/$file" ]; then
        mv "$HOME/$file" "$backup_dir/"
        echo "Backed up $file"
    fi
done

# Stow packages
stow zsh vim git tmux
[ "$OS" = "mac" ] && stow mac
[ "$OS" = "linux" ] && stow linux

# Install vim plugins
if [ -f ~/.vimrc ]; then
    vim +PlugInstall +qall 2>/dev/null || true
fi

# Set shell
if [ "$SHELL" != "$(which zsh)" ]; then
    chsh -s "$(which zsh)"
fi

echo "=== Bootstrap Complete ==="
echo "Restart your terminal or run: exec zsh"

Run on any new machine:

1
curl -fsSL https://raw.githubusercontent.com/username/dotfiles/main/bootstrap.sh | bash

The Dotfiles Checklist

  • All configs in Git
  • Symlinks managed (stow or script)
  • Secrets excluded from repo
  • Cross-platform support
  • Bootstrap script for fresh machines
  • README documenting what’s included
  • Tested on clean install
  • mathiasbynens/dotfiles (macOS focused)
  • holman/dotfiles (organized, well documented)
  • thoughtbot/dotfiles (clean, minimal)

Look at how others organize, then build your own.


Your dev environment is an extension of how you think. Version it, automate it, never rebuild it from memory again.