Dotfiles: How to Manage and Sync Your Dev Environment
Setting up a new development machine from scratch takes hours if you do it manually — reconfiguring your shell, reinstalling tools, recreating aliases, copying settings. Dotfiles fix this. Your configuration files (~/.zshrc, ~/.gitconfig, ~/.vimrc, VS Code settings) live in a git repository, and symlinks connect them to where the tools expect them. When you get a new machine, you clone the repo and run one script. This guide shows a practical setup without over-engineering it.
What Are Dotfiles?
“Dotfiles” is the informal name for configuration files that start with a dot (.) — Unix’s convention for hidden files. They live in your home directory and control the behavior of your shell, editor, git, and other tools.
Common ones:
~/.zshrc Zsh shell configuration (aliases, exports, plugins)
~/.bashrc Bash equivalent
~/.gitconfig Git user info, aliases, and defaults
~/.vimrc Vim configuration
~/.tmux.conf Tmux key bindings and appearance
~/.ssh/config SSH host aliases and connection settings
The insight is that these files are just text — they belong in version control like any other source code.
The Symlink Approach
The standard pattern: keep all your dotfiles in a ~/dotfiles directory, then symlink each one to its expected location in ~.
~/dotfiles/
zshrc ← actual file
gitconfig ← actual file
vimrc ← actual file
tmux.conf ← actual file
~/.zshrc → ~/dotfiles/zshrc (symlink)
~/.gitconfig → ~/dotfiles/gitconfig (symlink)
~/.vimrc → ~/dotfiles/vimrc (symlink)
~/.tmux.conf → ~/dotfiles/tmux.conf (symlink)
When a tool reads ~/.zshrc, it follows the symlink and reads from your git-tracked file. Any change you make is immediately tracked.
Step 1: Create the Dotfiles Repository
$ mkdir ~/dotfiles && cd ~/dotfiles
$ git init
$ git remote add origin git@github.com:yourusername/dotfiles.git
Move your existing config files in:
$ mv ~/.zshrc ~/dotfiles/zshrc
$ mv ~/.gitconfig ~/dotfiles/gitconfig
$ mv ~/.vimrc ~/dotfiles/vimrc # if you use vim
$ mv ~/.tmux.conf ~/dotfiles/tmux.conf # if you use tmux
Create symlinks back to the expected locations:
$ ln -sf ~/dotfiles/zshrc ~/.zshrc
$ ln -sf ~/dotfiles/gitconfig ~/.gitconfig
$ ln -sf ~/dotfiles/vimrc ~/.vimrc
$ ln -sf ~/dotfiles/tmux.conf ~/.tmux.conf
Verify the symlinks:
$ ls -la ~ | grep "\->"
lrwxr-xr-x .gitconfig -> /Users/mukulkadel/dotfiles/gitconfig
lrwxr-xr-x .tmux.conf -> /Users/mukulkadel/dotfiles/tmux.conf
lrwxr-xr-x .vimrc -> /Users/mukulkadel/dotfiles/vimrc
lrwxr-xr-x .zshrc -> /Users/mukulkadel/dotfiles/zshrc
Commit and push:
$ cd ~/dotfiles
$ git add .
$ git commit -m "Initial dotfiles"
$ git push -u origin main
Step 2: Write an Install Script
Manual symlinking works for the first setup, but an install script makes restoration on a new machine a single command. Create ~/dotfiles/install.sh:
#!/usr/bin/env bash
set -e
DOTFILES="$HOME/dotfiles"
link() {
local src="$DOTFILES/$1"
local dst="$HOME/.$1"
if [ -e "$dst" ] && [ ! -L "$dst" ]; then
echo "Backing up existing $dst → $dst.bak"
mv "$dst" "$dst.bak"
fi
ln -sf "$src" "$dst"
echo "Linked: $dst → $src"
}
link zshrc
link gitconfig
link vimrc
link tmux.conf
link ssh/config
echo "Done."
$ chmod +x ~/dotfiles/install.sh
The if [ -e "$dst" ] && [ ! -L "$dst" ] check backs up any existing file before overwriting — so you don’t lose configuration that’s already on a machine when you run the script.
Step 3: Add a Brewfile
Pair your dotfiles with a Brewfile to capture all installed packages:
$ brew bundle dump --file=~/dotfiles/Brewfile
Add to your install script:
if command -v brew &>/dev/null; then
brew bundle install --file="$DOTFILES/Brewfile"
fi
Handling Machine-Specific Configuration
Some settings differ between machines (work vs. personal, Mac vs. Linux). The cleanest pattern is a local override file that isn’t committed:
In ~/dotfiles/zshrc:
# Load machine-local overrides if present
[ -f ~/.zshrc.local ] && source ~/.zshrc.local
On a work machine, ~/.zshrc.local might set:
export WORK_API_KEY="..."
export JAVA_HOME="/opt/homebrew/opt/openjdk@17"
alias vpn="open -a 'Cisco AnyConnect'"
This file never goes into the repo (add *.local to .gitignore), so secrets and machine-specific paths stay off GitHub.
SSH Config in Dotfiles
Your ~/.ssh/config is safe to version-control as long as it doesn’t contain private keys (those live in ~/.ssh/ but are never committed). A typical config:
# ~/.ssh/config
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519
AddKeysToAgent yes
Host work-server
HostName 10.0.1.42
User deploy
IdentityFile ~/.ssh/work_ed25519
ForwardAgent yes
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
Symlink it:
$ mkdir -p ~/dotfiles/ssh
$ mv ~/.ssh/config ~/dotfiles/ssh/config
$ ln -sf ~/dotfiles/ssh/config ~/.ssh/config
Setting Up a New Machine
With the repo in place, getting a new Mac developer-ready is:
# Install Homebrew
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Clone dotfiles
$ git clone git@github.com:yourusername/dotfiles.git ~/dotfiles
# Run install script (links dotfiles + installs Brewfile packages)
$ ~/dotfiles/install.sh
# Reload shell
$ source ~/.zshrc
From bare macOS to a fully configured environment in under 10 minutes, without touching the mouse.
What to Keep Out of Dotfiles
A few things that look like good candidates but aren’t:
- Private keys (
~/.ssh/id_ed25519) — never commit these. Use 1Password SSH agent or similar. - Tokens and secrets — put these in
~/.zshrc.localor a secrets manager. - IDE-specific binary caches — VS Code’s
~/.vscodeextension installations are machine-specific; just commitextensions.jsonrecommendations instead.
Conclusion
A dotfiles repo costs about 30 minutes to set up the first time and pays for itself on the first new machine setup. The symlink approach is the right primitive — your tools read from their expected paths, your config lives in git, and the relationship is transparent. Keep the install script simple, use a local override file for machine-specific secrets and paths, and add a Brewfile so package installation is just as automated.