Stockio

Manage Dotfiles Across Machines With Git and GNU Stow

Create a portable dotfiles repository with GNU Stow, safe backups, machine-specific overrides, and a bootstrap process you can test before trusting it.

By Grace Chen

AI Tools Editor ·

On this page

A second laptop should not require reconstructing years of shell aliases from memory. Dotfiles make personal tooling repeatable, but copying an entire home directory into Git creates a different problem: secrets, platform assumptions, and accidental overwrites travel with the useful configuration.

Inventory configuration before synchronizing it

Start with three small targets: shell initialization, Git preferences, and one editor configuration. For JSON-based settings, use the JSON formatter with a sanitized sample to inspect structure before deciding what belongs in the shared repository. Some editor files use JSON with comments rather than strict JSON, so a strict formatter may reject otherwise valid editor configuration.

Separate portable preferences from local state. Aliases and editor indentation preferences are good candidates. Shell history, credential stores, browser profiles, caches, and generated session files are not. Read each candidate file rather than assuming a filename beginning with a dot is harmless.

Define the supported machines explicitly: perhaps macOS with Zsh and Linux with Bash. You do not need universal compatibility. You need predictable behavior on the systems you actually use. Keep the first version small enough to review line by line, and expand it only after a successful second-machine installation.

Choose a repository layout with visible ownership

A normal Git repository plus GNU Stow is a straightforward starting point. Stow creates symbolic links from organized package directories into a target directory. It does not install programs or magically translate configuration between operating systems. Read the GNU Stow manual before using advanced options.

Organize packages so each owns a clear set of paths. A minimal tree could contain a shell package with a shared environment file, a Git package with Git configuration, and an editor package with selected settings:

dotfiles/
shell/
  .config/
    shell/
      common.sh
git/
  .gitconfig
editor/
  .config/
    example-editor/
      settings.json

The package name is not part of the resulting home-directory path. Stowing the Git package targets ~/.gitconfig, while the shell package targets ~/.config/shell/common.sh. Use explicit target paths so the working directory does not silently determine where links appear.

An alternative such as chezmoi can manage templates and machine differences more directly. Choose one ownership model initially. Having two managers generate or link the same file makes troubleshooting harder than maintaining a few simple files manually.

Install with a dry run and preserve existing files

Clone the repository into a stable location such as ~/dotfiles. Moving it later breaks links that point into it unless you recreate them. Install Stow through your platform's package manager, then preview the operation before changing the home directory.

stow --simulate --verbose --target="$HOME" git shell
stow --verbose --target="$HOME" git shell

If Stow reports a conflict, inspect the existing path. It might be valuable configuration, a directory created by an application, or a link owned by another manager. Do not make automatic deletion the default solution. Move conflicting files into a clearly named backup location after reviewing them, then retry the dry run.

Be cautious with adoption features that move existing files into the repository: they can replace your intended source configuration with local contents. A conflict is a useful review point, not merely an obstacle to suppress. Commit only after inspecting the diff.

Test the rollback path too. Unstowing a package removes links Stow manages, but restoring previously backed-up files is a separate action. Document both steps. A setup that can be installed but cannot be safely removed is not yet reliable across machines.

Keep machine differences in deliberate overrides

Shared configuration should establish defaults; local configuration should express machine identity. For Git, include a local file that is excluded from the repository. Consult the Git configuration documentation for include ordering and conditional includes when work and personal repositories need separate identities.

[core]
  editor = code --wait
[include]
  path = ~/.gitconfig.local

Set your name, email, and any machine-specific signing configuration in that local file. Verify the effective result with git config --show-origin --get user.email inside representative repositories. Checking origins is more useful than guessing which global setting won.

For shell startup, load common settings from the appropriate startup file for that shell. Keep common code within a compatible syntax subset, and guard optional commands with command -v. Bash and Zsh have different startup rules; a file that works interactively may not run in a login session or noninteractive script.

Avoid turning the shared file into dozens of hostname branches. Prefer local overrides or small platform-specific files with clear entry points. Quote paths, avoid hard-coded usernames, and test on machines where optional tools are absent. Silence is not always success: a missing required dependency should produce a useful message.

Protect secrets and prove a fresh setup works

Add exclusions before the first commit, but do not treat .gitignore as security. Already tracked files remain tracked. Inspect staged changes with git diff --cached, search for accidental tokens, and use a secret scanner where appropriate. If a real credential enters history, rotate it; removing the latest copy does not revoke earlier exposure.

Do not store private SSH keys, cloud tokens, or complete environment dumps in the dotfiles repository. Even private repositories can be cloned onto unsuitable machines or exposed through integrations. Keep secrets in a suitable credential manager and document how to provision them separately.

A bootstrap script should be short, rerunnable, and explicit about changes. Avoid running downloaded scripts without review. Separate installing dependencies from linking files, and require confirmation before modifying existing configuration. Idempotence means rerunning produces the same desired state, not that errors are hidden with blanket success exits.

Test in a temporary user account or disposable environment before a real migration. Confirm shell startup, Git identity, editor launch, and removal. Revisit the existing Git workflow guide for review habits, and use development resources to document related tool prerequisites. The final acceptance test is a new machine working from the README, without undocumented manual repairs.

Key takeaways

  • Track selected configuration, not an entire home directory.
  • Use clear package ownership and preview link changes before applying them.
  • Keep machine identity and credentials outside shared defaults.
  • Back up conflicts and document installation as well as removal.
  • Test the repository on a fresh environment before relying on it.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in Dev Tools & AI Coding
Dev Tools & AI Coding

8 Best AI Code Review Tools

Compare eight AI-assisted code review tools for pull requests, security checks, and repository workflows, with practical cautions about false alarms, context, and human responsibility.

Includes: JSON formatterSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

15 Best VS Code Extensions for Web Development

Find useful VS Code extensions for formatting, debugging, writing and remote work. This practical list explains what each add-on does, who benefits, and when built-in features are enough.

Includes: JSON formatterSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Turborepo Monorepo With pnpm Workspaces

Set up pnpm workspace boundaries and Turborepo task caching with explicit package dependencies, reliable outputs, and a structure that stays understandable as teams grow.

Includes: JSON formatterSofia MarchettiSofia Marchetti
Dev Tools & AI Coding

Chrome DevTools Performance Panel: From Trace to Fix

Record focused Chrome Performance traces, identify expensive main-thread work, and verify targeted fixes with repeatable scenarios instead of guessing from a flame chart.

Includes: JSON formatterRavi DesaiRavi Desai
Dev Tools & AI Coding

direnv and .env Files: Managing Local Environment Variables

Make local project configuration predictable with .env templates, explicit loading rules, direnv approval, and checks that catch missing values before startup.

Includes: JSON formatterRavi DesaiRavi Desai
Dev Tools & AI Coding

AI Tailwind CSS: Get Clean Styles From AI Assistants

AI assistants can produce a convincing interface quickly, but the stylesheet often reveals how little was specified. Arbitrary spacing, duplicated utilities and brittle positioning accumulate when every prompt.

Includes: JSON formatterRavi DesaiRavi Desai