Submodules
Git submodules: nested repositories with pinned commits. When to use, when to avoid, and the alternatives.
Git — submodules
EXAMPLE
# ===== What submodules are ===== # A submodule is a reference to a SPECIFIC COMMIT of another repository, # placed at a path inside your repo. Your repo tracks: the URL + that commit SHA. # Use cases: # - Vendored dependencies in pre-package-manager days # - Monorepo-lite: many repos sharing a piece # - Theme + extension separation # In 2026: usually a sign you should use a package manager or monorepo tool instead. # ===== Add ===== git submodule add https://github.com/example/libfoo.git vendor/libfoo # Creates .gitmodules + adds vendor/libfoo as a submodule reference # Commit the parent: git add .gitmodules vendor/libfoo git commit -m 'add libfoo submodule' # ===== Clone with submodules ===== git clone --recurse-submodules https://github.com/me/my-app.git # OR after a regular clone: git submodule update --init --recursive # ===== Update submodule to its remote ===== cd vendor/libfoo git fetch git checkout v1.2.0 cd ../.. git add vendor/libfoo git commit -m 'bump libfoo to v1.2.0' # Or one-liner from parent: git submodule update --remote vendor/libfoo # ===== After someone else updates submodules ===== git pull git submodule update --init --recursive # ===== Remove ===== git submodule deinit -f vendor/libfoo git rm -f vendor/libfoo rm -rf .git/modules/vendor/libfoo # Then commit. # ===== Gotchas ===== # 1. Forgetting --recurse-submodules on clone -> empty submodule directory # 2. Working in submodule without committing pin -> parent silently drifts # 3. Conflicts in .gitmodules during merges # 4. CI without 'recurse submodules' -> build fails # 5. Detached HEAD inside submodules by default -> commits not pointing anywhere # ===== Alternatives ===== # - npm / pip / cargo / go modules: actual dependency managers # - Git subtree: merges history into the parent repo # - Monorepo tools: Nx, Turborepo, Bazel, Pants # - Git LFS for large files # ===== When submodules win ===== # - Truly different release cadence for the inner repo # - Shared between many projects with strict pin # - You want to keep the submodule's git history separate # - No suitable package manager (rare) # ===== When submodules hurt ===== # - Frequent updates from many devs (merge conflicts) # - Submodule has dependencies of its own (chains of pain) # - Team unfamiliar with git internals # - CI / CD pipelines that don't recurse # ===== Useful commands ===== git submodule status # show current commits git submodule foreach git pull origin main git config --global submodule.recurse true # auto-recurse on pull / clone / checkout # ===== Patterns ===== # - Document the bump policy in README # - .gitmodules committed, but submodule pins reviewed in PR # - Pin to immutable refs (tags, sha) not branches # - CI: clone with --recurse-submodules; update --init --recursive # ===== Pitfalls ===== # - submodule.recurse = false default -> people forget # - Working inside submodule without pushing first -> CI breaks for teammates # - Branch-tracking submodules drift between machines # - Detached HEAD changes lost without commit
Why it matters
Submodules pin a specific commit of another repo into yours. Useful for truly independent inner repos; painful for fast-moving shared code. Prefer package managers or monorepo tools where they fit. When you must use submodules, document the bump policy and configure submodule.recurse globally.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
git submodule add https://github.com/user/lib lib git submodule update --init --recursiveTry it Yourself »
Discussion
Loading…