Submodules & Monorepos
Sometimes one repo needs to contain another. By the end of this lesson you'll add a submodule, understand why fresh clones leave it empty, update it safely, and know when a monorepo is the simpler choice instead.
Learn Submodules & Monorepos in our free Git course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Git course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
1️⃣ Adding a Submodule
git submodule add <url> <path> clones another repo into your project and registers it. Git creates a .gitmodules file (the path + URL, which you commit and share) and a special gitlink at the path that pins one exact commit.
2️⃣ What Gets Committed
The parent stores a pointer to one specific commit of the submodule — not a copy of its files. That's the whole point: every teammate checks out the same version. Notice the 160000 mode in the commit output — that's Git's marker for a gitlink.
3️⃣ The Empty-Folder Surprise
The most common submodule confusion: a plain git clone leaves submodule folders empty , because their contents aren't fetched by default. Fix it with git submodule update --init --recursive , or avoid it entirely by cloning with --recurse-submodules .
Your turn. Add a submodule and make a fresh clone fill it in. Fill in the two blanks.
4️⃣ Updating a Submodule
To move a submodule forward, update it inside its folder, then commit the changed pointer in the parent . Forgetting that second commit (or to push the submodule's own new commits) is the classic way teammates end up on the wrong version.
5️⃣ Submodules vs. Monorepos
A monorepo keeps multiple projects in one repository, so tightly-coupled code is versioned and changed together in a single atomic commit — ideal when cross-cutting changes are common. Submodules keep each project independent, linked by commit pointers — better when components have separate owners, lifecycles, or release schedules. Because submodules add ceremony and footguns, many teams reach for a monorepo (or a package manager) unless they truly need separate repositories.
Common Errors (and the fix)
- Submodule folder is empty after clone: run git submodule update --init --recursive , or clone with --recurse-submodules .
- Teammates see an old version: you moved the submodule but forgot to commit the pointer in the parent — git add libs/dep && git commit .
- "fatal: reference is not a tree" on update: you committed a pointer to a submodule commit you never pushed. Push the submodule's repo first.
- Detached HEAD inside the submodule: that's normal — submodules check out a specific commit. Run git checkout main inside it before making new commits.
- Pull doesn't update submodules: use git pull --recurse-submodules (or set submodule.recurse=true ).
Pro Tips
- 💡 Auto-recurse: set git config --global submodule.recurse true so pull/checkout handle submodules for you.
- 💡 Inspect quickly: git submodule status shows each submodule's pinned commit.
- 💡 Remove cleanly: git submodule deinit then git rm the path — don't just delete the folder.
- 💡 Prefer a package manager for ordinary dependencies; reserve submodules for vendoring whole repos.
📋 Quick Reference
Command
Purpose
git submodule add <url> <path>
Embed another repo
git submodule update --init --recursive
Populate submodules after clone
git clone --recurse-submodules <url>
Clone parent + submodules
git submodule status
Show pinned submodule commits
git pull --recurse-submodules
Pull and advance submodules
git submodule deinit <path>
Start removing a submodule
Frequently Asked Questions
Mini-Challenge: Share a Library Two Ways
No commands given this time — just the plan. Embed a library as a submodule, prove the empty-folder fix, and weigh up a monorepo.
🎉 Lesson Complete!
- ✅ A submodule embeds another repo pinned to one exact commit
- ✅ .gitmodules records the path/URL; the parent stores a pointer, not files
- ✅ Fresh clones leave submodules empty — update --init fills them in
- ✅ --recurse-submodules clones and pulls everything together
- ✅ Updating means committing the moved pointer in the parent
- ✅ Prefer a monorepo when code is tightly coupled and changes together
- ✅ Next lesson: automate your workflow with Git hooks
Practice quiz
What is a Git submodule?
- A branch inside a folder
- A type of merge
- A separate Git repository embedded at a fixed commit inside a parent repo
- A compressed archive
Answer: A separate Git repository embedded at a fixed commit inside a parent repo. A submodule is another repo nested in yours, pinned to a specific commit rather than copied.
Which command adds a submodule?
- git submodule add <url> <path>
- git add submodule <url>
- git clone --sub <url>
- git module add <url>
Answer: git submodule add <url> <path>. git submodule add <url> <path> registers and clones the submodule into the given path.
What file records a repo's submodules?
- .gitignore
- .submodules
- .git/config only
- .gitmodules
Answer: .gitmodules. The .gitmodules file (committed to the repo) lists each submodule's path and URL.
What exactly does the parent repo store for a submodule?
- A full copy of the files
- A pointer to one specific commit of the submodule
- Just the URL
- Nothing
Answer: A pointer to one specific commit of the submodule. The parent pins the submodule to an exact commit (a 'gitlink'), so everyone gets the same version.
After cloning a repo with submodules, why are the submodule folders empty?
- Submodule contents aren't fetched by default — you must init/update them
- A bug
- They were deleted
- They need a rebuild
Answer: Submodule contents aren't fetched by default — you must init/update them. A plain clone leaves submodules uninitialized; their files arrive only after init/update.
Which command initializes and fetches submodule contents after a clone?
- git submodule fetch
- git pull --sub
- git submodule init && git submodule update
- git checkout --sub
Answer: git submodule init && git submodule update. git submodule update --init (optionally --recursive) fills in the submodule files.
How do you clone a repo AND its submodules in one step?
- git clone --all
- git clone --recurse-submodules <url>
- git clone --deep
- git clone --modules
Answer: git clone --recurse-submodules <url>. git clone --recurse-submodules clones the parent and populates submodules together.
To move a submodule to its latest upstream commit, you...
- delete and re-add it
- run git merge
- edit .gitmodules
- cd into it, pull, then commit the new pointer in the parent
Answer: cd into it, pull, then commit the new pointer in the parent. You update the submodule, then commit the changed pointer in the parent so the new commit is recorded.
When is a monorepo often preferred over submodules?
- Never
- When tightly-coupled code should be versioned and changed together atomically
- Only for tiny repos
- Only for binaries
Answer: When tightly-coupled code should be versioned and changed together atomically. A monorepo keeps interdependent projects in one repo so cross-cutting changes land in a single commit.
A common pain point of submodules is...
- They run too fast
- They can't be cloned
- It's easy to forget to commit/push the updated submodule pointer
- They delete the parent
Answer: It's easy to forget to commit/push the updated submodule pointer. Forgetting to commit the moved pointer (or to push the submodule) leaves teammates on the wrong commit.
Continue this course
- Previous: Intro to GitHub Actions (CI/CD)
- Next: Git Hooks & Automation