Skip to content
Codeloom
Productivity

Dotfiles Management: Sync Your Dev Setup Across Machines

Learn to manage dotfiles with GNU Stow, chezmoi, or a bare git repo. Bootstrap any new machine in minutes with your exact config.

·6 min read · By Codeloom
Intermediate 9 min read

What you'll learn

  • Why dotfiles management matters and what belongs in a dotfiles repo
  • Three approaches: GNU Stow, chezmoi, and bare git repos
  • How to organize your dotfiles directory for clarity
  • Writing a bootstrap script to set up a fresh machine in minutes

Prerequisites

  • Basic command-line comfort (terminal tools helps)
  • Git fundamentals — commits, push, pull, clone
Diagram showing dotfiles syncing across multiple machines via git

Every developer accumulates a constellation of config files: .zshrc, .gitconfig, .vimrc, Starship config, tmux settings, SSH config. These files live in your home directory, and losing them when you switch machines means hours of painful reconstruction.

Dotfiles management solves this. You put your config files in a git repo, create symlinks into your home directory, and push to GitHub. On a new machine, you clone and run one script. Done.

What Belongs in a Dotfiles Repo

Include: Shell config (.zshrc, .bashrc), git config (.gitconfig), editor settings (.vimrc, VS Code settings.json), terminal emulator config, tmux/screen config, tool configs (Starship, fzf, ripgrep), and your bootstrap script.

Exclude: Anything with secrets (SSH private keys, API tokens, .env files). Use a secrets manager or keep those in a separate, encrypted repo.

Approach 1: GNU Stow (Simplest)

GNU Stow is a symlink farm manager. You organize configs into “packages” (directories), and Stow creates the correct symlinks into your home directory.

Directory structure

~/.dotfiles/
├── zsh/
│   └── .zshrc
├── git/
│   ├── .gitconfig
│   └── .gitignore_global
├── starship/
│   └── .config/
│       └── starship.toml
├── vim/
│   └── .vimrc
└── tmux/
    └── .tmux.conf

Each top-level folder is a “package.” The internal structure mirrors where files should land relative to $HOME.

Usage

# Install
brew install stow        # macOS
sudo apt install stow    # Ubuntu

# Navigate to your dotfiles directory
cd ~/.dotfiles

# Symlink the zsh package
stow zsh
# This creates: ~/.zshrc -> ~/.dotfiles/zsh/.zshrc

# Symlink everything at once
stow */

# Remove symlinks for a package
stow -D vim

Stow is appealing because it has zero config files of its own. It uses directory structure as its API.

Gotcha

Stow fails if the target file already exists and is not a symlink. Back up or remove existing dotfiles before running stow for the first time:

mv ~/.zshrc ~/.zshrc.backup
stow zsh

Approach 2: chezmoi (Most Powerful)

chezmoi is a purpose-built dotfiles manager that handles templates, secrets, and machine-specific config.

# Install
brew install chezmoi

# Initialize
chezmoi init

# Add a file
chezmoi add ~/.zshrc

# Edit a managed file
chezmoi edit ~/.zshrc

# See what would change
chezmoi diff

# Apply changes
chezmoi apply

Why choose chezmoi over Stow?

chezmoi shines when you need templates — config that varies between machines:

# ~/.local/share/chezmoi/dot_gitconfig.tmpl
[user]
    name = "{{ .name }}"
    email = "{{ .email }}"

[core]
    editor = {{ if eq .chezmoi.os "darwin" }}code{{ else }}vim{{ end }}

When you run chezmoi init on a new machine, it prompts for values and renders the template. One dotfiles repo works across your personal Mac, work laptop, and Linux server.

chezmoi also supports encrypted secrets via age or gpg, so you can safely store API tokens.

When Stow is enough

If all your machines are similar and you do not need templating or secrets, Stow is simpler. If you manage configs across macOS and Linux with different paths and tools, chezmoi earns its complexity.

Approach 3: Bare Git Repo (No Dependencies)

This approach uses a bare git repo in your home directory with an alias. No extra tools required.

# Initialize
git init --bare $HOME/.dotfiles-repo

# Create an alias (add this to .zshrc too)
alias dotfiles='git --git-dir=$HOME/.dotfiles-repo --work-tree=$HOME'

# Ignore untracked files (your entire home directory)
dotfiles config --local status.showUntrackedFiles no

# Now use the alias like git
dotfiles add ~/.zshrc
dotfiles commit -m "add zsh config"
dotfiles remote add origin git@github.com:you/dotfiles.git
dotfiles push -u origin main

Setting up a new machine

git clone --bare git@github.com:you/dotfiles.git $HOME/.dotfiles-repo
alias dotfiles='git --git-dir=$HOME/.dotfiles-repo --work-tree=$HOME'
dotfiles checkout
dotfiles config --local status.showUntrackedFiles no

The bare repo approach is clever, but the alias is easy to forget, and status.showUntrackedFiles no hides potential issues. I recommend Stow or chezmoi for most people.

Writing a Bootstrap Script

Regardless of which approach you choose, write a bootstrap.sh script that automates a fresh machine setup:

#!/usr/bin/env bash
set -euo pipefail

echo "==> Installing Homebrew"
if ! command -v brew &>/dev/null; then
  /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi

echo "==> Installing packages"
brew install \
  git stow zsh starship fzf ripgrep bat eza zoxide \
  node python go rust \
  tmux neovim

echo "==> Cloning dotfiles"
if [ ! -d "$HOME/.dotfiles" ]; then
  git clone git@github.com:you/dotfiles.git "$HOME/.dotfiles"
fi

echo "==> Linking dotfiles"
cd "$HOME/.dotfiles"
stow */

echo "==> Setting default shell to zsh"
chsh -s $(which zsh)

echo "==> Done. Open a new terminal to see changes."

Store this script in your dotfiles repo and make it executable. On a new machine, your entire workflow becomes:

git clone git@github.com:you/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
./bootstrap.sh

Organizing for Clarity

A well-organized dotfiles repo looks like this:

~/.dotfiles/
├── bootstrap.sh
├── README.md
├── Brewfile              # brew bundle list
├── git/
│   ├── .gitconfig
│   └── .gitignore_global
├── zsh/
│   ├── .zshrc
│   └── .zsh/
│       ├── aliases.zsh
│       ├── functions.zsh
│       └── exports.zsh
├── starship/
│   └── .config/starship.toml
├── vscode/
│   └── .config/Code/User/settings.json
└── macos/
    └── defaults.sh       # macOS system preferences

Split your .zshrc into focused files (aliases.zsh, exports.zsh, functions.zsh) and source them. This makes each piece easy to find and edit.

Which Approach Should You Use?

FeatureGNU StowchezmoiBare Git
Dependenciesstowchezmoinone
TemplatesNoYesNo
Secret managementNoYes (age/gpg)No
Learning curveLowMediumLow
Multi-OS supportManualBuilt-inManual

Start with Stow. It takes ten minutes to set up and handles 90% of use cases. If you later need templates or secrets, migrate to chezmoi. Avoid the bare git approach unless you specifically want zero dependencies.

The best dotfiles setup is the one you actually maintain. Pick a method, commit your configs today, and your future self on a new machine will thank you.