---
title: "Dotfiles"
slug: "dotfiles"
category: "file-organization"
tags: ["file-organization", "dotfiles", "configuration", "chezmoi", "stow"]
status: "stable"
last_updated: 2026-05-14
summary: "Use ~/.config/ first, sync with chezmoi or stow, never commit secrets, and split public from private."
related: ["[[file-organization/config-files]]", "[[file-organization/naming-conventions]]", "[[coding/shell]]", "[[coding/git]]", "[[tooling/github]]"]
---

## Overview

Dotfiles are the per-user configuration that follows a developer across machines: shell, editor, git, terminal, language toolchains. A good dotfile setup boots a new laptop in under an hour. A bad one stays scattered across `$HOME` with secrets mixed in. The rules below cover the XDG layout, sync tooling, public/private splits, and the rule that distinguishes a dotfile from an env var. They pair with [[file-organization/config-files]] for project-level config.

## Prefer `~/.config/` over `$HOME` for new tools

The XDG Base Directory Specification puts per-user config under `~/.config/<tool>/`. Use it for everything that supports it.

- `~/.config/git/config` instead of `~/.gitconfig` (git supports both since 1.7.12).
- `~/.config/nvim/init.lua` for Neovim.
- `~/.config/zsh/.zshrc` plus `ZDOTDIR=~/.config/zsh` in `/etc/zsh/zshenv` (or `~/.zshenv`).
- `~/.config/starship.toml`, `~/.config/alacritty/alacritty.toml`, `~/.config/wezterm/wezterm.lua`.

`$HOME` stays clean and the entire config tree is one directory away. Tools that still demand top-level dotfiles (`.bashrc`, `.profile`, `.ssh/config`) keep their old paths; do not fight the toolchain.

## Use chezmoi or stow to sync across machines

A dotfiles git repo plus a sync tool beats every shell-script symlink installer. Pick one:

- **chezmoi.** Template engine, machine-specific overrides via `.chezmoi.toml.tmpl`, secret integration with 1Password and Bitwarden, and an `apply` model. Best for heterogeneous fleets (work laptop, personal laptop, Linux server).
- **GNU stow.** Pure symlinks, no templating. Each tool's config lives in its own directory and `stow zsh` symlinks the contents into `$HOME`. Best when machines are similar and templating is overkill.

Both produce a single source of truth in a git repo. New machine bootstrap reduces to `git clone` plus one command.

## Never commit secrets to a dotfiles repo

A dotfiles repo is usually public or semi-public; it is the wrong place for API keys, tokens, or private SSH keys.

- Store secrets in a password manager (1Password CLI, Bitwarden CLI, `pass`, `gopass`).
- Reference them from dotfiles via chezmoi templates or shell `command substitution`: `export OPENAI_API_KEY="$(op read 'op://Personal/OpenAI/token')"`.
- If a secret lands in git history, treat it as compromised. Rotate and force-push only after running `git filter-repo` or `bfg`. See [[coding/git]].
- Run `gitleaks detect` as a pre-commit hook on the dotfiles repo.

`.gitignore` is not enough. A scanner is.

## Split public from private

Some dotfiles are safe to publish; others contain machine identifiers, internal hostnames, or work-only aliases. Split them.

- **Public repo:** shell prompt, editor config, generic aliases, public language settings. Visible to recruiters and collaborators.
- **Private repo:** work email in `git/config`, internal SSH hosts in `~/.ssh/config`, company VPN scripts. Visible only to the author.

chezmoi handles this natively with `private_` file prefixes and per-source configuration. With stow, use two separate repos and stow them into the same target.

Never blend the two. A single repo with secrets behind `.gitignore` is one accidental `git add -A` from a leak.

## Per-machine config goes in env vars; per-user config goes in dotfiles

The rule that ends most "which file should this go in?" debates:

- **If the value changes per machine, it is an env var.** Hostname, work username, monitor DPI, machine-specific paths. Set them in `~/.config/zsh/local.zsh` (gitignored), or in `~/.zshenv` outside the dotfiles repo.
- **If the value is the same across every machine, it is a dotfile.** Editor preferences, shell aliases, prompt theme, git user name.
- **If the value is a secret, it is in the password manager**, referenced from the dotfile or env file.

The corollary: a dotfile that hardcodes `/Users/alice/work` is wrong. Use `$HOME` or `$WORK_DIR` and set `WORK_DIR` per machine.

## Bootstrap script earns its keep

A `bootstrap.sh` (or `install.sh`) at the dotfiles repo root takes a fresh machine and installs the toolchain. Keep it small and idempotent.

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

# Install Homebrew (macOS) or apt packages (Linux).
# Install chezmoi.
# Run `chezmoi init --apply <github-user>`.
# Install language toolchains (rustup, mise, fnm).
```

The script should be re-runnable. A second invocation should change nothing on a healthy machine. See [[coding/shell]] for the script-writing rules.

## Organize the repo by tool, not by file type

A dotfiles repo's directory layout should mirror the destination structure under `~/.config/`.

```
dotfiles/
├── README.md
├── bootstrap.sh
├── git/
│   └── config
├── nvim/
│   └── init.lua
├── zsh/
│   ├── .zshrc
│   └── aliases.zsh
└── starship/
    └── starship.toml
```

When the layout mirrors the target, `stow git` Just Works and chezmoi templates stay readable. Avoid lumping everything into a flat `files/` directory; the structure is the documentation.

## Related

- [[file-organization/config-files]]
- [[file-organization/naming-conventions]]
- [[coding/shell]]
- [[coding/git]]
- [[tooling/github]]
