My personal dotfiles for macOS, managed with GNU Stow and Homebrew.
The goal is to automate setting up a new Mac from scratch: shell, editor, Git, dev tools, and apps. Also I want to avoid having a "version drift" between machines and forget how I set up my daily working environment. Each tool's config lives in its own "package" that can be installed independently.
Built for my specific workflow as a Cloud Engineer (backend/infra, Go, Node, Terraform, Neovim + VS Code, iTerm2), but designed to be modular enough to fork and adapt.
Early stage: This repo is actively evolving. Expect rough edges and breaking changes. If you'd like to use it, fork it and adapt to your needs (after careful review, at your own risk!). Issues and PRs for bugs are welcome.
For the repo and replace CHANGEME with your GitHub handle:
git clone https://github.com/CHANGEME/dotfiles.git ~/dotfiles
cd ~/dotfiles
cp config.example config.local # Fill in your personal values
./install.sh --helpIf you have existing configs for e.g. zsh you would like to consider, run the following. You can use the diff to consider the changes you need. Make sure to also manually adopt other config files for your tool if required (this will only adopt files that are defined in this repo):
./install.sh --dry-run # Preview what would happen
./install.sh --adopt # Pull existing configs into the repo, then symlink
git diff # Review what was adoptedIf in doubt, run:
./install --helpHere is a quick overview:
./install.sh # Interactive install (gum picker)
./install.sh -p zsh -p git -p starship # Non-interactive: install specific packages
./install.sh -p zsh,git,starship # Same, comma-separated
./install.sh -p zsh,git --macos # Install packages and apply macOS defaults
./install.sh update # Pull latest, update brew deps, re-stow packages
./install.sh uninstall # Remove symlinks for selected packages-p, --package PKG # Packages to install (repeatable, comma-separated)
-n, --dry-run # Preview changes without installing
--adopt # Adopt existing files into the repo (first-time setup)
--macos # Apply macOS system defaults (skipped by default)
-h, --help # Show all optionsWithout -p, the script runs interactively using gum for package selection. With -p, all prompts are skipped.
On first run, install.sh prompts for your name, email, and GPG key, saving them to config.local (gitignored). These values are used by per-package pre-hooks to generate local config files (e.g., ~/.gitconfig.user).
To configure manually, copy the example file:
cp config.example config.local| Variable | Used in | Purpose |
|---|---|---|
GIT_NAME |
.gitconfig |
Git author name |
GIT_EMAIL |
.gitconfig |
Git author email |
GIT_SIGNING_KEY |
.gitconfig |
GPG signing key ID |
Each package is a self-contained folder with its configs, a Brewfile for dependencies, and optional prehooks.sh / posthooks.sh for setup steps.
| Package | What it configures | Brewfile | Hooks |
|---|---|---|---|
editorconfig |
Global .editorconfig (LF, UTF-8, spaces) |
- | - |
zsh |
Shell config, aliases, functions, completions | zsh | - |
git |
Git config (GPG signing, delta pager), SSH config, global gitignore | git, git-delta, lazygit, gh, gnupg | Pre-hook: generates ~/.gitconfig.user |
starship |
Prompt theme (gruvbox dark) | starship | - |
nvim |
Neovim (LazyVim) | neovim, luarocks | - |
vscode |
VS Code settings, extensions | visual-studio-code, claude-code | Post-hook: installs extensions |
iterm2 |
iTerm2 preferences, 0xProto Nerd Font | iterm2, font-0xproto-nerd-font | Post-hook: configures prefs folder sync |
docker |
Docker client config (ghcr.io, osxkeychain) | rancher, docker-compose | - |
k8s |
Kubernetes tools | kubectl, helm, k9s, k3d | - |
terraform |
OpenTofu CLI config | opentofu | Post-hook: creates plugin cache dir |
go |
Go environment variables | go | - |
node |
Node.js and pnpm | node@24, pnpm | - |
proton |
Proton suite + SSH agent + GPG key via Pass CLI | pass-cli, protonvpn, proton-mail, proton-drive, proton-pass | Post-hook: logs in and imports GPG key |
apps |
Personal apps (no configs, just installs) | obsidian, ollama, localsend, pika, tailscale-app, spotify, opera, anki | - |
Each top-level folder is a "package". Its contents mirror $HOME. Running stow zsh creates symlinks from ~/dotfiles/zsh/.zshrc to ~/.zshrc, etc.
stow git # Symlink a package
stow --restow git # Update symlinks
stow -D git # Remove symlinksEach package declares its own dependencies. The install script runs brew bundle only for packages you select.
Packages can include:
prehooks.sh— runs before stowing (e.g., generating local config files)posthooks.sh— runs after stowing (e.g., configuring iTerm2 prefs sync, installing VS Code extensions)
lefthook runs gitleaks on every commit to prevent accidentally committing secrets by checking against a large list of known patterns. Still, you should be careful to not push credentials.
Some tools don't auto-save to a custom folder. Lefthook warns on commit if things are out of sync, and shell functions let you update manually (so you are in control):
| Tool | Auto-sync | Manual sync command |
|---|---|---|
| iTerm2 | Yes (prefs folder sync) | - |
| VS Code extensions | Warn on commit | dotfiles-sync-extensions |
SSH and GPG keys are managed through Proton Pass CLI — keys never touch the filesystem as plain files.
- SSH:
pass-cliruns as an SSH agent (started in.zshrc). Keys are loaded from your vault on demand. - GPG: The
protonhook imports your signing key from Proton Pass into the local GPG keyring.
On a fresh machine, select the proton package during install. The hook will prompt you to log in and import your GPG key automatically.
- Xcode (App Store only — use
xcode-select --installfor CLI tools) - Keynote, Numbers, Pages (App Store)
- Create the folder structure mirroring
$HOME, e.g.:mkdir -p newpkg/.config/newtool
- Add your config files inside
- (Optional) Add a
Brewfilefor dependencies - (Optional) Add a
prehooks.shfor pre-stow setup - (Optional) Add a
posthooks.shfor post-stow setup - Add a
.stow-local-ignoreif the package has a Brewfile or hooks:Brewfile prehooks\.sh posthooks\.sh \.stow-local-ignore - Add the package name to
ALL_PACKAGESininstall.sh - Run
stow newpkg
- Ask for sudo/credentials once (keep-alive for brew cask installs)
- End-to-end testing on a fresh installation
- More apps + settings
- Improve structure (dependencies,...)
- GNU Stow — symlink farm manager used here
- Homebrew —
Homebrewreference - Mathias Bynens' dotfiles — popular macOS defaults reference (a lot of default values!)
- awesome-dotfiles — curated list of dotfile resources