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.jsonThe 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 shellIf 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.localSet 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.
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