Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

A Practical Dotfiles Setup

By Kokil Thapa | Last reviewed: September 2026

You rebuild a laptop or SSH into a fresh Ubuntu box far more often than frameworks change. Without a practical dotfiles setup, you lose aliases, Git defaults, SSH host blocks, and editor keymaps. You re-type the same fixes for the tenth time. I maintain several production servers and local dev machines with the same Git-based dotfiles repo. This guide shows the structure I actually use—not a perfect abstraction, but one that survives real client work, Ubuntu server provisioning, and late-night deploys.

What is a practical dotfiles setup and why does it matter?

Dotfiles are hidden config files in your home directory: .bashrc, .gitconfig, .ssh/config, and editor settings. A practical dotfiles setup treats them like application code. You commit, review, and replay them.

The payoff is speed and consistency. Your ll alias, Git identity, and SSH jump-host rules appear on a new VPS in five minutes. Your local PHP 8.4 path and Deployer shortcuts match the machine you used yesterday. Small teams benefit too. One shared baseline reduces "works on my laptop" friction during Linux server administration work.

What dotfiles are not: a second job. Skip exotic frameworks until plain Git plus Stow feels boring. Fancy tools help when you manage ten machines with different OS versions. Most freelancers and small agencies need a repo, a symlink tool, and a bootstrap script.

A Practical Dotfiles Setup — Core IdeaGit Repo~/.dotfilesGNU StowSymlink layerHome DirLive configsLaptopLocal devStaging VPSDeploy targetCI RunnerGitLab jobOne repo, many machines — same shell and Git defaultsBootstrap script replays Stow on each host
A practical dotfiles setup: one Git repository feeds GNU Stow, which symlinks configs into every machine you use.

Start with pain you feel weekly. For me that is Git ergonomics, SSH host entries, and shell paths for PHP and Composer. Editor config comes next. Window manager themes can wait.

How do you organize dotfiles in a Git repository?

Keep the repo at ~/.dotfiles or ~/dotfiles. Use a flat top level with one folder per config bundle. Each bundle mirrors the path Stow will create under your home directory.

A layout that scales on Ubuntu 22/24 and macOS looks like this:

dotfiles/
├── README.md
├── install.sh
├── bash/
│   └── .bashrc
│   └── .bash_profile
├── git/
│   └── .gitconfig
│   └── .gitignore_global
├── ssh/
│   └── .ssh/
│       └── config
├── nvim/
│   └── .config/nvim/init.lua
└── deployer/
    └── .deployer/

The bash/ folder holds files that Stow links into ~/. The nvim/ folder holds .config/nvim/ because Neovim expects XDG paths. Match real paths, not clever nesting.

Initialize the repository

  1. Clone or create the repo: git init ~/.dotfiles.
  2. Add a root .gitignore for OS junk: .DS_Store, *~, .cache/.
  3. Commit one bundle at a time so rollbacks stay obvious.
  4. Push to a private remote on GitHub or GitLab. Treat it like credentials-adjacent infrastructure.

Document assumptions in README.md: target OS, shell, and packages the bootstrap script installs. Future you—or a contractor on a Laravel Livewire booking project—should not guess.

Tag stable snapshots after major changes: git tag v2026.09. Roll back a bad .bashrc edit without archaeology.

Manual symlinks rot fast. You forget which file is canonical. GNU Stow treats each bundle as a mini tree and links files into your home directory. It is package-manager logic applied to dotfiles.

Install Stow on Ubuntu:

sudo apt update
sudo apt install -y stow

From inside the repo, stow one bundle:

cd ~/.dotfiles
stow --verbose bash
stow --verbose git
stow --verbose ssh
stow --verbose nvim

Stow creates symlinks like ~/.bashrc -> ../.dotfiles/bash/.bashrc. Edit the repo file; the live config updates instantly. Unstow before removing a bundle:

stow --delete bash

Never stow over files you have not inspected. Stow refuses some conflicts; others silently nest. Before first stow on a new machine, back up existing configs:

mkdir -p ~/dotfiles-backup-$(date +%F)
cp -a ~/.bashrc ~/.gitconfig ~/dotfiles-backup-$(date +%F)/ 2>/dev/null || true
GNU Stow Bundle Layout~/.dotfiles repobash/git/ssh/nvim/deployer/Home directory~/.bashrc → symlink~/.gitconfig → symlink~/.ssh/config → symlink~/.config/nvim → symlink
Each Stow bundle mirrors home-directory paths; stow creates symlinks without manual ln -s maintenance.

Alternative tools exist—chezmoi, yadm, bare-repo methods—but Stow stays readable for the next developer. That matters on shared Ansible-driven server setups where dotfiles complement playbooks rather than replace them.

Which dotfiles should you version first on Ubuntu?

Prioritize files that save time on every session. Skip anything that embeds secrets or machine-specific hardware paths until you have a split strategy.

File / bundleTrack in Git?Notes
.bashrc / .zshrcYesAliases, PATH, history settings; keep portable
.gitconfigYesName, email, aliases; use conditional includes per directory
.ssh/configYes (no keys)Host blocks, IdentityFile paths, ProxyJump chains
.ssh/id_*NeverGenerate per machine; add pubkey to Git hosting manually
.env / API tokensNeverUse .env.local outside the repo or a secret manager
.config/nvim/YesSee Neovim setup for DevOps engineers for a lean baseline
.deployer/PartialShared recipes yes; host passwords never
.mysql_historyNeverMay contain query data; exclude via global gitignore

Example portable .bashrc excerpt

# ~/.dotfiles/bash/.bashrc
export EDITOR="nvim"
export PATH="$HOME/.config/composer/vendor/bin:$PATH"

alias ll='ls -lah'
alias gs='git status -sb'
alias dep='vendor/bin/dep'

# PHP version switch — override in ~/.bashrc.local
export PHP_BIN="${PHP_BIN:-php8.4}"

Load local overrides from an untracked file:

# at end of .bashrc
[[ -f ~/.bashrc.local ]] && source ~/.bashrc.local

That pattern keeps the repo portable. Your laptop can set PHP_BIN=php8.5 while a server stays on 8.4.

Example .gitconfig with conditional identity

[user]
    name = Kokil Thapa
    email = you@personal.example

[includeIf "gitdir:~/work/"]
    path = ~/.gitconfig-work

[alias]
    co = checkout
    br = branch
    lg = log --oneline --graph --decorate -20

[init]
    defaultBranch = main

Store .gitconfig-work in a separate stow bundle if client email must differ. That avoids wrong-commit-author mistakes on law-firm repos and eCommerce projects alike.

SSH config without private keys

# ~/.dotfiles/ssh/.ssh/config
Host gitlab-prod
    HostName gitlab.example.com
    User git
    IdentityFile ~/.ssh/id_ed25519_gitlab
    IdentitiesOnly yes

Host client-vps
    HostName 203.0.113.10
    User deploy
    ForwardAgent no

Pair this with a hardened Ubuntu SSH server setup on the remote side. Keys stay generated on each host. Only the layout is shared.

How do you bootstrap a new machine from dotfiles?

The bootstrap script is the product. Cloning the repo is step one. install.sh should install packages, run Stow, and verify symlinks—idempotently.

Bootstrap Flow — New Machine1. Fresh OSUbuntu 242. Clone repogit clone3. install.shapt + stow4. SSH keyssh-keygen5. Verify: shell, git, ssh -T, nvimRun deploy or composer install6. Ready — match existing workflowSame aliases as production EC2
Bootstrap a new Ubuntu or macOS machine: clone dotfiles, run install.sh, generate SSH keys locally, then verify Git and shell tooling.

Sample install.sh skeleton:

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

DOTFILES_DIR="$(cd "$(dirname "$0")" && pwd)"
BUNDLES=(bash git ssh nvim)

if command -v apt-get &>/dev/null; then
  sudo apt-get update
  sudo apt-get install -y stow git curl
fi

for bundle in "${BUNDLES[@]}"; do
  stow --dir="$DOTFILES_DIR" --target="$HOME" --restow "$bundle"
done

echo "Dotfiles linked. Open a new shell or: source ~/.bashrc"

Run with bash install.sh the first time. chmod +x once committed. Add optional flags: --minimal for servers without Neovim, --work to include the work Git bundle.

On production VPS hosts I keep bootstrap minimal. Shell, Git, SSH, and Deployer aliases only—no editor plugins. Full dev bundles stay on laptops. That mirrors how I split CI runners versus local builds in YAML pipeline workflows.

For teams, combine dotfiles with Ansible when you need package versions pinned system-wide. Dotfiles own user-level prefs; playbooks own php8.4-fpm packages and firewall rules. The overlap is intentional, not redundant.

How do you keep secrets out of your dotfiles repo?

One leaked API key in a public dotfiles fork becomes a billing incident. Private repos help. They are not sufficient. Assume the repo may become public someday.

  • Never commit private keys, .env files, or password manager exports.
  • Use *.local files loaded from untracked paths.
  • Add a repo .gitignore plus a global gitignore for .ssh/id_* and .env*.
  • Run git log -p before making a repo public; history keeps secrets forever.
  • Use a password generator for service accounts; store vault copies outside Git.
Track in Git or Keep Local?New config fileContains secret?Key, token, passwordMachine-specific?GPU path, hostnameKeep local*.local untrackedTemplate only.env.example in GitTrack in GitShell, Git, SSH layoutyes → localno → Git
Decision flow for a practical dotfiles setup: secrets and machine-specific values stay local; portable prefs go in Git.

For Deployer host definitions, I commit structure and usernames. Passwords live in .env on the deploy runner or in GitLab CI variables. Same pattern as Laravel apps: tracked config templates, untracked secrets. Reference the official gitignore documentation for pattern syntax.

If you already leaked a key, rotate it immediately. Removing the file from HEAD is not enough. Use git filter-repo or BFG only when you understand rewrite impact on clones.

Encrypt sensitive dotfiles only when you must sync them across machines. Tools like SOPS or age work. They add ops overhead. For most PHP/Laravel freelancers, untracked .local files plus CI secrets is simpler and safer.

Key Takeaways

  • Store dotfiles in a private Git repo with one Stow bundle per config domain: bash, git, ssh, editor.
  • Use GNU Stow instead of hand-maintained symlinks; always back up existing files before first stow.
  • Keep secrets and private keys out of Git; load *.local overrides from untracked paths.
  • Write an idempotent install.sh that installs Stow, restows bundles, and documents OS assumptions.
  • Split minimal server bundles from full laptop bundles to reduce attack surface on production VPS hosts.
  • Tag repo snapshots so you can roll back a bad shell change without rebuilding from memory.

People Also Ask

Should dotfiles be a public or private Git repository?

Use a private repository by default. Shell and SSH configs reveal hostnames, internal URLs, and tooling choices. Public dotfiles are fine for generic editor themes with zero infrastructure hints. If you publish, audit history with git log -p first.

GNU Stow vs bare Git repo for dotfiles—which is simpler?

Stow is simpler for mixed paths like .config/nvim alongside .bashrc. Bare-repo methods keep everything in ~/.cfg with alias tricks. They work but confuse newcomers. Stow reads clearly in a README and pairs well with Ansible on cloud VPS provisioning.

How often should you update dotfiles?

Commit when a change survives one week of daily use. Batch tiny tweaks monthly. Pull before bootstrap on a new machine. After OS upgrades, run stow --restow on each bundle to fix broken symlinks.

Do dotfiles replace infrastructure-as-code tools?

No. Dotfiles manage user-level preferences on a host. Terraform, Ansible, and cloud-init manage servers, networks, and packages. They complement each other. See infrastructure as code with Terraform for the server layer; dotfiles finish the developer experience on top.

Ship a repeatable environment you trust

A practical dotfiles setup pays off the third time you provision a machine—not the first afternoon you build it. Start with shell, Git, and SSH. Add editor config when those feel stable. Keep secrets local, bootstrap with Stow, and document what your script expects.

If you want help standardizing developer machines alongside production servers—Deployer pipelines, GitLab CI, backups, and monitoring—I handle that end to end. See support and maintenance services, review Notary Kathmandu and sister sites on shared deploy infrastructure, or contact us to talk through your team setup.

Frequently Asked Questions

A practical dotfiles setup stores shell, Git, SSH, and editor configs in a Git repo, symlinks them with GNU Stow, and bootstraps new machines via one idempotent install script—keeping secrets out of version control.

Dotfiles are hidden config files like .bashrc, .gitconfig, and .ssh/config. Without a practical setup you re-type aliases, Git defaults, and SSH jump-host rules every time you rebuild a laptop or SSH into a fresh Ubuntu box. Treating them like application code means you commit, review, and replay them. Your ll alias, Git identity, and Deployer shortcuts appear on a new VPS in minutes. Small teams get the same baseline, which cuts works-on-my-laptop friction during Linux server work.

Keep the repo at ~/.dotfiles or ~/dotfiles with a flat top level and one folder per config bundle. Each bundle mirrors the path GNU Stow will create under your home directory—for example bash/ for ~/.bashrc, git/ for ~/.gitconfig, ssh/.ssh/ for ~/.ssh/config, and nvim/.config/nvim/ for Neovim XDG paths. Initialize with git init, add a root .gitignore for OS junk like .DS_Store, commit one bundle at a time, push to a private GitHub or GitLab remote, document OS and shell assumptions in README.md, and tag stable snapshots such as v2026.09 for easy rollbacks.

Install Stow on Ubuntu with sudo apt update and sudo apt install -y stow. From inside the repo run stow --verbose bash, git, ssh, and nvim to create symlinks like ~/.bashrc pointing to ../.dotfiles/bash/.bashrc. Edit the repo file and the live config updates instantly. Use stow --delete bash before removing a bundle. Never stow over files you have not inspected—Stow refuses some conflicts but others nest silently. Before first stow on a new machine, back up existing configs to a dated folder with cp -a.

Prioritize files that save time every session. Track .bashrc or .zshrc for aliases, PATH, and history settings—keep them portable. Track .gitconfig for name, email, and aliases using conditional includes per directory. Track .ssh/config for host blocks and ProxyJump chains but never private keys. Track .config/nvim/ for editor baseline. Partially track .deployer/ for shared recipes, not host passwords. Never commit .ssh/id_*, .env, API tokens, or .mysql_history. Start with Git ergonomics, SSH host entries, and shell paths for PHP and Composer before editor themes.

The bootstrap script is the product. Clone the repo, then run an idempotent install.sh that installs packages, runs Stow, and verifies symlinks. A typical script uses set -euo pipefail, defines bundles like bash git ssh nvim, installs stow git curl via apt-get on Ubuntu, and loops stow --dir with --target=$HOME and --restow for each bundle. Run bash install.sh the first time after chmod +x. Generate SSH keys locally after bootstrap, then verify Git and shell tooling. Add flags like --minimal for servers without Neovim and --work for a separate work Git bundle.

Assume the repo may become public someday even if it starts private. Never commit private keys, .env files, or password manager exports. Use .local files loaded from untracked paths— for example source ~/.bashrc.local at the end of .bashrc. Add a repo .gitignore plus a global gitignore for .ssh/id_ and .env*. Run git log -p before making a repo public because history keeps secrets forever. For Deployer host definitions, commit structure and usernames; passwords live in .env on the deploy runner or GitLab CI variables. If a key leaked, rotate it immediately—removing the file from HEAD is not enough.

Use a private repository by default. Shell and SSH configs reveal hostnames, internal URLs, and tooling choices that aid reconnaissance. Public dotfiles work only for generic editor themes with zero infrastructure hints. If you publish, audit full history with git log -p first.

GNU Stow is simpler for mixed paths like .config/nvim alongside .bashrc. Bare-repo methods keep everything in ~/.cfg with alias tricks—they work but confuse newcomers and the next contractor reading your README.

Commit when a change survives one week of daily use. Batch tiny tweaks monthly. Pull before bootstrap on a new machine. After OS upgrades, run stow --restow on each bundle to fix broken symlinks.

No. Dotfiles manage user-level preferences on a host—aliases, Git identity, SSH layout, editor keymaps. Terraform, Ansible, and cloud-init manage servers, networks, and system packages. They complement each other rather than overlap redundantly. On shared Ansible-driven server setups, dotfiles finish the developer experience while playbooks pin php8.4-fpm packages and firewall rules. For teams provisioning cloud VPS hosts, combine both: playbooks own system-wide package versions, dotfiles own per-user prefs. Dotfiles are not a second job; plain Git plus Stow covers most freelancers and small agencies.

Keep portable defaults in the tracked repo and load local overrides from untracked files. At the end of .bashrc add a check like sourcing ~/.bashrc.local if it exists. Your laptop can set PHP_BIN=php8.5 while a server stays on php8.4 using export PHP_BIN="${PHP_BIN:-php8.4}" in the shared file. For Git, use conditional includes in .gitconfig with includeIf gitdir pointing to a separate .gitconfig-work stored in its own Stow bundle when client email must differ. That pattern avoids wrong-commit-author mistakes on client repos while keeping one repo portable across Ubuntu 22/24, macOS, laptops, and production VPS hosts.

chezmoi, yadm, and bare-repo methods are common alternatives mentioned alongside Stow. chezmoi and yadm add templating and encryption features that help when you manage many machines with different OS versions. Bare-repo approaches store everything under ~/.cfg and use shell alias tricks to make Git treat your home directory as the work tree. In practice Stow stays readable for the next developer and pairs well with Ansible on cloud VPS provisioning. The article recommends skipping exotic frameworks until plain Git plus Stow feels boring—most freelancers and small agencies need a repo, a symlink tool, and a bootstrap script, not a complex abstraction layer.

Keep bootstrap minimal on production VPS hosts—shell, Git, SSH, and Deployer aliases only, with no editor plugins. Full dev bundles including Neovim stay on laptops. Mirror how you split CI runners versus local builds in pipeline workflows. Add an install.sh --minimal flag for servers without Neovim and --work for a separate work Git bundle. This reduces attack surface on production hosts while keeping developer ergonomics on local machines. Tag repo snapshots after major changes so you can roll back a bad .bashrc edit on either environment without rebuilding settings from memory. Document which bundles each machine type expects in README.md.

Rotate the exposed credential immediately—removing the file from the current commit is not enough because Git history retains it forever. Run git log -p to audit what was exposed before making any repo public. For history cleanup, use git filter-repo or BFG Repo-Cleaner only when you understand rewrite impact on existing clones—every collaborator must re-clone or reset. Going forward, keep secrets in untracked .local files or CI variables, add .ssh/id_* and .env* to global gitignore, and treat the dotfiles repo like credentials-adjacent infrastructure with a private remote. Encrypt sensitive synced configs with SOPS or age only when cross-machine secret sync is truly required.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: