Dotfiles: Managing Developer Configurations
Dotfiles are configuration files that start with a dot (.bashrc, .vimrc, .gitconfig, .tmux.conf). Managing them well means you can set up a new machine in minutes and keep configurations synchronized across all your environments.
Why Manage Dotfiles
Without dotfile management, every new machine requires:
- Recreating aliases, environment variables, and shell prompts
- Remembering which tools you use and how they are configured
- Manually copying files, inevitably missing some
A dotfiles repository solves this by putting all configuration in version control with a single bootstrap command.
Git-Based Dotfiles
The most common approach: a Git repository in your home directory.
Bare Repository Method
# Create a bare repository in a hidden directory
git init --bare $HOME/.dotfiles
# Create an alias to manage it
alias dotfiles='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
# Add files
dotfiles add .bashrc .vimrc .tmux.conf .config/nvim/init.lua
dotfiles commit -m "Initial dotfiles"
dotfiles remote add origin git@github.com:username/dotfiles.git
dotfiles push -u origin mainOn a new machine:
# Clone into a bare repo
git clone --bare git@github.com:username/dotfiles.git $HOME/.dotfiles
# Checkout without overwriting existing files
git --git-dir=$HOME/.dotfiles --work-tree=$HOME checkout
# If there are conflicts, back up and retry
mkdir -p .config-backup
git --git-dir=$HOME/.dotfiles --work-tree=$HOME checkout 2>&1 | \
grep -E "^\s+" | awk "{print $1}" | xargs -I{} sh -c 'mkdir -p .config-backup/$(dirname {}) && mv {} .config-backup/{}'
git --git-dir=$HOME/.dotfiles --work-tree=$HOME checkoutThe bare repository method is clean. It keeps files in their natural locations and does not require symlinks or wrapper scripts.
Symlink Method
An alternative is storing dotfiles in a dedicated directory and symlinking them:
mkdir ~/dotfiles
mv ~/.bashrc ~/dotfiles/bashrc
ln -s ~/dotfiles/bashrc ~/.bashrcTools like GNU Stow automate this:
# ~/dotfiles/bash/.bashrc
# ~/dotfiles/git/.gitconfig
# ~/dotfiles/tmux/.tmux.conf
# Create all symlinks at once
cd ~/dotfiles
stow bash git tmuxStow treats each subdirectory as a package and creates symlinks while respecting the directory structure. Running stow bash creates ~/.bashrc → ~/dotfiles/bash/.bashrc.
Comparison: Bare Repo vs Symlinks
| Method | Pros | Cons |
|---|---|---|
| Bare repo | Files stay in natural locations, no symlink maintenance | Requires alias setup, accidental git add risk |
| Symlinks with Stow | Clean separation, easy to understand | Symlinks can break, must remember to restow |
Essential Dotfiles to Manage
Shell Configuration
# ~/.bashrc — common aliases and functions
alias ll='ls -la'
alias g='git'
alias gst='git status'
alias gc='git commit'
alias gp='git push'
alias ..='cd ..'
alias ...='cd ../..'
# Export common environment variables
export EDITOR=vim
export VISUAL=vim
export HISTSIZE=50000
export HISTFILESIZE=50000
# Colorize grep output
alias grep='grep --color=auto'# ~/.zshrc — common for zsh users
# If using Oh My Zsh
ZSH_THEME="agnoster"
plugins=(git docker kubectl autojump)
source $ZSH/oh-my-zsh.sh
# Or with starship prompt
eval "$(starship init zsh)"Git Configuration
; ~/.gitconfig
[user]
name = Your Name
email = you@example.com
[core]
editor = vim
autocrlf = input
[alias]
st = status
ci = commit
co = checkout
br = branch
lg = log --oneline --graph --decorate --all
amend = commit --amend --no-edit
undo = reset --soft HEAD~1
[init]
defaultBranch = main
[color]
ui = autoEditor Configuration
-- ~/.config/nvim/init.lua — minimal Neovim config
local opt = vim.opt
opt.number = true
opt.relativenumber = true
opt.tabstop = 2
opt.shiftwidth = 2
opt.expandtab = true
opt.mouse = "a"
opt.clipboard = "unnamedplus"
opt.termguicolors = true
-- Key mappings
vim.g.mapleader = " "
vim.keymap.set("n", "<leader>w", ":w<CR>")
vim.keymap.set("n", "<leader>q", ":q<CR>")SSH Configuration
# ~/.ssh/config
Host *
ServerAliveInterval 60
AddKeysToAgent yes
UseKeychain yes
Host myserver
HostName 192.168.1.100
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519_workBootstrap Script
A bootstrap script automates setup on a new machine:
#!/bin/bash
# bootstrap.sh — run on a fresh machine
set -e
DOTFILES_REPO="git@github.com:username/dotfiles.git"
echo "Cloning dotfiles..."
git clone --bare $DOTFILES_REPO $HOME/.dotfiles
alias dotfiles='git --git-dir=$HOME/.dotfiles --work-tree=$HOME'
dotfiles checkout
echo "Installing packages..."
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
sudo apt update
sudo apt install -y vim tmux git curl build-essential
elif [[ "$OSTYPE" == "darwin"* ]]; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install vim tmux git
fi
echo "Installing Oh My Zsh..."
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
echo "Done! Restart your shell."Secret Management
Never commit secrets (API keys, passwords, tokens) to a public dotfiles repository.
| Approach | How it works |
|---|---|
.local files | Source a separate .bashrc.local file that is .gitignored |
| Environment files | Use .env files loaded at shell startup |
| Password managers | Use op run (1Password CLI) or pass to inject secrets |
| Git-crypt | Encrypt specific files in the repository |
| SOPS | Mozilla’s tool for encrypted config files |
# ~/.bashrc — source a private local file
if [ -f ~/.bashrc.local ]; then
source ~/.bashrc.local
fiThe .gitignore ensures .bashrc.local is never committed. Secrets stay local, and the main config references them conditionally.
Organization Strategies
| Strategy | Pros | Cons |
|---|---|---|
| Single monolithic repo | Simple, one command to clone | Large, hard to navigate |
| Per-tool repos | Modular, share selectively | Many repos to manage |
| Modular monorepo | One repo, separate directories, stow/install per tool | Requires symlink tool |
| Framework (chezmoi) | Built-in templating, encryption, diff | Learning curve |
chezmoi is the most popular dotfile manager. It allows per-machine templates, encrypted secrets, and a dry-run preview of changes. It applies configurations rather than symlinking, making it safe to adopt on existing systems without conflicts.
# chezmoi workflow
chezmoi init # Initialize
chezmoi add ~/.bashrc # Track a file
chezmoi edit ~/.bashrc # Edit the managed version
chezmoi diff # Preview changes
chezmoi apply # Apply to home directory
chezmoi update # Pull and apply latestPer-Machine Configuration with Templates
chezmoi supports Go templates in managed files, allowing you to maintain a single dotfile repo across different operating systems and environments:
# ~/.local/share/chezmoi/.bashrc.tmpl
export EDITOR=vim
{{- if eq .chezmoi.os "darwin" }}
export PATH="/opt/homebrew/bin:$PATH"
{{- else if eq .chezmoi.os "linux" }}
export PATH="/usr/local/bin:$PATH"
{{- end }}This pattern is invaluable when you maintain configurations across macOS, Linux, and WSL environments. Template conditions can check the OS, architecture, hostname, or any custom variable you define.
Maintenance and Evolution
Dotfiles are living documents. As you adopt new tools and workflows, your configurations should evolve. Schedule a quarterly review of your dotfiles to remove unused aliases, update tool versions, and consolidate configuration files. A clean dotfiles repository makes onboarding to new machines faster and reduces the cognitive overhead of maintaining muscle memory for commands you no longer use.
The key to dotfile management is simplicity. Start by tracking your most important configs (shell, git, editor), commit early, and add more as you identify files you configure on every machine.
FAQ
Should I commit my entire .ssh directory?
No. Never commit private keys. Only commit ~/.ssh/config and ~/.ssh/known_hosts (if needed). Always add id_rsa, id_ed25519, and *.key patterns to your .gitignore.
How do I handle different configurations for work and personal machines?
Use conditional logic in your shell config (if hostname matches work-*) or use chezmoi templates. Alternatively, create separate branches for work and personal configs.
What is the best dotfile management tool?
chezmoi is the most popular and feature-rich option. For a simpler approach, the bare Git repo method works well. YADM (Yet Another Dotfiles Manager) is a good middle ground with Git-like commands and built-in encryption support.
How do I keep my dotfiles in sync across machines?
Push to your remote repository after changes and pull on each machine. With chezmoi, run chezmoi update to pull and apply the latest changes. Set up a cron job or git hook to auto-sync if you find yourself forgetting.
Can I use dotfiles with Windows?
Yes. Windows Terminal, PowerShell profiles, and WSL configurations can all be managed as dotfiles. WSL configuration lives in ~/.wslconfig and Windows Terminal settings are typically stored in %APPDATA%\Microsoft\Windows Terminal\settings.json.
Dotfiles Strategy at Scale
Managing dotfiles across multiple machines requires a strategy that balances personalization with consistency. A bare Git repository (the --bare flag approach) tracks files without moving them into a separate directory, letting you version-control files in-place. Symlink-based tools like GNU Stow or yadm provide higher-level interfaces for organizing configs by program. For secrets management, store API keys and tokens in a separate encrypted file sourced by your shell config but excluded from version control with .gitignore. Environment-specific overrides let your shell detect whether it is on a work laptop, personal desktop, or SSH’d into a server and adjust configurations accordingly. The XDG Base Directory Specification (~/.config, ~/.local/share, ~/.cache) reduces home-directory clutter. Most modern tools (Git, VS Code, Neovim, i3, sway) already support XDG paths. Shell-agnostic configs work best: write common aliases and exports in a separate file that both .bashrc and .zshrc can source. Consider using chezmoi for dotfiles that need per-machine template expansion, such as different Git user.email values for work versus personal repos.
Bootstrapping and CI
A bootstrap script turns a new machine into a productive environment in minutes. It should install package managers (Homebrew, apt, winget), core packages (Git, curl, build tools), and programming language runtimes (via asdf, mise, or nvm). The script should be idempotent — running it multiple times produces the same result. Test dotfiles with GitHub Actions by spinning up a fresh VM, running the bootstrap script, and verifying that all tools initialize without errors. Document the bootstrap process in a README or BOOTSTRAP file inside the dotfiles repo.
For a comprehensive overview, read our article on Advanced Git Commands.
For a comprehensive overview, read our article on Chrome Devtools Guide.