Git Submodules — Managing External Dependencies Across Repos
In this tutorial, you'll learn about Git Submodules. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
What You'll Learn
Use Git submodules to embed one Repository inside another — add shared libraries, pin dependencies to specific commits, and update them across projects without copy-pasting.
Why It Matters
When multiple projects share a utility library, you can copy the files (and manually sync them) or use submodules — a reference to an external repo at a specific commit. Submodules keep the dependency linked to its source, so updates are deliberate and traceable.
Real-World Use
A Hugo website includes a theme as a submodule, a backend project pins a shared auth library at a specific version, and a Monorepo-style project breaks its codebase into submodules for independent team ownership.
What Are Submodules?
A submodule is a reference to another Git Repository at a fixed commit:
your-project/
├── src/
├── themes/hugo-theme → github.com/themes/hugo-theme (at commit a1b2c3)
├── lib/utils → github.com/org/utils (at commit d4e5f6)
└── .gitmodules # Tracks submodule URLs and paths
graph TD
A[Your Repository] --> B[.gitmodules]
A --> C[themes/hugo-theme]
A --> D[lib/shared-utils]
B --> E[URL: github.com/themes/hugo-theme]
B --> F[URL: github.com/org/shared-utils]
C --> G[Pinned commit: a1b2c3]
D --> H[Pinned commit: d4e5f6]
G --> I[External Repo: hugo-theme]
H --> J[External Repo: shared-utils]
Adding a Submodule
# Add a theme as a submodule
git submodule add https://github.com/themes/hugo-theme.git themes/hugo-theme
# Cloning into 'themes/hugo-theme'...
# remote: Enumerating objects: 150, done.
# Receiving objects: 100% (150/150), done.
# Add a shared library
git submodule add https://github.com/org/shared-utils.git lib/shared-utils
# Cloning into 'lib/shared-utils'...
# This creates .gitmodules and stages the submodule
git status
# new file: .gitmodules
# new file: themes/hugo-theme
# new file: lib/shared-utils
git commit -m "chore: add hugo theme and shared utils as submodules"
Cloning a Repository with Submodules
# Clone the parent repo
git clone https://github.com/user/your-project.git
cd your-project
# Submodule directories exist but are empty — initialize them
git submodule init
git submodule update
# Submodule path 'themes/hugo-theme': checked out 'a1b2c3...'
# Submodule path 'lib/shared-utils': checked out 'd4e5f6...'
# One-command clone + submodule init
git clone --recurse-submodules https://github.com/user/your-project.git
Updating Submodules
# Update all submodules to their latest remote commit
git submodule update --remote
# remote: Enumerating objects: 5, done.
# Submodule path 'lib/shared-utils': checked out 'f7a8b9...'
# Update a specific submodule
git submodule update --remote lib/shared-utils
# Check what changed
cd lib/shared-utils
git log --oneline HEAD..origin/main
# f7a8b9 Add caching layer
# a1b2c3 Fix memory leak
# After updating, commit the new submodule pointer
cd ../..
git add lib/shared-utils
git commit -m "chore: update shared-utils to include caching layer"
Working Inside a Submodule
# Enter the submodule
cd lib/shared-utils
# Check out a specific branch
git checkout main
# Pull latest changes
git pull origin main
# Make changes, commit, and push
echo "new function" >> utils.py
git add utils.py
git commit -m "feat: add new utility function"
git push origin main
# Go back to parent repo — it sees the submodule changed
cd ../..
git status
# modified: lib/shared-utils (new commits)
git add lib/shared-utils
git commit -m "chore: update shared-utils with new utility"
Removing a Submodule
# Remove a submodule (Git 2.12+)
git submodule deinit -f themes/hugo-theme
# Clears the submodule's working tree
git rm themes/hugo-theme
# Removes the submodule entry and .gitmodules reference
git commit -m "chore: remove hugo-theme submodule"
# Clean up leftover .git/modules entry
rm -rf .git/modules/themes/hugo-theme
Submodule Workflows for Teams
# Developer A: Updates a submodule
cd lib/shared-utils
git checkout main
git pull origin main
cd ../..
git add lib/shared-utils
git commit -m "chore: update shared-utils to latest"
# Developer B: Pulls the update
git pull # Parent repo updates
git submodule update --init --recursive
# Submodule path 'lib/shared-utils': checked out 'f7a8b9...'
Common Pitfalls
| Mistake | Solution |
|---|---|
| Submodule at wrong commit after clone | Always use git clone --recurse-submodules |
| Detached HEAD inside submodule | Run git checkout main inside the submodule before making changes |
| Forgetting to push submodule changes | Submodule commits must exist on remote before parent repo references them |
| Nested submodules | Use --recursive with git submodule update |
Practice Questions
- What file tracks submodule URLs and paths?
- How do you clone a Repository and initialize all submodules in one command?
- Why might you see a detached HEAD inside a submodule, and how do you fix it?
FAQs
Next Steps
After mastering submodules, learn version control strategies for monorepos, or explore Git Worktrees to work across multiple branches simultaneously.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro