Skip to content

Commit 5d31b1e

Browse files
committed
POST 4: Manage Dotfiles with Git
1 parent 7e1b27c commit 5d31b1e

File tree

30 files changed

+6065
-19
lines changed

30 files changed

+6065
-19
lines changed

content/posts/003-setting-up-blog/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ tags:
1717
This is a step-by-step guide on how I created the blog you’re reading right now.
1818

1919
You can find all the code in my repo.
20-
{{< github repo="solumath/blog" >}}
20+
{{< github repo="solumath/blog" showThumbnail=false >}}
2121

2222
## Why Write a Blog?
2323

content/posts/004-dotfiles/004.psd

529 KB
Binary file not shown.
49.1 KB
Loading
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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

Comments
 (0)