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.

Manage Dotfiles and Server Config with Git

By Kokil Thapa | Last reviewed: September 2026

You change a Bash alias on your laptop, tweak PHP-FPM pool settings on staging, and patch an Apache vhost on production — then forget what you changed. That drift costs hours. When you manage dotfiles and server config with Git, every shell profile, editor setting, and web-server snippet has a history you can diff, roll back, and replay on a new machine. On real client projects I often handle dev, deploy, and server maintenance in one role, so a single Git workflow for personal tooling and production config beats scattered notes and ad hoc SSH edits. This guide walks through a practical setup for PHP/Laravel developers running Ubuntu 22/24 servers.

The pattern fits naturally alongside the Ubuntu server setup workflow and the Git habits covered in Git rebase vs merge. If you maintain multiple Laravel sites on shared EC2 infrastructure, versioned config is as important as versioned application code.

What does it mean to manage dotfiles and server config with Git?

Dotfiles are hidden config files in your home directory: .bashrc, .gitconfig, .vimrc, and tool-specific folders like .config/nvim. Server config lives under /etc: Nginx site files, Apache vhosts, PHP-FPM pools, UFW snippets, and systemd drop-ins.

Git gives you three things manual copying cannot. You get a full change history with blame. You get branches for experiments. You get tags that mark known-good production states.

In practice I split repos by risk and audience. Personal dotfiles live in one private repo. Shared server baselines live in another. Application deploy configs — Deployer, GitLab CI — stay inside each project repo. That separation keeps a mistaken git push from exposing production paths on a public dotfiles repo.

Dotfiles and Server Config with GitDev Laptop.bashrc, .gitconfigEditor + SSH configPrivate Git RemoteBranches + tagsNo secrets in historyUbuntu ServerNginx, PHP-FPMUFW, systemd unitsDeploy Layergit pull tagged release → validate → symlink → reload serviceRollback = checkout previous tag + reload
Architecture to manage dotfiles and server config with Git across development machines and Ubuntu production hosts

Version control is not a backup tool. Pair Git with the backup patterns in Ubuntu server backup strategies and automated server backup setup. Git tracks intent. Backups capture live state including databases and uploaded files.

What belongs in Git vs what does not

  • Track: Shell profiles, Git aliases, editor settings, SSH config (without private keys), Nginx/Apache templates, PHP-FPM pool defaults, UFW rule scripts, fail2ban jails, cron templates.
  • Never track: Private keys, .env files, TLS private keys, database passwords, API tokens, /etc/shadow, or anything under /var/www that users upload.
  • Track as templates: Use .env.example or pool.conf.example with placeholder values. Fill real values on the host from a secrets store or encrypted file outside Git.

Run secrets scanning in Git and CI with gitleaks on every push. One leaked Deployer SSH key in a dotfiles repo can compromise every site on a shared EC2 box.

How do you set up a Git-backed dotfiles repository?

The most reliable pattern for personal dotfiles is a bare repository in your home directory. Your working tree is your actual home folder. This avoids moving files into a subdirectory and breaking paths.

Step 1: Create the bare repo on your laptop

  1. Create a private remote on GitHub, GitLab, or a self-hosted instance.
  2. On your machine, initialise the bare repo:
git clone --bare git@gitlab.com:you/dotfiles.git ~/.dotfiles
cd ~/.dotfiles
git config --local status.showUntrackedFiles no
echo 'alias dotfiles="/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME"' >> ~/.bashrc
source ~/.bashrc

The alias lets you run dotfiles status instead of passing --git-dir every time. Official Git documentation covers bare repositories at git-scm.com/docs/git-clone.

Step 2: Add files selectively

dotfiles add .bashrc .gitconfig .config/starship.toml
dotfiles commit -m "Initial shell and Git config"
dotfiles push origin main

Add a root .gitignore inside the bare repo's index for paths you never want:

# inside ~/.dotfiles as a tracked file at repo root
.DS_Store
.cache/
.local/share/nvim/swap/
*.secret
.env
id_rsa
id_ed25519

Step 3: Bootstrap a new machine

git clone --bare git@gitlab.com:you/dotfiles.git ~/.dotfiles
echo 'alias dotfiles="/usr/bin/git --git-dir=$HOME/.dotfiles/ --work-tree=$HOME"' >> ~/.bashrc
source ~/.bashrc
dotfiles checkout main

On first checkout Git may refuse to overwrite existing files. Move conflicting defaults aside, then rerun checkout. That is normal on fresh Ubuntu installs.

Bare Repo Dotfiles WorkflowEdit ~/.bashrcdotfiles adddotfiles commitgit pushNew laptopclone bareGNU Stow optional layerRepo layout: ~/dotfiles/bash/.bashrc → ~/dotfiles/vim/.vimrcstow bash → symlinks ~/.bashrc to package fileEnables modular packages without bare-repo path quirks
Bare Git repo workflow and optional GNU Stow package layout for dotfiles

GNU Stow as an alternative

GNU Stow creates symlinks from a structured repo into your home directory. It shines when you maintain optional packages: stow nvim on dev machines, skip it on minimal servers. Stow documentation lives at gnu.org/software/stow. I use bare repos for speed on laptops and Stow when a repo grows past twenty packages.

Generate strong unique passwords for service accounts with the password generator tool. Store them in a password manager, not in Git.

Which approach should you use for dotfiles and server config?

Teams ask whether bare repos, Stow, Chezmoi, or Ansible is "best." Each solves a different slice of the problem. Pick based on machine count, team size, and how often you reprovision servers.

ApproachBest forLearning curveServer config fit
Git bare repoSolo dev, fast personal syncLowPoor alone — home dir only
GNU StowModular dotfiles packagesLowFair with /etc via custom targets
ChezmoiTemplated dotfiles, secrets encryptionMediumLimited — dev-focused
Ansible playbooksMulti-server baselines, PHP stacksMedium–highExcellent — idempotent /etc deploy
Git + symlink deploy scriptSmall fleet, full controlLow–mediumExcellent for 1–5 Ubuntu web servers

For Laravel hosting stacks I often combine Git-tracked config trees with Ansible or a thin shell deploy script. Read Ansible playbooks for PHP server provisioning and automate server setup with Ansible when you outgrow manual symlinks. Ansible handles drift correction. Git remains the source of truth.

Chezmoi and yadm are fine for personal machines. They do not replace root-level web-server management on production. Keep that boundary clear.

How do you version Apache, Nginx, and PHP-FPM server config with Git?

Never edit /etc/nginx/sites-available/app.conf in place without a Git copy elsewhere. I mirror production config into a private repo under a predictable tree, then deploy with symlinks or rsync --dry-run first.

server-config/
├── README.md
├── bin/
│   ├── deploy-config.sh
│   └── validate-nginx.sh
├── common/
│   ├── nginx/snippets/ssl-params.conf
│   ├── php/8.3/fpm/pool.d/www.conf.example
│   └── ufw/rules.sh
├── hosts/
│   ├── staging.example.com/
│   │   ├── nginx/site.conf
│   │   └── php-fpm/pool.conf
│   └── prod.example.com/
│       ├── nginx/site.conf
│       └── php-fpm/pool.conf
└── secrets/          # gitignored — never committed
    └── prod.example.com.env

This mirrors how I maintain sister sites on shared EC2 with Deployer 7 and GitLab CI. Application code deploys through one pipeline. Server baseline config deploys through another, less frequent pipeline.

Layered Server Config in Gitcommon/ — SSL snippets, PHP defaults, UFW basehosts/staging/Lower worker countshosts/production/Tuned PHP-FPM poolsLive /etc on Ubuntu serverSymlink or copy from /opt/server-config checkoutnginx -t && systemctl reload nginx
Layer common and host-specific server config when you manage dotfiles and server config with Git

PHP-FPM and multi-version PHP

Ubuntu servers often run PHP 8.3 and 8.4 side by side. Track pool files per version:

common/php/8.3/fpm/pool.d/www.conf.example
common/php/8.4/fpm/pool.d/www.conf.example

After deploy, reload the correct FPM socket. Wrong socket paths break Laravel apps silently — the site loads but sessions fail. I have debugged this on production Laravel deployments more than once.

Apache vs Nginx paths

Track site configs under host folders. For Apache, map to sites-available and enable with a2ensite. For Nginx, symlink into sites-enabled. The comparison in Nginx vs Apache performance and config helps you standardise snippets across both.

Include systemd unit drop-ins if you override OOM scores or restart policies. See systemd manage services on Linux for reload vs restart decisions.

How do you deploy dotfiles and server config to production safely?

Treat config deploys like application deploys: tagged releases, a checklist, and a rollback path. Never git pull on production as root without reviewing the diff first.

A minimal deploy script

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

CONFIG_ROOT="/opt/server-config"
HOSTNAME="$(hostname -f)"
RELEASE_TAG="${1:?Usage: deploy-config.sh v2026.09.01}"

cd "$CONFIG_ROOT"
git fetch origin
git checkout "$RELEASE_TAG"

install -d /etc/nginx/snippets
ln -sf "$CONFIG_ROOT/common/nginx/snippets/ssl-params.conf" /etc/nginx/snippets/ssl-params.conf
ln -sf "$CONFIG_ROOT/hosts/${HOSTNAME}/nginx/site.conf" /etc/nginx/sites-available/app.conf

nginx -t
systemctl reload nginx
systemctl reload "php8.3-fpm"

echo "Deployed ${RELEASE_TAG} for ${HOSTNAME}"

Run validation before reload. nginx -t and apachectl configtest catch syntax errors. PHP-FPM accepts php-fpm8.3 -t on Ubuntu when the package ships the test flag.

Safe Config Deploy PipelineGit taggit diffReview changesValidateReloadZero downtimeRollbackSecrets stay outside Git/root/.env.production on server onlyDeploy script sources vars — never commits themPair with UFW and hardening from server security guides
Tagged Git releases, validation, reload, and rollback when deploying server config to production

Integrate with CI/CD

GitLab CI can lint Nginx config on every merge request:

lint:nginx:
  image: nginx:1.27
  script:
    - nginx -t -c $CI_PROJECT_DIR/test/nginx.conf

Keep application pipelines separate from infrastructure config pipelines. A broken .bashrc should not block a Laravel release. A broken Nginx snippet should not ride along with a frontend asset build.

For permission issues after deploy, see how to stop Git from tracking file permissions. Config files in /etc often need root ownership. Track content in Git. Apply ownership in the deploy script with chown root:root.

Rollback in under two minutes

  1. Identify the last good tag: git tag --sort=-creatordate | head
  2. Checkout that tag in /opt/server-config
  3. Re-run symlinks and validation
  4. Reload services
  5. Document the incident in your ticket system

If you need to recover lost Git commits locally, Git reflog recovery applies to dotfiles repos too.

What are common mistakes when you manage dotfiles and server config with Git?

Most failures are operational, not Git syntax problems. These recur on client projects and my own machines.

Committing secrets "just once"

Removing a password in the next commit does not erase it from history. Rotate the credential immediately. Use BFG Repo-Cleaner or git filter-repo if the repo ever went public. Prevention beats cleanup.

Editing production directly

SSH into the box, fix Nginx, close the terminal — the fix exists only in production. Force a habit: edit in Git first, deploy second. Emergency hotfixes get cherry-picked back into the repo within 24 hours.

Mixing personal dotfiles with production server config

Your .vimrc does not belong in the same repo as client vhost files. Split repos by audience and access control. Give contractors access to application code, not your personal shell aliases or unrelated client vhosts.

Forgetting service reload after config change

PHP-FPM and Nginx do not auto-reread files on every request. Opcache adds another layer after deploy. On Laravel stacks I reload PHP-FPM after symlink swaps so opcache picks up changed .env permissions and autoload paths.

Ignoring drift on long-lived servers

A server running two years accumulates manual edits. Schedule a quarterly audit:

diff -ru /etc/nginx/snippets/ssl-params.conf /opt/server-config/common/nginx/snippets/ssl-params.conf || true

Reconcile diffs into Git or delete orphan files. Pair this with server hardening for Ubuntu web servers and CIS benchmarks for server hardening.

UFW rules belong in version control as executable scripts, not screenshots. Document the intent in comments. Apply with UFW firewall rules for web servers as reference.

Skipping monitoring after config deploy

After reload, watch error logs and health checks for five minutes. Tools covered in server monitoring with Netdata and Ubuntu server monitoring guide catch 502 spikes faster than client emails.

For broader security context in Nepal hosting environments, read how to secure your website and server in Nepal. Version control supports security. It does not replace it.

Key Takeaways

  • Use a private Git bare repo for personal dotfiles; bootstrap new laptops with git clone --bare and a shell alias.
  • Keep server config in a separate repo with common/ baselines and hosts/ overrides for each Ubuntu web server.
  • Never commit secrets — track .example templates and inject live values on the host outside Git history.
  • Deploy config with Git tags, run nginx -t before reload, and keep a one-command rollback path.
  • Audit /etc against Git quarterly to eliminate silent drift on long-lived production boxes.
  • Combine Git config management with Ansible when you manage more than a handful of servers.

People Also Ask

Should dotfiles be a public or private Git repository?

Keep dotfiles private unless you are certain no path, alias, or host hint exposes internal infrastructure. Public dotfiles repos are fine for generic editor settings. Production hostnames, VPN endpoints, and client paths should stay in a private repo with restricted access.

Can you use the same Git repo for dotfiles and /etc/nginx config?

Technically yes, but separate repos scale better. Personal machine settings change often and belong to you. Server config changes on a release cadence and may involve client staff. Split repos simplify permissions and CI pipelines.

How do you handle machine-specific settings in a shared dotfiles repo?

Use conditional includes in .bashrc, Git config includeIf blocks, or host-named branches. GNU Stow lets you enable packages per machine: stow git everywhere, stow nvim only on dev laptops. Chezmoi templates work if you prefer .tmpl files over shell conditionals.

Does Git replace configuration management tools like Ansible?

No. Git stores desired state. Ansible applies it idempotently and can install packages Git should not manage. For one or two servers, Git plus a deploy script is enough. For a growing fleet, pair versioned config with Ansible playbooks and keep Git as the authoritative text source.

Build a repeatable config workflow before the next server emergency

When you manage dotfiles and server config with Git, rebuilding a laptop or rolling back a bad Nginx change stops being a multi-hour rescue job. Start with a bare repo this week. Add your shell and Git config. Then extract one production vhost into a private server-config repo with a tagged deploy script.

If you want help standardising Ubuntu hosting baselines for Laravel, WordPress, or multi-site EC2 setups, see Linux system administration services and support and maintenance services. For full-stack delivery including deploy pipelines, review the Adventure Third Pole Trek portfolio case and other work on the portfolio page.

Need hands-on help auditing drift on an existing box or wiring GitLab CI to lint Nginx config? Contact us with your stack details — PHP version, web server, and host count are enough to start.

Frequently Asked Questions

Dotfiles are hidden home-directory configs like .bashrc, .gitconfig, and .config/nvim. Server config lives under /etc: Nginx sites, Apache vhosts, PHP-FPM pools, UFW scripts, and systemd drop-ins. Git tracks every change with history, branches for experiments, and tags for known-good production states.

Create a private remote on GitHub or GitLab, then run git clone --bare into ~/.dotfiles on your machine. Set status.showUntrackedFiles to no locally, add a dotfiles shell alias pointing --git-dir at ~/.dotfiles and --work-tree at $HOME, and commit files selectively with dotfiles add .bashrc .gitconfig. Add a root .gitignore blocking .cache, .env, and private keys before your first push.

Keep dotfiles private unless you are certain nothing exposes internal infrastructure. Public repos work for generic editor settings only.

Technically yes, but separate repos scale better. Personal settings change often; server config follows a release cadence and may involve client staff. Splitting repos simplifies permissions, access control, and CI pipelines.

Bare repos suit solo developers wanting fast personal sync with low learning curve. GNU Stow helps when dotfiles grow past twenty optional packages like nvim on dev machines only. Chezmoi fits templated dotfiles with secrets encryption but stays dev-focused. Ansible excels at multi-server PHP stack baselines and idempotent /etc deploys. For Laravel hosting on one to five Ubuntu web servers, Git plus a thin deploy script or Ansible combined with Git-tracked config trees is the pattern I use most.

Track shell profiles, Git aliases, editor settings, SSH config without private keys, Nginx and Apache templates, PHP-FPM pool defaults, UFW rule scripts, fail2ban jails, and cron templates. Never commit private keys, .env files, TLS private keys, database passwords, API tokens, or /etc/shadow. Use .example templates with placeholders and inject live values on the host from a secrets store outside Git. Run gitleaks on every push in CI.

Never edit /etc files in place without a Git copy elsewhere. Mirror production into a private repo with common/ baselines and hosts/ overrides per hostname. Track PHP pool files per version on side-by-side installs, for example common/php/8.3/fpm/pool.d/www.conf.example and common/php/8.4/fpm/pool.d/www.conf.example. Map Apache configs to sites-available and enable with a2ensite; symlink Nginx configs into sites-enabled. Include systemd drop-ins when you override OOM scores or restart policies.

Use a tree like server-config/ with README.md, bin/deploy-config.sh and validate-nginx.sh, common/ for shared snippets such as nginx/snippets/ssl-params.conf and php/8.3/fpm/pool.d/www.conf.example, hosts/staging.example.com/ and hosts/prod.example.com/ for per-host Nginx and PHP-FPM files, and a gitignored secrets/ folder that is never committed. This mirrors how I maintain sister sites on shared EC2 with Deployer 7 and GitLab CI running separate pipelines for application code and server baseline config.

Treat config deploys like application deploys: tagged releases, a checklist, and rollback path. Never git pull on production as root without reviewing the diff first. Checkout a release tag in /opt/server-config, symlink files into /etc, run nginx -t or apachectl configtest, then systemctl reload nginx and the correct php-fpm service. Track content in Git but apply root ownership with chown in the deploy script. Watch error logs and health checks for five minutes after reload.

Identify the last good tag with git tag --sort=-creatordate, checkout that tag in /opt/server-config, re-run symlinks and validation with nginx -t, reload affected services, and document the incident in your ticket system. Target rollback in under two minutes. Git reflog recovery applies to dotfiles repos too if you lose commits locally.

Run git clone --bare from your remote into ~/.dotfiles, append the dotfiles alias to .bashrc, source it, then run dotfiles checkout main. On fresh Ubuntu installs Git may refuse to overwrite existing defaults; move conflicting files aside and rerun checkout. That first-checkout conflict is normal.

Use conditional includes in .bashrc, Git config includeIf blocks, or host-named branches. GNU Stow lets you enable packages per machine: stow git everywhere but stow nvim only on dev laptops. Chezmoi templates work if you prefer .tmpl files over shell conditionals instead of inline bash if-statements.

No. Git is the source of truth for config content; Ansible handles drift correction and idempotent provisioning across many servers. For Laravel hosting stacks I often combine Git-tracked config trees with Ansible or a thin shell deploy script. Chezmoi and yadm work for personal machines but do not replace root-level web-server management on production. Read Ansible playbooks for PHP server provisioning when you outgrow manual symlinks.

No. Git tracks intent; backups capture live state including databases and uploaded files. Pair version control with the backup patterns in Ubuntu server backup strategies.

Committing secrets even once requires immediate credential rotation and history cleanup with BFG Repo-Cleaner or git filter-repo if the repo went public. Editing production directly without committing back causes drift; emergency hotfixes should be cherry-picked into Git within twenty-four hours. Mixing personal dotfiles with client vhost files in one repo breaks access control. Forgetting to reload PHP-FPM and Nginx after changes leaves stale opcache and broken sessions. Skipping quarterly audits lets long-lived servers drift silently from Git; diff /etc against /opt/server-config and reconcile.

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: