Git Submodules: Architecture Trade-offs, Deployment Pipelines, and the GitOps Reality CheckWhen 'just add a submodule' is a design decision: how to use submodules responsibly across architecture, CI/CD, and team workflows—without inheriting a maintenance nightmare.

Introduction

Git submodules sit at an uncomfortable intersection of version control, dependency management, and software architecture. For many teams, they begin as an elegant solution to code reuse—a way to share common libraries, configuration, or infrastructure code across multiple projects while maintaining independent repositories. Within months, that elegance gives way to broken builds, frustrated developers manually syncing updates, and deployment pipelines that fail inexplicably because someone forgot to run git submodule update --remote. The "just add a submodule" decision, often made casually during sprint planning, carries architectural implications that ripple through every layer of your development and deployment workflow.

This article examines Git submodules not as a Git feature to be mastered, but as an architectural pattern with specific trade-offs. We'll explore when submodules genuinely solve problems that alternatives cannot, how they interact with modern CI/CD systems, and why their relationship with GitOps principles creates tension that teams must explicitly design around. The goal is not to advocate for or against submodules, but to provide the context needed to make informed decisions about repository architecture—and to implement submodules in ways that minimize the operational overhead that gives them their notorious reputation.

The reality is that submodules expose a deeper challenge in software engineering: managing dependencies and coupling across organizational boundaries. Whether you choose submodules, monorepos, package managers, or vendor directories, you're making decisions about how tightly to couple components, who owns integration points, and where complexity lives in your system. Understanding submodules means understanding these trade-offs clearly enough to choose deliberately rather than inheriting someone else's architecture by default.

What Are Git Submodules? The Technical Foundation

A Git submodule is a Git repository embedded inside another Git repository, with the parent repository tracking a specific commit SHA of the child repository. When you add a submodule using git submodule add <repository-url> <path>, Git creates a .gitmodules file in your repository root that maps local paths to remote repository URLs. The parent repository doesn't store the submodule's files directly; instead, it stores a pointer—a specific commit hash—indicating exactly which version of the submodule should be present at that path. This design means that submodules provide deterministic, version-controlled dependencies: multiple developers checking out the same parent commit will receive identical submodule states, assuming they correctly initialize and update submodules.

The technical implementation creates several non-obvious behaviors. First, the submodule directory exists in a detached HEAD state by default, pointing to the specific commit recorded in the parent repository. This means casual Git operations like git pull inside a submodule directory won't update the parent repository's record—you must explicitly commit the new submodule pointer in the parent. Second, Git operations in the parent repository don't automatically propagate to submodules. A git clone of the parent repository creates empty submodule directories by default; developers must explicitly run git submodule init and git submodule update to populate them. Modern Git versions provide convenience flags like git clone --recurse-submodules, but these don't eliminate the conceptual complexity: submodules exist as independent repositories with their own branches, remotes, and commit histories that happen to be referenced by a parent repository.

Understanding submodules requires grasping their position in Git's object model. The parent repository's tree objects contain gitlink entries—special entries that point to commit objects in external repositories rather than to blob or tree objects within the repository. This design allows Git to represent submodule relationships without duplicating content, but it also means that submodule information exists in two places: the .gitmodules file (which maps paths to URLs) and the index/tree (which records specific commit SHAs). These can become inconsistent if not managed carefully, leading to situations where developers have correct URLs but wrong commit pointers, or vice versa. The .gitmodules file is versioned with your code, but the submodule commits themselves must be fetched from remote repositories, creating a distributed dependency that can fail if those remote repositories become unavailable.

Architecture Trade-offs: When Submodules Make Sense

Git submodules address a specific architectural challenge: maintaining shared code across multiple independent repositories when that code must remain synchronized at the version control level, not just the artifact level. This pattern emerges most naturally when teams need to share infrastructure-as-code configurations, Kubernetes manifests, or CI/CD pipeline definitions across services while maintaining strict version alignment. Unlike package dependencies distributed through artifact registries, submodules preserve the complete Git history of the shared code and make it available directly in the development environment. This creates transparency—developers can inspect, modify, and test changes to shared code without context-switching to external repositories or dealing with publish-and-consume cycles. The trade-off is coupling: changes to submodule structure, location, or remote URL affect all consuming repositories simultaneously.

The architectural fit becomes clearer when compared to alternatives. Package managers (npm, pip, Maven) excel at consuming stable, versioned artifacts but introduce friction when shared code changes frequently and consumers need immediate access to updates. Publishing every minor change to a package registry, updating version constraints, and reinstalling dependencies creates overhead that submodules eliminate—at the cost of tighter Git-level coupling. Monorepos solve the same problem differently by consolidating all related code into a single repository, providing atomic cross-cutting changes and simplified dependency management. However, monorepos require specialized tooling (Bazel, Nx, Turborepo), impose uniform processes across all contained projects, and can create scaling challenges as repository size grows. Submodules occupy a middle ground: they provide some monorepo benefits (unified versioning, local access to source) while preserving repository independence, but they inherit complexity from both approaches.

The strongest case for submodules emerges in organizations with semi-independent teams maintaining related but distinct products. Consider a company building multiple customer-facing applications that share design system components, authentication libraries, and deployment templates. A monorepo would force all teams to coordinate on build tools, deployment pipelines, and release schedules. Package dependencies would work but require publishing changes through a registry and managing version compatibility across potentially dozens of services. Submodules allow teams to maintain independent repositories for each application while pulling shared components from common submodule repositories. Teams update to new shared component versions by updating the submodule pointer in their repository—a visible, version-controlled change that can be tested before merging. The trade-off is that someone must maintain the shared repositories, manage versioning across consumers, and handle the inevitable merge conflicts when multiple teams modify shared code simultaneously.

The architectural decision extends to repository topology. Submodules can be nested (submodules containing their own submodules), creating dependency hierarchies that mirror system architecture. This feels natural for layered architectures where high-level orchestration repositories depend on service repositories that depend on library repositories. However, nested submodules amplify complexity: updating a leaf-level submodule requires explicit commits at every level of the hierarchy, and CI/CD systems must recursively initialize submodules, potentially introducing exponential time complexity. Flat dependency structures—where the parent repository directly references all submodules without nesting—avoid these issues but lose the ability to encapsulate dependency relationships. Teams must consciously design their repository topology to match their architectural boundaries and change patterns, recognizing that Git won't prevent coupling decisions that create maintenance burden.

Submodules in CI/CD and Deployment Pipelines

Continuous integration systems must explicitly accommodate submodules because the default behavior of most CI platforms is to perform shallow clones for performance—and shallow clones don't automatically fetch submodule commits. GitHub Actions, GitLab CI, Jenkins, and similar platforms require explicit configuration to initialize and update submodules during checkout. In GitHub Actions, this means using actions/checkout@v3 with submodules: true or submodules: recursive parameters. Without this configuration, the CI workspace contains empty submodule directories, causing builds to fail with cryptic errors about missing files or directories. The failure mode is particularly insidious for teams new to submodules: local development works perfectly because developers have initialized submodules, but the pipeline fails in ways that don't clearly indicate the root cause is missing submodule content.

# GitHub Actions example: explicit submodule checkout
name: CI Pipeline
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository with submodules
        uses: actions/checkout@v3
        with:
          submodules: recursive  # Initialize all submodules
          fetch-depth: 0         # Full history if needed for submodule operations
          
      - name: Update submodules to latest
        run: |
          git submodule update --remote --merge
          # Or for specific submodule: git submodule update --remote --merge path/to/submodule

Authentication becomes a critical concern when submodules reference private repositories. The CI system needs credentials not just for the parent repository, but for every submodule repository as well. This requirement multiplies the attack surface for credential leaks and complicates least-privilege access controls. If submodules reference repositories across organizational boundaries—for example, a company repository pulling submodules from a client's private repository—credential management becomes significantly more complex. Solutions range from SSH key deployment (ensuring the CI runner has SSH keys with access to all required repositories) to using Git credential helpers that fetch tokens from secret stores. The .gitmodules file can use HTTPS URLs with token substitution, but this requires careful secret management to avoid exposing tokens in logs or error messages. Teams often discover these authentication issues only after merging changes that add or modify submodules, when the CI pipeline suddenly fails with authentication errors that don't appear in local development environments where developers already have credentials configured.

# GitLab CI example: handling private submodules with authentication
variables:
  GIT_SUBMODULE_STRATEGY: recursive  # GitLab-specific strategy

before_script:
  - git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/"
  # This rewrites submodule URLs to use CI job token for authentication
  - git submodule sync --recursive
  - git submodule update --init --recursive

Deployment pipelines face additional challenges because submodule state is implicitly inherited from the parent repository's commit, but that state must be explicitly reconstructed during deployment. Container builds using Docker require RUN git submodule update --init commands in the Dockerfile, or submodule content must be copied into the build context before Docker runs. Kubernetes GitOps workflows using tools like ArgoCD or Flux must configure submodule initialization as part of their sync process—and these tools have varying levels of submodule support, with some requiring custom init containers or configuration to handle nested submodules correctly. Serverless deployments that package code into ZIP files or similar artifacts must ensure submodule content is included in the package, which often means custom build scripts that replace the framework's default packaging behavior. Each deployment target introduces potential failure modes where submodule content is missing or incorrect, and debugging these failures requires understanding both the deployment tool's behavior and Git's submodule implementation.

The performance impact can be substantial for large submodules or repositories with many submodules. Each submodule requires a separate Git fetch operation, and if submodules are large or reference repositories with deep history, clone times can grow dramatically. CI platforms often cache Git repositories to improve performance, but submodule caching is more complex because the cache must track not just the parent repository but also the specific commit SHAs of each submodule. Sparse checkout and shallow clone features help—git submodule update --depth 1 fetches only the required commit without history—but these optimizations must be balanced against operations that require history, like changelog generation or blame analysis. Teams with dozens of submodules sometimes implement parallel fetch operations using git submodule update --jobs <n> to improve performance, but this introduces variability in build times and can exhaust CI platform rate limits or network resources.

Common Pitfalls and Maintenance Nightmares

The most frequent pitfall is the "detached HEAD problem," where developers make changes inside a submodule directory without realizing they're in a detached HEAD state. When a developer navigates into a submodule directory, checks out a branch, makes commits, and then runs git submodule update in the parent repository, Git resets the submodule to the commit recorded in the parent, potentially losing uncommitted or unpushed work. This happens because git submodule update by default checks out the commit SHA recorded in the parent repository's index, moving the submodule HEAD to that specific commit regardless of what branch the developer was on. The fix requires understanding that submodules exist as independent repositories: changes must be committed in the submodule, pushed to the submodule's remote, and then the parent repository's submodule pointer must be updated to reference the new commit—a three-step process that's non-obvious to developers accustomed to working in single-repository workflows.

Merge conflicts in submodule pointers create another class of frustration. When two branches update the same submodule to different commits and then merge, Git creates a conflict in the submodule pointer—represented in the parent repository as a special merge conflict that doesn't appear in any file. Resolving this conflict requires manually choosing which submodule commit to use or merging the submodule's branches independently and pointing to the resulting merge commit. Developers unfamiliar with submodule internals often attempt to resolve these conflicts by editing files in the submodule directory, which doesn't affect the parent repository's submodule pointer. The correct resolution involves git checkout --ours path/to/submodule or git checkout --theirs path/to/submodule followed by git add path/to/submodule, or manually navigating into the submodule, merging its branches, and updating the parent's pointer to the merge commit. These operations are conceptually straightforward once understood but are sufficiently different from normal file-based merge conflict resolution that they generate support tickets and block developers.

# Resolving submodule pointer conflicts: the correct workflow
cd path/to/submodule

# Fetch all branches in the submodule
git fetch --all

# Option 1: Choose one side of the conflict
# cd back to parent repo
cd ../..
git checkout --ours path/to/submodule  # or --theirs
git add path/to/submodule

# Option 2: Merge the conflicting commits in the submodule itself
cd path/to/submodule
git merge <other-branch-commit-sha>
cd ../..
git add path/to/submodule

# Finalize the merge in parent repo
git commit -m "Resolve submodule conflict by merging submodule branches"

Stale submodule references create subtle bugs where different developers or CI environments have different submodule content despite being on the same parent repository commit. This happens when a developer updates a submodule locally (running git pull inside the submodule directory) but doesn't commit the updated pointer in the parent repository. Their local environment has new submodule code, but anyone else checking out the same parent commit gets the old submodule version. Builds work locally but fail in CI, or worse, pass in CI but behave differently in staging environments where someone has inadvertently updated submodules without committing the change. The only reliable solution is discipline: every submodule content change must be accompanied by a commit in the parent repository that updates the submodule pointer, and teams must establish code review practices that explicitly check for submodule pointer updates when changes to submodule content are expected.

The distributed nature of submodules creates fragility when submodule remote repositories become unavailable. If a submodule references a repository on a now-defunct server, or if the repository has been renamed or deleted, anyone attempting to clone the parent repository will fail during submodule initialization. This risk is particularly acute for open-source projects that reference submodules from external sources, or for companies that reorganize repository structures without maintaining redirects or mirrors. Teams sometimes discover this problem years after the original submodule was added, when onboarding a new developer or setting up a new CI pipeline fails because a submodule URL is no longer valid. The mitigation requires either maintaining stable URLs and redirects for submodule repositories, or periodically auditing and updating .gitmodules files to ensure all referenced repositories remain accessible—work that's easy to defer until it becomes urgent.

GitOps, Monorepos, and Alternative Approaches

GitOps principles—where the desired state of infrastructure and applications is declaratively defined in Git and automatically reconciled by operators like Flux or ArgoCD—create specific tension with submodules. GitOps tools treat Git repositories as the source of truth, watching for commits and automatically applying changes to clusters. However, submodules introduce two-phase updates: first the submodule repository changes, then the parent repository must update its submodule pointer. From a GitOps tool's perspective, the parent repository hasn't changed even though the underlying content might have if someone runs git submodule update --remote. Some GitOps tools support automatic submodule updates, but this behavior can bypass the intended control flow—changes to a submodule repository immediately propagate to all environments watching the parent repository without explicit version control at the parent level. Teams must decide whether submodule updates should be automatic (sacrificing explicit control for convenience) or manual (requiring explicit commits to update pointers, maintaining GitOps purity but increasing operational overhead).

The monorepo alternative has gained significant traction precisely because it sidesteps the complexity submodules introduce. Companies like Google, Facebook, and Microsoft have publicly documented their use of massive monorepos (Google's repository reportedly contains billions of lines of code) managed with custom tooling that enables efficient operations at scale. For these organizations, the benefits—atomic cross-cutting changes, simplified dependency management, universal visibility into the codebase—outweigh the costs of building and maintaining sophisticated build systems. However, monorepo success requires investment in tooling that most teams can't or won't make. Off-the-shelf solutions like Nx, Turborepo, and Lerna provide monorepo capabilities for JavaScript ecosystems, and Bazel offers language-agnostic build orchestration, but adopting these tools means committing to their mental models, configuration patterns, and operational characteristics. Submodules offer a lighter-weight alternative that leverages standard Git without requiring specialized tooling, but at the cost of manual coordination where monorepo tools provide automation.

// Monorepo structure with shared libraries: transparent imports
// apps/web-app/src/auth.ts
import { authenticate } from '@company/auth-library';
import { logEvent } from '@company/logging';

// The monorepo build tool ensures these are resolved correctly
// Changes to auth-library are immediately visible to web-app
// Dependency tracking is automatic, not manual

export async function login(credentials: Credentials) {
  logEvent('login_attempt', { username: credentials.username });
  return await authenticate(credentials);
}

// Contrast with submodules: imports work, but version synchronization is manual
// Developer must explicitly update submodule pointers to get library changes

Package managers represent a third path that separates version control from dependency management entirely. Publishing shared code as npm packages, Python wheels, or Maven artifacts introduces a publish-consume boundary that decouples producing teams from consuming teams. Changes to shared code don't affect consumers until they explicitly update dependency versions—providing stability at the cost of potential version fragmentation where different services use different versions of shared libraries. This approach scales well for organizations with mature artifact registries and disciplined versioning practices, but it introduces friction during active development when shared code changes frequently. The "install-publish-update-install" cycle becomes tedious, leading teams to seek alternatives. Some organizations combine approaches: using package managers for stable shared libraries and submodules for rapidly-evolving shared configurations or deployment templates that benefit from immediate visibility.

The landscape has evolved with language-specific solutions that address specific submodule use cases. Go modules explicitly support versioning dependencies from Git repositories, allowing teams to reference specific commits or tags without submodules. Rust's Cargo similarly supports Git dependencies. These tools provide submodule-like benefits (direct Git references, specific version pinning) through the package manager itself, eliminating the need to interact with Git submodule commands directly. However, these solutions are language-specific and don't address polyglot codebases where shared configuration files, infrastructure definitions, or cross-language libraries need synchronized versions across multiple repositories. Teams working in these ecosystems should evaluate whether language-native dependency management eliminates their submodule use case before introducing submodule complexity.

Best Practices: Using Submodules Responsibly

Establish clear ownership and update conventions for each submodule at the organizational level. Designate specific teams as maintainers for shared submodule repositories, and define explicit processes for how consuming repositories should track submodule updates. Two common strategies emerge: aggressive tracking, where consuming repositories automatically update to the latest submodule commits using git submodule update --remote, and conservative tracking, where submodule updates require explicit manual decisions and pull requests. Aggressive tracking reduces drift and ensures all consumers rapidly adopt fixes and improvements, but it means submodule changes can break consuming repositories without explicit coordination. Conservative tracking provides stability and allows teams to test submodule updates before adopting them, but it creates version fragmentation where different consumers use different submodule versions. The choice depends on organizational risk tolerance and the nature of the shared code—infrastructure configurations often benefit from aggressive tracking to ensure security updates propagate quickly, while shared application libraries might warrant conservative tracking to allow thorough testing.

# Aggressive tracking: script to update all submodules to latest
#!/bin/bash
# update-submodules.sh - Run regularly (daily CI job, pre-commit hook, etc.)

echo "Updating all submodules to latest upstream commits..."

git submodule update --remote --merge

# Check if any submodules actually changed
if git diff --quiet --staged; then
  echo "All submodules already up-to-date."
  exit 0
fi

# Stage the submodule pointer changes
git add -A

# Create a commit with details
git commit -m "Update submodules to latest

$(git submodule status)"

echo "Submodules updated. Review and push when ready."

Document submodule structure and rationale in architecture decision records (ADRs) or similar documentation that survives team turnover. Submodules introduce implicit dependencies that aren't obvious from repository structure alone—a new team member looking at the parent repository won't necessarily understand why certain directories are submodules, what relationship they represent, or what conventions govern their updates. Comprehensive documentation should cover: the purpose of each submodule, which team owns it, how frequently it should be updated, what process to follow when making changes to submodule content, and what the update policy is (aggressive vs. conservative). This documentation transforms submodules from mysterious Git mechanics into explicit architectural patterns that teams can reason about and maintain deliberately. Include runbooks for common operations like adding new submodules, removing deprecated ones, and resolving submodule-related merge conflicts.

Minimize nesting depth to reduce operational complexity. Each level of submodule nesting multiplies the number of operations required for updates and increases the surface area for version skew problems. Repositories with deeply nested submodules (submodules that themselves contain submodules, recursively) require --recursive flags on nearly every Git operation and create scenarios where updating a leaf-level submodule requires commits at every intermediate level. Flatten submodule structures where possible, having the parent repository directly reference all required submodules rather than pulling some in transitively through other submodules. If your architecture truly requires nested dependencies, consider whether that dependency structure suggests a different repository organization—perhaps the intermediate layer should be a separate service or library managed through package dependencies rather than submodules.

Use submodule branches or tags strategically to communicate stability expectations. While submodules track specific commits by default, you can configure them to track branches or tags using the .gitmodules file's branch field. Tracking a stable or release branch allows consuming repositories to pull updates selectively, while tracking main or master indicates aggressive update expectations. Tag-based tracking (v1.0.0, v2.1.3) provides maximum stability but requires more explicit update decisions. Combine this with semantic versioning practices in submodule repositories: breaking changes trigger major version increments, features trigger minor increments, and bug fixes trigger patch increments. Consuming repositories can then update submodules based on their risk tolerance—automatically pulling patch-level updates but requiring explicit team review for major version updates.

# .gitmodules with branch tracking for selective updates
[submodule "shared/design-system"]
  path = shared/design-system
  url = https://github.com/company/design-system.git
  branch = stable  # Track the stable branch, not main

[submodule "infrastructure/k8s-manifests"]
  path = infrastructure/k8s-manifests
  url = https://github.com/company/k8s-manifests.git
  branch = v2  # Track major version branch

Implement automated checks in CI to verify submodule consistency and prevent common errors. Build pipelines should verify that submodule pointers are consistent with the state of submodule directories, catching situations where developers updated submodule content locally but forgot to commit the new pointer in the parent repository. Add linters or pre-commit hooks that warn when .gitmodules URLs change, or when submodule updates affect multiple consuming repositories without coordinated updates. Consider implementing "freshness" checks that fail CI if submodules are more than a certain age behind their tracked branches, forcing teams to make conscious decisions about deferring updates rather than letting submodules slowly drift out of date.

Key Takeaways

  1. Treat submodules as architectural decisions, not Git features: Before adding a submodule, explicitly document why it's preferable to alternatives (package dependencies, monorepo, code duplication), and establish clear ownership and update conventions that match your team structure and change frequency.
  2. Configure CI/CD explicitly for submodules: Don't assume default checkout behavior will work—add explicit --recurse-submodules flags or platform-specific configurations, handle authentication for private submodules using secrets, and implement health checks that verify submodule content matches expected state.
  3. Establish update discipline: Every change to submodule content must be accompanied by an explicit commit in the parent repository updating the submodule pointer. Use automation (pre-commit hooks, CI checks) to enforce this discipline rather than relying on developer memory.
  4. Minimize nesting and maximize documentation: Keep submodule structures flat to reduce operational complexity, and document the purpose, ownership, and update policies for each submodule in architecture decision records that survive team turnover.
  5. Evaluate modern alternatives before committing: Language-native dependency managers (Go modules, Cargo) often solve submodule use cases with less complexity. Assess whether monorepo tools or package registries better match your organizational structure and investment capacity before choosing submodules as your long-term strategy.

80/20 Insight

Approximately 80% of submodule-related problems stem from 20% of the conceptual surface area: understanding that submodules exist as independent repositories with their own state, and that operations in the parent repository don't automatically propagate to submodules. Mastering three specific operations—git submodule update --init --recursive (to sync submodule state with parent), git submodule update --remote (to pull latest submodule changes), and the three-step process for committing submodule changes (commit in submodule, push submodule, commit updated pointer in parent)—eliminates most daily friction. The remaining complexity (nested submodules, authentication, GitOps integration) matters primarily for teams using submodules at scale or in specialized deployment contexts. Start by ensuring every team member can reliably execute these three operations, and add sophistication only as specific problems emerge that require it.

Conclusion

Git submodules occupy an awkward but sometimes necessary space in modern software architecture: they're simultaneously too complex for simple use cases and too simple for complex ones. Teams that succeed with submodules do so not by mastering Git mechanics, but by treating them as explicit architectural patterns with clear trade-offs that must be actively managed through documentation, tooling, and organizational conventions. The submodule decision is fundamentally a decision about coupling—how tightly do you want to bind repository evolution across organizational boundaries, and how much operational overhead are you willing to accept for that coupling? There's no universal right answer, only trade-offs that fit specific organizational contexts.

The broader lesson extends beyond submodules to all dependency management strategies. Whether you're evaluating submodules against monorepos, package managers, or language-native solutions, you're making decisions about where complexity lives in your system. Submodules push complexity into Git operations and require disciplined human processes. Monorepos centralize complexity in build tooling and require significant infrastructure investment. Package managers push complexity into versioning and release coordination across teams. Each approach works brilliantly in contexts where its trade-offs align with organizational realities—and each creates frustration when deployed in contexts where those trade-offs don't fit. The engineering challenge isn't learning submodule commands; it's understanding your organization's structure, change patterns, and risk tolerance well enough to choose deliberately rather than inheriting architectural patterns by accident.

As GitOps, platform engineering, and cloud-native deployment patterns continue to evolve, the tensions between repository independence and synchronized evolution will remain. Submodules represent one solution to these tensions—imperfect but pragmatic, complex but comprehensible, maintainable with discipline but fragile without it. Use them when they genuinely solve problems that alternatives don't, implement them with explicit process and automation, and remain willing to migrate away when organizational context changes. The goal isn't to use submodules correctly; it's to maintain codebases that teams can understand, modify, and deploy reliably over years of evolution. Sometimes submodules support that goal. Often, simpler alternatives work better. The difference is knowing which situation you're in before the architectural decision becomes difficult to reverse.

References

  1. Git Documentation - Submodules
    Git SCM. 7.11 Git Tools - Submodules. Available at: https://git-scm.com/book/en/v2/Git-Tools-Submodules
    Official Git documentation covering submodule commands, workflows, and technical implementation.

  2. GitHub Documentation - Working with Submodules
    GitHub Docs. Working with submodules. Available at: https://docs.github.com/en/get-started/getting-started-with-git/working-with-submodules
    Platform-specific guidance for using submodules with GitHub repositories.

  3. Atlassian Git Tutorial - Git Submodules
    Atlassian. Git Submodules Tutorial. Available at: https://www.atlassian.com/git/tutorials/git-submodule
    Practical guide covering common submodule workflows and troubleshooting.

  4. ArgoCD Documentation - Git Submodule Support
    ArgoCD. Private Repositories and Git Submodule Support. Available at: https://argo-cd.readthedocs.io/en/stable/user-guide/private-repositories/
    Documentation on how GitOps tools handle submodules in deployment workflows.

  5. Flux Documentation - Git Repository Sources
    FluxCD. Git Repositories. Available at: https://fluxcd.io/flux/components/source/gitrepositories/
    Coverage of submodule handling in Flux v2 GitOps workflows.

  6. GitHub Actions Documentation - Checkout Action
    GitHub. actions/checkout. Available at: https://github.com/actions/checkout
    Documentation for the standard checkout action including submodule handling parameters.

  7. GitLab CI/CD - Git Submodules
    GitLab Docs. Git submodules. Available at: https://docs.gitlab.com/ee/ci/git_submodules.html
    GitLab-specific CI/CD configuration for submodule handling.

  8. Monorepo Tools Comparison
    monorepo.tools. Why a monorepo? Available at: https://monorepo.tools
    Community-maintained comparison of monorepo approaches and tooling.

  9. Google Engineering Practices - Monorepo Philosophy
    Potvin, Rachel and Levenberg, Josh. "Why Google Stores Billions of Lines of Code in a Single Repository." Communications of the ACM, Vol. 59 No. 7, July 2016, pp. 78-87.
    Peer-reviewed article documenting Google's monorepo architecture and tooling.

  10. Semantic Versioning Specification
    Preston-Werner, Tom. Semantic Versioning 2.0.0. Available at: https://semver.org
    Standard for version numbering relevant to submodule update strategies.

  11. Architecture Decision Records (ADRs)
    Nygard, Michael. "Documenting Architecture Decisions." Cognitect Blog, November 2011.
    Available at: https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
    Framework for documenting architectural decisions including repository structure choices.

  12. GitOps Principles
    OpenGitOps. GitOps Principles. Available at: https://opengitops.dev
    Community-defined principles for GitOps practices, relevant to understanding submodule tensions.