Git Submodules

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

Pin one repository inside another. Cloning with --recurse-submodules, the two different update commands, detached HEAD, and subtrees compared.

What Are Git Submodules?

A Git submodule lets one repository include another repository at a specific commit. The parent repository stores a reference to the child repository and exact commit to check out.

The parent project tracks a pointer.

Common use cases include shared libraries, external repositories, and components owned by another team.

Tip: Use submodules when the nested code really needs its own repository lifecycle. If you mainly want easy dependency installation, a package manager is often a better fit.

When to Use Submodules

Submodules make sense when several projects consume the same repository at different versions, ownership stays separate, and you want exact commit-level control.

Adding a Submodule

Use git submodule add with a repository URL and path:

git submodule add https://github.com/example/shared-lib.git libs/shared-lib

This normally creates a .gitmodules file and a gitlink entry that records the submodule commit.

Example .gitmodules content:

[submodule "libs/shared-lib"]
  path = libs/shared-lib
  url = https://github.com/example/shared-lib.git

Commit both changes in the parent repo:

git add .gitmodules libs/shared-lib
git commit -m "Add shared-lib as a submodule"

Cloning with Submodules

A normal clone does not always fetch submodule contents automatically. The safest approach is:

git clone --recurse-submodules https://github.com/example/platform.git

If you already cloned the parent repository without that flag, initialize submodules afterward:

git submodule update --init --recursive

Note: This is one of the most common submodule mistakes. New contributors often clone the repository, see an empty nested folder, and think the repository is broken.

Updating Submodules

There are two common update patterns.

Syncing to the Parent Repository's Expected Version

If the parent repository changed its recorded submodule pointer, pull the parent changes and sync the submodule checkout:

git pull origin main
git submodule update --init --recursive

Moving the Submodule to a New Commit

If you want to advance the submodule itself:

cd libs/shared-lib
git switch main
git pull origin main

Then go back to the parent repository and commit the new pointer:

cd ../..
git add libs/shared-lib
git commit -m "Update shared-lib submodule"

Updating the child repo is not enough. The parent repository must record the new submodule commit too.

Helpful Commands

Useful inspection commands:

git submodule status
git diff --submodule
git status

Removing a Submodule

Submodule removal is more involved than deleting a normal directory.

A common sequence is:

git submodule deinit -f libs/shared-lib
git rm -f libs/shared-lib
rm -rf .git/modules/libs/shared-lib

Then commit the cleanup:

git add .gitmodules
git commit -m "Remove shared-lib submodule"

Common Pitfalls

Forgetting --recurse-submodules

This leads to missing contents after clone.

Detached HEAD Confusion

Submodules often check out a specific commit rather than following a branch tip automatically.

Pointer Drift

A developer updates the submodule repository but forgets to commit the new pointer in the parent repository. Teammates then keep getting the old version.

CI and Onboarding Friction

Automation must initialize submodules properly.

Alternatives to Submodules

Git Subtree

Git subtree brings another repository into a subdirectory, but the content is stored directly in the parent repository history.

Advantages include easier cloning and no separate initialization step. The trade-off is that syncing changes back can be less obvious.

Package Managers

For many dependencies, package managers are simpler than submodules.

Examples:

  • npm for JavaScript
  • pip for Python
  • Go modules for Go

Package managers usually offer easier onboarding for standard libraries.

What You Should Remember

Submodules let a parent repository track another repository at a specific commit. They are useful for shared libraries and external repos that need independent history, but they add complexity around cloning, updating, CI, and removal. Learn the core commands, watch out for pointer updates, and remember that git subtree or a package manager may be easier for many projects.

Test Your Understanding

Exercise

Exercise 1: Understanding what the parent repo stores

What does the parent repository primarily track for a submodule?

Exercise

Exercise 2: Cloning correctly

Which command is the safest way to clone a repository and fetch all of its submodules in one step?

Exercise

Exercise 3: Choosing an alternative

When might a package manager be a better choice than a Git submodule?


Frequently Asked Questions

What is a Git submodule?

A submodule is a reference from one repository to a specific commit of another. The parent repository records the child's URL in .gitmodules and the exact commit in its tree, not the files themselves. That pinning is the feature: the parent always checks out a known version of the dependency rather than whatever the other repository's main happens to be. It is also the source of most confusion, because the submodule is a separate repository with its own history, branches and remote.

Why is my submodule directory empty after cloning?

Because a plain git clone records the submodule but does not fetch its contents. Clone with git clone --recurse-submodules <url>, or fix an existing clone with git submodule update --init --recursive. Setting git config --global submodule.recurse true makes clone, pull and checkout handle submodules automatically, which removes an entire category of "it works on my machine" from a team using them.

How do I update a submodule to the latest commit?

Two different operations, easily conflated. git submodule update --init --recursive checks out the commit the parent has pinned. This is what you want after pulling someone else's change. git submodule update --remote fetches the submodule's tracked branch and moves the pin to its newest commit, which is a change to the parent repository and must be committed there. Running the second when you meant the first is how a submodule silently jumps versions.

Why is my submodule in a detached HEAD state?

Because that is by design: the parent pins a commit, not a branch, so the submodule is checked out at that exact commit with no branch attached. Committing there produces work that belongs to no branch. If you intend to make changes, cd into the submodule and git switch main (or the relevant branch) first, then commit and push in the submodule, and only afterwards commit the new pin in the parent.

What is the difference between Git submodules and subtrees?

Submodules keep the dependency as a separate repository and store only a pointer, which keeps histories clean and requires every contributor to understand submodule commands. Subtrees copy the dependency's files into the parent repository, so a normal clone gets everything and no extra commands are needed, at the cost of merging the other project's history into yours and a fiddlier update path. Subtree is kinder to contributors; submodule is stricter about separation.

Should I use submodules at all?

Only when you genuinely need to pin another repository at a known commit and cannot use a package manager. A published package, npm, PyPI, Go modules, a Terraform module registry, solves the same problem with far better tooling, versioning and caching. Submodules earn their place for vendored forks, shared configuration repositories, and monorepo-adjacent setups where no packaging exists. If a package manager can express the dependency, use it instead.

Continue Learning

Explore Related Topics

When it breaks

Related Resources