Dotfiles & config

Status: 🟩 COMPLETE Last updated: 2026-06-19 Plain-English tagline: The hidden files (named with a leading dot) that configure your tools β€” .gitconfig, .npmrc, .bashrc, ~/.claude/, .env, .vscode/ β€” and how to manage them across projects and machines.


In plain English

If you peek at your home folder (C:\Users\georg\ on Windows, ~ on macOS) with hidden files visible, you’ll find dozens of files and folders whose names start with .. Things like:

.gitconfig
.npmrc
.bashrc        (or .zshrc on macOS)
.ssh/
.claude/
.config/
.vscode/

These are dotfiles β€” configuration files that customize the tools you use. The leading dot makes them hidden on Unix systems (by convention, not enforced). On Windows, they’re not hidden by default but the convention persists for cross-platform consistency.

Inside projects you’ll also see them:

.gitignore
.env
.env.local
.env.example
.eslintrc.json     (or .eslintrc.js, eslint.config.mjs)
.prettierrc
.editorconfig
.vscode/settings.json
.nvmrc
.gitattributes

Together, dotfiles encode your tool preferences AND your project conventions. Understanding which lives where, who reads it, and how to back it up across machines is what this entry is about.

The mental model:

  • User dotfiles (in your home folder) = personal preferences applied to every project
  • Project dotfiles (in the project folder) = project-wide conventions that apply to everyone working on the project
  • System config files (in /etc/ or Windows Registry) = OS-level, rarely touched

For Bible Quest-style projects, the relevant files are mostly project-level (in the repo) plus a few high-leverage user-level files (.gitconfig, .claude/).


Why it matters

Three reasons dotfiles deserve attention:

  1. They’re load-bearing. Your .gitconfig controls every commit’s author info. Your .env.local holds the secrets your app reads. Your .claude/settings.json controls how Claude Code behaves. Get them wrong, things break.

  2. They’re the part of your setup that doesn’t survive a fresh machine. Reinstall Windows; lose every customization unless you backed up dotfiles.

  3. Some are sensitive. .env* files hold credentials. .ssh/ holds private keys. Mishandling these is the #1 way developers leak secrets.

The trade-off: dotfiles can sprawl. A power user accumulates 30+ over years. Curation matters; not every config needs to be customized.


The user-level dotfiles you’ll actually touch

~/.gitconfig β€” Git user config

The single most-used dotfile. Created the first time you run a Git command. Edit with git config --global:

git config --global user.name "George Barsom"
git config --global user.email "george.barsom.au@gmail.com"
git config --global init.defaultBranch main
git config --global core.autocrlf input         # Windows-specific

Or edit directly: nano ~/.gitconfig (or code ~/.gitconfig):

[user]
  name = George Barsom
  email = george.barsom.au@gmail.com
[init]
  defaultBranch = main
[core]
  autocrlf = input
  editor = code --wait
[pull]
  rebase = false
[push]
  autoSetupRemote = true
[alias]
  st = status
  co = checkout
  br = branch
  cm = "commit -m"
  lg = "log --oneline --graph --decorate"

Useful aliases save typing. git st instead of git status.

~/.ssh/config β€” SSH connections

If you SSH into servers or push to GitHub via SSH:

Host github.com
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes

Host my-server
  HostName 1.2.3.4
  User george
  Port 22
  IdentityFile ~/.ssh/my-server-key

Then ssh my-server opens the connection without retyping everything.

The ~/.ssh/ folder also holds:

  • id_ed25519 / id_rsa β€” your PRIVATE key (NEVER share, NEVER commit)
  • id_ed25519.pub / id_rsa.pub β€” your PUBLIC key (safe to share; this is what you paste into GitHub’s SSH key settings)
  • known_hosts β€” fingerprints of servers you’ve connected to

File permissions matter: SSH refuses to use private keys readable by anyone else. On Unix, chmod 600 ~/.ssh/id_ed25519.

~/.npmrc β€” npm configuration

# Use a specific registry
registry=https://registry.npmjs.org/
 
# Use a private registry for a specific scope
@my-company:registry=https://npm.pkg.github.com/
 
# Auth tokens (NEVER commit; per-user only)
//npm.pkg.github.com/:_authToken=ghp_xxxxxxxxxxxx

Mostly auto-managed by npm login. The user-level file is for things you want for ALL projects. Per-project .npmrc overrides.

~/.bashrc / ~/.zshrc / PowerShell profile β€” shell config

The script that runs every time you open a new shell session. Customize the prompt, set env vars, define aliases.

Bash (~/.bashrc on Linux, ~/.bash_profile on Mac if using Bash):

# Aliases
alias ll='ls -la'
alias gs='git status'
alias gco='git checkout'
alias dev='npm run dev'
 
# Env vars
export EDITOR="code --wait"
export PATH="$HOME/.local/bin:$PATH"
 
# Source nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

Zsh (~/.zshrc β€” macOS default since Catalina):

# Plus oh-my-zsh framework popular for theming
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="robbyrussell"
plugins=(git node npm)
source $ZSH/oh-my-zsh.sh
 
# Same aliases / env vars as bash
alias ll='ls -la'
alias gs='git status'

PowerShell profile ($PROFILE β€” usually at Documents\PowerShell\Microsoft.PowerShell_profile.ps1):

# Aliases
Set-Alias -Name ll -Value Get-ChildItem
Set-Alias -Name code -Value "code.cmd"
 
# Functions
function gs { git status @args }
function gco { git checkout @args }
function dev { npm run dev }
 
# Env vars
$env:EDITOR = "code --wait"

To find your PowerShell profile: echo $PROFILE. Create the file if missing: New-Item -Path $PROFILE -ItemType File -Force.

~/.claude/ β€” Claude Code config

This is George’s setup. Lives at:

  • Windows: C:\Users\georg\.claude\
  • macOS/Linux: ~/.claude/

Notable files:

~/.claude/
β”œβ”€β”€ CLAUDE.md                              ← global instructions for every project
β”œβ”€β”€ settings.json                          ← hooks, permissions, env vars
β”œβ”€β”€ projects/<project-name>/
β”‚   β”œβ”€β”€ settings.json                      ← per-project settings
β”‚   β”œβ”€β”€ memory/
β”‚   β”‚   β”œβ”€β”€ MEMORY.md                      ← index of memories
β”‚   β”‚   β”œβ”€β”€ user_role.md
β”‚   β”‚   β”œβ”€β”€ playbook_*.md
β”‚   β”‚   β”œβ”€β”€ project_*.md
β”‚   β”‚   β”œβ”€β”€ feedback_*.md
β”‚   β”‚   └── reference_*.md
β”‚   └── ...
β”œβ”€β”€ plugins/
└── ...

~/.claude/CLAUDE.md is auto-loaded for every conversation in every project β€” this is where the playbook lives, where β€œGeorge is not a programmer” is recorded, etc.

~/.claude/projects/C--Users-georg/memory/MEMORY.md is loaded automatically β€” the index of memories that should always be in context.

This is the highest-leverage dotfile in George’s workflow. Configuring ~/.claude/ well affects every Claude Code conversation forever.

~/.config/ β€” XDG config home

Many tools follow the XDG Base Directory spec and put config in ~/.config/<toolname>/:

~/.config/
β”œβ”€β”€ nvim/        (Neovim config)
β”œβ”€β”€ git/         (alternative to ~/.gitconfig)
β”œβ”€β”€ starship/    (cross-shell prompt)
└── ...

On Windows, the equivalent is %APPDATA% (C:\Users\georg\AppData\Roaming\). Some cross-platform tools respect XDG vars on Windows too.


The project-level dotfiles you’ll see

These live in the project root, not your home folder.

.gitignore β€” what Git ignores

Lists files and folders Git should never track:

# Dependencies
node_modules
.pnp.cjs

# Production
.next
dist
build

# Environment
.env*.local

# Vercel
.vercel

# IDE
.vscode/settings.json   # debatable; sometimes you WANT to commit this
.idea

# OS
.DS_Store
Thumbs.db

# Logs
*.log

Critical. Without a good .gitignore, you might commit secrets, huge directories, or generated files.

.gitattributes β€” per-file Git behavior

* text=auto eol=lf
*.png binary
*.jpg binary
*.ico binary

Pairs with core.autocrlf to normalize line endings across OS. Most projects need only a few lines; the global core.autocrlf input setting handles the rest.

.env* β€” environment variables

See Environment variables for the full story. The pattern:

  • .env β€” defaults, committed (no secrets)
  • .env.example β€” template with empty values, committed
  • .env.local β€” actual values, GITIGNORED (contains secrets)
  • .env.production.local β€” production overrides, GITIGNORED

If you ever accidentally commit a real .env.local, rotate every secret in it. Git history is forever.

.editorconfig β€” cross-editor formatting

root = true
 
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

Almost every editor respects this. The single most useful β€œmake collaborators consistent” file.

.prettierrc and .prettierignore

Prettier’s config. See Linting.

{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2
}

.eslintrc.* or eslint.config.mjs

ESLint’s config. Modern flat config (ESLint 9+):

// eslint.config.mjs
export default [
  // your rules
];

Legacy: .eslintrc.json. Most active projects have migrated to flat config.

.nvmrc β€” Node version

22.11.0

Tells nvm / fnm / volta which Node version this project uses. New tab in VS Code β†’ nvm use β†’ instant switch. Vercel reads engines.node in package.json; nvmrc is just for local dev.

.vscode/ folder

Project-specific VS Code settings:

.vscode/
β”œβ”€β”€ settings.json       ← workspace settings (often committed)
β”œβ”€β”€ extensions.json     ← recommended extensions (committed)
└── launch.json         ← debug configurations (committed)

Whether to commit .vscode/settings.json is a team decision. Pro: every contributor gets the same baseline. Con: forces a tool choice. Most modern teams commit it.


A concrete example: a Bible Quest-style project’s dotfiles

The root of C:\Users\georg\st-marks-bible-quest:

st-marks-bible-quest/
β”œβ”€β”€ .git/                       ← Git's internal state (NEVER commit; never edit)
β”œβ”€β”€ .next/                      ← Next.js build output (gitignored)
β”œβ”€β”€ node_modules/               ← Dependencies (gitignored)
β”œβ”€β”€ .vscode/
β”‚   β”œβ”€β”€ settings.json           ← Project VS Code settings
β”‚   └── extensions.json         ← Recommended extensions
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── ci.yml              ← GitHub Actions
β”œβ”€β”€ .env.example                ← Committed; template
β”œβ”€β”€ .env.local                  ← GITIGNORED; real secrets
β”œβ”€β”€ .gitignore                  ← What git ignores
β”œβ”€β”€ .gitattributes              ← Per-file git behavior
β”œβ”€β”€ .editorconfig               ← Editor normalization
β”œβ”€β”€ .nvmrc                      ← Node version
β”œβ”€β”€ .prettierrc                 ← Prettier config
β”œβ”€β”€ eslint.config.mjs           ← ESLint flat config
β”œβ”€β”€ next.config.ts              ← Next.js config
β”œβ”€β”€ tailwind.config.ts          ← Tailwind config
β”œβ”€β”€ tsconfig.json               ← TypeScript config
β”œβ”€β”€ package.json                ← npm manifest
β”œβ”€β”€ package-lock.json           ← npm lockfile
└── ...code files...

George’s home folder also has:

~/.claude/                      ← Claude Code config (covered above)
~/.gitconfig                    ← Git user config
~/.npmrc                        ← npm config

That’s the full picture: project dotfiles are committed to the project; user dotfiles live in the home folder and apply globally.


Backing up dotfiles across machines

A common pattern: store your user-level dotfiles in a Git repository (often called β€œyour dotfiles repo”) so you can clone them on any machine.

Two common approaches:

mkdir ~/dotfiles
mv ~/.gitconfig ~/dotfiles/gitconfig
ln -s ~/dotfiles/gitconfig ~/.gitconfig
cd ~/dotfiles && git init

Repeat for each dotfile. Push to GitHub. On a new machine: clone, run a setup script.

2. Bare git repo + chezmoi / stow

Tools like chezmoi, yadm, GNU stow manage the symlinking + templating. More powerful, more complexity.

For most solo developers, this is overkill. Backing up just your ~/.gitconfig, ~/.claude/, and a list of installed apps is enough. You can rebuild from there in an hour.

The Bible Quest stack doesn’t require an elaborate dotfiles setup. Just keep ~/.claude/ backed up.


What NEVER to commit

  • .env* files with real values β€” secrets
  • ~/.ssh/ keys β€” credentials
  • ~/.aws/credentials, ~/.kube/config β€” cloud credentials
  • API tokens in .npmrc or anywhere else β€” credentials
  • Personal info in commit messages β€” privacy

The two-step prevention:

  1. .gitignore everything that might be sensitive.
  2. Audit commits before push. git diff --staged is your friend.

If you DID commit a secret: rotate the secret immediately. Trying to remove it from git history with git filter-branch / BFG is possible but stressful. Assume it was leaked the moment you pushed.


Common gotchas

  • Hidden files are HIDDEN. On macOS Finder, Cmd+Shift+. toggles visibility. On Windows Explorer, View β†’ Show β†’ Hidden items. Newcomers spend hours wondering β€œwhere IS my .env.local?”

  • Don’t commit .env.local. Triple-check. .gitignore should have .env*.local from day one of every project.

  • Don’t commit your private SSH key. id_ed25519 (no .pub) is private. Only id_ed25519.pub is safe to share.

  • A dotfile in a project applies to the whole project. A .editorconfig at the root affects every file. Be aware before adding one.

  • .env (no .local suffix) is sometimes committed, sometimes not. Convention: .env for defaults, .env.local for secrets. Some projects don’t make this distinction. Check your project’s pattern.

  • Symlinks in dotfiles can break on Windows without Developer Mode. See Windows dev environment.

  • .gitignore doesn’t ignore files already tracked. If you accidentally committed a file, .gitignore doesn’t un-track it. Run git rm --cached <file>, then commit. The file is no longer tracked but stays on disk.

  • Different tools read different file names. .eslintrc.json vs eslint.config.mjs (flat config). prettier.config.js vs .prettierrc. Modern conventions prefer .config.{js,mjs,ts} files; legacy uses dotfiles. Check your tool’s docs.

  • VS Code workspace settings can leak personal preferences. Carefully decide what goes in .vscode/settings.json (committed) vs your user settings (personal).

  • Per-project .npmrc overrides user .npmrc. Useful for monorepo registry settings; gotcha if you don’t realize your global settings are being overridden.

  • ~/.bashrc runs in interactive shells; ~/.bash_profile runs in login shells. macOS Terminal opens login shells; iTerm2 by default doesn’t. This is why some users’ Mac .bashrc is never read. Source it from .bash_profile to fix.

  • PowerShell has multiple profile files. $PROFILE.CurrentUserCurrentHost (per-user, per-host) is the most common. $PROFILE.AllUsersAllHosts requires admin to edit.

  • Aliases shadow real commands. An alias ls=ls -la makes plain ls always show long format. Some scripts assume default ls behavior; aliases can break them. Use functions instead of aliases for shell config that might affect scripts.

  • Sourcing the .bashrc after editing. Changes to .bashrc don’t take effect in already-open shells. source ~/.bashrc reloads it.

  • Per-OS dotfiles differ. A .bashrc written for Linux may not work on Mac (different default tools). Use conditionals: if [[ "$OSTYPE" == "darwin"* ]]; then ... fi.

  • chmod 600 on private keys. Required on Unix for SSH to use them. chmod 700 ~/.ssh on the directory too.

  • code --wait is the right EDITOR value. Tells Git to wait until you close the file before continuing. Without --wait, Git thinks you’re done before you’ve typed anything.

  • .editorconfig precedence: the closest one wins. A .editorconfig deep in a subfolder overrides one at the root. Use root = true at the project’s root to stop search.

  • Newlines at end of files. insert_final_newline = true in .editorconfig ensures every file ends with \n. Some tools (older shells, certain parsers) require this; some don’t care.

  • Trailing whitespace matters in Markdown. Two trailing spaces become a <br> in some Markdown flavors. trim_trailing_whitespace = false for .md files is a thing.

  • Locks on dotfiles. A few tools acquire locks on their config (e.g., npm’s package-lock.json is technically a config). Don’t edit while the tool is running.

  • Don’t symlink across cloud-synced folders. Syncing OneDrive or Dropbox can break symlinks unpredictably.

  • xdg-open opens files with the user’s preferred app on Linux. Useful for β€œopen this URL” scripts. Mac uses open; Windows uses start.

  • ~/.config/ is NOT auto-created. Many tools expect it to exist; some create it on first run; some don’t. Create it manually if needed.

  • AI agents (including Claude Code) read ~/.claude/. It’s a privileged config location. Mistyping a setting can confuse the agent. Keep it tidy.

  • MEMORY.md is auto-loaded; other memory files are loaded by reference. Order matters; index-only entries vs full file content.

  • Don’t put real credentials in ~/.claude/ files. Memories may be included in conversations. Use environment variables for secrets.

  • The .git/ folder is special. Never edit its contents directly. .git/config is sometimes acceptable to read but always edit via git config commands.

  • A new machine starts with very few dotfiles. Most are created lazily as you use tools. The .gitconfig doesn’t exist until your first git config or git commit.

  • AI tools can suggest committing dotfiles that shouldn’t be committed. Watch for β€œlet me add .env to the repo” type suggestions. Always verify before staging.


When to invest more in dotfile management

For a one-machine, one-developer setup: keep dotfiles simple. Don’t build a dotfiles repo unless you have a reason.

Invest in serious dotfile management when:

  • You work on 2+ machines and want them to be identical
  • You frequently set up new machines (job changes, distro hops)
  • You collaborate with others who’d benefit from your setup
  • You enjoy the customization for its own sake

Otherwise, the casual approach (just keep the important files backed up somewhere) is enough.


See also


Sources