Beyond Go Modules:
Sharing Resources Across Repositories
Managing dependencies has long been routine in almost all modern programming languages. In Go, however, this routine arrived relatively late: it was first introduced experimentally with Version 1.11 (August 2018) and then integrated as a stable feature—Go Modules—into the official toolchain starting with Version 1.13 (2019). Before that, developers had to rely on manual steps or alternative tools. This was an important step forward for the Go ecosystem.
Challenges in Multi-Repository Setups
When working with many repositories that also share additional resources, things aren’t always so straightforward. Monorepos can offer a solution here, since you ultimately manage everything in one central place. However, this isn’t suitable for every environment, and even today, working across multiple repositories still makes sense.
Such dependencies aren’t always code. They can also be configurations or Docker Compose services, for example. What options are available to sensibly maintain such resources without having to adjust everything manually?
Starting from Scratch: Project Bootstrapping
Let’s start from the beginning: many necessary tasks repeat themselves in a new project. For this bootstrapping, some frameworks offer CLIs so that you no longer have to do it manually. Even if all basic files and settings are structured identically this way, there’s no real connection like with a dependent library. A specific snapshot for the project is created, which exists independently. Ideally, the CLI will later be able to apply future optimizations—such as improved linter settings—so that not only bootstrapping but also ongoing maintenance of these files remains possible.
Custom Generators for Repetitive Tasks
Such generators can also take the project’s specific environment into account, ensuring that many recurring tasks don’t have to be solved manually. At the same time, transparency isn’t sacrificed, because clearly traceable code is created. So instead of providing generic routing mechanics via a library for web APIs, for example, a generator can create specific snapshots for the project. This generator also maintains future changes, so that all projects can stay up to date—without having to share a classic library.
Sharing Non-Code Resources
Another type of shared resource isn’t directly related to code or settings. In a central repository, for example, I collect mocks of my services that can be used to build tests in various scenarios. Sharing these mocks with individual projects isn’t quite as straightforward. Packaging them as a Go module is theoretically possible (with limitations), but proves to be very cumbersome. A generator isn’t the right approach here either, because no adapted code is created—only an actual copy is needed.
Classic File Sharing Between Projects
This brings us to very classic methods for sharing files between projects. This doesn’t even have to take place within the programming language’s ecosystem. Git has been used for such purposes for a long time. Git submodules are one option—but they only include the external repository as a pointer and therefore require additional commands even during checkout.
The simpler variant: Git subtrees. In the end, the source repo is integrated directly into the project and checked in accordingly—as if you had copied it in manually, but with the ability to update it via Git in the future. The downside: no decoupling and an actual copy of the data. Therefore, it’s less suitable for very large repos. For a collection of a few mocks—as source code with Docker and Docker Compose files—it’s absolutely usable, however.
The Minimalist Alternative
And if even subtree feels like too much magic, often a tiny Taskfile (or Make) entry is enough, using curl to fetch
the latest files directly from the raw branch of the central repo.
There isn’t always a single—or even an obvious—way to create a maintainable project: one that minimizes manual maintenance while still ensuring consistency and long-term maintainability.
Further Explorations
If this topic sparked your interest, you might want to explore other dependency management approaches. The Go Blog: Using Go Modules is an excellent starting point for mastering the official tool. For a deeper dive into repository management strategies, consider Atlassian’s guide on Git Submodules or Git subtree: the alternative to Git submodule for a more integrated approach. Finally, tools like Taskfile or the classic Make can help you automate these and many other workflows.