|
| 1 | +--- |
| 2 | +title: Manage Dotfiles with Git |
| 3 | +published: 2025-09-05T23:48:04+02:00 |
| 4 | +modified: 2025-09-06T20:57:50+02:00 |
| 5 | +draft: false |
| 6 | +description: How to manage Dotfiles with Git. |
| 7 | +cover: "[[featured.jpg]]" |
| 8 | +tags: |
| 9 | + - git |
| 10 | + - tutorial |
| 11 | + - bash |
| 12 | + - zsh |
| 13 | + - ohmyzsh |
| 14 | + - dotfiles |
| 15 | +--- |
| 16 | +If you’d rather skip the details, you can check out my dotfiles directly. |
| 17 | + |
| 18 | +{{< github repo="solumath/.dotfiles" showThumbnail=false >}} |
| 19 | + |
| 20 | +## The Problem |
| 21 | + |
| 22 | +If you’ve ever had to set up a new machine from scratch, you probably know the pain of recreating your development environment. Especially when you are reinstalling systems a lot. Those quality of life tweaks like autocompletion, aliases, suggestions for commands, ... they are missing and it's like missing your right hand. |
| 23 | + |
| 24 | +I needed a way to: |
| 25 | + |
| 26 | +- Keep all my dotfiles versioned |
| 27 | +- Sync them across multiple machines (especially [Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh) configs) |
| 28 | +- Use [Git](https://git-scm.com/) (since I already know it well) |
| 29 | +- Avoid third-party dotfile managers I’d have to learn and forget the commands every so often |
| 30 | + |
| 31 | +For long, I kept putting off setting up a proper dotfiles configuration. But it was only avoiding the inevitable. So here we are reading this post. I must have come up with something smart right? RIGHT?! |
| 32 | + |
| 33 | +## Exploring Existing Solutions |
| 34 | + |
| 35 | +My first intuition was to look at existing tools see if any of them fit my needs: |
| 36 | +- [yadm](https://github.com/yadm-dev/yadm) - Essentially a wrapper around Git, built specifically for dotfiles. It simplifies common tasks and even has encryption support for sensitive files |
| 37 | +- [stow](https://www.gnu.org/software/stow/) - Simple symlink manager. You keep your dotfiles organized in a dedicated folder structure, and Stow creates symlinks into your home directory |
| 38 | +- [chezmoi](https://github.com/twpayne/chezmoi) - A more full-featured manager. Handles symlinks and multiple machine setups but also supports templating. That means you can maintain one config file that adapts based on the machine you’re deploying to |
| 39 | + |
| 40 | +All three of these tools are powerful, stable, and widely used, which is reassuring. But here’s the thing. While they solve the problem at hand, they also add an extra layer I’d have to learn and maintain. |
| 41 | + |
| 42 | +And honestly? I didn’t want another abstraction between me and my configs. If something breaks in my shell, the last thing I want is to debug _another tool_ that sits between me and my dotfiles. |
| 43 | + |
| 44 | +So I kept digging, looking for the simplest solution — one that relied only on [**Git**](https://git-scm.com/). |
| 45 | + |
| 46 | +## Bare Git Repository |
| 47 | + |
| 48 | +While searching for options, I stumbled upon [Atlassian’s tutorial](https://www.atlassian.com/git/tutorials/dotfiles) on managing dotfiles with bare Git repository. |
| 49 | +This caught my attention. Git is the core of every developer and I know many of its commands by heart. This could be it. |
| 50 | + |
| 51 | +So I gave it a shot: |
| 52 | +```bash |
| 53 | +git init --bare $HOME/.cfg |
| 54 | +# Create alias config to manage the dotfiles |
| 55 | +# Work-tree set to $HOME enables us to track every file in that path |
| 56 | +alias config='/usr/bin/git --git-dir=$HOME/.cfg/ --work-tree=$HOME' |
| 57 | +# Show only tracked files, necessary or whole $HOME would show up on `git status` |
| 58 | +config config --local status.showUntrackedFiles no |
| 59 | +``` |
| 60 | + |
| 61 | +And now I could start adding files directly from my home directory: |
| 62 | +```bash |
| 63 | +config add .zshrc |
| 64 | +config commit -m "Add zshrc" |
| 65 | +config push |
| 66 | +``` |
| 67 | + |
| 68 | +I was almost ready to call it a winner. **But there's a catch.** |
| 69 | + |
| 70 | +All the tracked files have to live in your **home** directory. That works for some files but what about dotfiles that are in subdirectories? |
| 71 | + |
| 72 | +That was too limiting for my set up, since I like keeping certain configs neatly organized in subfolders. |
| 73 | + |
| 74 | +## My Dotfile Manager |
| 75 | + |
| 76 | +After testing existing tools and the bare repo method, I decided to roll my own solution. It’s nothing fancy, just **Git plus a few symlinks**, but it gives me exactly the control I want without extra abstraction. |
| 77 | + |
| 78 | +### 1. Create a Central Folder for Dotfiles |
| 79 | + |
| 80 | +I keep everything in one place under `~/.dotfiles` and make it a Git repo: |
| 81 | +```bash |
| 82 | +mkdir ~/.dotfiles |
| 83 | +git init ~/.dotfiles |
| 84 | +``` |
| 85 | + |
| 86 | +### 2. Move Dotfiles into Structured Subfolders |
| 87 | + |
| 88 | +For example, I keep Zsh-related files under a `zsh` subdirectory: |
| 89 | +```bash |
| 90 | +mkdir ~/.dotfiles/zsh |
| 91 | +mv ~/.gitconfig ~/.dotfiles/ |
| 92 | +mv ~/.zshrc ~/.p10k.zsh ~/.dotfiles/zsh/ |
| 93 | +``` |
| 94 | + |
| 95 | +### 3. Handle Oh My Zsh and Plugins |
| 96 | + |
| 97 | +[Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh) itself is a Git repo, and so are most of its plugins. To avoid fighting that, I just move the `custom` folder into my dotfiles repo: |
| 98 | +```bash |
| 99 | +mv ~/.oh-my-zsh/custom ~/.dotfiles/zsh |
| 100 | +``` |
| 101 | + |
| 102 | +Then, I update `.zshrc` so Oh My Zsh looks in the right place: |
| 103 | +```bash |
| 104 | +# Must be placed before sourcing oh-my-zsh |
| 105 | +ZSH_CUSTOM="${HOME}/.dotfiles/zsh/custom" |
| 106 | +``` |
| 107 | + |
| 108 | +For plugins and themes, I add them as Git submodules. For example: |
| 109 | +```bash |
| 110 | +git submodule add [email protected]:marlonrichert/zsh-autocomplete.git zsh/custom/plugins/zsh-autocomplete |
| 111 | +git submodule update --init --recursive |
| 112 | +``` |
| 113 | + |
| 114 | +That way, I can pull them down consistently on any new machine. |
| 115 | + |
| 116 | +### 4. Symlink Files back into place |
| 117 | + |
| 118 | +Now that the _source of truth_ is `~/.dotfiles`, symlink the actual config files back where apps expect them. |
| 119 | +```bash |
| 120 | +ln -sf $(realpath ~/.dotfiles/.gitconfig) ~/.gitconfig |
| 121 | +ln -sf $(realpath ~/.dotfiles/zsh/.zshrc) ~/.zshrc |
| 122 | +ln -sf $(realpath ~/.dotfiles/zsh/.p10k.zsh) ~/.p10k.zsh |
| 123 | +``` |
| 124 | + |
| 125 | +### 5. Write an Install Script |
| 126 | + |
| 127 | +To set up a new machine quickly, I wrote a simple `install.sh`: |
| 128 | + |
| 129 | +```bash |
| 130 | +#!/bin/bash |
| 131 | +set -e |
| 132 | +set -u |
| 133 | + |
| 134 | +if [ -d ~/.dotfiles ]; then |
| 135 | + read -p "The ~/.dotfiles directory already exists. Do you want to replace it? (y/n) " choice |
| 136 | + case "$choice" in |
| 137 | + y|Y ) rm -rf ~/.dotfiles ;; |
| 138 | + n|N ) exit 1 ;; |
| 139 | + * ) echo "Invalid choice. Exiting." ; exit 1 ;; |
| 140 | + esac |
| 141 | +fi |
| 142 | + |
| 143 | +git clone https://github.com/solumath/.dotfiles.git ~/.dotfiles |
| 144 | + |
| 145 | +ln -sf $(realpath ~/.dotfiles/.gitconfig) ~/.gitconfig |
| 146 | +ln -sf $(realpath ~/.dotfiles/zsh/.zshrc) ~/.zshrc |
| 147 | +ln -sf $(realpath ~/.dotfiles/zsh/.p10k.zsh) ~/.p10k.zsh |
| 148 | + |
| 149 | +# Initialize submodules |
| 150 | +cd ~/.dotfiles |
| 151 | +git submodule update --init --recursive |
| 152 | +``` |
| 153 | + |
| 154 | +{{< alert icon="fire" >}} |
| 155 | +[GitHub](https://github.com) exposes your public SSH keys through its [API](https://docs.github.com/en/rest/users/keys?apiVersion=2022-11-28). |
| 156 | + |
| 157 | +You can fetch them onto any machine with a single command. Useful if you also want to setup SSH access. |
| 158 | + |
| 159 | +`curl https://github.com/solumath.keys | tee -a ~/.ssh/authorized_keys` |
| 160 | +{{< /alert >}} |
| 161 | + |
| 162 | +### 6. Commit |
| 163 | + |
| 164 | +Commit your changes: |
| 165 | +```bash |
| 166 | +git add . |
| 167 | +git commit -m "Init dotfiles" |
| 168 | +``` |
| 169 | + |
| 170 | +Add your GitHub repo as remote and push: |
| 171 | +```bash |
| 172 | +# Add GitHub repo as remote |
| 173 | +git remote add origin [email protected]:solumath/.dotfiles.git |
| 174 | + |
| 175 | +# Push initial commit to main branch |
| 176 | +git branch -M main |
| 177 | +git push -u origin main |
| 178 | +``` |
| 179 | + |
| 180 | +Now I can install everything with one command: |
| 181 | +```bash |
| 182 | +sh -c "$(curl -fsSL https://raw.githubusercontent.com/solumath/.dotfiles/main/install.sh)" |
| 183 | +``` |
| 184 | + |
| 185 | +## Reflection |
| 186 | + |
| 187 | +At the end of the day, yes, I reinvented the wheel (again). But honestly, that’s the beauty of it. I know exactly how every piece fits together, and it all runs on one tool I already trust, **Git**. |
| 188 | + |
| 189 | +Sure, there are limitations. It doesn’t have encryption out of the box, it doesn’t do fancy templating, and it requires a few symlinks here and there. But if your goal is **simplicity and full control**, this approach nails it (at least for me). |
| 190 | + |
| 191 | +If you’ve been putting off organizing your dotfiles, I’d encourage you to try this (or one of the other tools I mentioned) — future you will thank you when setting up the next machine. |
| 192 | + |
| 193 | +# References |
| 194 | + |
| 195 | +- <https://git-scm.com/> |
| 196 | +- <https://github.com/ohmyzsh/ohmyzsh> |
| 197 | +- <https://www.atlassian.com/git/tutorials/dotfiles> |
| 198 | +- <https://github.com/twpayne/chezmoi> |
| 199 | +- <https://www.gnu.org/software/stow/> |
| 200 | +- <https://github.com/yadm-dev/yadm> |
| 201 | +- <https://github.com/marlonrichert/zsh-autocomplete> |
| 202 | +- <https://docs.github.com/en/rest/users/keys?apiVersion=2022-11-28> |
0 commit comments