Skip to content
Pipelines and Pizza 🍕
Go back

Git Submodules: Managing Shared Code Across Repos

4 min read

In the previous post, we set up Git hooks to enforce code quality automatically. Now let’s tackle another Git power feature: submodules.

When you have Terraform modules, Ansible roles, or shared libraries used across multiple repositories, copy-paste becomes a maintenance nightmare. Submodules let you embed one repository inside another while keeping them independently versioned.


What Are Submodules?

A submodule embeds another Git repo at a specific commit. Use them for:

  • Shared infrastructure modules consumed by multiple repos
  • Vendor code you can’t or don’t want to fork
  • Documentation that must match code versions

The Catch

Submodules point to a commit, not a branch. You must explicitly update the pointer. This trips up every team at least once.

When you clone a repo with submodules, the submodule directories are empty by default. When you update a submodule, you’re checking out a specific commit - not pulling the latest. These behaviors are intentional but surprising if you’re not expecting them.


Basic Workflow

Add a submodule

git submodule add https://github.com/example/shared-terraform-modules.git modules/shared
git commit -m "chore(submodule): add shared terraform modules"

This creates a .gitmodules file tracking the submodule URL and path.

Clone with submodules

git clone --recurse-submodules [email protected]:yourorg/app.git

Or if you already cloned:

git submodule update --init --recursive

Update to a newer upstream commit

cd modules/shared
git fetch origin
git checkout v1.4.2  # pin to a tag for predictability
cd ../..
git add modules/shared
git commit -m "chore(submodule): bump shared modules to v1.4.2"

The parent repo tracks the submodule’s commit SHA, not a branch name. Pinning to tags makes it clear what version you’re using.


CI Configuration

Your CI must initialize submodules explicitly:

# GitHub Actions
- uses: actions/checkout@v6
  with:
    submodules: recursive

Without this, your CI will see empty submodule directories and fail in confusing ways.


Hands-On Lab: Add a Submodule

Building on the repo from the hooks article:

cd git-hooks-lab

# Add a submodule
git submodule add https://github.com/example/shared-terraform-modules.git modules/shared
git commit -m "chore(submodule): add shared modules"

Verify it worked:

cat .gitmodules
git submodule status

Common Pitfalls

”Modified content” warnings

If you see modified content next to a submodule in git status, someone checked out a different commit inside the submodule without staging it:

git add modules/shared
git commit -m "chore(submodule): update to latest commit"

Detached HEAD in submodule

This is normal. Submodules check out commits, not branches. If you need to make changes inside the submodule, create a branch first:

cd modules/shared
git checkout -b my-feature
# make changes, commit, push
cd ../..
git add modules/shared
git commit -m "chore(submodule): point to my-feature branch"

Teammates see empty directories

They need to initialize submodules after cloning:

git submodule update --init --recursive

Or remind them to clone with --recurse-submodules.


Troubleshooting Guide

ProblemCauseFix
Submodule shows “modified content”Checked out new commit without staginggit add modules/shared
Empty submodule directoryNot initializedgit submodule update --init --recursive
”Detached HEAD” in submoduleNormal when pinned to commitUse tags for readability
CI fails with missing filesSubmodules not checked outAdd submodules: recursive to checkout action

Quick Reference

git submodule add <url> <path>           # Add submodule
git submodule update --init --recursive  # Initialize after clone
git clone --recurse-submodules <url>     # Clone with submodules
git submodule status                     # Show current commit for each submodule

What’s Next

Next post: Managing Large Git Repositories. We’ll cover Git LFS for large binaries, partial clones to skip what you don’t need, and sparse checkouts for monorepo workflows.

Happy automating!