Workspaces and Multi-Module Development
Overview
Go workspaces, introduced in Go 1.18, solve the challenge of developing multiple related modules simultaneously. With go.work, you can work on a main project and its dependencies together without modifying go.mod files.
The Problem Workspaces Solve
Without Workspaces
When developing multiple modules together, you’d need replace directives:
// main-app/go.mod
module github.com/company/main-app
replace github.com/company/shared-lib => ../shared-lib
Problems: - Must edit go.mod (can accidentally commit) - Each developer has different paths - CI/CD requires cleanup
With Workspaces
$ go work init main-app shared-lib// go.work
go 1.26
use (
./main-app
./shared-lib
)
Benefits: - No go.mod modifications - Works for entire team - Automatically ignored by Git
Creating a Workspace
Initial Setup
# Directory structure
workspace/
├── go.work # Workspace file
├── main-app/ # Main application
│ ├── go.mod
│ └── main.go
└── shared-lib/ # Library module
├── go.mod
└── lib.go# Create workspace
$ cd workspace
$ go work init ./main-app ./shared-libThe go.work File
go 1.26
use (
./main-app
./shared-lib
)
All commands (go build, go test, etc.) now use local modules.
Workspace Commands
go work init
Create a new workspace:
# Initialize with modules
$ go work init ./module1 ./module2
# Initialize empty workspace
$ go work initgo work use
Add modules to workspace:
# Add single module
$ go work use ./new-module
# Add multiple modules
$ go work use ./mod1 ./mod2
# Recursively find and add all modules
$ go work use -r .go work edit
Programmatically modify workspace:
# Add a use directive
$ go work edit -use ./new-module
# Remove a use directive
$ go work edit -dropuse ./old-module
# Set Go version
$ go work edit -go 1.26go work sync
Sync workspace modules with their dependencies:
$ go work syncThis updates each module’s go.mod to match workspace requirements.
Multi-Module Development Workflow
Project Structure
company-workspace/
├── go.work
├── api/
│ ├── go.mod # module github.com/company/api
│ ├── api.go
│ └── api_test.go
├── core/
│ ├── go.mod # module github.com/company/core
│ └── core.go
├── cli/
│ ├── go.mod # module github.com/company/cli
│ └── main.go
└── internal-tools/
├── go.mod # module github.com/company/tools
└── tools.go
Setting Up
$ cd company-workspace
$ go work init ./api ./core ./cli ./internal-toolsDevelopment Flow
# Changes in core/ are immediately available to cli/
$ cd cli
$ go build # Uses local core package
# Run tests across all modules
$ cd ..
$ go test ./...
# Build all binaries
$ go build ./cli/...Replace vs Workspace
When to Use replace
| Use Case | Approach |
|---|---|
| Permanent fork | replace in go.mod |
| CI workaround | replace in go.mod |
| Single dev experiment | replace in go.mod |
When to Use Workspace
| Use Case | Approach |
|---|---|
| Multi-module development | go.work |
| Team development | go.work |
| Monorepo-style work | go.work |
Workspace with Replace
You can combine both. go.work supports replace:
go 1.26
use (
./main-app
./shared-lib
)
replace github.com/external/dep => ./custom-fork
Best Practices
1. Git Ignore go.work
# .gitignore
go.work
go.work.sum
Each developer creates their own workspace.
2. Document Setup
Include setup instructions in README:
## Development Setup
1. Clone all repositories:
```bash
git clone github.com/company/api
git clone github.com/company/core
git clone github.com/company/cliCreate workspace:
go work init ./api ./core ./cli```
3. CI Without Workspaces
In CI, build each module independently:
# GitHub Actions
jobs:
test:
strategy:
matrix:
module: [api, core, cli]
steps:
- uses: actions/checkout@v4
- name: Test
working-directory: ${{ matrix.module }}
run: go test ./...Common Patterns
Microservices Development
workspace/
├── go.work
├── services/
│ ├── user-service/
│ ├── order-service/
│ └── payment-service/
└── shared/
├── proto/
├── middleware/
└── database/
$ go work init
$ go work use -r .Library Development
Testing a library change across consumers:
workspace/
├── go.work
├── my-library/ # The library
├── app-using-library/ # Consumer 1
└── another-consumer/ # Consumer 2
Troubleshooting
Module Not Found in Workspace
# Verify workspace includes module
$ cat go.work
# Re-add module
$ go work use ./missing-moduleWrong Version Used
Workspace modules take priority. To use published version:
# Temporarily disable workspace
$ GOWORK=off go buildWorkspace Mode Detection
Check if workspace is active:
$ go env GOWORK
/path/to/go.workSummary
| Command | Purpose |
|---|---|
go work init |
Create workspace |
go work use |
Add modules |
go work edit |
Modify workspace |
go work sync |
Sync dependencies |
GOWORK=off |
Disable workspace |
Exercises
- Create a workspace with two modules where one depends on the other
- Make changes to the dependency and verify the main module sees them
- Add a third module to an existing workspace