Dotfiles Deep Dive
Overview
This is a comprehensive reference for managing dotfiles across macOS and Linux systems using modern tooling and architectural patterns. Unlike the [[dotfiles-beginner-guide]], this deep-dive explores the internals of configuration management tools, cross-platform design patterns, and advanced techniques for maintaining complex dotfile repositories.
Dotfile management has evolved significantly. Beyond simple git repositories, professionals now use specialized tools like GNU Stow and chezmoi to handle templating, conditional logic, encryption, and multi-machine synchronization. This guide covers the philosophy, architecture, and operational patterns that enable maintainable, reproducible development environments.
Key design philosophy: Your dotfiles repository should be your source of truth for all reproducible environment configuration. Whether you work on macOS, Linux, a server, or multiple architectures, a well-designed dotfiles setup ensures consistency and enables rapid provisioning.
Prerequisites
Before diving into this material, you should have:
- Command-line proficiency: Comfortable with bash/zsh, shell scripting, and terminal navigation
- Git fundamentals: Understand commits, branches, remotes, and branching strategies
- Symlink understanding: Know the difference between hard and soft links, inode behavior
- System administration basics: Comfortable with file permissions, package managers (Homebrew, apt), and environment variables
- Familiarity with [[dotfiles-beginner-guide]]: Understand basic dotfile concepts and directory structures
- XDG Base Directory Specification: Know what
$XDG_CONFIG_HOME,$XDG_DATA_HOME, etc. mean
Key Concepts
Symlink Mechanics and Inode Behavior
Symlinks (soft links) are the foundation of dotfile management. Understanding their behavior prevents subtle bugs:
# Create a symlink
ln -s /path/to/source /path/to/link
# Verify symlink
ls -la ~/.config/nvim
# lrwxr-xr-x 1 user staff 34 Apr 11 10:30 .config/nvim -> /path/to/dotfiles/nvim
# Follow symlinks recursively
cat ~/.config/nvim/init.lua # Transparently follows the link
# Hard vs soft links
# Soft (symbolic) links: point to filename, break if target moves
# Hard links: point to inode, survive target relocation but can't cross filesystems
Critical gotchas:
- Relative symlinks break if you move the symlink directory
cd ~/.config && cd nvimcan behave unexpectedly with symlinks- Package managers sometimes refuse to follow symlinks
- Circular symlink chains cause infinite recursion
XDG Base Directory Specification
Most modern applications support XDG specification for config organization:
# Standard locations
export XDG_CONFIG_HOME="$HOME/.config" # Application configs
export XDG_DATA_HOME="$HOME/.local/share" # Application data
export XDG_CACHE_HOME="$HOME/.cache" # Temporary caches
export XDG_STATE_HOME="$HOME/.local/state" # Application state
# Benefits:
# - Centralized, organized location
# - Easier to backup and synchronize
# - Supports per-user and system-wide configs
Many legacy applications (.bashrc, .gitconfig) predate XDG but can be redirected. Your dotfiles strategy should consider which standards each application supports.
GNU Stow Internals: Tree Folding and Conflict Resolution
GNU Stow uses a sophisticated algorithm called tree folding to create efficient symlink structures:
Dotfiles directory structure:
stow-repo/
├── nvim/
│ └── .config/
│ └── nvim/
│ ├── init.lua
│ └── keymaps.lua
├── tmux/
│ └── .config/
│ └── tmux/
│ └── tmux.conf
└── shell/
├── .bashrc
└── .zshrc
After: stow -d stow-repo -t ~ nvim tmux shell
Home directory becomes:
~/.config/nvim -> stow-repo/nvim/.config/nvim
~/.config/tmux -> stow-repo/tmux/.config/tmux
~/.bashrc -> stow-repo/shell/.bashrc
~/.zshrc -> stow-repo/shell/.zshrc
Tree folding explanation: If a stow package contains a subtree that doesn't conflict with existing files, Stow creates a single symlink to that entire directory rather than symlinking each file individually. This reduces clutter but requires care when modifying.
Conflict resolution:
stowrefuses to overwrite existing files- Use
--adoptto move existing files into the stow package - Use
--ignoreor.stow-local-ignoreto skip certain files
chezmoi Architecture: Source vs Destination State
chezmoi inverts the traditional approach—you maintain a source directory that gets applied to the destination:
chezmoi structure:
~/.local/share/chezmoi/
├── .chezmoi.os.toml # OS-specific variables
├── .chezmoi.arch.toml # Architecture-specific variables
├── dot_bashrc.tmpl # Templates (dot_ prefix = .)
├── dot_zshrc.tmpl
├── dot_config/
│ ├── nvim/
│ │ └── init.lua.tmpl
│ └── tmux/
│ └── tmux.conf
├── .chezmoiignore # OS-specific ignore patterns
├── run_onchange_before_install.sh
└── run_once_setup.sh
Apply workflow:
chezmoi diff # Preview changes
chezmoi apply # Actually apply to system
chezmoi edit ~/.bashrc # Edit and auto-apply
Key architectural advantage: Source files are templates, not raw configs. This enables:
- Cross-platform conditionals
- Encrypted secrets
- OS-specific branching
- Automatic installation scripts
The Git Bare Repository Approach
Some power users eschew dedicated tools and use git directly:
# Initialize bare repo
git init --bare ~/dotfiles.git
# Create alias for management
alias dotfiles="git --git-dir=$HOME/dotfiles.git --work-tree=$HOME"
# Add files
dotfiles add ~/.bashrc
dotfiles add ~/.config/nvim/
# Commit and push
dotfiles commit -m "Update nvim config"
dotfiles push
# On new machine
git clone --bare https://example.com/dotfiles.git ~/.dotfiles.git
alias dotfiles="git --git-dir=$HOME/.dotfiles.git --work-tree=$HOME"
dotfiles checkout
Pros: Minimal dependencies, complete version control, simple mental model Cons: No templating, manual symlink management, conflicts with untracked files
Step-by-Step Instructions
GNU Stow Deep Dive
1. Directory Structure Conventions
Structure your stow packages to mirror the target hierarchy:
# Principle: package layout = home directory layout
stow-repo/
├── shell/
│ ├── .bashrc
│ ├── .zshrc
│ ├── .profile
│ └── .config/
│ └── fzf/
│ └── fzf.bash
├── git/
│ └── .gitconfig
├── nvim/
│ └── .config/
│ └── nvim/
│ ├── init.lua
│ ├── lua/
│ │ └── config.lua
│ └── plugin/
├── tmux/
│ └── .config/
│ └── tmux/
│ └── tmux.conf
└── ssh/
└── .ssh/
├── config
└── authorized_keys
Convention benefits:
- Mirrors actual installation location
- Makes it obvious where files will end up
- Enables consistent unstow/restow
2. Stow Packages and Tree Folding
Understanding how stow folds trees into symlinks:
# Basic stow operation
cd ~/stow-repo
stow -v nvim tmux shell # Verbose output shows operations
# Output might look like:
# SYMLINK: .config/nvim => ../../stow-repo/nvim/.config/nvim
# SYMLINK: .bashrc => stow-repo/shell/.bashrc
# SYMLINK: .config/tmux => ../../stow-repo/tmux/.config/tmux
# Verify stow created correct links
ls -la ~/.config/nvim
# lrwxr-xr-x 1 user staff 42 Apr 11 10:30 .config/nvim -> ../../stow-repo/nvim/.config/nvim
Tree folding algorithm:
- If a directory contains only one stow package's content, it's folded into a single symlink
- If multiple packages contribute to same directory, stow creates symlinks for individual files
- This minimizes filesystem clutter while maintaining clarity
3. .stow-local-ignore for Multi-Machine Management
Use .stow-local-ignore to exclude files per machine:
# ~/.stow-local-ignore (in target directory)
^\.DS_Store
^\.git
\.swp$
\.swo$
__pycache__
# Or machine-specific:
stow-repo/nvim/.stow-local-ignore:
# Ignore Python cache on machines without Python dev setup
^\.config/nvim/\.venv
Better approach for true multi-machine management:
# Use git branches per machine
git branch macbook
git branch ubuntu-server
git branch m1-max
# In .stow-local-ignore, reference machine-specific patterns
^\.config/nvim/nightly-only-plugins
^\.ssh/aws-keys
4. Adoption Pattern with stow --adopt
When migrating existing configs into stow:
# Current state: you have ~/.bashrc outside stow
cat ~/.bashrc # Shows existing content
# Create stow package
mkdir -p stow-repo/shell
cp ~/.bashrc stow-repo/shell/.bashrc
# Use adopt to move the file into stow
stow -d stow-repo -t ~ --adopt shell
# Result: stow moves ~/.bashrc into stow package and creates symlink
cat ~/.bashrc # Now shows symlink target
chezmoi Deep Dive
1. Template System with Conditionals
chezmoi uses Go's text/template for powerful conditional logic:
# ~/.local/share/chezmoi/dot_bashrc.tmpl
# Basic variable substitution
export DOTFILES_VERSION="{{ .version }}"
# OS-specific configuration
{{ if eq .chezmoi.os "darwin" }}
# macOS-specific settings
export PATH="/opt/homebrew/bin:$PATH"
alias ls='ls -G'
{{ else if eq .chezmoi.os "linux" }}
# Linux-specific settings
export PATH="/usr/local/bin:$PATH"
alias ls='ls --color=auto'
{{ end }}
# Architecture-specific
{{ if eq .chezmoi.arch "arm64" }}
# Apple Silicon specific
export ARCH_OPTS="--enable-neon"
{{ end }}
# Feature flags
{{ if .enable_experimental }}
source ~/.bashrc.experimental
{{ end }}
Define variables in ~/.local/share/chezmoi/.chezmoi.toml:
[data]
version = "1.0.0"
enable_experimental = false
dotfiles_repo = "https://github.com/user/dotfiles"
# OS and architecture are auto-detected
2. Encrypted Secrets Management
Store sensitive credentials safely with age or GPG:
# Initialize age encryption
chezmoi init --keep-going
# chezmoi will prompt for age passphrase and create keys
cat ~/.config/chezmoi/chezmoi.toml
# encryption = "age"
# age.identity = "~/.config/chezmoi/age/key.txt"
# age.recipient = "age1..."
# Create encrypted template
cat > ~/.local/share/chezmoi/dot_ssh/encrypted_key.tmpl
{{ include "ssh-key.enc" }}
# chezmoi automatically decrypts on apply
chezmoi apply
# View decrypted content (requires passphrase)
chezmoi cat ~/.ssh/encrypted_key | gpg --decrypt
3. Run Scripts with Special Naming
Automate setup tasks with special-named scripts:
# run_once_* : Execute once, skip on subsequent runs
~/.local/share/chezmoi/run_once_install_homebrew.sh
#!/bin/bash
if ! command -v brew &> /dev/null; then
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
# run_onchange_* : Execute if template changes
~/.local/share/chezmoi/run_onchange_install_packages.sh.tmpl
#!/bin/bash
# checksum: {{ include "packages.txt" | sha256sum }}
{{ if eq .chezmoi.os "darwin" }}
brew install $(cat {{ .packages_file }})
{{ end }}
# run_before_* : Execute before other changes
~/.local/share/chezmoi/run_before_backup.sh
# run_after_* : Execute after all changes
~/.local/share/chezmoi/run_after_reload.sh
4. chezmoi Workflow: diff, apply, update
Core operational patterns:
# Preview changes without applying
chezmoi diff
# Output shows exact changes that will be made
# Apply changes to system
chezmoi apply -v
# Edit a specific file and auto-apply
chezmoi edit ~/.bashrc
# Opens in $EDITOR, auto-applies on save
# Pull latest from remote and show what changed
chezmoi update --dry-run
chezmoi update
# Check status of managed files
chezmoi status
# M = modified (differs from target state)
# A = added
# D = deleted
# Merge in upstream changes during conflicts
chezmoi diff | less
chezmoi apply # Accept all changes
5. External Sources and Archives
Pull config from external URLs:
# ~/.local/share/chezmoi/.chezmoi.toml
[data]
external_config_url = "https://raw.githubusercontent.com/org/configs/main"
# In template
{{ include .external_config_url "/nvim.lua" }}
# Pull entire archive
# ~/.local/share/chezmoi/.chezmoimannually
sourceDir = "chezmoi"
[[source]]
path = "chezmoi-backup.tar.gz"
# Automatically extracted on apply
Git Bare Repo Approach
Set up a bare repository for dotfile management:
# On local machine: create bare repository
git init --bare ~/.dotfiles.git
# Create convenient alias
alias dotfiles='git --git-dir=$HOME/.dotfiles.git --work-tree=$HOME'
# Configure git to not show untracked files
dotfiles config --local status.showUntrackedFiles no
# Add dotfiles
dotfiles add ~/.bashrc ~/.zshrc ~/.config/nvim/
dotfiles add -u # Stage all changes
# Commit and push
dotfiles commit -m "Initial dotfiles commit"
dotfiles remote add origin https://github.com/user/dotfiles.git
dotfiles push -u origin main
# On new machine: bootstrap
git clone --bare https://github.com/user/dotfiles.git ~/.dotfiles.git
alias dotfiles='git --git-dir=$HOME/.dotfiles.git --work-tree=$HOME'
dotfiles checkout
# Handle conflicts with existing files
dotfiles checkout 2>&1 | grep "^\s" | sed 's/^\s*//' | \
xargs -I {} rm -f {}
dotfiles checkout
dotfiles config --local status.showUntrackedFiles no
Architecture analysis: This approach treats your entire home directory as a git repository. It's minimalist but requires discipline to avoid accidentally committing secrets or large binaries.
Practical Examples
Example 1: Power User Dotfiles Repository Structure
Real-world structure supporting nvim, tmux, sesh, ghostty, zsh, git, ssh:
dotfiles/
├── README.md
├── .github/
│ └── workflows/
│ ├── lint.yml
│ └── sync.yml
├── bootstrap.sh # OS detection and setup
├── packages.txt # Package list for different OSes
├── stow/
│ ├── shell/
│ │ ├── .bashrc
│ │ ├── .zshrc
│ │ ├── .profile
│ │ └── .config/
│ │ ├── zsh/
│ │ │ └── .zshenv
│ │ └── fzf/
│ │ └── fzf-keybindings.bash
│ ├── nvim/
│ │ └── .config/
│ │ └── nvim/
│ │ ├── init.lua
│ │ ├── init.vim.backup
│ │ └── lua/
│ │ ├── config.lua
│ │ ├── keymaps.lua
│ │ ├── plugins/
│ │ │ ├── telescope.lua
│ │ │ ├── treesitter.lua
│ │ │ └── lsp.lua
│ │ └── utils.lua
│ ├── tmux/
│ │ └── .config/
│ │ └── tmux/
│ │ ├── tmux.conf
│ │ └── scripts/
│ │ └── switch_session.sh
│ ├── sesh/
│ │ └── .config/
│ │ └── sesh/
│ │ └── sesh.toml
│ ├── ghostty/
│ │ └── .config/
│ │ └── ghostty/
│ │ └── config
│ ├── git/
│ │ └── .gitconfig
│ └── ssh/
│ └── .ssh/
│ └── config
├── chezmoi/
│ ├── .chezmoi.toml
│ ├── .chezmoi.os.toml
│ ├── dot_zshenv.tmpl
│ └── run_once_setup.sh
└── docs/
├── setup.md
├── architecture.md
└── troubleshooting.md
Example 2: Cross-Platform Bootstrap Script
#!/bin/bash
set -euo pipefail
# bootstrap.sh: Initialize development environment
# Detect OS and architecture
OS="$(uname -s)"
ARCH="$(uname -m)"
echo "Detected: $OS on $ARCH"
# Install package manager and dependencies
install_packages() {
if [[ "$OS" == "Darwin" ]]; then
# macOS: 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 $(grep -v '^#' packages.txt | grep darwin)
elif [[ "$OS" == "Linux" ]]; then
# Linux: Install via apt
if command -v apt &> /dev/null; then
sudo apt update
sudo apt install -y $(grep -v '^#' packages.txt | grep linux)
fi
fi
}
# Initialize dotfiles
init_dotfiles() {
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Using Stow
cd "$DOTFILES_DIR/stow"
stow -v -t ~ shell nvim tmux sesh ghostty git ssh
# Or using chezmoi
# chezmoi init --repo "$DOTFILES_DIR/chezmoi"
# chezmoi apply
}
# Run shell configuration
reload_shell() {
# Source the new shell configs
if [[ -f ~/.zshrc ]]; then
source ~/.zshrc
fi
}
# Main
main() {
echo "Installing packages..."
install_packages
echo "Initializing dotfiles..."
init_dotfiles
echo "Reloading shell..."
reload_shell
echo "Bootstrap complete!"
}
main "$@"
Example 3: CI/CD for Dotfile Linting
# .github/workflows/lint.yml
name: Lint Dotfiles
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Lint shell scripts
run: |
sudo apt install -y shellcheck
find . -name "*.sh" -exec shellcheck {} +
- name: Validate TOML
run: |
pip install tomli_w
python3 -c "
import tomllib
with open('.github/workflows/lint.yml', 'rb') as f:
tomllib.load(f)
"
- name: Check symlinks
run: |
stow --dry-run -v -t /tmp/test-home shell nvim tmux
- name: Validate chezmoi templates
run: |
chezmoi init --dry-run
chezmoi diff --dry-run
Hands-On Exercises
Exercise 1: Migrate Stow Setup to chezmoi
Starting point: You have a working Stow repository.
# 1. Create chezmoi source directory
mkdir -p ~/.local/share/chezmoi
# 2. Convert each stow package to chezmoi template
cd stow-repo/shell
for file in $(find . -type f); do
dest="$HOME/.local/share/chezmoi/${file#./}"
mkdir -p "$(dirname "$dest")"
# Convert to template (add .tmpl extension for dot-files)
if [[ "$file" == .* ]]; then
mv "$file" "$dest.tmpl"
else
cp "$file" "$dest"
fi
done
# 3. Create .chezmoi.toml
cat > ~/.local/share/chezmoi/.chezmoi.toml << 'EOF'
[data]
version = "1.0.0"
# Conditionals
enable_nightly_nvim = false
EOF
# 4. Update templates with conditionals
# Edit each .tmpl file to add {{ if }} blocks for OS-specific content
# 5. Validate and test
chezmoi diff --dry-run
chezmoi apply --dry-run
# 6. Commit to git
git init ~/.local/share/chezmoi
git add -A
git commit -m "Migrated from Stow to chezmoi"
Exercise 2: Set Up Encrypted Secrets
# 1. Initialize age encryption
chezmoi init --keep-going
# 2. Create encrypted file
mkdir -p ~/.local/share/chezmoi/dot_ssh
cat > ~/.local/share/chezmoi/dot_ssh/encrypted_key.age << 'EOF'
# Paste your encrypted private key here
EOF
# 3. Reference in template
cat > ~/.local/share/chezmoi/dot_ssh/id_rsa.tmpl << 'EOF'
{{ include "encrypted_key.age" }}
EOF
# 4. Apply with password prompt
chezmoi apply
# 5. Verify
cat ~/.ssh/id_rsa
# Should show decrypted content
Exercise 3: Cross-Platform Template with Architecture Detection
# Create template that detects architecture
cat > ~/.local/share/chezmoi/dot_zshenv.tmpl << 'EOF'
# Platform detection
export OS="{{ .chezmoi.os }}"
export ARCH="{{ .chezmoi.arch }}"
{{ if eq .chezmoi.os "darwin" }}
# macOS paths
export PATH="/opt/homebrew/bin:$PATH"
export PATH="/usr/local/bin:$PATH"
{{ if eq .chezmoi.arch "arm64" }}
# Apple Silicon
export HOMEBREW_PREFIX="/opt/homebrew"
alias arch_intel="arch -x86_64"
{{ end }}
{{ else if eq .chezmoi.os "linux" }}
# Linux paths
export PATH="/usr/local/bin:$PATH"
{{ end }}
# Cross-platform: nvim nightly
{{ if .enable_nightly_nvim }}
export NVIM_NIGHTLY=1
{{ end }}
EOF
# Test rendering
chezmoi execute-template < ~/.local/share/chezmoi/dot_zshenv.tmpl
Troubleshooting
Symlink Conflicts and Resolution
Problem: stow refuses to create symlinks due to existing files.
# Error: ERROR: stow would create a symlink in the following directory,
# ERROR: if it could, but it can't because not all of the following paths
# ERROR: are directories:
# Solution 1: Remove conflicting file
rm ~/.bashrc
stow -v shell
# Solution 2: Use adopt to incorporate existing file
stow -d stow-repo -t ~ --adopt shell
# Moves ~/.bashrc into stow package and creates symlink
# Solution 3: Merge manually
diff ~/.bashrc stow-repo/shell/.bashrc
# Manually incorporate changes, then adopt
stow -d stow-repo -t ~ --adopt shell
Template Syntax Errors in chezmoi
Problem: Template won't render, showing Go template errors.
# Debug: Preview template rendering
chezmoi execute-template < ~/.local/share/chezmoi/dot_bashrc.tmpl
# Common errors:
# 1. Missing variable - check .chezmoi.toml
# 2. Incorrect comparison - use eq, ne, lt, gt
# 3. Unbalanced {{ }} - check for missing delimiters
# Fix: Test in isolation
cat > /tmp/test.tmpl << 'EOF'
{{ .chezmoi.os }}
{{ if eq .chezmoi.os "darwin" }}
macOS
{{ end }}
EOF
chezmoi execute-template < /tmp/test.tmpl
Relative Symlink Breakage
Problem: Symlinks break when you move directories or reorganize.
# Identify broken symlinks
find ~ -type l ! -exec test -L {} \; -print
# Root cause: Using relative symlinks that lose context
# stow creates relative symlinks by default
ls -la ~/.config/nvim
# If this shows: nvim -> ../../stow-repo/nvim/.config/nvim
# And stow-repo moves, this breaks
# Solution: Verify with absolute paths
stow -d "$(pwd)" -t ~ shell nvim tmux
# Or use cd into the correct directory
cd ~/stow-repo && stow -t ~ shell nvim tmux
Permission Issues with XDG Directories
Problem: Files created with wrong permissions or ownership.
# Symptom: ssh keys have 644 instead of 600
ls -la ~/.ssh/id_rsa
# -rw-r--r-- (should be -rw-------)
# Solution 1: chezmoi can manage permissions
# Add to template front-matter
cat > ~/.local/share/chezmoi/dot_ssh/id_rsa.tmpl << 'EOF'
{{- /* chezmoi:mode=0600 */ -}}
{{ include "private_key" }}
EOF
# Solution 2: Stow with manual permission fixing
stow -v shell
chmod 600 ~/.ssh/id_rsa
chmod 700 ~/.ssh
# Solution 3: Fix all SSH files
find ~/.ssh -type f -exec chmod 600 {} \;
find ~/.ssh -type d -exec chmod 700 {} \;
References
- GNU Stow Manual: https://www.gnu.org/software/stow/manual/
- chezmoi Documentation: https://www.chezmoi.io/user-guide/
- XDG Base Directory Specification: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
- POSIX Symlinks: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html
- Community Dotfiles:
Related Tutorials
Explore these complementary guides for comprehensive environment management:
- [[dotfiles-beginner-guide]] - Start here for fundamental concepts
- [[television-beginner-guide]] and [[television-deep-dive]] - Fuzzy finder setup for dotfile selection
- [[multiarch-hpc-alias-management]] - Managing .bashrc and shell configs across architectures
- [[ghostty-keyboard-shortcuts]] - Terminal emulator configuration strategies
- [[sesh-beginner-guide]] and [[sesh-deep-dive]] - Terminal session management in dotfiles
- [[worktrunk-beginner-guide]] and [[worktrunk-deep-dive]] - Git worktrees for dotfile branching
- [[git-worktrees-worktrunk-beginner-guide]] and [[git-worktrees-worktrunk-deep-dive]] - Advanced git worktree patterns
- [[macos-app-layout-beginner-guide]] and [[macos-app-layout-deep-dive]] - Versioning
.bunchfiles and Moom snapshots as dotfiles - [[bunch-beginner-guide|Bunch Beginner Guide]] and [[bunch-deep-dive|Bunch Deep Dive]] - Tracking
.bunchfiles as first-class dotfiles - [[moom-beginner-guide|Moom Beginner Guide]] and [[moom-deep-dive|Moom Deep Dive]] - Exporting and versioning Moom's plist
- [[karabiner-elements-beginner-guide|Karabiner-Elements Beginner Guide]] and [[karabiner-elements-deep-dive|Karabiner-Elements Deep Dive]] - Version
~/.config/karabinerconfigs and manage across machines
Summary
Key Takeaways
-
Symlink Architecture: Understand how symlinks work at the inode level. This prevents subtle bugs when reorganizing or moving dotfile repositories.
-
Tool Selection is Strategic:
- GNU Stow: Best for simple, declarative symlink management. Minimal dependencies, clear semantics.
- chezmoi: Best for complex cross-platform setups with templating, encryption, and automated scripts.
- Bare Git: Minimalist approach for users comfortable with git and willing to manage symlinks manually.
-
XDG Compliance: Modern applications support XDG Base Directory Specification. Structure your dotfiles to leverage this standard for cleaner home directories.
-
Templating Enables Portability: Cross-platform dotfiles require conditional logic. Both Stow and chezmoi support this, but chezmoi's templating is more powerful.
-
Bootstrapping is Critical: A solid bootstrap script makes provisioning new machines trivial. Include OS detection, package installation, and dotfile initialization.
-
Version Control Everything: Treat your dotfiles repository as a critical backup. Use git branches for machine-specific variants, not separate repositories.
-
Encryption for Secrets: Never store plain SSH keys or API tokens in version control. Use chezmoi's encrypted templates or a separate secrets management system.
Architecture Decision Guide
Choose based on your needs:
| Criterion | Stow | chezmoi | Bare Git |
|---|---|---|---|
| Learning curve | Low | Medium | Medium |
| Templating | Basic | Excellent | None |
| Encryption | No | Yes | Manual |
| Multi-machine | Git branches | Templates | Git branches |
| Automation | Scripts separate | Integrated | Manual |
| Symlink management | Automatic | Automatic | Manual |
Next Steps
-
Audit your current setup: List all dotfiles currently managed, identify what should be version-controlled vs. machine-specific.
-
Choose a tool: Pick the one that fits your complexity level and team culture.
-
Design structure: Plan directory layout before migrating existing files.
-
Write bootstrap script: Ensure new machine provisioning is fully automated.
-
Document decisions: Record why you chose certain patterns for future maintainers.
-
Set up CI/CD: Add linting and validation to your dotfiles repository workflow.
Your dotfiles repository should evolve with your environment. Revisit this guide as your needs grow in complexity.
Related Tutorials
Explore these complementary guides for comprehensive environment management:
-
[[dotfiles-beginner-guide]] - Start here for fundamental concepts
-
[[chezmoi-beginner-guide|Chezmoi Beginner Guide]] — Dedicated beginner guide for chezmoi setup and usage
-
[[chezmoi-deep-dive|Chezmoi Deep Dive]] — Advanced chezmoi: templates, encryption, run scripts, and multi-machine strategies
-
[[television-beginner-guide]] and [[television-deep-dive]] - Fuzzy finder setup for dotfile selection
-
[[multiarch-hpc-alias-management]] - Managing .bashrc and shell configs across architectures
-
[[ghostty-keyboard-shortcuts]] - Terminal emulator configuration strategies
-
[[sesh-beginner-guide]] and [[sesh-deep-dive]] - Terminal session management in dotfiles
-
[[worktrunk-beginner-guide]] and [[worktrunk-deep-dive]] - Git worktrees for dotfile branching
-
[[git-worktrees-worktrunk-beginner-guide]] and [[git-worktrees-worktrunk-deep-dive]] - Advanced git worktree patterns
-
[[mosh-beginner-guide|Mosh Beginner Guide]] — Persistent remote terminal sessions with Mosh + tmux (a key dotfile:
.tmux.conf) -
[[mosh-deep-dive|Mosh Deep Dive]] — Advanced Mosh configuration and HPC workflow patterns
-
[[just-deep-dive|Just Deep Dive]] — Advanced just patterns for automating dotfile workflows
-
[[just-vs-make|Just vs GNU Make]] — Why just is often preferred over Make for config management tasks
-
[[micropython-ttgo-t-display-beginner-guide|MicroPython TTGO T-Display Beginner Guide]] — practical use of venvs for embedded Python tooling
-
[[micropython-ttgo-t-display-deep-dive|MicroPython TTGO T-Display Deep Dive]] — advanced development workflow with justfiles and shell automation
-
[[docker-test-container-beginner-guide|Docker Test Container Beginner Guide]] and [[docker-test-container-deep-dive|Docker Test Container Deep Dive]] — test dotfile changes safely in Docker containers before deploying
-
[[honeymux-beginner-guide|Honeymux Beginner Guide]] — modern tmux wrapper with manageable configs
-
[[gh-cli-beginner-guide|GitHub CLI Beginner Guide]] — GitHub CLI basics for dotfile repo management
-
[[gh-cli-deep-dive|GitHub CLI Deep Dive]] — scripting
ghaliases and syncing config -
[[ssh-tutorial|SSH Tutorial]]
-
[[ssh-config-deep-dive|SSH Config Deep Dive]]
-
[[tmux-claude-code-beginner-guide|Tmux + Claude Code Beginner Guide]] — tmux.conf for Claude Code
-
[[tmux-claude-code-deep-dive|Tmux + Claude Code Deep Dive]] — Advanced tmux + Claude Code configuration
-
[[pet-beginner-guide|Pet Beginner Guide]] — CLI snippet manager; include its config and snippet TOML files in your dotfiles
-
[[pet-deep-dive|Pet Deep Dive]] — multi-directory snippet setups and Gist sync for cross-machine portability